feat(auth): include expires_at in token responses and KeyPair DTO
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
"""create pricing_configs table
|
||||
|
||||
Revision ID: 2e27aeb65f28
|
||||
Revises: 086c8d206446
|
||||
Create Date: 2026-07-24 13:22:30.845968
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '2e27aeb65f28'
|
||||
down_revision: Union[str, Sequence[str], None] = '086c8d206446'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,47 @@
|
||||
"""create pricing_configs table frn ow
|
||||
|
||||
Revision ID: 74bd9cedebdd
|
||||
Revises: 2e27aeb65f28
|
||||
Create Date: 2026-07-24 13:23:52.427837
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '74bd9cedebdd'
|
||||
down_revision: Union[str, Sequence[str], None] = '2e27aeb65f28'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('pricing_configs',
|
||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column('device_price', sa.FLOAT(), nullable=False),
|
||||
sa.Column('updated_at', sa.TIMESTAMP(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.CheckConstraint('id = 1', name='single_row_check'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
INSERT INTO pricing_configs (id, device_price)
|
||||
VALUES (1, 50.00)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('pricing_configs')
|
||||
# ### end Alembic commands ###
|
||||
@@ -47,7 +47,7 @@ def generate_pair(user_id: int, iss: ProvidersType) -> KeyPair:
|
||||
access_token = generate_jwt(payload.model_dump())
|
||||
refresh_token = secrets.token_urlsafe(32)
|
||||
|
||||
return KeyPair(access_token=access_token, refresh_token=refresh_token)
|
||||
return KeyPair(access_token=access_token, refresh_token=refresh_token, expires_at=payload.exp)
|
||||
|
||||
|
||||
def hash_refresh_token(token: str):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .pricing import PricingConfig
|
||||
from .sessions import Session
|
||||
from .users import User
|
||||
|
||||
__all__ = ["Session", "User"]
|
||||
__all__ = ["PricingConfig", "Session", "User"]
|
||||
|
||||
19
db/models/pricing.py
Normal file
19
db/models/pricing.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import FLOAT, INTEGER, TIMESTAMP, CheckConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from db.base import Base
|
||||
|
||||
|
||||
class PricingConfig(Base):
|
||||
__tablename__ = "pricing_configs"
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("id = 1", name="single_row_check"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(INTEGER, autoincrement=True, unique=True, nullable=False, primary_key=True)
|
||||
device_price: Mapped[float] = mapped_column(FLOAT, nullable=False)
|
||||
|
||||
updated_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now(), onupdate=func.now())
|
||||
@@ -72,4 +72,8 @@ async def refresh(refresh_token: str, iss: ProvidersType, session: AsyncSession
|
||||
raise HTTPException(status_code=401, detail="Refresh token is invalid.")
|
||||
|
||||
key_pair = await refresh_token_rotation(sessions_repo, token_entry, iss)
|
||||
return UserTokens(access_token=key_pair.access_token, refresh_token=key_pair.refresh_token)
|
||||
return UserTokens(
|
||||
access_token=key_pair.access_token,
|
||||
refresh_token=key_pair.refresh_token,
|
||||
expires_at=key_pair.expires_at,
|
||||
)
|
||||
|
||||
@@ -5,3 +5,4 @@ from dataclasses import dataclass
|
||||
class KeyPair:
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
expires_at: float
|
||||
|
||||
@@ -19,9 +19,11 @@ class UserLoginData(BaseModel):
|
||||
class UserLogin(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
expires_at: float
|
||||
user: UserInfo
|
||||
|
||||
|
||||
class UserTokens(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
expires_at: float
|
||||
|
||||
@@ -19,4 +19,5 @@ async def authorize_user(
|
||||
access_token=key_pair.access_token,
|
||||
refresh_token=key_pair.refresh_token,
|
||||
user=UserInfo(username=user.username, telegram_id=user.telegram_id),
|
||||
expires_at=key_pair.expires_at,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user