move app to gatehouse-app

This commit is contained in:
2026-01-15 03:40:29 +10:30
parent 5e4cffcf73
commit 2c0aaf484b
69 changed files with 1569 additions and 294 deletions
+34
View File
@@ -0,0 +1,34 @@
"""Schemas package."""
from gatehouse_app.schemas.user_schema import UserSchema, UserUpdateSchema, ChangePasswordSchema
from gatehouse_app.schemas.auth_schema import (
RegisterSchema,
LoginSchema,
RefreshTokenSchema,
ForgotPasswordSchema,
ResetPasswordSchema,
)
from gatehouse_app.schemas.organization_schema import (
OrganizationSchema,
OrganizationCreateSchema,
OrganizationUpdateSchema,
OrganizationMemberSchema,
InviteMemberSchema,
UpdateMemberRoleSchema,
)
__all__ = [
"UserSchema",
"UserUpdateSchema",
"ChangePasswordSchema",
"RegisterSchema",
"LoginSchema",
"RefreshTokenSchema",
"ForgotPasswordSchema",
"ResetPasswordSchema",
"OrganizationSchema",
"OrganizationCreateSchema",
"OrganizationUpdateSchema",
"OrganizationMemberSchema",
"InviteMemberSchema",
"UpdateMemberRoleSchema",
]
+88
View File
@@ -0,0 +1,88 @@
"""Authentication schemas for validation."""
from marshmallow import Schema, fields, validate, validates_schema, ValidationError
class RegisterSchema(Schema):
"""Schema for user registration."""
email = fields.Email(required=True)
password = fields.Str(
required=True,
validate=validate.Length(min=8, max=128),
)
password_confirm = fields.Str(required=True)
full_name = fields.Str(allow_none=True, validate=validate.Length(max=255))
@validates_schema
def validate_passwords_match(self, data, **kwargs):
"""Validate that passwords match."""
if data.get("password") != data.get("password_confirm"):
raise ValidationError("Passwords do not match", field_name="password_confirm")
class LoginSchema(Schema):
"""Schema for user login."""
email = fields.Email(required=True)
password = fields.Str(required=True, validate=validate.Length(min=1))
remember_me = fields.Bool(missing=False)
class RefreshTokenSchema(Schema):
"""Schema for token refresh."""
refresh_token = fields.Str(required=True)
class ForgotPasswordSchema(Schema):
"""Schema for forgot password request."""
email = fields.Email(required=True)
class ResetPasswordSchema(Schema):
"""Schema for password reset."""
token = fields.Str(required=True)
password = fields.Str(
required=True,
validate=validate.Length(min=8, max=128),
)
password_confirm = fields.Str(required=True)
@validates_schema
def validate_passwords_match(self, data, **kwargs):
"""Validate that passwords match."""
if data.get("password") != data.get("password_confirm"):
raise ValidationError("Passwords do not match", field_name="password_confirm")
class TOTPVerifyEnrollmentSchema(Schema):
"""Schema for TOTP enrollment verification."""
code = fields.Str(
required=True,
validate=validate.Regexp(
r"^\d{6}$",
error="Code must be a 6-digit number",
),
)
class TOTPVerifySchema(Schema):
"""Schema for TOTP code verification during login."""
code = fields.Str(required=True)
is_backup_code = fields.Bool(missing=False)
class TOTPDisableSchema(Schema):
"""Schema for disabling TOTP."""
password = fields.Str(required=True, validate=validate.Length(min=1))
class TOTPRegenerateBackupCodesSchema(Schema):
"""Schema for regenerating backup codes."""
password = fields.Str(required=True, validate=validate.Length(min=1))
@@ -0,0 +1,62 @@
"""Organization schemas for validation."""
from marshmallow import Schema, fields, validate
class OrganizationSchema(Schema):
"""Schema for Organization model."""
id = fields.Str(dump_only=True)
name = fields.Str(required=True, validate=validate.Length(min=1, max=255))
slug = fields.Str(required=True, validate=validate.Length(min=1, max=255))
description = fields.Str(allow_none=True)
logo_url = fields.Url(allow_none=True, validate=validate.Length(max=512))
is_active = fields.Bool(dump_only=True)
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class OrganizationCreateSchema(Schema):
"""Schema for creating an organization."""
name = fields.Str(required=True, validate=validate.Length(min=1, max=255))
slug = fields.Str(required=True, validate=validate.Length(min=1, max=255))
description = fields.Str(allow_none=True)
logo_url = fields.Url(allow_none=True, validate=validate.Length(max=512))
class OrganizationUpdateSchema(Schema):
"""Schema for updating an organization."""
name = fields.Str(validate=validate.Length(min=1, max=255))
description = fields.Str(allow_none=True)
logo_url = fields.Url(allow_none=True, validate=validate.Length(max=512))
class OrganizationMemberSchema(Schema):
"""Schema for Organization Member."""
id = fields.Str(dump_only=True)
user_id = fields.Str(dump_only=True)
organization_id = fields.Str(dump_only=True)
role = fields.Str(dump_only=True)
joined_at = fields.DateTime(dump_only=True)
created_at = fields.DateTime(dump_only=True)
class InviteMemberSchema(Schema):
"""Schema for inviting a member to an organization."""
email = fields.Email(required=True)
role = fields.Str(
required=True,
validate=validate.OneOf(["owner", "admin", "member", "guest"])
)
class UpdateMemberRoleSchema(Schema):
"""Schema for updating a member's role."""
role = fields.Str(
required=True,
validate=validate.OneOf(["owner", "admin", "member", "guest"])
)
+47
View File
@@ -0,0 +1,47 @@
"""User schemas for validation and serialization."""
from marshmallow import Schema, fields, validate, validates, ValidationError
from gatehouse_app.utils.constants import UserStatus
class UserSchema(Schema):
"""Schema for User model."""
id = fields.Str(dump_only=True)
email = fields.Email(required=True)
email_verified = fields.Bool(dump_only=True)
full_name = fields.Str(allow_none=True, validate=validate.Length(max=255))
avatar_url = fields.Url(allow_none=True, validate=validate.Length(max=512))
status = fields.Str(dump_only=True)
last_login_at = fields.DateTime(dump_only=True)
created_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)
class UserUpdateSchema(Schema):
"""Schema for updating user profile."""
full_name = fields.Str(allow_none=True, validate=validate.Length(max=255))
avatar_url = fields.Url(allow_none=True, validate=validate.Length(max=512))
class ChangePasswordSchema(Schema):
"""Schema for changing password."""
current_password = fields.Str(required=True, validate=validate.Length(min=1))
new_password = fields.Str(
required=True,
validate=validate.Length(min=8, max=128),
)
new_password_confirm = fields.Str(required=True)
@validates("new_password")
def validate_password_strength(self, value):
"""Validate password strength."""
if len(value) < 8:
raise ValidationError("Password must be at least 8 characters long")
if not any(char.isdigit() for char in value):
raise ValidationError("Password must contain at least one digit")
if not any(char.isupper() for char in value):
raise ValidationError("Password must contain at least one uppercase letter")
if not any(char.islower() for char in value):
raise ValidationError("Password must contain at least one lowercase letter")
+85
View File
@@ -0,0 +1,85 @@
"""WebAuthn schemas for validation."""
from marshmallow import Schema, fields, validate, validates_schema, ValidationError
class WebAuthnRegistrationBeginSchema(Schema):
"""Schema for beginning WebAuthn registration."""
# No required fields - uses authenticated user
pass
class WebAuthnRegistrationCompleteSchema(Schema):
"""Schema for completing WebAuthn registration."""
id = fields.Str(required=True)
rawId = fields.Str(required=True)
type = fields.Str(
required=True,
validate=validate.OneOf(["public-key"])
)
response = fields.Dict(required=True)
transports = fields.List(
fields.Str(validate=validate.OneOf(["usb", "nfc", "ble", "hybrid", "internal", "platform"])),
load_default=[]
)
@validates_schema
def validate_response(self, data, **kwargs):
"""Validate response contains required fields."""
response = data.get("response", {})
required_fields = ["attestationObject", "clientDataJSON"]
for field in required_fields:
if field not in response:
raise ValidationError(
f"Missing required field in response: {field}",
field_name=f"response.{field}"
)
class WebAuthnLoginBeginSchema(Schema):
"""Schema for beginning WebAuthn login."""
email = fields.Email(required=True)
class WebAuthnLoginCompleteSchema(Schema):
"""Schema for completing WebAuthn login."""
id = fields.Str(required=True)
rawId = fields.Str(required=True)
type = fields.Str(
required=True,
validate=validate.OneOf(["public-key"])
)
response = fields.Dict(required=True)
clientExtensionResults = fields.Dict(load_default={})
@validates_schema
def validate_response(self, data, **kwargs):
"""Validate response contains required fields."""
response = data.get("response", {})
required_fields = ["authenticatorData", "clientDataJSON", "signature"]
for field in required_fields:
if field not in response:
raise ValidationError(
f"Missing required field in response: {field}",
field_name=f"response.{field}"
)
class WebAuthnCredentialRenameSchema(Schema):
"""Schema for renaming a WebAuthn credential."""
name = fields.Str(
required=True,
validate=validate.Length(min=1, max=100)
)
class WebAuthnCredentialDeleteSchema(Schema):
"""Schema for deleting a WebAuthn credential."""
password = fields.Str(
required=True,
validate=validate.Length(min=1)
)