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())
|
access_token = generate_jwt(payload.model_dump())
|
||||||
refresh_token = secrets.token_urlsafe(32)
|
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):
|
def hash_refresh_token(token: str):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
from .pricing import PricingConfig
|
||||||
from .sessions import Session
|
from .sessions import Session
|
||||||
from .users import User
|
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.")
|
raise HTTPException(status_code=401, detail="Refresh token is invalid.")
|
||||||
|
|
||||||
key_pair = await refresh_token_rotation(sessions_repo, token_entry, iss)
|
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:
|
class KeyPair:
|
||||||
access_token: str
|
access_token: str
|
||||||
refresh_token: str
|
refresh_token: str
|
||||||
|
expires_at: float
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ class UserLoginData(BaseModel):
|
|||||||
class UserLogin(BaseModel):
|
class UserLogin(BaseModel):
|
||||||
access_token: str
|
access_token: str
|
||||||
refresh_token: str
|
refresh_token: str
|
||||||
|
expires_at: float
|
||||||
user: UserInfo
|
user: UserInfo
|
||||||
|
|
||||||
|
|
||||||
class UserTokens(BaseModel):
|
class UserTokens(BaseModel):
|
||||||
access_token: str
|
access_token: str
|
||||||
refresh_token: str
|
refresh_token: str
|
||||||
|
expires_at: float
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ async def authorize_user(
|
|||||||
access_token=key_pair.access_token,
|
access_token=key_pair.access_token,
|
||||||
refresh_token=key_pair.refresh_token,
|
refresh_token=key_pair.refresh_token,
|
||||||
user=UserInfo(username=user.username, telegram_id=user.telegram_id),
|
user=UserInfo(username=user.username, telegram_id=user.telegram_id),
|
||||||
|
expires_at=key_pair.expires_at,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user