20260730_04_add_local_auth.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. """add local users and server-side sessions
  2. Revision ID: 20260730_04
  3. Revises: 20260729_03
  4. Create Date: 2026-07-30
  5. """
  6. from __future__ import annotations
  7. from collections.abc import Sequence
  8. import sqlalchemy as sa
  9. from alembic import op
  10. revision: str = "20260730_04"
  11. down_revision: str | None = "20260729_03"
  12. branch_labels: str | Sequence[str] | None = None
  13. depends_on: str | Sequence[str] | None = None
  14. def upgrade() -> None:
  15. op.create_table(
  16. "auth_user",
  17. sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
  18. sa.Column("username", sa.String(length=64), nullable=False),
  19. sa.Column("password_hash", sa.String(length=255), nullable=False),
  20. sa.Column("display_name", sa.String(length=128), nullable=False),
  21. sa.Column("role", sa.String(length=16), nullable=False),
  22. sa.Column("status", sa.String(length=16), nullable=False),
  23. sa.Column("failed_login_count", sa.Integer(), nullable=False, server_default="0"),
  24. sa.Column("locked_until", sa.DateTime(), nullable=True),
  25. sa.Column("last_login_at", sa.DateTime(), nullable=True),
  26. sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
  27. sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
  28. sa.PrimaryKeyConstraint("id"),
  29. sa.UniqueConstraint("username", name="uq_auth_user_username"),
  30. mysql_charset="utf8mb4",
  31. )
  32. op.create_index("ix_auth_user_username", "auth_user", ["username"], unique=False)
  33. op.create_index(
  34. "idx_auth_user_role_status",
  35. "auth_user",
  36. ["role", "status"],
  37. unique=False,
  38. )
  39. op.create_table(
  40. "auth_session",
  41. sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
  42. sa.Column("token_hash", sa.String(length=64), nullable=False),
  43. sa.Column("user_id", sa.Integer(), nullable=False),
  44. sa.Column("expires_at", sa.DateTime(), nullable=False),
  45. sa.Column("last_active_at", sa.DateTime(), nullable=False),
  46. sa.Column("ip_address", sa.String(length=64), nullable=True),
  47. sa.Column("user_agent", sa.String(length=512), nullable=True),
  48. sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
  49. sa.ForeignKeyConstraint(["user_id"], ["auth_user.id"], ondelete="CASCADE"),
  50. sa.PrimaryKeyConstraint("id"),
  51. sa.UniqueConstraint("token_hash", name="uq_auth_session_token_hash"),
  52. mysql_charset="utf8mb4",
  53. )
  54. op.create_index(
  55. "ix_auth_session_token_hash",
  56. "auth_session",
  57. ["token_hash"],
  58. unique=False,
  59. )
  60. op.create_index(
  61. "idx_auth_session_user",
  62. "auth_session",
  63. ["user_id", "expires_at"],
  64. unique=False,
  65. )
  66. op.create_index(
  67. "idx_auth_session_expiry",
  68. "auth_session",
  69. ["expires_at"],
  70. unique=False,
  71. )
  72. def downgrade() -> None:
  73. op.drop_index("idx_auth_session_expiry", table_name="auth_session")
  74. op.drop_index("idx_auth_session_user", table_name="auth_session")
  75. op.drop_index("ix_auth_session_token_hash", table_name="auth_session")
  76. op.drop_table("auth_session")
  77. op.drop_index("idx_auth_user_role_status", table_name="auth_user")
  78. op.drop_index("ix_auth_user_username", table_name="auth_user")
  79. op.drop_table("auth_user")