Skip to content

Commit eb77776

Browse files
committed
feat: add email verification on register
1 parent 3c9c847 commit eb77776

46 files changed

Lines changed: 974 additions & 59 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,81 @@
1+
# Application metadata and runtime environment.
12
APP_NAME=Todo Modulith API
23
APP_ENV=production
4+
FRONTEND_URL=http://localhost:3000
35

6+
# Local service credentials used by Docker Compose.
47
POSTGRES_USER=postgres
58
POSTGRES_PASSWORD=
69
POSTGRES_DB=todo_db
710
REDIS_PASSWORD=
811

12+
# Database connection string and SQLAlchemy pool tuning.
913
DATABASE_URL=
1014
DATABASE_POOL_SIZE=20
1115
DATABASE_MAX_OVERFLOW=10
1216
DATABASE_POOL_TIMEOUT=30
1317
DATABASE_POOL_RECYCLE=3600
1418

19+
# Redis connection used by shared infrastructure such as rate limiting or caching.
1520
REDIS_URL=
1621

22+
# JWT signing secret. Change this in every deployed environment.
1723
SECRET_KEY=
1824

25+
# Maximum request body size in bytes.
1926
MAX_REQUEST_SIZE_MB=5242880 #5mb
2027

28+
# JWT signing, validation, and token lifetime settings.
2129
ALGORITHM=HS256
2230
JWT_ISSUER=todo-modulith-api
2331
JWT_AUDIENCE=todo-modulith-client
2432
ACCESS_TOKEN_EXPIRE_MINUTES=30
2533
REFRESH_TOKEN_EXPIRE_MINUTES=10080
2634

35+
# API rate limit rule.
2736
RATE_LIMIT="100/minute"
2837

38+
# CORS settings for browser clients.
2939
CORS_ALLOW_ORIGINS=http://localhost:3000
3040
CORS_ALLOW_METHODS=*
3141
CORS_ALLOW_HEADERS=*
3242

43+
# Content Security Policy header value.
3344
SECURITY_CONTENT_SECURITY_POLICY=default-src 'self'; frame-ancestors 'none'
3445

46+
# Idempotency key retention time in seconds.
3547
IDEMPOTENCY_TTL_SECONDS=86400
3648

49+
# Account lockout thresholds used to slow repeated failed login attempts.
3750
ACCOUNT_LOCKOUT_MAX_ATTEMPTS=5
3851
ACCOUNT_LOCKOUT_WINDOW_MINUTES=15
3952
ACCOUNT_LOCKOUT_DURATION_MINUTES=15
4053

54+
# Logging output format for application logs.
4155
LOG_FORMAT=json
4256

57+
# Email provider selection. Options: ses, sendgrid, smtp.
58+
EMAIL_PROVIDER=ses
59+
60+
# AWS SES configuration.
61+
AWS_REGION=us-east-1
62+
AWS_ACCESS_KEY_ID=
63+
AWS_SECRET_ACCESS_KEY=
64+
SES_FROM_EMAIL=noreply@example.com
65+
66+
# SendGrid configuration.
67+
SENDGRID_API_KEY=
68+
SENDGRID_FROM_EMAIL=noreply@example.com
69+
70+
# SMTP configuration for Gmail or other SMTP providers.
71+
SMTP_HOST=
72+
SMTP_PORT=587
73+
SMTP_USERNAME=
74+
SMTP_PASSWORD=
75+
SMTP_FROM_EMAIL=noreply@example.com
76+
SMTP_USE_TLS=true
77+
78+
# Optional admin and development users created by database seeders.
4379
SEED_ADMIN_EMAIL=
4480
SEED_ADMIN_PASSWORD=
4581
SEED_ADMIN_USERNAME=admin

poetry.lock

Lines changed: 153 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ dependencies = [
1919
"greenlet (>=3.5.1,<4.0.0)",
2020
"casbin (>=1.43.0,<2.0.0)",
2121
"fastapi-limiter (==0.1.6)",
22-
"redis (>=8.0.0,<9.0.0)"
22+
"redis (>=8.0.0,<9.0.0)",
23+
"jinja2 (>=3.1.6,<4.0.0)",
24+
"boto3 (>=1.43.34,<2.0.0)",
25+
"sendgrid (>=6.12.5,<7.0.0)"
2326
]
2427

2528
[tool.poetry]

src/core/authorization/domain/service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
from datetime import datetime
33
from uuid import UUID
44

5-
from src.core.utils.cursor import CursorDirection
65
from src.modules.authorization.domain.entities.permission import Permission
76
from src.modules.authorization.domain.entities.role import Role
7+
from src.shared.utils.cursor import CursorDirection
88

99

1010
class AuthorizationService(ABC):

src/core/authorization/infrastructure/models/permission_model.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import UUID
22

3-
from sqlalchemy import ForeignKey, String, UniqueConstraint
3+
from sqlalchemy import String, UniqueConstraint
44
from sqlalchemy.orm import Mapped, mapped_column
55

66
from src.shared.database.mixin.timestamp import SoftDeleteMixin, TimeStampMixin
@@ -14,10 +14,7 @@ class PermissionModel(Base, TimeStampMixin, SoftDeleteMixin):
1414
)
1515

1616
key: Mapped[str] = mapped_column(String(255), unique=True, index=True)
17-
resource_id: Mapped[UUID] = mapped_column(
18-
ForeignKey("authorization_resources.id"),
19-
index=True,
20-
)
17+
resource_id: Mapped[UUID] = mapped_column(index=True)
2118
resource: Mapped[str] = mapped_column(String(100), index=True)
2219
action: Mapped[str] = mapped_column(String(100), index=True)
2320
description: Mapped[str | None] = mapped_column(String(255), nullable=True)

src/core/authorization/infrastructure/models/role_permission_model.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import UUID
22

3-
from sqlalchemy import ForeignKey, UniqueConstraint
3+
from sqlalchemy import UniqueConstraint
44
from sqlalchemy.orm import Mapped, mapped_column
55

66
from src.shared.database.model import Base
@@ -16,8 +16,5 @@ class RolePermissionModel(Base):
1616
),
1717
)
1818

19-
role_id: Mapped[UUID] = mapped_column(ForeignKey("roles.id"), index=True)
20-
permission_id: Mapped[UUID] = mapped_column(
21-
ForeignKey("permissions.id"),
22-
index=True,
23-
)
19+
role_id: Mapped[UUID] = mapped_column(index=True)
20+
permission_id: Mapped[UUID] = mapped_column(index=True)

src/core/authorization/infrastructure/models/user_has_role_model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import UUID
22

3-
from sqlalchemy import ForeignKey, UniqueConstraint
3+
from sqlalchemy import UniqueConstraint
44
from sqlalchemy.orm import Mapped, mapped_column
55

66
from src.shared.database.model import Base
@@ -12,5 +12,5 @@ class UserHasRoleModel(Base):
1212
UniqueConstraint("user_id", "role_id", name="uq_user_has_roles_user_id_role_id"),
1313
)
1414

15-
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id"), index=True)
16-
role_id: Mapped[UUID] = mapped_column(ForeignKey("roles.id"), index=True)
15+
user_id: Mapped[UUID] = mapped_column(index=True)
16+
role_id: Mapped[UUID] = mapped_column(index=True)

src/core/authorization/infrastructure/repositories/casbin_policy_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from sqlalchemy import and_, delete, or_, select
55
from sqlalchemy.ext.asyncio import AsyncSession
66

7-
from src.core.utils.cursor import CursorDirection
87
from src.core.authorization.infrastructure.models.casbin_rule_model import (
98
CasbinRuleModel,
109
)
@@ -24,6 +23,7 @@
2423
from src.modules.authorization.domain.entities.permission import Permission
2524
from src.modules.authorization.domain.entities.resource import AuthorizationResource
2625
from src.modules.authorization.domain.entities.role import Role
26+
from src.shared.utils.cursor import CursorDirection
2727

2828

2929
class SQLAlchemyCasbinPolicyRepository:

src/core/authorization/infrastructure/services/casbin_authorization_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
SQLAlchemyCasbinPolicyRepository,
77
)
88
from src.core.authorization.permissions import permission_key
9-
from src.core.utils.cursor import CursorDirection
109
from src.modules.authorization.domain.entities.permission import Permission
1110
from src.modules.authorization.domain.entities.role import Role
11+
from src.shared.utils.cursor import CursorDirection
1212

1313
CASBIN_MODEL_TEXT = """
1414
[request_definition]

0 commit comments

Comments
 (0)