320 lines
12 KiB
Python
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
|