revoke access_tokens
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
name: excel-project
|
name: disexcel
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend-dev:
|
backend-dev:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
|
||||||
|
from src.cache.redis_client import redis_client
|
||||||
from src.database.auth.refresh_tokens import JwtCrudActions
|
from src.database.auth.refresh_tokens import JwtCrudActions
|
||||||
from src.database.users.crud import UsersCrudActions
|
from src.database.users.crud import UsersCrudActions
|
||||||
from src.errors.http_errors.errors import Errors
|
from src.errors.http_errors.errors import Errors
|
||||||
@@ -52,8 +53,13 @@ class CurrentUserService:
|
|||||||
async def get_current_user(self, token:str, *permissions: str)->UserOut:
|
async def get_current_user(self, token:str, *permissions: str)->UserOut:
|
||||||
|
|
||||||
payload= await self.jwt_service.jwt_decode(token)
|
payload= await self.jwt_service.jwt_decode(token)
|
||||||
|
|
||||||
sub=payload.get("sub")
|
sub=payload.get("sub")
|
||||||
|
jti_access=payload.get("jti")
|
||||||
|
|
||||||
|
if jti_access and await redis_client.get(f"revoked_access_token:{jti_access}"):
|
||||||
|
raise self.error.credentials_error(detail="Token has been revoked")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sub=UUID(sub)
|
sub=UUID(sub)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
@@ -167,21 +173,34 @@ class CurrentUserService:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def logout(self, refresh_token:str)->bool:
|
async def logout(self, refresh_token:str, access_token:str)->bool:
|
||||||
|
|
||||||
'''decode current refresh token'''
|
'''decode current refresh token'''
|
||||||
payload=await self.jwt_service.jwt_decode(refresh_token)
|
payload_refresh=await self.jwt_service.jwt_decode(refresh_token)
|
||||||
|
|
||||||
if (jti:=payload.get("jti")) is None:
|
'''decode current access token'''
|
||||||
raise self.error.credentials_error(detail="Jwt token is incorrect")
|
payload_access=await self.jwt_service.jwt_decode(access_token)
|
||||||
|
|
||||||
|
|
||||||
|
if (jti_refresh:=payload_refresh.get("jti")) is None:
|
||||||
|
raise self.error.credentials_error(detail="Jwt refresh token is incorrect")
|
||||||
|
|
||||||
|
if (jti_access:=payload_access.get("jti")) is None or (exp_access:=payload_access.get("exp")) is None:
|
||||||
|
raise self.error.credentials_error(detail="Jwt access token is incorrect")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
jti=UUID(jti)
|
jti_refresh=UUID(jti_refresh)
|
||||||
|
jti_access=UUID(jti_access)
|
||||||
except (ValueError, TypeError, AttributeError) as e:
|
except (ValueError, TypeError, AttributeError) as e:
|
||||||
raise self.error.credentials_error(detail="Jwt token is incorrect") from e
|
raise self.error.credentials_error(detail="Jwt token is incorrect") from e
|
||||||
|
|
||||||
|
if jti_access and exp_access:
|
||||||
|
exp_datetime = datetime.fromtimestamp(exp_access, tz=UTC)
|
||||||
|
remaining_ttl = max(int((exp_datetime - datetime.now(UTC)).total_seconds()), 1)
|
||||||
|
await redis_client.set(f"revoked_access_token:{jti_access}", "1", ex=remaining_ttl) #revoke tokens and set them to redis until their exp ends
|
||||||
|
|
||||||
'''logout by assigning revoked flag'''
|
'''logout by assigning revoked flag'''
|
||||||
if await self.jwt_db_actions.logout(jti):
|
if await self.jwt_db_actions.logout(jti_refresh):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise self.error.not_found_error(detail="Refresh Token Not Found")
|
raise self.error.not_found_error(detail="Refresh Token Not Found")
|
||||||
|
|||||||
@@ -35,14 +35,17 @@ class JwtService:
|
|||||||
if not (data.get("sub")) or data.get("sub") == "":
|
if not (data.get("sub")) or data.get("sub") == "":
|
||||||
raise self.error.credentials_error(detail="Jwt token is incorrect")
|
raise self.error.credentials_error(detail="Jwt token is incorrect")
|
||||||
|
|
||||||
|
|
||||||
async def create_access_token(self, data:dict)->str:
|
async def create_access_token(self, data:dict)->str:
|
||||||
|
|
||||||
user_info=data.copy()
|
user_info=data.copy()
|
||||||
|
jti=str(uuid4())
|
||||||
|
|
||||||
await self._validate_sub(user_info)
|
await self._validate_sub(user_info)
|
||||||
|
|
||||||
user_info.update({"exp": datetime.now(UTC)+timedelta(minutes=env_settings.ACCESS_TOKEN_EXPIRE_MINUTES),
|
user_info.update({"exp": datetime.now(UTC)+timedelta(minutes=env_settings.ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||||
"token_type":"access"})
|
"token_type":"access",
|
||||||
|
"jti":jti})
|
||||||
return jwt.encode(user_info, env_settings.SECRET_KEY, env_settings.ALGORITHM)
|
return jwt.encode(user_info, env_settings.SECRET_KEY, env_settings.ALGORITHM)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,12 @@ async def get_refresh_token(request:Request,
|
|||||||
@router.get("/logout")
|
@router.get("/logout")
|
||||||
async def logout(response:Response,
|
async def logout(response:Response,
|
||||||
refresh_token: str = Cookie(),
|
refresh_token: str = Cookie(),
|
||||||
|
access_token: str = Depends(oauth2_schema),
|
||||||
auth:CurrentUserService=Depends(auth_service),
|
auth:CurrentUserService=Depends(auth_service),
|
||||||
current_user:UserOut=Depends(require_permissions()))->bool:
|
current_user:UserOut=Depends(require_permissions()))->bool:
|
||||||
|
|
||||||
response.delete_cookie("refresh_token")
|
response.delete_cookie("refresh_token")
|
||||||
return await auth.logout(refresh_token)
|
return await auth.logout(refresh_token, access_token)
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
async def protected(current_user:UserOut=Depends(require_permissions()))->dict:
|
async def protected(current_user:UserOut=Depends(require_permissions()))->dict:
|
||||||
|
|||||||
Reference in New Issue
Block a user