Initial commit
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
from typing import List
|
||||
|
||||
from scm.models.units import UnitModel
|
||||
|
||||
from auth.utils.mongo import use_auth_collection
|
||||
from core.app import app
|
||||
from core.utils.api import get_namespace_api
|
||||
|
||||
BASE = get_namespace_api('user')
|
||||
|
||||
|
||||
@app.get(BASE + '/me')
|
||||
async def get():
|
||||
return {"code": 0,
|
||||
"result": {"userId": "1",
|
||||
"username": "admin",
|
||||
"realName": "احمد رضا عزیزان",
|
||||
"avatar": "https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640",
|
||||
"desc": "manager",
|
||||
"password": "admin",
|
||||
"token": "fakeToken1",
|
||||
"homePath": "/dashboard/analysis",
|
||||
"roles": [{"roleName": "Super Admin",
|
||||
"value": "super"}]},
|
||||
"message": "ok",
|
||||
"type": "success"}
|
||||
|
||||
|
||||
@app.get(BASE + '/units')
|
||||
async def get_units() -> List[UnitModel]:
|
||||
cursor = use_auth_collection('units').find()
|
||||
return [UnitModel(**document) async for document in cursor]
|
||||
|
||||
|
||||
@app.post(BASE + '/units/create')
|
||||
async def post(document: UnitModel):
|
||||
document = document.init_id().mongo_dict()
|
||||
result = await use_auth_collection('units').insert_one(document)
|
||||
return result.inserted_id
|
||||
@@ -0,0 +1 @@
|
||||
from .tasks import *
|
||||
@@ -0,0 +1,2 @@
|
||||
from .auth import *
|
||||
from .property import *
|
||||
@@ -0,0 +1,242 @@
|
||||
import datetime
|
||||
import uuid
|
||||
from typing import Optional, Union, List, Literal
|
||||
|
||||
import pydantic
|
||||
from fastapi import Body
|
||||
from pydantic import Field, validator
|
||||
|
||||
from core.models.base import BaseModel, BaseCUAt
|
||||
from core.utils.utils import is_mobile_number, is_email
|
||||
|
||||
|
||||
class AuthModel(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
class SessionModel(BaseModel):
|
||||
id: Optional[uuid.UUID] = Field(alias='_id')
|
||||
user_id: Optional[uuid.UUID]
|
||||
access_token: Optional[str]
|
||||
token: Optional[str]
|
||||
refresh_token: Optional[str]
|
||||
token_type: Optional[str]
|
||||
agent: Optional[List[str]]
|
||||
ip_info: Optional[dict]
|
||||
|
||||
|
||||
class NotifyConfigModel(pydantic.BaseModel):
|
||||
sms_info: Optional[bool]
|
||||
sms_emergency: Optional[bool]
|
||||
push: Optional[bool]
|
||||
email: Optional[bool]
|
||||
|
||||
|
||||
class UserOptions(pydantic.BaseModel):
|
||||
active_password_login: Optional[bool]
|
||||
last_password_changes: Optional[datetime.datetime]
|
||||
active_otp: Optional[bool]
|
||||
active_token: Optional[bool]
|
||||
dark_mode: Optional[Literal['LIGHT', 'DARK', 'SYSTEM', 'DYNAMIC']]
|
||||
time_format: Optional[str]
|
||||
date_format: Optional[str]
|
||||
date_type: Optional[str]
|
||||
lang: Optional[str]
|
||||
notify: Optional[NotifyConfigModel]
|
||||
|
||||
|
||||
class VisualUserUpdate(pydantic.BaseModel):
|
||||
storage: Optional[str]
|
||||
|
||||
|
||||
class UserUpdateModel(BaseModel):
|
||||
username: Optional[str]
|
||||
email: Optional[str]
|
||||
name: Optional[str]
|
||||
first_name: Optional[str]
|
||||
last_name: Optional[str]
|
||||
unit: Optional[str]
|
||||
personnel_code: Optional[str]
|
||||
avatar: Optional[str]
|
||||
desc: Optional[str]
|
||||
gender: Optional[Literal[1, 2, 3]]
|
||||
home_path: Optional[str]
|
||||
force_password_change: Optional[bool]
|
||||
disabled: Optional[bool]
|
||||
|
||||
|
||||
class PasswordChangeModel(pydantic.BaseModel):
|
||||
old_password: str
|
||||
new_password: str = Field(min_length=6, max_length=28)
|
||||
|
||||
|
||||
class UserModel(BaseModel, BaseCUAt):
|
||||
id: Optional[Union[uuid.UUID, str]] = Field(alias='_id')
|
||||
username: Optional[str]
|
||||
mobile: Optional[str]
|
||||
email: Optional[str]
|
||||
name: Optional[Union[str,List[str]]]
|
||||
first_name: Optional[str]
|
||||
last_name: Optional[str]
|
||||
is_su: Optional[bool]
|
||||
unit: Optional[str]
|
||||
units: Optional[List[str]]
|
||||
personnel_code: Optional[str]
|
||||
avatar: Optional[str]
|
||||
desc: Optional[str]
|
||||
gender: Optional[Literal[1, 2, 3]]
|
||||
scopes: Optional[List[str]]
|
||||
options: Optional[UserOptions]
|
||||
home_path: Optional[str]
|
||||
force_password_change: Optional[bool]
|
||||
roles: Optional[List[uuid.UUID]]
|
||||
_options: Optional[dict]
|
||||
disabled: Optional[bool]
|
||||
|
||||
async def get_roles(self, exclude: [] = None, include: [] = None):
|
||||
from auth.utils.mongo import use_auth_collection
|
||||
projection = {}
|
||||
if exclude:
|
||||
projection = {
|
||||
f: 0 for f in exclude
|
||||
}
|
||||
|
||||
if include:
|
||||
projection = {
|
||||
f: 1 for f in include
|
||||
}
|
||||
|
||||
cursor = use_auth_collection('roles').find(
|
||||
{'_id': {'$in': [uuid.UUID(str(role)) for role in self.roles]}}, projection)
|
||||
return [role async for role in cursor]
|
||||
|
||||
async def get_units(self):
|
||||
roles = await self.get_roles(include=['units'])
|
||||
units = []
|
||||
for role in roles:
|
||||
units.extend(role.get('units', []))
|
||||
return units
|
||||
|
||||
def is_superuser(self) -> bool:
|
||||
return self.is_su
|
||||
|
||||
@validator("mobile")
|
||||
def mobile_validation(cls, v):
|
||||
if not is_mobile_number(v):
|
||||
raise ValueError('Mobile not valid')
|
||||
return v
|
||||
|
||||
@validator("email")
|
||||
def email_validation(cls, v):
|
||||
if not is_email(v):
|
||||
raise ValueError('Email not valid')
|
||||
return v
|
||||
|
||||
|
||||
class OAuth2PasswordRequestFormJson:
|
||||
"""
|
||||
This is a dependency class, use it like:
|
||||
|
||||
@app.post("/login")
|
||||
def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
data = form_data.parse()
|
||||
print(data.username)
|
||||
print(data.password)
|
||||
for scope in data.scopes:
|
||||
print(scope)
|
||||
if data.client_id:
|
||||
print(data.client_id)
|
||||
if data.client_secret:
|
||||
print(data.client_secret)
|
||||
return data
|
||||
|
||||
|
||||
It creates the following Form request parameters in your endpoint:
|
||||
|
||||
grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password".
|
||||
Nevertheless, this dependency class is permissive and allows not passing it. If you want to enforce it,
|
||||
use instead the OAuth2PasswordRequestFormStrict dependency.
|
||||
username: username string. The OAuth2 spec requires the exact field name "username".
|
||||
password: password string. The OAuth2 spec requires the exact field name "password".
|
||||
scope: Optional string. Several scopes (each one a string) separated by spaces. E.g.
|
||||
"items:read items:write users:read profile openid"
|
||||
client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
|
||||
using HTTP Basic auth, as: client_id:client_secret
|
||||
client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
|
||||
using HTTP Basic auth, as: client_id:client_secret
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
grant_type: str = Body(None, regex="password"),
|
||||
username: str = Body(...),
|
||||
password: str = Body(...),
|
||||
scopes: Optional[List[str]] = Body(['default']),
|
||||
client_id: Optional[str] = Body(None),
|
||||
client_secret: Optional[str] = Body(None),
|
||||
):
|
||||
self.grant_type = grant_type
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.scopes = scopes
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
|
||||
|
||||
class OAuth2OptRequest:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
grant_type: str = Body(None, regex="password"),
|
||||
allow_register: bool = Body(None),
|
||||
mobile: str = Body(...),
|
||||
token: Optional[int] = Body(None),
|
||||
scopes: Optional[List[str]] = Body(['customer']),
|
||||
step: Literal[1, 2] = Body(1),
|
||||
client_id: Optional[str] = Body(None),
|
||||
client_secret: Optional[str] = Body(None),
|
||||
):
|
||||
self.grant_type = grant_type
|
||||
self.allow_register = allow_register
|
||||
self.mobile = mobile
|
||||
self.token = token
|
||||
self.scopes = scopes
|
||||
self.step = step
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
username: Optional[str] = None
|
||||
scopes: List[str] = []
|
||||
|
||||
|
||||
class UserInDB(UserModel):
|
||||
password: Optional[str]
|
||||
|
||||
|
||||
class CreateTokenModel(BaseModel):
|
||||
id: Optional[Union[uuid.UUID, str]] = Field(alias='_id')
|
||||
user_id: Optional[uuid.UUID]
|
||||
mode: Optional[Literal['user_admin', 'customer']]
|
||||
access_token: str
|
||||
token: str
|
||||
refresh_token: str
|
||||
token_type: str = 'bearer'
|
||||
|
||||
|
||||
class PasswordForgottenInput(pydantic.BaseModel):
|
||||
password: str
|
||||
mode: Optional[Literal['user_admin', 'customer']]
|
||||
mobile: str
|
||||
token: int
|
||||
|
||||
|
||||
class UIdsInput(BaseModel):
|
||||
u_ids: List[uuid.UUID]
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Optional, Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from core.models.base import BaseModel
|
||||
|
||||
|
||||
class AuthPropertyModel(BaseModel):
|
||||
id: Optional[UUID] = Field(alias='_id')
|
||||
key: Optional[str]
|
||||
user_id: Optional[UUID]
|
||||
role_id: Optional[UUID]
|
||||
value: Optional[Any]
|
||||
meta: Optional[Any]
|
||||
parent_id: Optional[UUID]
|
||||
@@ -0,0 +1,22 @@
|
||||
import uuid
|
||||
from typing import Optional, Union, List
|
||||
|
||||
import pydantic
|
||||
from pydantic import Field
|
||||
|
||||
from core.models.base import BaseModel, BaseCUAt
|
||||
|
||||
|
||||
class ModuleModel(pydantic.BaseModel):
|
||||
title: str
|
||||
icon: str
|
||||
description: Optional[str]
|
||||
url: Optional[str]
|
||||
|
||||
|
||||
class RoleModel(BaseModel, BaseCUAt):
|
||||
id: Optional[Union[uuid.UUID, str]] = Field(alias='_id')
|
||||
title: str
|
||||
f_routes: Optional[List[str]]
|
||||
units: Optional[List[uuid.UUID]]
|
||||
modules: Optional[List[ModuleModel]]
|
||||
@@ -0,0 +1,12 @@
|
||||
import uuid
|
||||
from typing import Optional, Union, List
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from core.models.base import BaseSoftDelete, BaseModel
|
||||
|
||||
|
||||
class UnitModel(BaseModel, BaseSoftDelete):
|
||||
id: Union[Optional[uuid.UUID], str] = Field(alias='_id')
|
||||
title: str
|
||||
parents: Optional[List[uuid.UUID]]
|
||||
@@ -0,0 +1,165 @@
|
||||
from http.client import HTTPException
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
from auth.models import UserInDB, UserModel, UIdsInput, UserOptions, SessionModel, PasswordChangeModel, UserUpdateModel, \
|
||||
VisualUserUpdate
|
||||
from auth.models.roles import RoleModel, ModuleModel
|
||||
from auth.models.units import UnitModel
|
||||
from auth.utils.mongo import use_auth_collection
|
||||
from auth.utils.pwd import get_password_hash, verify_password
|
||||
from core.exceptions import ModifiedCountException, ValidationException
|
||||
from core.models.base import DELETED_AT_QUERY
|
||||
from core.utils.app_context import use_user
|
||||
from core.utils.crudify import mongo_soft_deleter
|
||||
|
||||
COLLECTION_UNITS = 'units'
|
||||
COLLECTION_USERS = 'users'
|
||||
COLLECTION_ROLES = 'roles'
|
||||
COLLECTION_SESSIONS = 'sessions'
|
||||
|
||||
_projection_user = {
|
||||
'mobile': 1,
|
||||
'email': 1,
|
||||
'username': 1,
|
||||
'_id': 1,
|
||||
'name': 1
|
||||
}
|
||||
|
||||
|
||||
class AuthRepository:
|
||||
@staticmethod
|
||||
async def create(_input: UserInDB) -> dict:
|
||||
record = _input.init_id().cu_at().mongo_dict()
|
||||
record['password'] = get_password_hash(_input.password)
|
||||
insert = await use_auth_collection(COLLECTION_USERS).insert_one(record)
|
||||
if not insert.acknowledged:
|
||||
raise HTTPException(status_code=400, detail="User already exists")
|
||||
return insert.inserted_id
|
||||
|
||||
@staticmethod
|
||||
async def user_query(query: str = '') -> List[UserModel]:
|
||||
q = {'$or': [{'name': {'$regex': query.strip()}},
|
||||
{'mobile': {'$regex': query.strip()}}
|
||||
]} if query.strip() else {}
|
||||
cursor = use_auth_collection('users').find(q, {'_id': 1, 'name': 1, 'mobile': 1})
|
||||
return [UserModel(**user) async for user in cursor]
|
||||
|
||||
@staticmethod
|
||||
async def get_units():
|
||||
cursor = use_auth_collection(COLLECTION_UNITS).find()
|
||||
return [UnitModel(**document) async for document in cursor]
|
||||
|
||||
@staticmethod
|
||||
async def get_user(_id: UUID) -> UserModel:
|
||||
user = await use_auth_collection(COLLECTION_USERS).find_one({"_id": _id}, _projection_user)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return UserModel(**user)
|
||||
|
||||
@staticmethod
|
||||
async def update_options(options: UserOptions) -> bool:
|
||||
user = use_user()
|
||||
user = await use_auth_collection(COLLECTION_USERS) \
|
||||
.update_one({"_id": user.id}, {"$set": {"options": options.dict()}})
|
||||
if user.modified_count == 0:
|
||||
raise ModifiedCountException("User not updated")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def update_avatar(av_input: VisualUserUpdate) -> bool:
|
||||
user = use_user()
|
||||
_update_query = {"$set": {"avatar": av_input.storage}} if av_input.storage else {'$unset': {'avatar': 1}}
|
||||
user = await use_auth_collection(COLLECTION_USERS) \
|
||||
.update_one({"_id": user.id}, _update_query)
|
||||
if user.modified_count == 0:
|
||||
raise ModifiedCountException("User not updated")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def update_password(_input: PasswordChangeModel) -> bool:
|
||||
user = use_user()
|
||||
user = await use_auth_collection(COLLECTION_USERS).find_one({'_id': user.id})
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if not verify_password(_input.old_password, user.get('password', None)):
|
||||
raise ValidationException('Old Password not valid')
|
||||
user['password'] = get_password_hash(_input.new_password)
|
||||
user = await use_auth_collection(COLLECTION_USERS).update_one({'_id': user.get('_id')},
|
||||
{'$set': {
|
||||
'password': user['password']}})
|
||||
if user.modified_count == 0:
|
||||
raise ModifiedCountException("Password")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def update_user(_input: UserUpdateModel) -> bool:
|
||||
user = use_user()
|
||||
user = await use_auth_collection(COLLECTION_USERS).find_one({'_id': UUID(str(user.id))})
|
||||
user = await use_auth_collection(COLLECTION_USERS).update_one({'_id': user.get('_id')},
|
||||
{'$set': _input.u_at().dict()})
|
||||
if user.modified_count == 0:
|
||||
raise ModifiedCountException("User not update")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def get_my_sessions() -> List[SessionModel]:
|
||||
user = use_user()
|
||||
user = use_auth_collection(COLLECTION_SESSIONS).find({"user_id": user.id, **DELETED_AT_QUERY})
|
||||
return [SessionModel(**x) async for x in user]
|
||||
|
||||
@staticmethod
|
||||
async def delete_session(session_id: UUID) -> bool:
|
||||
return await mongo_soft_deleter(session_id, COLLECTION_SESSIONS, client=use_auth_collection)
|
||||
|
||||
@staticmethod
|
||||
async def get_users_by_id(_input: UIdsInput) -> List[UserModel]:
|
||||
cursor = use_auth_collection(COLLECTION_USERS) \
|
||||
.find({'_id': {'$in': _input.u_ids}})
|
||||
return [UserModel(**user) async for user in cursor]
|
||||
|
||||
@staticmethod
|
||||
async def create_unit(_input: UnitModel) -> str:
|
||||
document = _input.init_id().mongo_dict()
|
||||
result = await use_auth_collection(COLLECTION_UNITS).insert_one(document)
|
||||
return result.inserted_id
|
||||
|
||||
@staticmethod
|
||||
async def get_roles() -> List[dict]:
|
||||
roles = await use_auth_collection(COLLECTION_ROLES).find().to_list(None)
|
||||
return [role for role in roles]
|
||||
|
||||
@staticmethod
|
||||
async def create_role(_input: RoleModel) -> str:
|
||||
record = _input.init_id().cu_at().mongo_dict()
|
||||
insert = await use_auth_collection(COLLECTION_ROLES).insert_one(record)
|
||||
if not insert.acknowledged:
|
||||
raise HTTPException("Error creating role")
|
||||
return insert.inserted_id
|
||||
|
||||
@staticmethod
|
||||
async def get_my_modules() -> List[ModuleModel]:
|
||||
user = use_user()
|
||||
records = await use_auth_collection(COLLECTION_ROLES).find(
|
||||
{'_id': {'$in': user.roles or []}, **DELETED_AT_QUERY}, {'modules': True}).to_list(None)
|
||||
if not records:
|
||||
return []
|
||||
_list = [x.get('modules', []) for x in records]
|
||||
if _list:
|
||||
return list([item for sublist in _list for item in sublist])
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
async def get_f_routes(roles_ids: List[UUID]) -> List[str]:
|
||||
|
||||
roles = await use_auth_collection(COLLECTION_ROLES).find(
|
||||
{'_id': {'$in': roles_ids}}) \
|
||||
.to_list(None)
|
||||
|
||||
def _union_list(*kwargs):
|
||||
final = []
|
||||
for kwarg in kwargs:
|
||||
final.extend(kwarg)
|
||||
return final
|
||||
|
||||
return _union_list(*[role['f_routes'] for role in roles])
|
||||
@@ -0,0 +1,55 @@
|
||||
from typing import Union, List
|
||||
from uuid import UUID
|
||||
|
||||
from auth.models import AuthPropertyModel
|
||||
from auth.utils.mongo import use_auth_collection
|
||||
from core.models.base import DELETED_AT_QUERY
|
||||
from core.utils.app_context import use_user
|
||||
from core.utils.crudify import mongo_inserter, mongo_updater, mongo_find_one, mongo_soft_deleter
|
||||
|
||||
COLLECTION_PROPERTY = 'property'
|
||||
|
||||
|
||||
class PropertyRepository:
|
||||
@staticmethod
|
||||
async def create(_input: AuthPropertyModel) -> dict:
|
||||
return await mongo_inserter(_input, COLLECTION_PROPERTY, client=use_auth_collection,
|
||||
getter=PropertyRepository.get)
|
||||
|
||||
@staticmethod
|
||||
async def update(_id: UUID, _input: AuthPropertyModel) -> bool:
|
||||
return await mongo_updater(_input,
|
||||
_id,
|
||||
COLLECTION_PROPERTY,
|
||||
client=use_auth_collection,
|
||||
getter=PropertyRepository.get)
|
||||
|
||||
@staticmethod
|
||||
async def get(_input: Union[UUID, str]) -> AuthPropertyModel:
|
||||
"""
|
||||
get argument from args
|
||||
Args:
|
||||
_input: Union[UUID,str]
|
||||
Returns:
|
||||
AuthPropertyModel
|
||||
"""
|
||||
q = {'key': _input} if isinstance(_input, str) else {'_id': _input} if isinstance(_input, UUID) else _input
|
||||
q['owner_id'] = use_user().id
|
||||
result = await mongo_find_one(q, COLLECTION_PROPERTY, client=use_auth_collection)
|
||||
return AuthPropertyModel(**result)
|
||||
|
||||
@staticmethod
|
||||
async def fetch() -> List[AuthPropertyModel]:
|
||||
q = {}
|
||||
q['owner_id'] = use_user().id
|
||||
result = await use_auth_collection(COLLECTION_PROPERTY) \
|
||||
.find({**q, **DELETED_AT_QUERY}) \
|
||||
.to_list(100)
|
||||
return [AuthPropertyModel(**x) for x in result]
|
||||
|
||||
@staticmethod
|
||||
async def delete(_input: Union[UUID, str]) -> bool:
|
||||
q = {'key': _input} if isinstance(_input, str) else {'_id': _input}
|
||||
q['owner_id'] = use_user().id
|
||||
result = await mongo_soft_deleter(q, COLLECTION_PROPERTY, client=use_auth_collection)
|
||||
return result
|
||||
@@ -0,0 +1,54 @@
|
||||
from urllib.request import Request
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException, Depends, Security
|
||||
from fastapi.security import SecurityScopes
|
||||
from httpx import ConnectError
|
||||
|
||||
from .config import AUTH_SERVER_URL
|
||||
from .model import UserModel, ValidateTokenInput
|
||||
from .schema import oauth2
|
||||
|
||||
|
||||
async def validate_token(_input: ValidateTokenInput):
|
||||
try:
|
||||
client = httpx.AsyncClient()
|
||||
headers = {'Content-Type': 'application/json', }
|
||||
result = await client.request('POST', AUTH_SERVER_URL, json=_input.dict(), headers=headers)
|
||||
export = result.text
|
||||
if 'application/json' in result.headers['content-type']:
|
||||
export = result.json()
|
||||
if result.status_code > 299:
|
||||
raise HTTPException(status_code=result.status_code, detail=export)
|
||||
return export
|
||||
except ConnectError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except HTTPException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(oauth2)):
|
||||
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 a",
|
||||
headers={"WWW-Authenticate": authenticate_value},
|
||||
)
|
||||
user = await validate_token(ValidateTokenInput(token=token, scopes=security_scopes.scopes))
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
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: UserModel = Security(get_current_user, scopes=["account"]),):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
request.state.user = UserModel(**current_user.dict())
|
||||
return current_user
|
||||
@@ -0,0 +1 @@
|
||||
AUTH_SERVER_URL = 'https://scm.hacoupian.net/api/scm/v1/auth/validate-token'
|
||||
@@ -0,0 +1,26 @@
|
||||
import uuid
|
||||
from typing import Optional, Union, List, Any
|
||||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
|
||||
class UserModel(BaseModel):
|
||||
id: Optional[Union[uuid.UUID, str]] = Field(alias='_id')
|
||||
username: Optional[str]
|
||||
mobile: Optional[str]
|
||||
name: Optional[str]
|
||||
first_name: Optional[str]
|
||||
last_name: Optional[str]
|
||||
is_su: Optional[bool]
|
||||
unit: Optional[str]
|
||||
avatar: Optional[str]
|
||||
desc: Optional[str]
|
||||
scopes: Optional[List[str]]
|
||||
home_path: Optional[str]
|
||||
roles: Optional[List[Any]]
|
||||
disabled: Optional[bool]
|
||||
|
||||
|
||||
class ValidateTokenInput(BaseModel):
|
||||
token: str
|
||||
scopes: Optional[List[str]] = ['account']
|
||||
@@ -0,0 +1,9 @@
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
oauth2 = OAuth2PasswordBearer(
|
||||
tokenUrl="token",
|
||||
scopes={
|
||||
"account": "Read information about the current user.",
|
||||
"scm": "Scm Modules",
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
from uuid import UUID
|
||||
|
||||
from app.models.customer import CustomerModel
|
||||
from auth.models import UserInDB
|
||||
from auth.utils.mongo import use_auth_collection
|
||||
|
||||
|
||||
async def get_user_by_username(username: str) -> UserInDB:
|
||||
"""
|
||||
Retrieves user from the database by username.
|
||||
"""
|
||||
result = await use_auth_collection('users').find_one({'username': username})
|
||||
return UserInDB(**result) if result else None
|
||||
|
||||
|
||||
async def get_customer_by_username(username: str) -> UserInDB:
|
||||
"""
|
||||
Retrieves user from the database by username.
|
||||
"""
|
||||
result = await use_auth_collection('customer').find_one({'$or': [{'username': username}, {'mobile': username}]})
|
||||
return UserInDB(**result) if result else None
|
||||
|
||||
|
||||
async def get_user_by_id(user_id: str) -> UserInDB | None:
|
||||
"""
|
||||
Retrieves user from the database by username.
|
||||
"""
|
||||
try:
|
||||
uuid = UUID(str(user_id))
|
||||
except ValueError:
|
||||
return None
|
||||
result = await use_auth_collection('users').find_one({'_id': uuid})
|
||||
return UserInDB(**result) if result else None
|
||||
|
||||
|
||||
async def get_customer_by_id(customer_id: str) -> UserInDB | None:
|
||||
"""
|
||||
Retrieves user from the database by username.
|
||||
"""
|
||||
try:
|
||||
uuid = UUID(str(customer_id))
|
||||
except ValueError:
|
||||
return None
|
||||
result = await use_auth_collection('customer').find_one({'_id': uuid})
|
||||
return CustomerModel(**result) if result else None
|
||||
@@ -0,0 +1,36 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from core.models.base import BaseModel
|
||||
|
||||
|
||||
class RouteMeta(BaseModel):
|
||||
orderNo: int
|
||||
title: str
|
||||
ignoreAuth: Optional[bool]
|
||||
roles: Optional[List[str]]
|
||||
ignoreKeepAlive: Optional[bool]
|
||||
affix: Optional[bool]
|
||||
icon: Optional[str]
|
||||
frameSrc: Optional[str]
|
||||
transitionName: Optional[str]
|
||||
hideBreadcrumb: Optional[bool]
|
||||
hideChildrenInMenu: Optional[bool]
|
||||
carryParam: Optional[bool]
|
||||
single: Optional[bool]
|
||||
currentActiveMenu: Optional[str]
|
||||
hideTab: Optional[bool]
|
||||
hideMenu: Optional[bool]
|
||||
isLink: Optional[bool]
|
||||
ignoreRoute: Optional[bool]
|
||||
hidePathForChildren: Optional[bool]
|
||||
|
||||
|
||||
class VueRouteNative(BaseModel):
|
||||
name: str
|
||||
meta: RouteMeta
|
||||
props: Optional[dict]
|
||||
fullPath: Optional[str]
|
||||
|
||||
|
||||
class VueRoute(VueRouteNative):
|
||||
children: List[VueRouteNative]
|
||||
@@ -0,0 +1,29 @@
|
||||
from uuid import UUID
|
||||
|
||||
import httpx
|
||||
from asgiref.sync import async_to_sync
|
||||
|
||||
from task import celery
|
||||
|
||||
|
||||
@celery.task(name='get_ip_info')
|
||||
def get_ip_info(ip: str, session_id: UUID):
|
||||
from auth.utils.ip_info import get_info
|
||||
from auth.utils.mongo import use_auth_collection
|
||||
result = get_info(ip)
|
||||
async_to_sync(use_auth_collection('sessions').update_one({'_id': session_id},
|
||||
{'$set': {'ip_info': result}}))
|
||||
|
||||
|
||||
@celery.task(name='send_otp')
|
||||
def send_otp(mobile: str, token: int):
|
||||
response = httpx.get("http://www.0098sms.com/sendsmslink.aspx",
|
||||
params={
|
||||
"TO": mobile,
|
||||
"FROM": 3000164545,
|
||||
"TEXT": f'وای گرام. بفرمایید کد ورود یکبار مصرف: {token} ',
|
||||
"USERNAME": "ysms1362",
|
||||
"PASSWORD": "1234@Abcgh",
|
||||
"DOMAIN": "0098"
|
||||
})
|
||||
return response.text
|
||||
@@ -0,0 +1,3 @@
|
||||
from .auth import *
|
||||
from .role import *
|
||||
from .user import *
|
||||
@@ -0,0 +1,68 @@
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import Depends, APIRouter, Body, Request
|
||||
from fastapi.security import SecurityScopes
|
||||
from pydantic import BaseModel
|
||||
|
||||
from auth.models import UserModel, OAuth2PasswordRequestFormJson, CreateTokenModel, OAuth2OptRequest, \
|
||||
PasswordForgottenInput
|
||||
from auth.utils import get_current_active_user, get_current_user, generate_token, reset_password
|
||||
from config import conf
|
||||
from core.app import app
|
||||
from core.utils import to_http_res
|
||||
from core.utils.router import BaseRoute, safe_route
|
||||
|
||||
_pre = conf.server.prefix_url
|
||||
|
||||
router = APIRouter(
|
||||
prefix=f"{_pre}/auth",
|
||||
route_class=BaseRoute,
|
||||
)
|
||||
|
||||
_tag_base = ['token']
|
||||
|
||||
|
||||
class ValidateTokenInput(BaseModel):
|
||||
token: str
|
||||
scopes: Optional[List[str]] = ['account']
|
||||
|
||||
|
||||
@router.post('/login/otp', response_model=CreateTokenModel, tags=_tag_base)
|
||||
async def login_with_otp(request: Request, form_data: OAuth2OptRequest = Depends()):
|
||||
client_host = request.client.host
|
||||
return await safe_route('login', generate_token, form_data, client_host, request.headers.get('user-agent'))
|
||||
|
||||
|
||||
@router.post("/token", response_model=CreateTokenModel, tags=_tag_base)
|
||||
async def login_for_access_token(request: Request, form_data: OAuth2PasswordRequestFormJson = Depends()):
|
||||
client_host = request.client.host
|
||||
return await safe_route('login', generate_token, form_data, client_host, request.headers.get('user-agent'))
|
||||
|
||||
|
||||
@router.post('/login', response_model=CreateTokenModel, tags=_tag_base)
|
||||
async def login(request: Request, data: OAuth2PasswordRequestFormJson = Depends()):
|
||||
client_host = request.client.host
|
||||
return await safe_route('login', generate_token, data, client_host, request.headers.get('user-agent'))
|
||||
|
||||
|
||||
@router.post('/reset-password', response_model=bool, tags=_tag_base)
|
||||
async def reset_password_p(_input: PasswordForgottenInput):
|
||||
return await safe_route('update', reset_password, _input)
|
||||
|
||||
|
||||
@router.get('/logout', response_model=bool, tags=_tag_base)
|
||||
async def logout():
|
||||
return to_http_res(True, 'token')
|
||||
|
||||
|
||||
@router.get("/status", tags=_tag_base)
|
||||
async def token_status(current_user: UserModel = Depends(get_current_active_user)):
|
||||
return to_http_res('get', {"status": "token is valid", "scope": current_user.scopes})
|
||||
|
||||
|
||||
@router.post('/validate-token', tags=_tag_base)
|
||||
async def validate_token(body: ValidateTokenInput = Body(...)):
|
||||
return await safe_route('get', get_current_user, SecurityScopes(scopes=body.scopes), body.token)
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
@@ -0,0 +1,36 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import Request, Body
|
||||
|
||||
from auth.models.roles import RoleModel
|
||||
from auth.repositories.AuthRepository import AuthRepository
|
||||
from core.app import app
|
||||
from core.utils.router import generate_router, safe_route
|
||||
|
||||
router = generate_router("roles", auth_dependencies=True)
|
||||
|
||||
_tag_base = ['roles']
|
||||
|
||||
|
||||
@router.get('/routes', tags=_tag_base)
|
||||
async def get_routes(request: Request) -> List[str]:
|
||||
roles_ids = request.state.user.roles if request.state.user.roles else []
|
||||
return await safe_route('fetch', AuthRepository.get_f_routes, roles_ids)
|
||||
|
||||
|
||||
@router.post('/create', tags=_tag_base)
|
||||
async def create(_input: RoleModel = Body(...)):
|
||||
return await safe_route('create', AuthRepository.create_role, _input)
|
||||
|
||||
|
||||
@router.get('/modules', tags=_tag_base)
|
||||
async def get_my_modules():
|
||||
return await safe_route('fetch', AuthRepository.get_my_modules)
|
||||
|
||||
|
||||
@router.post('', tags=_tag_base)
|
||||
async def fetch() -> List[str]:
|
||||
return await safe_route('fetch', AuthRepository.get_roles)
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
@@ -0,0 +1,85 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Body, Depends
|
||||
|
||||
from auth.models import UserModel, UserInDB, UIdsInput, UserOptions, PasswordChangeModel, UserUpdateModel, \
|
||||
VisualUserUpdate
|
||||
from auth.repositories.AuthRepository import AuthRepository
|
||||
from auth.utils import get_current_active_user
|
||||
from auth.utils.mongo import get_auth_db
|
||||
from core.app import app
|
||||
from core.utils import to_http_res
|
||||
from core.utils.router import safe_route, generate_router
|
||||
from core.utils.sumery import setup_sumery_object, SumeryObject
|
||||
|
||||
setup_sumery_object(
|
||||
SumeryObject(db=get_auth_db(),
|
||||
object_name='users',
|
||||
table_name='users',
|
||||
value_col='name',
|
||||
key_col='_id',
|
||||
cols_search=['name', 'mobile']
|
||||
))
|
||||
|
||||
router = generate_router("users", auth_dependencies=True)
|
||||
|
||||
_tag_base = ['users']
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserModel, tags=_tag_base)
|
||||
async def read_users_me(current_user: UserModel = Depends(get_current_active_user)):
|
||||
print("123")
|
||||
return to_http_res(current_user, 'get')
|
||||
|
||||
|
||||
@router.get('/query/{query}', tags=_tag_base)
|
||||
async def users(query: str = None):
|
||||
return await safe_route('fetch', AuthRepository.user_query, query)
|
||||
|
||||
|
||||
@router.get("/sessions", tags=_tag_base)
|
||||
async def get_my_sessions():
|
||||
return await safe_route('fetch', AuthRepository.get_my_sessions)
|
||||
|
||||
|
||||
@router.get("/{_id}", tags=_tag_base)
|
||||
async def get_user_by_id_api(_id: UUID):
|
||||
return await safe_route('fetch', AuthRepository.get_user, _id)
|
||||
|
||||
|
||||
@router.put("/update-password", tags=_tag_base)
|
||||
async def update_password(password: PasswordChangeModel = Body(...)):
|
||||
return await safe_route('update', AuthRepository.update_password, password)
|
||||
|
||||
|
||||
@router.put("/options", tags=_tag_base)
|
||||
async def update_options(options: UserOptions = Body(...)):
|
||||
return await safe_route('update', AuthRepository.update_options, options)
|
||||
|
||||
|
||||
@router.put("/update-avatar", tags=_tag_base)
|
||||
async def update_avatar(options: VisualUserUpdate = Body(...)):
|
||||
return await safe_route('update', AuthRepository.update_avatar, options)
|
||||
|
||||
|
||||
@router.put("", tags=_tag_base)
|
||||
async def update_user(_input: UserUpdateModel = Body(...)):
|
||||
return await safe_route('fetch', AuthRepository.update_user, _input)
|
||||
|
||||
|
||||
@router.delete("/sessions/{_id}", tags=_tag_base)
|
||||
async def delete_session(_id: UUID):
|
||||
return await safe_route('fetch', AuthRepository.delete_session, _id)
|
||||
|
||||
|
||||
@router.post("/create", tags=_tag_base)
|
||||
async def create_user(_input: UserInDB = Body(...)):
|
||||
return await safe_route('create', AuthRepository.create, _input)
|
||||
|
||||
|
||||
@router.post('', tags=_tag_base)
|
||||
async def users(body: UIdsInput = Body(...)):
|
||||
return await safe_route('fetch', AuthRepository.get_users_by_id, body)
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
@@ -0,0 +1,319 @@
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
from ip2geotools.databases.noncommercial import DbIpCity
|
||||
|
||||
|
||||
def get_info(ip: str):
|
||||
response = DbIpCity.get(ip, api_key='free')
|
||||
return response.__dict__
|
||||
@@ -0,0 +1,14 @@
|
||||
from pymongo.collection import Collection
|
||||
|
||||
from config import conf
|
||||
from core.service.mongo_db import use_collection
|
||||
|
||||
|
||||
def get_auth_db():
|
||||
return conf.server.is_prod and conf.database.get('mongo').production.db or conf.database.get(
|
||||
'mongo').dev.db
|
||||
|
||||
|
||||
def use_auth_collection(collection: str) -> Collection:
|
||||
print("pppppppppppppppppppp")
|
||||
return use_collection(collection, get_auth_db(), connection_name='mongo_auth')
|
||||
@@ -0,0 +1,11 @@
|
||||
from passlib.context import CryptContext
|
||||
|
||||
context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password):
|
||||
return context.hash(password)
|
||||
@@ -0,0 +1,9 @@
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
oauth2 = OAuth2PasswordBearer(
|
||||
tokenUrl="token",
|
||||
scopes={
|
||||
"account": "Read information about the current user.",
|
||||
"scm": "Scm Modules",
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user