feat: auth Добавлена поддержка сессий пользователей
Добавлена функциональность управления сессиями пользователей, включая создание сессий при входе, получение списка активных сессий, деактивацию отдельных сессий и деактивацию всех сессий пользователя.
This commit is contained in:
parent
89bfb07a23
commit
dc47e4b003
55
api/app/application/sessions_repository.py
Normal file
55
api/app/application/sessions_repository.py
Normal file
@ -0,0 +1,55 @@
|
||||
import datetime
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from sqlalchemy import update, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.domain.models.sessions import Session
|
||||
|
||||
|
||||
class SessionsRepository:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def create(self, session: Session) -> Session:
|
||||
self.db.add(session)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(session)
|
||||
return session
|
||||
|
||||
async def get_by_id(self, session_id: int) -> Optional[Session]:
|
||||
result = await self.db.execute(
|
||||
select(Session).filter_by(id=session_id)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
async def get_by_token(self, token: str) -> Optional[Session]:
|
||||
result = await self.db.execute(
|
||||
select(Session).filter_by(token=token, is_active=True)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_by_user_id(self, user_id: int) -> Sequence[Session]:
|
||||
result = await self.db.execute(
|
||||
select(Session).filter_by(user_id=user_id, is_active=True)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
async def deactivate_session(self, session_id: int) -> None:
|
||||
await self.db.execute(
|
||||
update(Session).filter_by(id=session_id).values(is_active=False)
|
||||
)
|
||||
await self.db.commit()
|
||||
|
||||
async def deactivate_all_sessions(self, user_id: int) -> None:
|
||||
await self.db.execute(
|
||||
update(Session).filter_by(user_id=user_id).values(is_active=False)
|
||||
)
|
||||
await self.db.commit()
|
||||
|
||||
async def cleanup_expired_sessions(self) -> None:
|
||||
await self.db.execute(
|
||||
delete(Session).filter(Session.expires_at < datetime.datetime.now())
|
||||
)
|
||||
await self.db.commit()
|
||||
@ -1,10 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, Response
|
||||
from fastapi import APIRouter, Depends, Response, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List
|
||||
|
||||
from app.database.session import get_db
|
||||
from app.domain.entities.auth import AuthEntity
|
||||
from app.domain.entities.responses.session import SessionEntity
|
||||
from app.domain.entities.token_entity import TokenEntity
|
||||
from app.infrastructure.auth_service import AuthService
|
||||
from app.infrastructure.dependencies import get_current_user
|
||||
from app.domain.models.users import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -19,10 +23,11 @@ router = APIRouter()
|
||||
async def auth_user(
|
||||
response: Response,
|
||||
user_data: AuthEntity,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
auth_service = AuthService(db)
|
||||
token = await auth_service.authenticate_user(user_data.login, user_data.password)
|
||||
token = await auth_service.authenticate_user(user_data.login, user_data.password, request)
|
||||
|
||||
response.set_cookie(
|
||||
key="users_access_token",
|
||||
@ -32,3 +37,46 @@ async def auth_user(
|
||||
)
|
||||
|
||||
return token
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sessions/",
|
||||
response_model=List[SessionEntity],
|
||||
summary="Get user sessions",
|
||||
description="Returns a list of active sessions for the current user",
|
||||
)
|
||||
async def get_sessions(
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
auth_service = AuthService(db)
|
||||
return await auth_service.get_user_sessions(user.id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/logout/",
|
||||
summary="Log out from a specific session",
|
||||
description="Deactivates a specific session by ID",
|
||||
)
|
||||
async def logout_session(
|
||||
session_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
auth_service = AuthService(db)
|
||||
await auth_service.deactivate_session(session_id, user.id)
|
||||
return {"message": "Session deactivated"}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/logout_all/",
|
||||
summary="Log out from all sessions",
|
||||
description="Deactivates all sessions for the current user",
|
||||
)
|
||||
async def logout_all_sessions(
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
auth_service = AuthService(db)
|
||||
await auth_service.deactivate_all_sessions(user.id)
|
||||
return {"message": "All sessions deactivated"}
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
"""0008 добавил тпблицу сессий
|
||||
|
||||
Revision ID: b013393cef10
|
||||
Revises: 4f3877d7a2b1
|
||||
Create Date: 2025-07-03 09:05:56.233095
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'b013393cef10'
|
||||
down_revision: Union[str, None] = '4f3877d7a2b1'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('sessions',
|
||||
sa.Column('token', sa.String(), nullable=False),
|
||||
sa.Column('device_info', sa.String(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('expires_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['public.users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('token'),
|
||||
schema='public'
|
||||
)
|
||||
op.drop_constraint('appointment_files_appointment_id_fkey', 'appointment_files', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'appointment_files', 'appointments', ['appointment_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('appointments_patient_id_fkey', 'appointments', type_='foreignkey')
|
||||
op.drop_constraint('appointments_doctor_id_fkey', 'appointments', type_='foreignkey')
|
||||
op.drop_constraint('appointments_type_id_fkey', 'appointments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'appointments', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'appointments', 'users', ['doctor_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'appointments', 'appointment_types', ['type_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('backups_user_id_fkey', 'backups', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'backups', 'users', ['user_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('lens_type_id_fkey', 'lens', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'lens', 'lens_types', ['type_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('lens_issues_doctor_id_fkey', 'lens_issues', type_='foreignkey')
|
||||
op.drop_constraint('lens_issues_patient_id_fkey', 'lens_issues', type_='foreignkey')
|
||||
op.drop_constraint('lens_issues_lens_id_fkey', 'lens_issues', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'lens_issues', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'lens_issues', 'lens', ['lens_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'lens_issues', 'users', ['doctor_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('mailing_user_id_fkey', 'mailing', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'mailing', 'users', ['user_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('mailing_options_option_id_fkey', 'mailing_options', type_='foreignkey')
|
||||
op.drop_constraint('mailing_options_mailing_id_fkey', 'mailing_options', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'mailing_options', 'mailing', ['mailing_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'mailing_options', 'mailing_delivery_methods', ['option_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('recipients_mailing_id_fkey', 'recipients', type_='foreignkey')
|
||||
op.drop_constraint('recipients_patient_id_fkey', 'recipients', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'recipients', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'recipients', 'mailing', ['mailing_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('scheduled_appointments_type_id_fkey', 'scheduled_appointments', type_='foreignkey')
|
||||
op.drop_constraint('scheduled_appointments_doctor_id_fkey', 'scheduled_appointments', type_='foreignkey')
|
||||
op.drop_constraint('scheduled_appointments_patient_id_fkey', 'scheduled_appointments', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'scheduled_appointments', 'appointment_types', ['type_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'scheduled_appointments', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'scheduled_appointments', 'users', ['doctor_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('set_contents_type_id_fkey', 'set_contents', type_='foreignkey')
|
||||
op.drop_constraint('set_contents_set_id_fkey', 'set_contents', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'set_contents', 'sets', ['set_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.create_foreign_key(None, 'set_contents', 'lens_types', ['type_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
op.drop_constraint('users_role_id_fkey', 'users', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'users', 'roles', ['role_id'], ['id'], source_schema='public', referent_schema='public')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'users', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('users_role_id_fkey', 'users', 'roles', ['role_id'], ['id'])
|
||||
op.drop_constraint(None, 'set_contents', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'set_contents', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('set_contents_set_id_fkey', 'set_contents', 'sets', ['set_id'], ['id'])
|
||||
op.create_foreign_key('set_contents_type_id_fkey', 'set_contents', 'lens_types', ['type_id'], ['id'])
|
||||
op.drop_constraint(None, 'scheduled_appointments', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'scheduled_appointments', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'scheduled_appointments', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('scheduled_appointments_patient_id_fkey', 'scheduled_appointments', 'patients', ['patient_id'], ['id'])
|
||||
op.create_foreign_key('scheduled_appointments_doctor_id_fkey', 'scheduled_appointments', 'users', ['doctor_id'], ['id'])
|
||||
op.create_foreign_key('scheduled_appointments_type_id_fkey', 'scheduled_appointments', 'appointment_types', ['type_id'], ['id'])
|
||||
op.drop_constraint(None, 'recipients', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'recipients', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('recipients_patient_id_fkey', 'recipients', 'patients', ['patient_id'], ['id'])
|
||||
op.create_foreign_key('recipients_mailing_id_fkey', 'recipients', 'mailing', ['mailing_id'], ['id'])
|
||||
op.drop_constraint(None, 'mailing_options', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'mailing_options', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('mailing_options_mailing_id_fkey', 'mailing_options', 'mailing', ['mailing_id'], ['id'])
|
||||
op.create_foreign_key('mailing_options_option_id_fkey', 'mailing_options', 'mailing_delivery_methods', ['option_id'], ['id'])
|
||||
op.drop_constraint(None, 'mailing', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('mailing_user_id_fkey', 'mailing', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'lens_issues', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'lens_issues', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'lens_issues', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('lens_issues_lens_id_fkey', 'lens_issues', 'lens', ['lens_id'], ['id'])
|
||||
op.create_foreign_key('lens_issues_patient_id_fkey', 'lens_issues', 'patients', ['patient_id'], ['id'])
|
||||
op.create_foreign_key('lens_issues_doctor_id_fkey', 'lens_issues', 'users', ['doctor_id'], ['id'])
|
||||
op.drop_constraint(None, 'lens', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('lens_type_id_fkey', 'lens', 'lens_types', ['type_id'], ['id'])
|
||||
op.drop_constraint(None, 'backups', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('backups_user_id_fkey', 'backups', 'users', ['user_id'], ['id'])
|
||||
op.drop_constraint(None, 'appointments', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'appointments', schema='public', type_='foreignkey')
|
||||
op.drop_constraint(None, 'appointments', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('appointments_type_id_fkey', 'appointments', 'appointment_types', ['type_id'], ['id'])
|
||||
op.create_foreign_key('appointments_doctor_id_fkey', 'appointments', 'users', ['doctor_id'], ['id'])
|
||||
op.create_foreign_key('appointments_patient_id_fkey', 'appointments', 'patients', ['patient_id'], ['id'])
|
||||
op.drop_constraint(None, 'appointment_files', schema='public', type_='foreignkey')
|
||||
op.create_foreign_key('appointment_files_appointment_id_fkey', 'appointment_files', 'appointments', ['appointment_id'], ['id'])
|
||||
op.drop_table('sessions', schema='public')
|
||||
# ### end Alembic commands ###
|
||||
16
api/app/domain/entities/responses/session.py
Normal file
16
api/app/domain/entities/responses/session.py
Normal file
@ -0,0 +1,16 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class SessionEntity(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
token: str
|
||||
device_info: Optional[str]
|
||||
created_at: datetime
|
||||
expires_at: datetime
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@ -16,6 +16,7 @@ from app.domain.models.patients import Patient
|
||||
from app.domain.models.recipients import Recipient
|
||||
from app.domain.models.roles import Role
|
||||
from app.domain.models.scheduled_appointments import ScheduledAppointment
|
||||
from app.domain.models.sessions import Session
|
||||
from app.domain.models.set_contents import SetContent
|
||||
from app.domain.models.sets import Set
|
||||
from app.domain.models.users import User
|
||||
|
||||
20
api/app/domain/models/sessions.py
Normal file
20
api/app/domain/models/sessions.py
Normal file
@ -0,0 +1,20 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from app.domain.models.base import BaseModel
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
class Session(BaseModel):
|
||||
__tablename__ = 'sessions'
|
||||
__table_args__ = {"schema": settings.SCHEMA}
|
||||
|
||||
token = Column(String, nullable=False, unique=True)
|
||||
device_info = Column(String, nullable=True)
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
||||
expires_at = Column(DateTime, nullable=False)
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
||||
|
||||
user = relationship("User", back_populates="sessions")
|
||||
@ -25,6 +25,7 @@ class User(BaseModel):
|
||||
appointments = relationship('Appointment', back_populates='doctor')
|
||||
mailing = relationship('Mailing', back_populates='user')
|
||||
scheduled_appointments = relationship('ScheduledAppointment', back_populates='doctor')
|
||||
sessions = relationship("Session", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password, password)
|
||||
|
||||
@ -1,23 +1,37 @@
|
||||
import datetime
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
import jwt
|
||||
from fastapi import HTTPException
|
||||
from fastapi import HTTPException, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette import status
|
||||
|
||||
from app.application.users_repository import UsersRepository
|
||||
from app.application.sessions_repository import SessionsRepository
|
||||
from app.domain.entities.responses.session import SessionEntity
|
||||
from app.domain.models.sessions import Session
|
||||
from app.settings import get_auth_data
|
||||
|
||||
|
||||
class AuthService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.users_repository = UsersRepository(db)
|
||||
self.sessions_repository = SessionsRepository(db)
|
||||
|
||||
async def authenticate_user(self, login: str, password: str) -> Optional[dict]:
|
||||
async def authenticate_user(self, login: str, password: str, request: Request) -> Optional[dict]:
|
||||
user = await self.users_repository.get_by_login(login)
|
||||
if user and user.check_password(password):
|
||||
access_token = self.create_access_token({"user_id": user.id})
|
||||
# Создаем сессию
|
||||
session = Session(
|
||||
user_id=user.id,
|
||||
token=access_token,
|
||||
device_info=request.headers.get("User-Agent", "Unknown"),
|
||||
created_at=datetime.datetime.now(), # Naive datetime
|
||||
expires_at=datetime.datetime.now() + datetime.timedelta(days=30),
|
||||
is_active=True
|
||||
)
|
||||
await self.sessions_repository.create(session)
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"user_id": user.id
|
||||
@ -25,12 +39,27 @@ class AuthService:
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Неправильный логин или пароль")
|
||||
|
||||
async def get_user_sessions(self, user_id: int) -> List[SessionEntity]:
|
||||
sessions = await self.sessions_repository.get_by_user_id(user_id)
|
||||
return [SessionEntity.from_orm(session) for session in sessions]
|
||||
|
||||
async def deactivate_session(self, session_id: int, user_id: int) -> None:
|
||||
session = await self.sessions_repository.get_by_id(session_id)
|
||||
if not session or session.user_id != user_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Сессия не найдена или доступ запрещен")
|
||||
await self.sessions_repository.deactivate_session(session_id)
|
||||
|
||||
async def deactivate_all_sessions(self, user_id: int) -> None:
|
||||
await self.sessions_repository.deactivate_all_sessions(user_id)
|
||||
|
||||
async def cleanup_expired_sessions(self) -> None:
|
||||
await self.sessions_repository.cleanup_expired_sessions()
|
||||
|
||||
@staticmethod
|
||||
def create_access_token(data: dict) -> str:
|
||||
to_encode = data.copy()
|
||||
expire = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30)
|
||||
expire = datetime.datetime.now() + datetime.timedelta(days=30) # Naive datetime
|
||||
to_encode.update({"exp": expire})
|
||||
auth_data = get_auth_data()
|
||||
encode_jwt = jwt.encode(to_encode, auth_data['secret_key'], algorithm=auth_data['algorithm'])
|
||||
|
||||
return encode_jwt
|
||||
|
||||
@ -8,6 +8,7 @@ from app.database.session import get_db
|
||||
from app.domain.models.users import User
|
||||
from app.settings import get_auth_data
|
||||
from app.application.users_repository import UsersRepository
|
||||
from app.application.sessions_repository import SessionsRepository
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
@ -29,6 +30,10 @@ async def get_current_user(
|
||||
if user_id is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Неправильный токен")
|
||||
|
||||
session = await SessionsRepository(db).get_by_token(credentials.credentials)
|
||||
if not session or not session.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Сессия неактивна или не найдена")
|
||||
|
||||
user = await UsersRepository(db).get_by_id_with_role(user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Пользователь не найден")
|
||||
|
||||
@ -17,7 +17,7 @@ const AdminRoute = () => {
|
||||
}
|
||||
|
||||
if (isUserError) {
|
||||
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке данных пользователя"/>;
|
||||
return <Navigate to="/login"/>;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
|
||||
@ -3,13 +3,13 @@ import {useSelector} from "react-redux";
|
||||
import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
|
||||
const PrivateRoute = () => {
|
||||
const {user, userData, isLoading} = useSelector((state) => state.auth);
|
||||
const {user, userData, isLoading, error} = useSelector((state) => state.auth);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingIndicator/>;
|
||||
}
|
||||
|
||||
if (!user || !userData || userData.is_blocked) {
|
||||
if (error || !user || !userData || userData.is_blocked) {
|
||||
return <Navigate to="/login"/>;
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ import {
|
||||
useDeleteBackupMutation,
|
||||
useGetBackupsQuery,
|
||||
useUploadBackupMutation,
|
||||
useRestoreBackupMutation,
|
||||
} from "../../../../../Api/backupsApi.js";
|
||||
import {notification} from "antd";
|
||||
import {useState} from "react";
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Grid, notification } from "antd";
|
||||
import { setError, setUser } from "../../../Redux/Slices/authSlice.js";
|
||||
import { useLoginMutation } from "../../../Api/authApi.js";
|
||||
import { checkAuth } from "../../../Redux/Slices/authSlice.js";
|
||||
import {useEffect, useRef} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {Grid, notification} from "antd";
|
||||
import {setError, setUser} from "../../../Redux/Slices/authSlice.js";
|
||||
import {useLoginMutation} from "../../../Api/authApi.js";
|
||||
import {checkAuth} from "../../../Redux/Slices/authSlice.js";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const useLoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [loginUser, { isLoading }] = useLoginMutation();
|
||||
const { user, userData } = useSelector((state) => state.auth);
|
||||
const [loginUser, {isLoading}] = useLoginMutation();
|
||||
const {user, userData} = useSelector((state) => state.auth);
|
||||
const screens = useBreakpoint();
|
||||
const hasRedirected = useRef(false);
|
||||
|
||||
@ -74,10 +74,15 @@ const useLoginPage = () => {
|
||||
const response = await loginUser(loginData).unwrap();
|
||||
const token = response.access_token || response.token;
|
||||
if (!token) {
|
||||
throw new Error("Сервер не вернул токен авторизации");
|
||||
notification.error({
|
||||
message: "Ошибка при входе",
|
||||
description: "Не удалось войти. Проверьте логин и пароль.",
|
||||
placement: "topRight",
|
||||
});
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("access_token", token);
|
||||
dispatch(setUser({ token }));
|
||||
dispatch(setUser({token}));
|
||||
|
||||
await dispatch(checkAuth()).unwrap();
|
||||
} catch (error) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { usersApi } from "../../Api/usersApi.js";
|
||||
import {createSlice, createAsyncThunk} from "@reduxjs/toolkit";
|
||||
import {usersApi} from "../../Api/usersApi.js";
|
||||
|
||||
export const checkAuth = createAsyncThunk("auth/checkAuth", async (_, { dispatch, rejectWithValue }) => {
|
||||
export const checkAuth = createAsyncThunk("auth/checkAuth", async (_, {dispatch, rejectWithValue}) => {
|
||||
try {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
@ -9,10 +9,10 @@ export const checkAuth = createAsyncThunk("auth/checkAuth", async (_, { dispatch
|
||||
}
|
||||
|
||||
const userData = await dispatch(
|
||||
usersApi.endpoints.getAuthenticatedUserData.initiate(undefined, { forceRefetch: true })
|
||||
usersApi.endpoints.getAuthenticatedUserData.initiate(undefined, {forceRefetch: true})
|
||||
).unwrap();
|
||||
|
||||
return { token, userData };
|
||||
return {token, userData};
|
||||
} catch (error) {
|
||||
localStorage.removeItem("access_token");
|
||||
return rejectWithValue(error?.data?.detail || "Failed to authenticate");
|
||||
@ -38,6 +38,9 @@ const authSlice = createSlice({
|
||||
setError(state, action) {
|
||||
state.error = action.payload;
|
||||
state.isLoading = false;
|
||||
state.user = null;
|
||||
state.userData = null;
|
||||
localStorage.removeItem("access_token");
|
||||
},
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
@ -57,7 +60,7 @@ const authSlice = createSlice({
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(checkAuth.fulfilled, (state, action) => {
|
||||
state.user = { token: action.payload.token };
|
||||
state.user = {token: action.payload.token};
|
||||
state.userData = action.payload.userData;
|
||||
state.isLoading = false;
|
||||
})
|
||||
@ -70,5 +73,5 @@ const authSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { setUser, setError, logout, setUserData } = authSlice.actions;
|
||||
export const {setUser, setError, logout, setUserData} = authSlice.actions;
|
||||
export default authSlice.reducer;
|
||||
Loading…
x
Reference in New Issue
Block a user