Skip to content

feat(BA-4905): add LoginSecurityPolicy model, login_sessions table, and repository layer#9720

Open
HyeockJinKim wants to merge 6 commits intomainfrom
BA-4905
Open

feat(BA-4905): add LoginSecurityPolicy model, login_sessions table, and repository layer#9720
HyeockJinKim wants to merge 6 commits intomainfrom
BA-4905

Conversation

@HyeockJinKim
Copy link
Collaborator

Summary

  • Add LoginSecurityPolicy Pydantic model with max_concurrent_logins: int | None field and login_security_policy JSONB column on UserRow
  • Add LoginSessionRow ORM model and Alembic migration for the login_sessions table with proper FK, indexes, and LoginSessionExpiryReason enum
  • Implement LoginSessionRepository (cache-first via Valkey Sorted Set + DB fallback) and LoginSessionService (create/expire/evict/check concurrency) following existing repository and service patterns

Test plan

  • Alembic migration applies cleanly: alembic upgrade head
  • LoginSessionRepository ZADD/ZSCORE/ZCARD/ZPOPMIN/ZREM operations work against Valkey
  • LoginSessionService.check_concurrency_limit enforces max_concurrent_logins correctly

Resolves BA-4905

HyeockJinKim and others added 5 commits March 6, 2026 01:24
…ason enum

- Add LoginSessionExpiryReason(StrEnum) with LOGOUT/EVICTED/EXPIRED values in data/login_session/types.py
- Add LoginSessionData frozen dataclass in data/login_session/types.py
- Add LoginSecurityPolicy(BaseModel) Pydantic model in models/login_session/types.py
  (placed in models/ per CLAUDE.md rules — data/ layer prohibits Pydantic imports)
- Document deviation from plan in DEVIATION.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PydanticColumn(LoginSecurityPolicy) to UserRow.login_security_policy (JSONB, nullable)
- Add login_security_policy: dict[str, Any] | None field to UserData (default None)
- Update UserRow.to_data() to serialize policy via model_dump()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in_security_policy column

- Create login_sessions table with id (UUID PK), user_uuid (FK users),
  session_token (unique), client_ip, created_at, expired_at, reason columns
- Add index on login_sessions.user_uuid
- Add login_security_policy JSONB column to users table
- down_revision chains from ffcf0ed13a26

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add LOGIN_SESSION to EntityType enum and LOGIN_SESSION_REPOSITORY to LayerType
- Add to_dataclass() to LoginSessionRow for data layer conversion
- Create LoginSessionCacheSource: Valkey Sorted Set (ZADD/ZSCORE/ZCARD/ZPOPMIN/ZREM)
  keyed by login_session:{user_uuid}; uses execute_command for sorted set ops
- Create LoginSessionDBSource: create_session, expire_session, list_active_sessions,
  count_active_sessions via SQLAlchemy async
- Create LoginSessionRepository: cache-first reads with DB fallback, DB-first writes
  with suppress_with_log for cache failures; includes evict_oldest_session
- Create LoginSessionService with create_session, expire_session, evict_oldest_session,
  check_concurrency_limit; follows Action/ActionResult pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 5, 2026 16:41
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

container_uid: int | None = field(compare=False)
container_main_gid: int | None = field(compare=False)
container_gids: list[int] | None = field(compare=False)
login_security_policy: dict[str, Any] | None = field(default=None, compare=False)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using dict. We recommend handling data through PydanticModel or dataclass.

Comment on lines +242 to +244
login_security_policy: Mapped[LoginSecurityPolicy | None] = mapped_column(
"login_security_policy", PydanticColumn(LoginSecurityPolicy), nullable=True
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the default value rather than leaving it optional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants