mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: user init & refactors
This commit is contained in:
parent
b4dcc14749
commit
a50fc67104
25 changed files with 356 additions and 358 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from config.config import LearnHouseConfig, get_learnhouse_config
|
from config.config import LearnHouseConfig, get_learnhouse_config
|
||||||
from src.core.events.events import shutdown_app, startup_app
|
from src.core.events.events import shutdown_app, startup_app
|
||||||
from src.router import v1_router, rewrite
|
from src.router import v1_router
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
@ -60,7 +60,6 @@ app.mount("/content", StaticFiles(directory="content"), name="content")
|
||||||
|
|
||||||
# Global Routes
|
# Global Routes
|
||||||
app.include_router(v1_router)
|
app.include_router(v1_router)
|
||||||
app.include_router(rewrite)
|
|
||||||
|
|
||||||
# General Routes
|
# General Routes
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from fastapi import FastAPI
|
||||||
import motor.motor_asyncio
|
import motor.motor_asyncio
|
||||||
from sqlmodel import Field, SQLModel, Session, create_engine
|
from sqlmodel import Field, SQLModel, Session, create_engine
|
||||||
|
|
||||||
from src.rewrite.services.db import (
|
from src.db import (
|
||||||
user_organizations,
|
user_organizations,
|
||||||
users,
|
users,
|
||||||
roles,
|
roles,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from sqlmodel import Field, SQLModel
|
||||||
class RoleTypeEnum(str, Enum):
|
class RoleTypeEnum(str, Enum):
|
||||||
ORGANIZATION = "ORGANIZATION"
|
ORGANIZATION = "ORGANIZATION"
|
||||||
ORGANIZATION_API_TOKEN = "ORGANIZATION_API_TOKEN"
|
ORGANIZATION_API_TOKEN = "ORGANIZATION_API_TOKEN"
|
||||||
DEFAULT = "DEFAULT"
|
GLOBAL = "GLOBAL"
|
||||||
|
|
||||||
|
|
||||||
class RoleBase(SQLModel):
|
class RoleBase(SQLModel):
|
||||||
|
|
@ -19,7 +19,7 @@ class RoleBase(SQLModel):
|
||||||
class Role(RoleBase, table=True):
|
class Role(RoleBase, table=True):
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
org_id: int = Field(default=None, foreign_key="organization.id")
|
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||||
role_type: RoleTypeEnum = RoleTypeEnum.DEFAULT
|
role_type: RoleTypeEnum = RoleTypeEnum.GLOBAL
|
||||||
role_uuid: str
|
role_uuid: str
|
||||||
creation_date: str
|
creation_date: str
|
||||||
update_date: str
|
update_date: str
|
||||||
|
|
@ -16,7 +16,18 @@ class UserCreate(UserBase):
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(UserBase):
|
class UserUpdate(UserBase):
|
||||||
password: Optional[str] = None
|
username: str
|
||||||
|
first_name: Optional[str]
|
||||||
|
last_name: Optional[str]
|
||||||
|
email: str
|
||||||
|
avatar_image: Optional[str] = ""
|
||||||
|
bio: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdatePassword(SQLModel):
|
||||||
|
user_id: int
|
||||||
|
old_password: str
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
|
||||||
class UserRead(UserBase):
|
class UserRead(UserBase):
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends, Request
|
|
||||||
from sqlmodel import Session
|
|
||||||
from src.core.events.database import get_db_session
|
|
||||||
|
|
||||||
from src.rewrite.services.db.users import UserCreate, UserRead
|
|
||||||
from src.rewrite.services.users.users import create_user
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=UserRead, tags=["users"])
|
|
||||||
async def api_create_user(
|
|
||||||
*,
|
|
||||||
request: Request,
|
|
||||||
db_session: Session = Depends(get_db_session),
|
|
||||||
user_object: UserCreate,
|
|
||||||
org_slug: str
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Create new user
|
|
||||||
"""
|
|
||||||
return await create_user(request, db_session, None, user_object, org_slug)
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
from uuid import uuid4
|
|
||||||
from fastapi import Request
|
|
||||||
from sqlmodel import Session
|
|
||||||
from src.rewrite.services.db.users import User, UserCreate
|
|
||||||
from src.security.security import security_hash_password
|
|
||||||
from src.services.users.schemas.users import PublicUser
|
|
||||||
|
|
||||||
|
|
||||||
async def create_user(
|
|
||||||
request: Request,
|
|
||||||
db_session: Session,
|
|
||||||
current_user: PublicUser | None,
|
|
||||||
user_object: UserCreate,
|
|
||||||
org_slug: str,
|
|
||||||
):
|
|
||||||
user = User.from_orm(user_object)
|
|
||||||
|
|
||||||
# Complete the user object
|
|
||||||
user.user_uuid = f"user_{uuid4()}"
|
|
||||||
user.password = await security_hash_password(user_object.password)
|
|
||||||
user.email_verified = False
|
|
||||||
user.creation_date = str(datetime.now())
|
|
||||||
user.update_date = str(datetime.now())
|
|
||||||
|
|
||||||
# Verifications
|
|
||||||
#todo: add username uniqueness verification
|
|
||||||
#todo: add email uniqueness verification
|
|
||||||
|
|
||||||
|
|
||||||
#todo: add user to org as member if org is not None
|
|
||||||
|
|
||||||
# Exclude unset values
|
|
||||||
user_data = user.dict(exclude_unset=True)
|
|
||||||
for key, value in user_data.items():
|
|
||||||
setattr(user, key, value)
|
|
||||||
|
|
||||||
# Add user to database
|
|
||||||
db_session.add(user)
|
|
||||||
db_session.commit()
|
|
||||||
db_session.refresh(user)
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from src import rewrite
|
|
||||||
from src.routers import blocks, dev, trail, users, auth, orgs, roles
|
from src.routers import blocks, dev, trail, users, auth, orgs, roles
|
||||||
from src.rewrite.routers import users as rw_users
|
|
||||||
from src.routers.courses import chapters, collections, courses, activities
|
from src.routers.courses import chapters, collections, courses, activities
|
||||||
from src.routers.install import install
|
from src.routers.install import install
|
||||||
from src.services.dev.dev import isDevModeEnabledOrRaise
|
from src.services.dev.dev import isDevModeEnabledOrRaise
|
||||||
|
|
@ -9,7 +7,6 @@ from src.services.install.install import isInstallModeEnabled
|
||||||
|
|
||||||
|
|
||||||
v1_router = APIRouter(prefix="/api/v1")
|
v1_router = APIRouter(prefix="/api/v1")
|
||||||
rewrite = APIRouter(prefix="/api/rewrite")
|
|
||||||
|
|
||||||
|
|
||||||
# API Routes
|
# API Routes
|
||||||
|
|
@ -39,5 +36,3 @@ v1_router.include_router(
|
||||||
dependencies=[Depends(isInstallModeEnabled)],
|
dependencies=[Depends(isInstallModeEnabled)],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rewrite Routes
|
|
||||||
rewrite.include_router(rw_users.router, prefix="/users", tags=["users"])
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
|
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src.db.users import UserRead
|
||||||
|
from src.core.events.database import get_db_session
|
||||||
from config.config import get_learnhouse_config
|
from config.config import get_learnhouse_config
|
||||||
from src.security.auth import AuthJWT, authenticate_user
|
from src.security.auth import AuthJWT, authenticate_user
|
||||||
from src.services.users.users import PublicUser
|
from src.services.users.users import PublicUser
|
||||||
|
|
@ -9,7 +12,7 @@ router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/refresh")
|
@router.post("/refresh")
|
||||||
def refresh(response: Response,Authorize: AuthJWT = Depends()):
|
def refresh(response: Response, Authorize: AuthJWT = Depends()):
|
||||||
"""
|
"""
|
||||||
The jwt_refresh_token_required() function insures a valid refresh
|
The jwt_refresh_token_required() function insures a valid refresh
|
||||||
token is present in the request before running any code below that function.
|
token is present in the request before running any code below that function.
|
||||||
|
|
@ -21,7 +24,12 @@ def refresh(response: Response,Authorize: AuthJWT = Depends()):
|
||||||
current_user = Authorize.get_jwt_subject()
|
current_user = Authorize.get_jwt_subject()
|
||||||
new_access_token = Authorize.create_access_token(subject=current_user) # type: ignore
|
new_access_token = Authorize.create_access_token(subject=current_user) # type: ignore
|
||||||
|
|
||||||
response.set_cookie(key="access_token_cookie", value=new_access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain)
|
response.set_cookie(
|
||||||
|
key="access_token_cookie",
|
||||||
|
value=new_access_token,
|
||||||
|
httponly=False,
|
||||||
|
domain=get_learnhouse_config().hosting_config.cookie_config.domain,
|
||||||
|
)
|
||||||
return {"access_token": new_access_token}
|
return {"access_token": new_access_token}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,8 +39,11 @@ async def login(
|
||||||
response: Response,
|
response: Response,
|
||||||
Authorize: AuthJWT = Depends(),
|
Authorize: AuthJWT = Depends(),
|
||||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
user = await authenticate_user(request, form_data.username, form_data.password)
|
user = await authenticate_user(
|
||||||
|
request, form_data.username, form_data.password, db_session
|
||||||
|
)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|
@ -44,8 +55,14 @@ async def login(
|
||||||
refresh_token = Authorize.create_refresh_token(subject=form_data.username)
|
refresh_token = Authorize.create_refresh_token(subject=form_data.username)
|
||||||
Authorize.set_refresh_cookies(refresh_token)
|
Authorize.set_refresh_cookies(refresh_token)
|
||||||
# set cookies using fastapi
|
# set cookies using fastapi
|
||||||
response.set_cookie(key="access_token_cookie", value=access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain)
|
response.set_cookie(
|
||||||
user = PublicUser(**user.dict())
|
key="access_token_cookie",
|
||||||
|
value=access_token,
|
||||||
|
httponly=False,
|
||||||
|
domain=get_learnhouse_config().hosting_config.cookie_config.domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
user = UserRead.from_orm(user)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"user": user,
|
"user": user,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,24 @@
|
||||||
from fastapi import Depends, APIRouter, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
|
from sqlmodel import Session
|
||||||
from src.security.auth import get_current_user
|
from src.security.auth import get_current_user
|
||||||
from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserWithPassword
|
from src.core.events.database import get_db_session
|
||||||
from src.services.users.users import create_user, delete_user, get_profile_metadata, get_user_by_userid, update_user, update_user_password
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from src.db.users import (
|
||||||
|
User,
|
||||||
|
UserCreate,
|
||||||
|
UserRead,
|
||||||
|
UserUpdate,
|
||||||
|
UserUpdatePassword,
|
||||||
|
)
|
||||||
|
from src.services.users.users import (
|
||||||
|
create_user,
|
||||||
|
create_user_without_org,
|
||||||
|
delete_user_by_id,
|
||||||
|
read_user_by_id,
|
||||||
|
read_user_by_uuid,
|
||||||
|
update_user,
|
||||||
|
update_user_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
@ -17,50 +31,95 @@ async def api_get_current_user(current_user: User = Depends(get_current_user)):
|
||||||
"""
|
"""
|
||||||
return current_user.dict()
|
return current_user.dict()
|
||||||
|
|
||||||
@router.get("/profile_metadata")
|
|
||||||
async def api_get_current_user_metadata(request: Request,current_user: User = Depends(get_current_user)):
|
@router.post("/org_id/{org_id}", response_model=UserRead, tags=["users"])
|
||||||
|
async def api_create_user_with_orgid(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
user_object: UserCreate,
|
||||||
|
org_id: int,
|
||||||
|
) -> UserRead:
|
||||||
"""
|
"""
|
||||||
Get current user
|
Create User with Org ID
|
||||||
"""
|
"""
|
||||||
return await get_profile_metadata(request , current_user.dict())
|
return await create_user(request, db_session, None, user_object, org_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=UserRead, tags=["users"])
|
||||||
@router.get("/user_id/{user_id}")
|
async def api_create_user_without_org(
|
||||||
async def api_get_user_by_userid(request: Request,user_id: str):
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
user_object: UserCreate,
|
||||||
|
org_id: int,
|
||||||
|
) -> UserRead:
|
||||||
"""
|
"""
|
||||||
Get single user by user_id
|
Create User
|
||||||
"""
|
"""
|
||||||
return await get_user_by_userid(request, user_id)
|
return await create_user_without_org(request, db_session, None, user_object)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/")
|
@router.get("/user_id/{user_id}", response_model=UserRead, tags=["users"])
|
||||||
async def api_create_user(request: Request,user_object: UserWithPassword, org_slug: str ):
|
async def api_get_user_by_id(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
user_id: int,
|
||||||
|
) -> UserRead:
|
||||||
"""
|
"""
|
||||||
Create new user
|
Get User by ID
|
||||||
"""
|
"""
|
||||||
return await create_user(request, None, user_object, org_slug)
|
return await read_user_by_id(request, db_session, None, user_id)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/user_id/{user_id}")
|
@router.get("/user_uuid/{user_uuid}", response_model=UserRead, tags=["users"])
|
||||||
async def api_delete_user(request: Request, user_id: str, current_user: PublicUser = Depends(get_current_user)):
|
async def api_get_user_by_uuid(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
user_uuid: str,
|
||||||
|
) -> UserRead:
|
||||||
"""
|
"""
|
||||||
Delete user by ID
|
Get User by UUID
|
||||||
"""
|
"""
|
||||||
|
return await read_user_by_uuid(request, db_session, None, user_uuid)
|
||||||
return await delete_user(request, current_user, user_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/user_id/{user_id}")
|
@router.put("/", response_model=UserRead, tags=["users"])
|
||||||
async def api_update_user(request: Request, user_object: User, user_id: str, current_user: PublicUser = Depends(get_current_user)):
|
async def api_update_user(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
user_object: UserUpdate,
|
||||||
|
) -> UserRead:
|
||||||
"""
|
"""
|
||||||
Update user by ID
|
Update User
|
||||||
"""
|
"""
|
||||||
return await update_user(request, user_id, user_object, current_user)
|
return await update_user(request, db_session, None, user_object)
|
||||||
|
|
||||||
@router.put("/password/user_id/{user_id}")
|
|
||||||
async def api_update_user_password(request: Request, user_id: str , passwordChangeForm : PasswordChangeForm, current_user: PublicUser = Depends(get_current_user)):
|
@router.put("/change_password/", response_model=UserRead, tags=["users"])
|
||||||
|
async def api_update_user_password(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
form: UserUpdatePassword,
|
||||||
|
) -> UserRead:
|
||||||
"""
|
"""
|
||||||
Update user password by ID
|
Update User Password
|
||||||
"""
|
"""
|
||||||
return await update_user_password(request,current_user, user_id, passwordChangeForm)
|
return await update_user_password(request, db_session, None, form)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/user_id/{user_id}", tags=["users"])
|
||||||
|
async def api_delete_user(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
user_id: int,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Delete User
|
||||||
|
"""
|
||||||
|
return await delete_user_by_id(request, db_session, None, user_id)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src.core.events.database import get_db_session
|
||||||
|
from src.db.users import User, UserRead
|
||||||
|
from src.services.users.users import security_get_user
|
||||||
from config.config import get_learnhouse_config
|
from config.config import get_learnhouse_config
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from fastapi import Depends, HTTPException, Request, status
|
from fastapi import Depends, HTTPException, Request, status
|
||||||
|
|
@ -6,7 +10,7 @@ from jose import JWTError, jwt
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from src.services.dev.dev import isDevModeEnabled
|
from src.services.dev.dev import isDevModeEnabled
|
||||||
from src.services.users.schemas.users import AnonymousUser, PublicUser
|
from src.services.users.schemas.users import AnonymousUser, PublicUser
|
||||||
from src.services.users.users import security_get_user, security_verify_password
|
from src.services.users.users import security_verify_password
|
||||||
from src.security.security import ALGORITHM, SECRET_KEY
|
from src.security.security import ALGORITHM, SECRET_KEY
|
||||||
from fastapi_jwt_auth import AuthJWT
|
from fastapi_jwt_auth import AuthJWT
|
||||||
|
|
||||||
|
|
@ -45,10 +49,13 @@ class TokenData(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
#### Classes ####################################################
|
#### Classes ####################################################
|
||||||
|
async def authenticate_user(
|
||||||
|
request: Request,
|
||||||
async def authenticate_user(request: Request, email: str, password: str):
|
email: str,
|
||||||
user = await security_get_user(request, email)
|
password: str,
|
||||||
|
db_session: Session,
|
||||||
|
) -> User | bool:
|
||||||
|
user = await security_get_user(request, db_session, email)
|
||||||
if not user:
|
if not user:
|
||||||
return False
|
return False
|
||||||
if not await security_verify_password(password, user.password):
|
if not await security_verify_password(password, user.password):
|
||||||
|
|
@ -67,7 +74,11 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
||||||
return encoded_jwt
|
return encoded_jwt
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user(request: Request, Authorize: AuthJWT = Depends()):
|
async def get_current_user(
|
||||||
|
request: Request,
|
||||||
|
Authorize: AuthJWT = Depends(),
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
):
|
||||||
credentials_exception = HTTPException(
|
credentials_exception = HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="Could not validate credentials",
|
detail="Could not validate credentials",
|
||||||
|
|
@ -81,10 +92,10 @@ async def get_current_user(request: Request, Authorize: AuthJWT = Depends()):
|
||||||
except JWTError:
|
except JWTError:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
if username:
|
if username:
|
||||||
user = await security_get_user(request, email=token_data.username) # type: ignore # treated as an email
|
user = await security_get_user(request, db_session, email=token_data.username) # type: ignore # treated as an email
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return PublicUser(**user.dict())
|
return UserRead(**user.dict())
|
||||||
else:
|
else:
|
||||||
return AnonymousUser()
|
return AnonymousUser()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ import requests
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
from src.services.users.schemas.users import UserInDB
|
||||||
from src.security.security import security_hash_password
|
from src.security.security import security_hash_password
|
||||||
from src.services.courses.chapters import CourseChapter, create_coursechapter
|
from src.services.courses.chapters import CourseChapter, create_coursechapter
|
||||||
from src.services.courses.activities.activities import Activity, create_activity
|
from src.services.courses.activities.activities import Activity, create_activity
|
||||||
from src.services.users.users import PublicUser, UserInDB
|
from src.services.users.users import PublicUser
|
||||||
|
|
||||||
from src.services.orgs.orgs import Organization, create_org
|
from src.services.orgs.orgs import Organization, create_org
|
||||||
from src.services.roles.schemas.roles import Permission, Elements, RoleInDB
|
from src.services.roles.schemas.roles import Permission, Elements, RoleInDB
|
||||||
|
|
|
||||||
|
|
@ -1,215 +1,285 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from fastapi import HTTPException, Request, status
|
from fastapi import HTTPException, Request, status
|
||||||
from src.security.rbac.rbac import (
|
from sqlmodel import Session, select
|
||||||
authorization_verify_based_on_roles,
|
from src.db.organizations import Organization
|
||||||
authorization_verify_if_user_is_anon,
|
from src.db.users import (
|
||||||
)
|
|
||||||
from src.security.security import security_hash_password, security_verify_password
|
|
||||||
from src.services.users.schemas.users import (
|
|
||||||
PasswordChangeForm,
|
|
||||||
PublicUser,
|
|
||||||
User,
|
User,
|
||||||
UserOrganization,
|
UserCreate,
|
||||||
UserRolesInOrganization,
|
UserRead,
|
||||||
UserWithPassword,
|
UserUpdate,
|
||||||
UserInDB,
|
UserUpdatePassword,
|
||||||
)
|
)
|
||||||
|
from src.db.user_organizations import UserOrganization
|
||||||
|
from src.security.security import security_hash_password, security_verify_password
|
||||||
|
from src.services.users.schemas.users import PublicUser
|
||||||
|
|
||||||
|
|
||||||
async def create_user(
|
async def create_user(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
current_user: PublicUser | None,
|
current_user: PublicUser | None,
|
||||||
user_object: UserWithPassword,
|
user_object: UserCreate,
|
||||||
org_slug: str,
|
org_id: int,
|
||||||
):
|
):
|
||||||
users = request.app.db["users"]
|
user = User.from_orm(user_object)
|
||||||
|
|
||||||
isUsernameAvailable = await users.find_one({"username": user_object.username})
|
# Complete the user object
|
||||||
isEmailAvailable = await users.find_one({"email": user_object.email})
|
user.user_uuid = f"user_{uuid4()}"
|
||||||
|
user.password = await security_hash_password(user_object.password)
|
||||||
|
user.email_verified = False
|
||||||
|
user.creation_date = str(datetime.now())
|
||||||
|
user.update_date = str(datetime.now())
|
||||||
|
|
||||||
if isUsernameAvailable:
|
# Verifications
|
||||||
|
|
||||||
|
# Check if Organization exists
|
||||||
|
statement = select(Organization).where(Organization.id == org_id)
|
||||||
|
result = db_session.exec(statement)
|
||||||
|
|
||||||
|
if not result.first():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Username already exists"
|
status_code=400,
|
||||||
|
detail="Organization does not exist",
|
||||||
)
|
)
|
||||||
|
|
||||||
if isEmailAvailable:
|
# Username
|
||||||
|
statement = select(User).where(User.username == user.username)
|
||||||
|
result = db_session.exec(statement)
|
||||||
|
|
||||||
|
if result.first():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Email already exists"
|
status_code=400,
|
||||||
|
detail="Username already exists",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate user_id with uuid4
|
# Email
|
||||||
user_id = str(f"user_{uuid4()}")
|
statement = select(User).where(User.email == user.email)
|
||||||
|
result = db_session.exec(statement)
|
||||||
|
|
||||||
# Check if the requesting user is authenticated
|
if result.first():
|
||||||
if current_user is not None:
|
|
||||||
# Verify rights
|
|
||||||
await verify_user_rights_on_user(request, current_user, "create", user_id)
|
|
||||||
|
|
||||||
# Set the username & hash the password
|
|
||||||
user_object.username = user_object.username.lower()
|
|
||||||
user_object.password = await security_hash_password(user_object.password)
|
|
||||||
|
|
||||||
# Get org_id from org_slug
|
|
||||||
orgs = request.app.db["organizations"]
|
|
||||||
|
|
||||||
# Check if the org exists
|
|
||||||
isOrgExists = await orgs.find_one({"slug": org_slug})
|
|
||||||
|
|
||||||
# If the org does not exist, raise an error
|
|
||||||
if not isOrgExists and (org_slug != "None"):
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
status_code=400,
|
||||||
detail="You are trying to create a user in an organization that does not exist",
|
detail="Email already exists",
|
||||||
)
|
)
|
||||||
|
|
||||||
org_id = isOrgExists["org_id"] if org_slug != "None" else ''
|
# Exclude unset values
|
||||||
|
user_data = user.dict(exclude_unset=True)
|
||||||
|
for key, value in user_data.items():
|
||||||
|
setattr(user, key, value)
|
||||||
|
|
||||||
# Create initial orgs list with the org_id passed in
|
# Add user to database
|
||||||
orgs = (
|
db_session.add(user)
|
||||||
[UserOrganization(org_id=org_id, org_role="member")]
|
db_session.commit()
|
||||||
if org_slug != "None"
|
db_session.refresh(user)
|
||||||
else []
|
|
||||||
)
|
|
||||||
|
|
||||||
# Give role
|
# Link user and organization
|
||||||
roles = (
|
user_organization = UserOrganization(
|
||||||
[UserRolesInOrganization(role_id="role_member", org_id=org_id)]
|
user_id=user.id is not None,
|
||||||
if org_slug != "None"
|
org_id=int(org_id),
|
||||||
else []
|
role_id=1,
|
||||||
)
|
|
||||||
|
|
||||||
# Create the user
|
|
||||||
user = UserInDB(
|
|
||||||
user_id=user_id,
|
|
||||||
creation_date=str(datetime.now()),
|
creation_date=str(datetime.now()),
|
||||||
update_date=str(datetime.now()),
|
update_date=str(datetime.now()),
|
||||||
orgs=orgs,
|
|
||||||
roles=roles,
|
|
||||||
**user_object.dict(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Insert the user into the database
|
db_session.add(user_organization)
|
||||||
await users.insert_one(user.dict())
|
db_session.commit()
|
||||||
|
db_session.refresh(user_organization)
|
||||||
|
|
||||||
return User(**user.dict())
|
user = UserRead.from_orm(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
async def read_user(request: Request, current_user: PublicUser, user_id: str):
|
async def create_user_without_org(
|
||||||
users = request.app.db["users"]
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
current_user: PublicUser | None,
|
||||||
|
user_object: UserCreate,
|
||||||
|
):
|
||||||
|
user = User.from_orm(user_object)
|
||||||
|
|
||||||
# Check if the user exists
|
# Complete the user object
|
||||||
isUserExists = await users.find_one({"user_id": user_id})
|
user.user_uuid = f"user_{uuid4()}"
|
||||||
|
user.password = await security_hash_password(user_object.password)
|
||||||
|
user.email_verified = False
|
||||||
|
user.creation_date = str(datetime.now())
|
||||||
|
user.update_date = str(datetime.now())
|
||||||
|
|
||||||
# Verify rights
|
# Verifications
|
||||||
await verify_user_rights_on_user(request, current_user, "read", user_id)
|
|
||||||
|
|
||||||
# If the user does not exist, raise an error
|
# Username
|
||||||
if not isUserExists:
|
statement = select(User).where(User.username == user.username)
|
||||||
|
result = db_session.exec(statement)
|
||||||
|
|
||||||
|
if result.first():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
status_code=400,
|
||||||
|
detail="Username already exists",
|
||||||
)
|
)
|
||||||
|
|
||||||
return User(**isUserExists)
|
# Email
|
||||||
|
statement = select(User).where(User.email == user.email)
|
||||||
|
result = db_session.exec(statement)
|
||||||
|
|
||||||
|
if result.first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Email already exists",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exclude unset values
|
||||||
|
user_data = user.dict(exclude_unset=True)
|
||||||
|
for key, value in user_data.items():
|
||||||
|
setattr(user, key, value)
|
||||||
|
|
||||||
|
# Add user to database
|
||||||
|
db_session.add(user)
|
||||||
|
db_session.commit()
|
||||||
|
db_session.refresh(user)
|
||||||
|
|
||||||
|
user = UserRead.from_orm(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
async def update_user(
|
async def update_user(
|
||||||
request: Request, user_id: str, user_object: User, current_user: PublicUser
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
current_user: PublicUser | None,
|
||||||
|
user_object: UserUpdate,
|
||||||
):
|
):
|
||||||
users = request.app.db["users"]
|
# Get user
|
||||||
|
statement = select(User).where(User.username == user_object.username)
|
||||||
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
# Verify rights
|
if not user:
|
||||||
await verify_user_rights_on_user(request, current_user, "update", user_id)
|
|
||||||
|
|
||||||
isUserExists = await users.find_one({"user_id": user_id})
|
|
||||||
isUsernameAvailable = await users.find_one({"username": user_object.username})
|
|
||||||
isEmailAvailable = await users.find_one({"email": user_object.email})
|
|
||||||
|
|
||||||
if not isUserExists:
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
status_code=400,
|
||||||
|
detail="User does not exist",
|
||||||
)
|
)
|
||||||
|
|
||||||
# okay if username is not changed
|
# Update user
|
||||||
if isUserExists["username"] == user_object.username:
|
user_data = user_object.dict(exclude_unset=True)
|
||||||
user_object.username = user_object.username.lower()
|
for key, value in user_data.items():
|
||||||
|
setattr(user, key, value)
|
||||||
|
|
||||||
else:
|
# Update user in database
|
||||||
if isUsernameAvailable:
|
db_session.add(user)
|
||||||
raise HTTPException(
|
db_session.commit()
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Username already used"
|
db_session.refresh(user)
|
||||||
)
|
|
||||||
|
|
||||||
if isEmailAvailable:
|
user = UserRead.from_orm(user)
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Email already used"
|
|
||||||
)
|
|
||||||
|
|
||||||
updated_user = {"$set": user_object.dict()}
|
return user
|
||||||
users.update_one({"user_id": user_id}, updated_user)
|
|
||||||
|
|
||||||
return User(**user_object.dict())
|
|
||||||
|
|
||||||
|
|
||||||
async def update_user_password(
|
async def update_user_password(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: PublicUser,
|
db_session: Session,
|
||||||
user_id: str,
|
current_user: PublicUser | None,
|
||||||
password_change_form: PasswordChangeForm,
|
form: UserUpdatePassword,
|
||||||
):
|
):
|
||||||
users = request.app.db["users"]
|
# Get user
|
||||||
|
statement = select(User).where(User.username == form.user_id)
|
||||||
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
isUserExists = await users.find_one({"user_id": user_id})
|
if not user:
|
||||||
|
|
||||||
# Verify rights
|
|
||||||
await verify_user_rights_on_user(request, current_user, "update", user_id)
|
|
||||||
|
|
||||||
if not isUserExists:
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
status_code=400,
|
||||||
|
detail="User does not exist",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not await security_verify_password(
|
if not await security_verify_password(form.old_password, user.password):
|
||||||
password_change_form.old_password, isUserExists["password"]
|
|
||||||
):
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password"
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password"
|
||||||
)
|
)
|
||||||
|
|
||||||
new_password = await security_hash_password(password_change_form.new_password)
|
# Update user
|
||||||
|
user.password = await security_hash_password(form.new_password)
|
||||||
|
|
||||||
updated_user = {"$set": {"password": new_password}}
|
# Update user in database
|
||||||
await users.update_one({"user_id": user_id}, updated_user)
|
db_session.add(user)
|
||||||
|
db_session.commit()
|
||||||
|
db_session.refresh(user)
|
||||||
|
|
||||||
return {"detail": "Password updated"}
|
user = UserRead.from_orm(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
async def delete_user(request: Request, current_user: PublicUser, user_id: str):
|
async def read_user_by_id(
|
||||||
users = request.app.db["users"]
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
current_user: PublicUser | None,
|
||||||
|
user_id: int,
|
||||||
|
):
|
||||||
|
# Get user
|
||||||
|
statement = select(User).where(User.id == user_id)
|
||||||
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
isUserExists = await users.find_one({"user_id": user_id})
|
if not user:
|
||||||
|
|
||||||
# Verify is user has permission to delete the user
|
|
||||||
await verify_user_rights_on_user(request, current_user, "delete", user_id)
|
|
||||||
|
|
||||||
if not isUserExists:
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
status_code=400,
|
||||||
|
detail="User does not exist",
|
||||||
)
|
)
|
||||||
|
|
||||||
await users.delete_one({"user_id": user_id})
|
user = UserRead.from_orm(user)
|
||||||
|
|
||||||
return {"detail": "User deleted"}
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def read_user_by_uuid(
|
||||||
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
current_user: PublicUser | None,
|
||||||
|
uuid: str,
|
||||||
|
):
|
||||||
|
# Get user
|
||||||
|
statement = select(User).where(User.user_uuid == uuid)
|
||||||
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="User does not exist",
|
||||||
|
)
|
||||||
|
|
||||||
|
user = UserRead.from_orm(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_user_by_id(
|
||||||
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
current_user: PublicUser | None,
|
||||||
|
user_id: int,
|
||||||
|
):
|
||||||
|
# Get user
|
||||||
|
statement = select(User).where(User.id == user_id)
|
||||||
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="User does not exist",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete user
|
||||||
|
db_session.delete(user)
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
return "User deleted"
|
||||||
|
|
||||||
|
|
||||||
# Utils & Security functions
|
# Utils & Security functions
|
||||||
|
|
||||||
|
|
||||||
async def security_get_user(request: Request, email: str):
|
async def security_get_user(request: Request, db_session: Session, email: str) -> User:
|
||||||
users = request.app.db["users"]
|
# Check if user exists
|
||||||
|
statement = select(User).where(User.email == email)
|
||||||
user = await users.find_one({"email": email})
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -217,105 +287,6 @@ async def security_get_user(request: Request, email: str):
|
||||||
detail="User with Email does not exist",
|
detail="User with Email does not exist",
|
||||||
)
|
)
|
||||||
|
|
||||||
return UserInDB(**user)
|
user = User(**user.dict())
|
||||||
|
|
||||||
|
|
||||||
async def get_userid_by_username(request: Request, username: str):
|
|
||||||
users = request.app.db["users"]
|
|
||||||
|
|
||||||
user = await users.find_one({"username": username})
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
return user["user_id"]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_user_by_userid(request: Request, user_id: str):
|
|
||||||
users = request.app.db["users"]
|
|
||||||
|
|
||||||
user = await users.find_one({"user_id": user_id})
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
user = User(**user)
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
async def get_profile_metadata(request: Request, user):
|
|
||||||
users = request.app.db["users"]
|
|
||||||
request.app.db["roles"]
|
|
||||||
|
|
||||||
user = await users.find_one({"user_id": user["user_id"]})
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"user_object": PublicUser(**user), "roles": "random"}
|
|
||||||
|
|
||||||
|
|
||||||
# Verification of the user's permissions on the roles
|
|
||||||
|
|
||||||
|
|
||||||
async def verify_user_rights_on_user(
|
|
||||||
request: Request,
|
|
||||||
current_user: PublicUser,
|
|
||||||
action: Literal["create", "read", "update", "delete"],
|
|
||||||
user_id: str,
|
|
||||||
):
|
|
||||||
users = request.app.db["users"]
|
|
||||||
user = UserInDB(**await users.find_one({"user_id": user_id}))
|
|
||||||
|
|
||||||
if action == "create":
|
|
||||||
return True
|
|
||||||
|
|
||||||
if action == "read":
|
|
||||||
await authorization_verify_if_user_is_anon(current_user.user_id)
|
|
||||||
|
|
||||||
if current_user.user_id == user_id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for org in current_user.orgs:
|
|
||||||
if org.org_id in [org.org_id for org in user.orgs]:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
if action == "update":
|
|
||||||
await authorization_verify_if_user_is_anon(current_user.user_id)
|
|
||||||
|
|
||||||
if current_user.user_id == user_id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for org in current_user.orgs:
|
|
||||||
if org.org_id in [org.org_id for org in user.orgs]:
|
|
||||||
if org.org_role == "owner":
|
|
||||||
return True
|
|
||||||
|
|
||||||
await authorization_verify_based_on_roles(
|
|
||||||
request, current_user.user_id, "update", user["roles"], user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
if action == "delete":
|
|
||||||
await authorization_verify_if_user_is_anon(current_user.user_id)
|
|
||||||
|
|
||||||
if current_user.user_id == user_id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for org in current_user.orgs:
|
|
||||||
if org.org_id in [org.org_id for org in user.orgs]:
|
|
||||||
if org.org_role == "owner":
|
|
||||||
return True
|
|
||||||
|
|
||||||
await authorization_verify_based_on_roles(
|
|
||||||
request, current_user.user_id, "update", user["roles"], user_id
|
|
||||||
)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue