-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: user creation, use username as identifier, admin account creation, configure auth in env * chore: linter * chore: linter * chore: .envexample update * chore: update poetry lock
- Loading branch information
1 parent
90649c6
commit 85c7e10
Showing
30 changed files
with
729 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,13 @@ | ||
import json | ||
import os | ||
|
||
from cpf.core.ports.provided.services import ManageService | ||
from cpf.core.ports.provided.services import ManageService, UserManagementService | ||
|
||
manage_service: ManageService | None = None | ||
user_management_service: UserManagementService | None = None | ||
|
||
|
||
def set_manage_service(service: ManageService): | ||
def set_manage_service(service: ManageService) -> None: | ||
global manage_service | ||
manage_service = service | ||
|
||
|
@@ -17,6 +18,17 @@ def get_manage_service() -> ManageService: | |
return manage_service | ||
|
||
|
||
def set_user_management_service(service: UserManagementService) -> None: | ||
global user_management_service | ||
user_management_service = service | ||
|
||
|
||
def get_user_management_service() -> UserManagementService: | ||
if not user_management_service: | ||
raise RuntimeError("User management service not set") | ||
return user_management_service | ||
|
||
|
||
def start_data_upload() -> None: | ||
service = get_manage_service() | ||
if service.check_if_data_is_exists(): | ||
|
@@ -38,3 +50,7 @@ def start_data_upload() -> None: | |
ladder_data = json.loads(file.read()) | ||
service.create_ladder(ladder_data=ladder_data) | ||
print(f"Ladder created from file {ladder_data_path}") | ||
# Create admin account | ||
user_service = get_user_management_service() | ||
# TODO Create new service method for admin account creation when role management system will be ready | ||
user_service.create_new_user(email="[email protected]", first_name="Cpf", last_name="Admin") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 16 additions & 1 deletion
17
backend/src/cpf/adapters/inbound/rest_api/models/responses/core.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,20 @@ | ||
from cpf.adapters.inbound.rest_api.ion import IonBaseModel, IonLink | ||
|
||
|
||
class RootResponse(IonBaseModel): | ||
class UserResponse(IonBaseModel): | ||
first_name: str | ||
last_name: str | ||
|
||
|
||
class UnauthenticatedRootResponse(IonBaseModel): | ||
login: IonLink | ||
|
||
|
||
class AuthenticatedRootResponse(IonBaseModel): | ||
user: UserResponse | ||
get_ladders: IonLink | ||
|
||
|
||
class LadderResponse(IonBaseModel): | ||
ladder_name: str | ||
ladder_slug: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from abc import ABC, abstractmethod | ||
from functools import wraps | ||
|
||
from fastapi import HTTPException | ||
from starlette import status | ||
|
||
from cpf.core.ports.required.dtos import UserDTO | ||
|
||
|
||
class Permission(ABC): | ||
|
||
@classmethod | ||
@abstractmethod | ||
def validate_permission(cls, user: UserDTO) -> bool: | ||
pass | ||
|
||
|
||
def check_permissions(permission_classes: list[type[Permission]]): | ||
def inner(func): | ||
@wraps(func) | ||
def wrapper(*args, **kwargs): | ||
user: UserDTO = kwargs.get("user") | ||
if not user: | ||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authenticated") | ||
if not all(permission.validate_permission(user=user) for permission in permission_classes): | ||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User has no permission") | ||
return func(*args, **kwargs) | ||
|
||
return wrapper | ||
|
||
return inner |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import os | ||
from typing import Annotated | ||
|
||
from fastapi import APIRouter, Depends | ||
|
||
from cpf.adapters.inbound.rest_api.permissions import check_permissions | ||
from cpf.core.ports.provided.services import UserManagementService | ||
from cpf.core.ports.required.dtos import UserDTO | ||
|
||
from ..rest_api import auth, get_user_management_service | ||
from .models.requests import PutUser | ||
from .models.responses import UserResponse | ||
|
||
router = APIRouter(prefix=f"{os.getenv('BASE_URL')}/users") | ||
|
||
|
||
@router.post(path="", response_model_exclude_none=True) | ||
@check_permissions(permission_classes=[]) | ||
def create_new_user( | ||
request: PutUser, | ||
user: Annotated[UserDTO, Depends(auth)], | ||
service: UserManagementService = Depends(get_user_management_service), | ||
) -> UserResponse: | ||
new_user: UserDTO = service.create_new_user( | ||
first_name=request.first_name, last_name=request.last_name, email=request.email | ||
) | ||
return UserResponse( | ||
username=new_user.username, | ||
email=new_user.email, | ||
first_name=new_user.first_name, | ||
last_name=new_user.last_name, | ||
) |
Empty file.
7 changes: 7 additions & 0 deletions
7
backend/src/cpf/adapters/inbound/rest_api/users/models/requests.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from pydantic import BaseModel, EmailStr, Field | ||
|
||
|
||
class PutUser(BaseModel): | ||
first_name: str = Field(..., description="New user first name") | ||
last_name: str = Field(..., description="New user last name") | ||
email: EmailStr = Field(..., description="New user email") |
8 changes: 8 additions & 0 deletions
8
backend/src/cpf/adapters/inbound/rest_api/users/models/responses.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from cpf.adapters.inbound.rest_api.ion import IonBaseModel | ||
|
||
|
||
class UserResponse(IonBaseModel): | ||
username: str | ||
email: str | ||
first_name: str | ||
last_name: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,16 @@ | |
from fastapi import FastAPI | ||
from starlette.routing import Route | ||
|
||
from cpf.core.ports.required.dtos import UserDTO | ||
|
||
|
||
def env_to_bool(value: str) -> bool: | ||
return value.lower() in ("true", "1") | ||
|
||
|
||
def fake_user_factory() -> UserDTO: | ||
return UserDTO(identifier="[email protected]", first_name="Mock", last_name="John") | ||
|
||
|
||
class EndpointInfo(TypedDict): | ||
method: str | None | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import os | ||
|
||
from fastapi import HTTPException | ||
from fusionauth.fusionauth_client import FusionAuthClient | ||
from starlette import status | ||
|
||
from cpf.core.ports.required.clients import AuthenticationClient | ||
from cpf.core.ports.required.readmodels import UserReadModel | ||
|
||
|
||
class FusionAuthAuthenticationClient(AuthenticationClient): | ||
|
||
def __init__(self, client: FusionAuthClient): | ||
self._client = client | ||
|
||
@staticmethod | ||
def _get_user_read_model_from_response(response_data: dict) -> UserReadModel: | ||
user_data = response_data.get("user") | ||
return UserReadModel( | ||
username=user_data.get("username"), | ||
email=user_data.get("email"), | ||
first_name=user_data.get("data").get("first_name"), | ||
last_name=user_data.get("data").get("last_name"), | ||
) | ||
|
||
def get_user_data(self, access_token: str) -> UserReadModel | None: | ||
fusion_auth_response = self._client.retrieve_user_using_jwt(encoded_jwt=access_token) | ||
if fusion_auth_response.status != status.HTTP_200_OK: | ||
return None | ||
|
||
return self._get_user_read_model_from_response(fusion_auth_response.success_response) | ||
|
||
def create_user(self, email: str, username: str, first_name: str, last_name: str) -> UserReadModel: | ||
user_request = { | ||
"user": { | ||
"email": email, | ||
"username": username, | ||
"password": "dev-use-only", | ||
"data": { | ||
"first_name": first_name, | ||
"last_name": last_name, | ||
}, | ||
} | ||
} | ||
client_response = self._client.create_user(request=user_request, user_id=None) | ||
if not client_response.was_successful(): | ||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) | ||
|
||
return self._get_user_read_model_from_response(client_response.success_response) | ||
|
||
|
||
fusion_auth_client = FusionAuthClient(api_key=os.getenv("FUSIONAUTH_API_KEY"), base_url=os.getenv("FUSIONAUTH_URL")) | ||
|
||
|
||
def authentication_client_factory() -> AuthenticationClient: | ||
return FusionAuthAuthenticationClient(client=fusion_auth_client) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.