Skip to content

Commit 3f4ffbd

Browse files
committed
feat(backend): Initial account creation endpoints
1 parent e48e28c commit 3f4ffbd

File tree

9 files changed

+186
-1
lines changed

9 files changed

+186
-1
lines changed

backend-services/src/common/types.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
from typing import NewType
1+
from typing import NewType, TypeAlias
2+
from odmantic import AIOEngine
23

34
WalletAddress = NewType("WalletAddress", str)
45
"""
56
A type for vault wallet addresses.
67
"""
8+
9+
Engine: TypeAlias = AIOEngine
10+
"""
11+
A database engine instance.
12+
"""
13+
14+
HashString = NewType("HashString", str)
15+
"""
16+
HashString
17+
"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
User Authentication service for the Nautilus Vault backend.
3+
"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
The collection of operations provided by the user authenticatioin service.
3+
"""
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from common.types import Engine
2+
from create_new_user import argon2_context
3+
from fastapi import HTTPException
4+
from user_auth_service.schema.actions import AuthenticateUser, AuthenticateUserResult, AuthenticateUserSuccess, AuthenticateUserFailure
5+
from user_auth_service.schema.entites import UserDetailsStorable
6+
7+
def verify_password(password_attempt: str, hashed_password: str):
8+
return argon2_context.verify(password_attempt, hashed_password)
9+
10+
async def authenticate_user(engine: Engine, params: AuthenticateUser) -> AuthenticateUserResult:
11+
"""
12+
Look through DB to see if any user matches to the above.
13+
If user exists, verify password.
14+
"""
15+
existing_user = await engine.find_one(UserDetailsStorable, UserDetailsStorable.email_address == AuthenticateUser.email_address)
16+
if existing_user is None:
17+
return AuthenticateUserFailure(
18+
Failed = 'Username does not exist.'
19+
)
20+
21+
if not verify_password(AuthenticateUser.password,existing_user.hashed_password):
22+
return AuthenticateUserFailure(
23+
Failed = 'Invalid Password'
24+
)
25+
return existing_user
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from user_auth_service.schema.actions import CreateNewUser, CreateNewUserResult, CreateNewUserSuccess, CreateNewUserFailure
2+
from user_auth_service.schema.entites import UserDetailsStorable, UserDisplay
3+
from common.types import Engine
4+
from passlib.context import CryptContext
5+
6+
argon2_context = CryptContext(schemes=['argon2'], depricated='auto')
7+
8+
def password_hash(password: str):
9+
return argon2_context.hash(password)
10+
11+
12+
async def create_new_user(engine: Engine, params: CreateNewUser) -> CreateNewUserResult:
13+
"""
14+
User Creation.
15+
"""
16+
hash_password = password_hash(params.password)
17+
18+
new_user = UserDetailsStorable(
19+
email_address = params.email_address,
20+
full_name = params.full_name,
21+
phone_number = params.phone_number,
22+
password_hash_string = hash_password
23+
)
24+
try:
25+
await engine.save(new_user)
26+
user_display = UserDisplay(
27+
user_id = UserDetailsStorable.id,
28+
email_address = params.email_address,
29+
full_name = params.full_name,
30+
phone_number = params.phone_number
31+
)
32+
return CreateNewUserSuccess(created=user_display)
33+
except:
34+
return CreateNewUserFailure(
35+
Failed = 'Unable to save user credentials.'
36+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Internals and abstractions for the User Authentication service.
3+
"""
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from user_auth_service.schema.entites import UserDisplay
2+
from pydantic import BaseModel
3+
4+
from common.types import WalletAddress
5+
from datetime import datetime
6+
from typing import TypeAlias
7+
8+
9+
class CreateNewUser(BaseModel):
10+
"""
11+
User creation parameters.
12+
"""
13+
14+
full_name: str
15+
phone_number: str
16+
email_address: str
17+
password: str
18+
19+
class CreateNewUserSuccess(BaseModel):
20+
"""
21+
Return email address, full name, and phone number.
22+
"""
23+
24+
Created: UserDisplay
25+
26+
class CreateNewUserFailure(BaseModel):
27+
"""
28+
Return Failure if user's credentials is not created.
29+
"""
30+
Failed: str
31+
32+
CreateNewUserResult: TypeAlias = CreateNewUserSuccess | CreateNewUserFailure
33+
34+
class AuthenticateUser(BaseModel):
35+
"""
36+
Authentic user parameters.
37+
"""
38+
email_address: str
39+
password: str
40+
41+
class AuthenticateUserSuccess(BaseModel):
42+
"""
43+
Successfully authenticated user.
44+
"""
45+
46+
Opened: str
47+
48+
class AuthenticateUserFailure(BaseModel):
49+
"""
50+
Failed to authenticate user.
51+
"""
52+
53+
Failed: str
54+
55+
class AuthenticateUserResult: TypeAlias = AuthenticateUserSuccess | AuthenticateUserFailure
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import TypeAlias
2+
from common.types import HashString
3+
4+
from odmantic import Model
5+
from pydantic import BaseModel
6+
7+
from common.types import HashString
8+
9+
class UserDetailsStorable(Model):
10+
"""
11+
Storing new ueser's credentials.
12+
"""
13+
14+
email_address: str
15+
full_name: str;
16+
phone_number: str
17+
password_hash_string: HashString;
18+
19+
class Config:
20+
collection = 'user'
21+
22+
class UserDisplay(BaseModel):
23+
"""
24+
Return User credentials when user is created or opened.
25+
"""
26+
27+
user_id: str
28+
email_address: str
29+
owner_name: str
30+
phone_number: str
31+

backend-services/src/web_asgi/main.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
from data_service.schema.entities import Dataset, DatasetList, Datapool, DatapoolList, Dataschema
1515

1616
from data_service.operations.datapool import datapools, create_datapool, delete_datapool
17+
from data_service.operations.dataschema import create_dataschema
18+
19+
from user_auth_service.schema.actions import CreateNewUser, CreateNewUserResult, AuthenticateUser, AuthenticateUserResult
20+
from user_auth_service.operations import create_new_user, authenticate_user
1721

1822
from web_asgi.settings import AppSettings
1923

@@ -38,6 +42,20 @@
3842
)
3943

4044

45+
@app.post(
46+
"/auth/create", response_model=CreateNewUserResult, status_code=status.HTTP_201_CREATED
47+
)
48+
async def post_create_new_user(request: CreateNewUser) -> CreateNewUserResult:
49+
return await create_new_user(mongo_engine, request)
50+
51+
52+
@app.post(
53+
"/auth/login", response_model=AuthenticateUserResult, status_code=status.HTTP_201_CREATED
54+
)
55+
async def post_authenticate_user(request: AuthenticateUser) -> AuthenticateUserResult:
56+
return await authenticate_user(mongo_engine, request)
57+
58+
4159
@app.get("/datasets", response_model=DatasetList, status_code=status.HTTP_200_OK)
4260
async def get_datasets(wallet_id: WalletAddress) -> DatasetList:
4361
return await datasets(mongo_engine, wallet_id)

0 commit comments

Comments
 (0)