-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
397 additions
and
38 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -165,8 +165,8 @@ cython_debug/ | |
**/Thumbs.db | ||
|
||
# syft dirs | ||
data/ | ||
users/ | ||
/data/ | ||
/users/ | ||
.clients/ | ||
keys/ | ||
backup/ | ||
|
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
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,37 @@ | ||
import secrets | ||
from typing import Annotated, Literal | ||
|
||
from fastapi import Depends, HTTPException, status | ||
from fastapi.security import HTTPBasic, HTTPBasicCredentials | ||
|
||
security = HTTPBasic() | ||
|
||
ADMIN_USERNAME = "info@openmined.org" | ||
ADMIN_PASSWORD = "changethis" | ||
|
||
|
||
def verify_admin_credentials( | ||
credentials: Annotated[HTTPBasicCredentials, Depends(security)], | ||
) -> Literal[True]: | ||
""" | ||
HTTPBasic authentication that checks if the admin credentials are correct. | ||
Args: | ||
credentials (Annotated[HTTPBasicCredentials, Depends): HTTPBasic credentials | ||
Raises: | ||
HTTPException: 401 Unauthorized if the credentials are incorrect | ||
Returns: | ||
bool: True if the credentials are correct | ||
""" | ||
correct_username = secrets.compare_digest(credentials.username, ADMIN_USERNAME) | ||
correct_password = secrets.compare_digest(credentials.password, ADMIN_PASSWORD) | ||
|
||
if not (correct_username and correct_password): | ||
raise HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail="Incorrect credentials", | ||
headers={"WWW-Authenticate": "Basic"}, | ||
) | ||
return True |
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,92 @@ | ||
import fastapi | ||
from fastapi import Depends, HTTPException, Request | ||
|
||
from .auth import verify_admin_credentials | ||
from .user import User, UserManager | ||
|
||
user_router = fastapi.APIRouter( | ||
prefix="/users", | ||
tags=["users"], | ||
) | ||
|
||
|
||
def notify_user(user: User) -> None: | ||
print(f"New token {user.email}: {user.token}") | ||
|
||
|
||
def get_user_manager(request: Request) -> UserManager: | ||
return request.state.user_manager | ||
|
||
|
||
@user_router.post("/register_tokens") | ||
async def register_tokens( | ||
emails: list[str], | ||
user_manager: UserManager = Depends(get_user_manager), | ||
is_admin: bool = Depends(verify_admin_credentials), | ||
) -> list[User]: | ||
""" | ||
Register tokens for a list of emails. | ||
All users are created in the db with a random token, and an email is sent to each user. | ||
If the user already exists, the existing user is notified again with the same token. | ||
Args: | ||
emails (list[str]): list of emails to register. | ||
is_admin (bool, optional): checks if the user is an admin. | ||
user_manager (UserManager, optional): the user manager. Defaults to Depends(get_user_manager). | ||
Returns: | ||
list[User]: list of users created. | ||
""" | ||
users = [] | ||
for email in emails: | ||
user = user_manager.create_token_for_user(email) | ||
users.append(user) | ||
notify_user(user) | ||
|
||
return users | ||
|
||
|
||
@user_router.post("/ban") | ||
async def ban( | ||
email: str, | ||
is_admin: bool = Depends(verify_admin_credentials), | ||
user_manager: UserManager = Depends(get_user_manager), | ||
) -> User: | ||
try: | ||
user = user_manager.ban_user(email) | ||
return user | ||
except ValueError as e: | ||
raise HTTPException(status_code=404, detail=str(e)) | ||
|
||
|
||
@user_router.post("/unban") | ||
async def unban( | ||
email: str, | ||
is_admin: bool = Depends(verify_admin_credentials), | ||
user_manager: UserManager = Depends(get_user_manager), | ||
) -> User: | ||
try: | ||
user = user_manager.unban_user(email) | ||
return user | ||
except ValueError as e: | ||
raise HTTPException(status_code=404, detail=str(e)) | ||
|
||
|
||
@user_router.post("/register") | ||
async def register( | ||
email: str, | ||
token: str, | ||
user_manager: UserManager = Depends(get_user_manager), | ||
) -> None: | ||
"""Endpoint used by the user to register. This only works if the user has the correct token. | ||
Args: | ||
email (str): user email | ||
token (str): user token, generated by /register_tokens | ||
""" | ||
try: | ||
user = user_manager.register_user(email, token) | ||
print(f"User {user.email} registered") | ||
except ValueError as e: | ||
raise HTTPException(status_code=404, detail=str(e)) |
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,63 @@ | ||
import secrets | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class User(BaseModel): | ||
email: str | ||
token: str | ||
is_registered: bool = False | ||
is_banned: bool = False | ||
|
||
|
||
class UserManager: | ||
def __init__(self): | ||
self.users: dict[str, User] = {} | ||
|
||
def get_user(self, email: str) -> User | None: | ||
return self.users.get(email) | ||
|
||
def create_token_for_user(self, email: str) -> User: | ||
user = self.get_user(email) | ||
if user is not None: | ||
return user | ||
|
||
token = secrets.token_urlsafe(32) | ||
user = User(email=email, token=token) | ||
self.users[email] = user | ||
return user | ||
|
||
def register_user(self, email: str, token: str) -> User: | ||
user = self.get_user(email) | ||
if user is None: | ||
raise ValueError(f"User {email} not found") | ||
|
||
if user.token != token: | ||
raise ValueError("Invalid token") | ||
|
||
user.is_registered = True | ||
return user | ||
|
||
def ban_user(self, email: str) -> User: | ||
user = self.get_user(email) | ||
if user is None: | ||
raise ValueError(f"User {email} not found") | ||
|
||
user.is_banned = True | ||
return user | ||
|
||
def unban_user(self, email: str) -> User: | ||
user = self.get_user(email) | ||
if user is None: | ||
raise ValueError(f"User {email} not found") | ||
|
||
user.is_banned = False | ||
return user | ||
|
||
def __repr__(self) -> str: | ||
if len(self.users) == 0: | ||
return "UserManager()" | ||
res = "UserManager(\n" | ||
for email, user in self.users.items(): | ||
res += f" {email}: {user}\n" | ||
res += ")" |
Oops, something went wrong.