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