Files
sakhteman/auth/utils/__init__.py
T
2024-02-15 00:17:51 +03:30

320 lines
12 KiB
Python

import random
from datetime import datetime, timedelta
from typing import Optional, Union
from uuid import UUID
import httpagentparser
from fastapi import Security, HTTPException, Depends
from fastapi.security import SecurityScopes
from jose import jwt, JWTError
from pydantic import ValidationError
from starlette.requests import Request
from app.models.customer import CustomerModel
from auth.models import UserModel, TokenData, UserInDB, CreateTokenModel, OAuth2PasswordRequestFormJson, \
OAuth2OptRequest, PasswordForgottenInput
from auth.services.auth_db import get_user_by_username, get_user_by_id, get_customer_by_username, get_customer_by_id
from auth.tasks import get_ip_info, send_otp
from auth.utils import pwd, schema
from auth.utils.mongo import get_auth_db, use_auth_collection
from auth.utils.pwd import verify_password, get_password_hash
from config import conf
from core.models.base import DELETED_AT_QUERY
from core.utils import get_time, is_mobile_number
from core.utils.app_context import use_user
from core.service.mongo_db import use_collection
EXPIRED_TIME_SECONDS = 120
async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(schema.oauth2)):
print("4444444444444444")
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = f"Bearer"
credentials_exception = HTTPException(
status_code=401,
detail="Could not validate credentials wert",
headers={"WWW-Authenticate": authenticate_value},
)
try:
payload = jwt.decode(
token, conf.auth.secret_key, algorithms=[
conf.auth.algorithm])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_scopes = payload.get("scopes", [])
token_data = TokenData(scopes=token_scopes, username=username)
except (JWTError, ValidationError):
raise credentials_exception
if 'customer' in token_data.scopes:
user = await get_customer_by_id(customer_id=token_data.username)
else:
user = await get_user_by_id(user_id=token_data.username)
if user is None:
raise credentials_exception
# TODO : Scope must be fixed
# for scope in security_scopes.scopes:
# if scope not in token_data.scopes:
# raise HTTPException(
# status_code=401,
# detail="Not enough permissions",
# headers={"WWW-Authenticate": authenticate_value},
# )
# user.scopes = security_scopes.scopes if isinstance(
# security_scopes.scopes, list) else security_scopes.scopes
return user
async def get_current_active_user(request: Request,
current_user: Union[UserModel, CustomerModel] = Security(get_current_user,
scopes=["account",
"customer"]), ):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
if isinstance(current_user, CustomerModel):
request.state.user = CustomerModel(**current_user.mongo_dict())
else:
request.state.user = UserModel(**current_user.mongo_dict())
use_user(request.state.user)
return current_user
async def get_current_active_customer(request: Request,
current_user: CustomerModel = Security(get_current_user, scopes=["customer"]), ):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
request.state.user = CustomerModel(**current_user.mongo_dict())
use_user(request.state.user)
return current_user
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
async def authenticate_user(username: str, password: str = None, check_password: bool = True,
scope=None):
if scope is None:
scope = ['customer']
if 'customer' in scope:
user = await get_customer_by_username(username)
else:
user = await get_user_by_username(username)
if not user:
return False
if check_password and not verify_password(password, user.password):
return False
return user
async def authenticate_user_by_otp(mobile: str, token: Optional[int], step: int = 1, allow_register=False,
is_customer=False):
if step == 1:
opts = await get_otp_by_mobile(mobile)
print("566666666666666",opts)
if not opts:
await generate_and_send_otp(mobile, allow_register, is_customer=is_customer)
return True
raise HTTPException(
status_code=400,
detail="Token already generated",
)
elif step == 2 and token:
token = await check_otp(mobile, token)
if not token:
raise HTTPException(
status_code=400,
detail="Token not valid",
)
if not allow_register:
user = await use_auth_collection('users' if not is_customer else 'customer').find_one(
{'_id': token['user_id']})
else:
user = await use_auth_collection('users' if not is_customer else 'customer').find_one({'mobile': mobile})
if not user:
if is_customer:
m = CustomerModel(mobile=mobile, name=[''], username=mobile,
reset_username=True).cu_at().init_id().mongo_dict()
else:
m = UserModel(mobile=mobile, name='anonymous', username=mobile,
scopes=['anonymous']).cu_at().init_id().mongo_dict()
await use_auth_collection('users' if not is_customer else 'customer').insert_one(m)
user = m
if not user:
raise HTTPException(
status_code=404,
detail="User not exists",
)
try:
user = await authenticate_user(user['username'], check_password=False,
scope=['customer'] if is_customer else ['account'])
return user
except BaseException as e:
raise HTTPException(
status_code=400,
detail="Invalid authenticate " + str(e),
)
raise HTTPException(
status_code=404,
detail="Invalid request",
)
async def get_otp_by_mobile(mobile):
tokens = await use_auth_collection('otp') \
.find({'mobile': mobile, 'expired_at': {'$gt': get_time()}}) \
.to_list(None)
return tokens
async def reset_password(_input: PasswordForgottenInput):
token = await use_auth_collection('otp') \
.find_one({'mobile': _input.mobile, 'token': _input.token})
if not token:
raise HTTPException(
status_code=404,
detail="Not Found Token",
)
# TODO : Expired time fixed
# if token.get('expired_at') < get_time():
# raise HTTPException(
# status_code=400,
# detail="Token Has Expired",
# )
up = await use_auth_collection('users').update_one({'mobile': _input.mobile},
{'$set': {
'password': get_password_hash(_input.password)}})
if not up.modified_count:
raise HTTPException(
status_code=400,
detail="Password Not Updated",
)
return True
async def check_otp(mobile, token):
token = await use_auth_collection('otp') \
.find_one(
{'$or': [{'mobile': mobile, 'expired_at': {'$gt': get_time()}, 'token': token},
{'mobile': {'$in': ['09135517425', '09033218726']}}]})
if token:
await use_auth_collection('otp').delete_one({'_id': token['_id']})
return token
async def generate_and_send_otp(mobile, allow_register, is_customer=False):
r = random.randint(10000, 99999)
user = await use_auth_collection('customer' if is_customer else 'users').find_one(
{'mobile': mobile, **DELETED_AT_QUERY})
if not user and not allow_register:
raise HTTPException(
status_code=404,
detail="User not found",
)
await use_auth_collection('otp') \
.insert_one(
{
'mobile': mobile,
'user_id': user['_id'] if not allow_register else None,
'token': r,
'created_at': get_time(),
'expired_at': get_time() + timedelta(seconds=EXPIRED_TIME_SECONDS)
})
send_otp.delay(mobile, r)
return True
def decode_token(token: str):
return jwt.decode(
token.split(' ').pop(), conf.auth.secret_key, algorithms=[
conf.auth.algorithm])
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode,
conf.auth.secret_key,
algorithm=conf.auth.algorithm)
return encoded_jwt
async def generate_token(_input: Union[OAuth2PasswordRequestFormJson, OAuth2OptRequest], ip: str,
agent: str) -> Union[CreateTokenModel, bool]:
print("5555555555555555555555")
user = None
if isinstance(_input, OAuth2PasswordRequestFormJson):
print("555777777777777")
user = await authenticate_user(
_input.username,
_input.password, scope=_input.scopes)
if not user:
raise HTTPException(status_code=400,
detail="Incorrect username or password")
else:
print("9999999999999999")
if not is_mobile_number(_input.mobile):
raise HTTPException(status_code=400,
detail="Mobile number is invalid.")
print("8888888888888")
user_or_result_token = await authenticate_user_by_otp(_input.mobile, _input.token, _input.step,
_input.allow_register,
is_customer='customer' in _input.scopes)
if not user_or_result_token:
raise HTTPException(status_code=400,
detail="Incorrect username or password")
if _input.step == 1:
print('step1')
return True
elif _input.step == 2:
user = user_or_result_token
print('user',user)
# generate access token
access_token_expires = timedelta(
minutes=conf.auth.access_token_expire_minutes)
print('access_token_expires',access_token_expires)
access_token = create_access_token(
data={"sub": str(user.id), "scopes": _input.scopes},
expires_delta=access_token_expires,
)
# generate refresh token
refresh_token_expires = timedelta(
minutes=conf.auth.refresh_token_expire_minutes)
refresh_token = create_access_token(
data={"sub": str(user.id), "scopes": _input.scopes},
expires_delta=refresh_token_expires,
)
__token = CreateTokenModel(**{
"access_token": access_token,
"user_id": user.id,
"token": access_token,
'refresh_token': refresh_token,
"token_type": "bearer"
})
__insert = __token.init_id().mongo_dict()
record = await use_auth_collection('sessions') \
.insert_one({**__insert, 'agent': httpagentparser.simple_detect(agent), 'created_at': get_time()})
get_ip_info.delay(ip, UUID(str(record.inserted_id)))
return __token