Files
g0v0-server/migrations/versions/2025-10-02_72a9b8f3f863_session_support_multi_session.py
MingxuanGame 40670c094b feat(auth): support trusted device (#52)
New API to maintain sessions and devices:

- GET /api/private/admin/sessions
- DELETE /api/private/admin/sessions/{session_id}
- GET /api/private/admin/trusted-devices
- DELETE /api/private/admin/trusted-devices/{device_id}

Auth:

web clients request `/oauth/token` and `/api/v2/session/verify` with `X-UUID` header to save the client as trusted device.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-03 11:26:43 +08:00

103 lines
3.7 KiB
Python

"""session: support multi-session
Revision ID: 72a9b8f3f863
Revises: b1ac2154bd0d
Create Date: 2025-10-02 07:17:19.297498
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision: str = "72a9b8f3f863"
down_revision: str | Sequence[str] | None = "b1ac2154bd0d"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"trusted_devices",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("ip_address", sa.VARCHAR(length=45), nullable=False),
sa.Column("user_agent", sa.Text(), nullable=False),
sa.Column("client_type", sa.VARCHAR(length=10), nullable=False),
sa.Column("web_uuid", sa.VARCHAR(length=36), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("last_used_at", sa.DateTime(), nullable=False),
sa.Column("expires_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["user_id"],
["lazer_users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.alter_column(
"login_sessions",
"is_new_location",
new_column_name="is_new_device",
existing_type=mysql.TINYINT(display_width=1),
)
op.create_index(op.f("ix_trusted_devices_user_id"), "trusted_devices", ["user_id"], unique=False)
op.add_column("login_sessions", sa.Column("web_uuid", sa.VARCHAR(length=36), nullable=True))
op.alter_column(
"login_sessions",
"ip_address",
existing_type=mysql.VARCHAR(length=255),
type_=sa.VARCHAR(length=45),
existing_nullable=False,
)
op.alter_column(
"login_sessions", "user_agent", existing_type=mysql.VARCHAR(length=250), type_=sa.Text(), existing_nullable=True
)
op.drop_index(op.f("ix_login_sessions_session_token"), table_name="login_sessions")
op.create_foreign_key(None, "login_sessions", "lazer_users", ["user_id"], ["id"])
op.drop_column("login_sessions", "country_code")
op.drop_column("login_sessions", "session_token")
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("login_sessions", sa.Column("session_token", sa.VARCHAR(length=64), nullable=True))
op.add_column("login_sessions", sa.Column("country_code", sa.VARCHAR(length=255), nullable=True))
op.create_index(op.f("ix_login_sessions_session_token"), "login_sessions", ["session_token"], unique=False)
op.alter_column(
"login_sessions",
"user_agent",
existing_type=sa.Text(),
type_=mysql.VARCHAR(length=250),
existing_nullable=True,
)
op.alter_column(
"login_sessions",
"ip_address",
existing_type=sa.String(length=45),
type_=mysql.VARCHAR(length=255),
existing_nullable=False,
)
op.drop_column("login_sessions", "web_uuid")
op.alter_column(
"login_sessions",
"is_new_device",
new_column_name="is_new_location",
existing_type=mysql.TINYINT(display_width=1),
)
op.drop_constraint(op.f("fk_login_sessions_user_id_lazer_users"), "login_sessions", type_="foreignkey")
op.drop_index(op.f("ix_trusted_devices_user_id"), table_name="trusted_devices")
op.drop_table("trusted_devices")
# ### end Alembic commands ###