✨ Update all for Postgres and new techniques
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.api_v1.endpoints.role import router as roles_router
|
||||
from app.api.api_v1.endpoints.token import router as token_router
|
||||
from app.api.api_v1.endpoints.user import router as user_router
|
||||
from app.api.api_v1.endpoints.utils import router as utils_router
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(roles_router)
|
||||
api_router.include_router(token_router)
|
||||
api_router.include_router(user_router)
|
||||
api_router.include_router(utils_router)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from app.core.jwt import get_current_user
|
||||
from app.crud.user import check_if_user_is_active, check_if_user_is_superuser
|
||||
from app.crud.utils import ensure_enums_to_strs
|
||||
from app.models.role import RoleEnum, Roles
|
||||
from app.models.user import UserInDB
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/roles/", response_model=Roles)
|
||||
def route_roles_get(current_user: UserInDB = Depends(get_current_user)):
|
||||
"""
|
||||
Retrieve roles
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
elif not (check_if_user_is_superuser(current_user)):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The current user does not have enogh privileges"
|
||||
)
|
||||
roles = ensure_enums_to_strs(RoleEnum)
|
||||
return {"roles": roles}
|
||||
@@ -1,22 +1,19 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from starlette.exceptions import HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.utils.db import get_db
|
||||
from app.api.utils.security import get_current_user
|
||||
from app.core import config
|
||||
from app.core.jwt import create_access_token, get_current_user
|
||||
from app.crud.user import (
|
||||
authenticate_user,
|
||||
check_if_user_is_active,
|
||||
check_if_user_is_superuser,
|
||||
get_user,
|
||||
update_user,
|
||||
)
|
||||
from app.db.database import get_default_bucket
|
||||
from app.core.jwt import create_access_token
|
||||
from app.core.security import get_password_hash
|
||||
from app.crud import user as crud_user
|
||||
from app.db_models.user import User as DBUser
|
||||
from app.models.msg import Msg
|
||||
from app.models.token import Token
|
||||
from app.models.user import User, UserInDB, UserInUpdate
|
||||
from app.models.user import User
|
||||
from app.utils import (
|
||||
generate_password_reset_token,
|
||||
send_reset_password_email,
|
||||
@@ -27,70 +24,73 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/login/access-token", response_model=Token, tags=["login"])
|
||||
def route_login_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
def login_access_token(
|
||||
db: Session = Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
|
||||
):
|
||||
"""
|
||||
OAuth2 compatible token login, get an access token for future requests
|
||||
"""
|
||||
bucket = get_default_bucket()
|
||||
user = authenticate_user(bucket, form_data.username, form_data.password)
|
||||
user = crud_user.authenticate(
|
||||
db, email=form_data.username, password=form_data.password
|
||||
)
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="Incorrect email or password")
|
||||
elif not check_if_user_is_active(user):
|
||||
elif not crud_user.is_active(user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
return {
|
||||
"access_token": create_access_token(
|
||||
data={"username": form_data.username}, expires_delta=access_token_expires
|
||||
data={"user_id": user.id}, expires_delta=access_token_expires
|
||||
),
|
||||
"token_type": "bearer",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/login/test-token", tags=["login"], response_model=User)
|
||||
def route_test_token(current_user: UserInDB = Depends(get_current_user)):
|
||||
def test_token(current_user: DBUser = Depends(get_current_user)):
|
||||
"""
|
||||
Test access token
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.post("/password-recovery/{username}", tags=["login"], response_model=Msg)
|
||||
def route_recover_password(username: str):
|
||||
@router.post("/password-recovery/{email}", tags=["login"], response_model=Msg)
|
||||
def recover_password(email: str, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Password Recovery
|
||||
"""
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username)
|
||||
user = crud_user.get_by_email(db, email=email)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system.",
|
||||
)
|
||||
password_reset_token = generate_password_reset_token(username)
|
||||
password_reset_token = generate_password_reset_token(email=email)
|
||||
send_reset_password_email(
|
||||
email_to=user.email, username=username, token=password_reset_token
|
||||
email_to=user.email, email=email, token=password_reset_token
|
||||
)
|
||||
return {"msg": "Password recovery email sent"}
|
||||
|
||||
|
||||
@router.post("/reset-password/", tags=["login"], response_model=Msg)
|
||||
def route_reset_password(token: str, new_password: str):
|
||||
def reset_password(token: str, new_password: str, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Reset password
|
||||
"""
|
||||
username = verify_password_reset_token(token)
|
||||
if not username:
|
||||
email = verify_password_reset_token(token)
|
||||
if not email:
|
||||
raise HTTPException(status_code=400, detail="Invalid token")
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username)
|
||||
user = crud_user.get_by_email(db, email=email)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system.",
|
||||
)
|
||||
elif not check_if_user_is_active(user):
|
||||
elif not crud_user.is_active(user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
user_in = UserInUpdate(name=username, password=new_password)
|
||||
user = update_user(bucket, user_in)
|
||||
hashed_password = get_password_hash(new_password)
|
||||
user.hashed_password = hashed_password
|
||||
db.add(user)
|
||||
db.commit()
|
||||
return {"msg": "Password updated successfully"}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Body, Depends
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic.types import EmailStr
|
||||
from starlette.exceptions import HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.utils.db import get_db
|
||||
from app.api.utils.security import get_current_user
|
||||
from app.core import config
|
||||
from app.core.jwt import get_current_user
|
||||
from app.crud.user import (
|
||||
check_if_user_is_active,
|
||||
check_if_user_is_superuser,
|
||||
get_user,
|
||||
get_users,
|
||||
search_users,
|
||||
update_user,
|
||||
upsert_user,
|
||||
)
|
||||
from app.db.database import get_default_bucket
|
||||
from app.crud import user as crud_user
|
||||
from app.db_models.user import User as DBUser
|
||||
from app.models.user import User, UserInCreate, UserInDB, UserInUpdate
|
||||
from app.utils import send_new_account_email
|
||||
|
||||
@@ -23,116 +17,99 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/users/", tags=["users"], response_model=List[User])
|
||||
def route_users_get(
|
||||
skip: int = 0, limit: int = 100, current_user: UserInDB = Depends(get_current_user)
|
||||
def read_users(
|
||||
db: Session = Depends(get_db),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Retrieve users
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
if not crud_user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
elif not check_if_user_is_superuser(current_user):
|
||||
elif not crud_user.is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
users = get_users(bucket, skip=skip, limit=limit)
|
||||
return users
|
||||
|
||||
|
||||
@router.get("/users/search/", tags=["users"], response_model=List[User])
|
||||
def route_search_users(
|
||||
q: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Search users, use Bleve Query String syntax: http://blevesearch.com/docs/Query-String-Query/
|
||||
|
||||
For typeahead sufix with `*`. For example, a query with: `email:johnd*` will match users with
|
||||
email `johndoe@example.com`, `johndid@example.net`, etc.
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
elif not check_if_user_is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
users = search_users(bucket=bucket, query_string=q, skip=skip, limit=limit)
|
||||
users = crud_user.get_multi(db, skip=skip, limit=limit)
|
||||
return users
|
||||
|
||||
|
||||
@router.post("/users/", tags=["users"], response_model=User)
|
||||
def route_users_post(
|
||||
*, user_in: UserInCreate, current_user: UserInDB = Depends(get_current_user)
|
||||
def create_user(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
user_in: UserInCreate,
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create new user
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
if not crud_user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
elif not check_if_user_is_superuser(current_user):
|
||||
elif not crud_user.is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, user_in.username)
|
||||
user = crud_user.get_by_email(db, email=user_in.email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="The user with this username already exists in the system.",
|
||||
)
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
user = crud_user.create(db, user_in=user_in)
|
||||
if config.EMAILS_ENABLED and user_in.email:
|
||||
send_new_account_email(
|
||||
email_to=user_in.email, username=user_in.username, password=user_in.password
|
||||
email_to=user_in.email, username=user_in.email, password=user_in.password
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/users/me", tags=["users"], response_model=User)
|
||||
def route_users_me_put(
|
||||
def update_user_me(
|
||||
*,
|
||||
password: str = None,
|
||||
full_name: str = None,
|
||||
email: EmailStr = None,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
password: str = Body(None),
|
||||
full_name: str = Body(None),
|
||||
email: EmailStr = Body(None),
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update own user
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
if not crud_user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
user_in = UserInUpdate(**current_user.dict())
|
||||
current_user_data = jsonable_encoder(current_user)
|
||||
user_in = UserInUpdate(**current_user_data)
|
||||
if password is not None:
|
||||
user_in.password = password
|
||||
if full_name is not None:
|
||||
user_in.full_name = full_name
|
||||
if email is not None:
|
||||
user_in.email = email
|
||||
bucket = get_default_bucket()
|
||||
user = update_user(bucket, user_in)
|
||||
user = crud_user.update(db, user=current_user, user_in=user_in)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/users/me", tags=["users"], response_model=User)
|
||||
def route_users_me_get(current_user: UserInDB = Depends(get_current_user)):
|
||||
def read_user_me(
|
||||
db: Session = Depends(get_db), current_user: DBUser = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get current user
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
if not crud_user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
|
||||
@router.post("/users/open", tags=["users"], response_model=User)
|
||||
def route_users_post_open(
|
||||
def create_user_open(
|
||||
*,
|
||||
username: str = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
password: str = Body(...),
|
||||
email: EmailStr = Body(None),
|
||||
email: EmailStr = Body(...),
|
||||
full_name: str = Body(None),
|
||||
):
|
||||
"""
|
||||
@@ -143,63 +120,61 @@ def route_users_post_open(
|
||||
status_code=403,
|
||||
detail="Open user resgistration is forbidden on this server",
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username)
|
||||
user = crud_user.get_by_email(db, email=email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="The user with this username already exists in the system",
|
||||
)
|
||||
user_in = UserInCreate(
|
||||
username=username, password=password, email=email, full_name=full_name
|
||||
)
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
user_in = UserInCreate(password=password, email=email, full_name=full_name)
|
||||
user = crud_user.create(db, user_in=user_in)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/users/{username}", tags=["users"], response_model=User)
|
||||
def route_users_id_get(
|
||||
username: str, current_user: UserInDB = Depends(get_current_user)
|
||||
@router.get("/users/{user_id}", tags=["users"], response_model=User)
|
||||
def read_user_by_id(
|
||||
user_id: int,
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Get a specific user by username (email)
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
if not crud_user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username)
|
||||
user = crud_user.get(db, user_id=user_id)
|
||||
if user == current_user:
|
||||
return user
|
||||
if not check_if_user_is_superuser(current_user):
|
||||
if not crud_user.is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/users/{username}", tags=["users"], response_model=User)
|
||||
def route_users_put(
|
||||
@router.put("/users/{user_id}", tags=["users"], response_model=User)
|
||||
def update_user(
|
||||
*,
|
||||
username: str,
|
||||
db: Session = Depends(get_db),
|
||||
user_id: int,
|
||||
user_in: UserInUpdate,
|
||||
current_user: UserInDB = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update a user
|
||||
"""
|
||||
if not check_if_user_is_active(current_user):
|
||||
if not crud_user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
elif not check_if_user_is_superuser(current_user):
|
||||
elif not crud_user.is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username)
|
||||
user = crud_user.get(db, user_id=user_id)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system",
|
||||
)
|
||||
user = update_user(bucket, user_in)
|
||||
user = crud_user.update(db, user=user, user_in=user_in)
|
||||
return user
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic.types import EmailStr
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from app.api.utils.security import get_current_user
|
||||
from app.core.celery_app import celery_app
|
||||
from app.core.jwt import get_current_user
|
||||
from app.crud.user import check_if_user_is_superuser
|
||||
from app.crud import user as crud_user
|
||||
from app.models.msg import Msg
|
||||
from app.models.user import UserInDB
|
||||
from app.utils import send_test_email
|
||||
@@ -13,24 +12,22 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201)
|
||||
def route_test_celery(msg: Msg, current_user: UserInDB = Depends(get_current_user)):
|
||||
def test_celery(msg: Msg, current_user: UserInDB = Depends(get_current_user)):
|
||||
"""
|
||||
Test Celery worker
|
||||
"""
|
||||
if not check_if_user_is_superuser(current_user):
|
||||
if not crud_user.is_superuser(current_user):
|
||||
raise HTTPException(status_code=400, detail="Not a superuser")
|
||||
celery_app.send_task("app.worker.test_celery", args=[msg.msg])
|
||||
return {"msg": "Word received"}
|
||||
|
||||
|
||||
@router.post("/test-email/", tags=["utils"], response_model=Msg, status_code=201)
|
||||
def route_test_email(
|
||||
email_to: EmailStr, current_user: UserInDB = Depends(get_current_user)
|
||||
):
|
||||
def test_email(email_to: EmailStr, current_user: UserInDB = Depends(get_current_user)):
|
||||
"""
|
||||
Test emails
|
||||
"""
|
||||
if not check_if_user_is_superuser(current_user):
|
||||
if not crud_user.is_superuser(current_user):
|
||||
raise HTTPException(status_code=400, detail="Not a superuser")
|
||||
send_test_email(email_to=email_to)
|
||||
return {"msg": "Test email sent"}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from starlette.requests import Request
|
||||
|
||||
|
||||
def get_db(request: Request):
|
||||
return request.state.db
|
||||
@@ -0,0 +1,30 @@
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, Security
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt import PyJWTError
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.status import HTTP_403_FORBIDDEN
|
||||
|
||||
from app.api.utils.db import get_db
|
||||
from app.core import config
|
||||
from app.core.jwt import ALGORITHM
|
||||
from app.crud import user as crud_user
|
||||
from app.models.token import TokenPayload
|
||||
|
||||
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token")
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db), token: str = Security(reusable_oauth2)
|
||||
):
|
||||
try:
|
||||
payload = jwt.decode(token, config.SECRET_KEY, algorithms=[ALGORITHM])
|
||||
token_data = TokenPayload(**payload)
|
||||
except PyJWTError:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
|
||||
)
|
||||
user = crud_user.get(db, user_id=token_data.user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from app.db.external_session import db_session
|
||||
from app.db.session import db_session
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from app.db.external_session import db_session
|
||||
from app.db.session import db_session
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -21,7 +21,7 @@ SERVER_NAME = os.getenv("SERVER_NAME")
|
||||
SERVER_HOST = os.getenv("SERVER_HOST")
|
||||
BACKEND_CORS_ORIGINS = os.getenv(
|
||||
"BACKEND_CORS_ORIGINS"
|
||||
) # a string of origins separated by commas, e.g: "http://localhost, http://localhost:4200, http://localhost:3000, http://localhost:8080, http://dev.couchbase-project.com, https://stag.couchbase-project.com, https://couchbase-project.com, http://local.dockertoolbox.tiangolo.com"
|
||||
) # a string of origins separated by commas, e.g: "http://localhost, http://localhost:4200, http://localhost:3000, http://localhost:8080, http://local.dockertoolbox.tiangolo.com"
|
||||
PROJECT_NAME = os.getenv("PROJECT_NAME")
|
||||
SENTRY_DSN = os.getenv("SENTRY_DSN")
|
||||
|
||||
@@ -47,8 +47,6 @@ EMAIL_RESET_TOKEN_EXPIRE_HOURS = 48
|
||||
EMAIL_TEMPLATES_DIR = "/app/app/email-templates/build"
|
||||
EMAILS_ENABLED = SMTP_HOST and SMTP_PORT and EMAILS_FROM_EMAIL
|
||||
|
||||
ROLE_SUPERUSER = "superuser"
|
||||
|
||||
FIRST_SUPERUSER = os.getenv("FIRST_SUPERUSER")
|
||||
FIRST_SUPERUSER_PASSWORD = os.getenv("FIRST_SUPERUSER_PASSWORD")
|
||||
|
||||
|
||||
@@ -1,37 +1,12 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import jwt
|
||||
from fastapi import Security
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt import PyJWTError
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.status import HTTP_403_FORBIDDEN
|
||||
|
||||
from app.core.config import SECRET_KEY
|
||||
from app.crud.user import get_user
|
||||
from app.db.database import get_default_bucket
|
||||
from app.models.token import TokenPayload
|
||||
from app.core import config
|
||||
|
||||
ALGORITHM = "HS256"
|
||||
access_token_jwt_subject = "access"
|
||||
|
||||
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token")
|
||||
|
||||
|
||||
def get_current_user(token: str = Security(reusable_oauth2)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
token_data = TokenPayload(**payload)
|
||||
except PyJWTError:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username=token_data.username)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
|
||||
|
||||
def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
||||
to_encode = data.copy()
|
||||
@@ -40,5 +15,5 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
to_encode.update({"exp": expire, "sub": access_token_jwt_subject})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@@ -3,9 +3,9 @@ from passlib.context import CryptContext
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
def verify_password(plain_password: str, hashed_password: str):
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password):
|
||||
def get_password_hash(password: str):
|
||||
return pwd_context.hash(password)
|
||||
|
||||
@@ -1,81 +1,47 @@
|
||||
from app.core.security import get_password_hash
|
||||
from app.models.role import Role
|
||||
from app.models.user import User
|
||||
from typing import List, Union
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from app.core.security import get_password_hash, verify_password
|
||||
from app.db_models.user import User
|
||||
from app.models.user import UserInCreate, UserInUpdate
|
||||
|
||||
|
||||
def get_user(username, db_session):
|
||||
return db_session.query(User).filter(User.id == username).first()
|
||||
def get(db_session, *, user_id: int) -> Union[User, None]:
|
||||
return db_session.query(User).filter(User.id == user_id).first()
|
||||
|
||||
|
||||
def check_if_user_is_active(user):
|
||||
def get_by_email(db_session, *, email: str) -> Union[User, None]:
|
||||
return db_session.query(User).filter(User.email == email).first()
|
||||
|
||||
|
||||
def authenticate(db_session, *, email: str, password: str) -> Union[User, bool]:
|
||||
user = get_by_email(db_session, email=email)
|
||||
if not user:
|
||||
return False
|
||||
if not verify_password(password, user.hashed_password):
|
||||
return False
|
||||
return user
|
||||
|
||||
|
||||
def is_active(user) -> bool:
|
||||
return user.is_active
|
||||
|
||||
|
||||
def check_if_user_is_superuser(user):
|
||||
def is_superuser(user) -> bool:
|
||||
return user.is_superuser
|
||||
|
||||
|
||||
def check_if_username_is_active(username, db_session):
|
||||
user = get_user(username, db_session)
|
||||
return check_if_user_is_active(user)
|
||||
def get_multi(db_session, *, skip=0, limit=100) -> Union[List[User], List[None]]:
|
||||
return db_session.query(User).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def get_role_by_name(name, db_session):
|
||||
role = db_session.query(Role).filter(Role.name == name).first()
|
||||
return role
|
||||
|
||||
|
||||
def get_role_by_id(role_id, db_session):
|
||||
role = db_session.query(Role).filter(Role.id == role_id).first()
|
||||
return role
|
||||
|
||||
|
||||
def create_role(name, db_session):
|
||||
role = Role(name=name)
|
||||
db_session.add(role)
|
||||
db_session.commit()
|
||||
return role
|
||||
|
||||
|
||||
def get_roles(db_session):
|
||||
return db_session.query(Role).all()
|
||||
|
||||
|
||||
def get_user_roles(user):
|
||||
return user.roles
|
||||
|
||||
|
||||
def get_user_by_username(username, db_session) -> User:
|
||||
user = db_session.query(User).filter(User.email == username).first() # type: User
|
||||
return user
|
||||
|
||||
|
||||
def get_user_by_id(user_id, db_session):
|
||||
user = db_session.query(User).filter(User.id == user_id).first() # type: User
|
||||
return user
|
||||
|
||||
|
||||
def get_user_hashed_password(user):
|
||||
return user.password
|
||||
|
||||
|
||||
def get_user_id(user):
|
||||
return user.id
|
||||
|
||||
|
||||
def get_users(db_session):
|
||||
return db_session.query(User).all()
|
||||
|
||||
|
||||
def create_user(
|
||||
db_session, username, password, first_name=None, last_name=None, is_superuser=False
|
||||
):
|
||||
def create(db_session, *, user_in: UserInCreate) -> User:
|
||||
user = User(
|
||||
email=username,
|
||||
password=get_password_hash(password),
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
is_superuser=is_superuser,
|
||||
email=user_in.email,
|
||||
hashed_password=get_password_hash(user_in.password),
|
||||
full_name=user_in.full_name,
|
||||
is_superuser=user_in.is_superuser,
|
||||
)
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
@@ -83,8 +49,16 @@ def create_user(
|
||||
return user
|
||||
|
||||
|
||||
def assign_role_to_user(role: Role, user: User, db_session):
|
||||
user.roles.append(role)
|
||||
def update(db_session, *, user: User, user_in: UserInUpdate) -> User:
|
||||
user_data = jsonable_encoder(user)
|
||||
for field in user_data:
|
||||
if field in user_in.fields:
|
||||
value_in = getattr(user_in, field)
|
||||
if value_in is not None:
|
||||
setattr(user, field, value_in)
|
||||
if user_in.password:
|
||||
passwordhash = get_password_hash(user_in.password)
|
||||
user.hashed_password = passwordhash
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
db_session.refresh(user)
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from typing import List, Sequence, Type, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import Field, Shape
|
||||
|
||||
from app.core.config import COUCHBASE_BUCKET_NAME
|
||||
from couchbase.bucket import Bucket
|
||||
from couchbase.fulltext import MatchAllQuery, QueryStringQuery
|
||||
from couchbase.n1ql import CONSISTENCY_REQUEST, N1QLQuery
|
||||
|
||||
|
||||
def generate_new_id():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def ensure_enums_to_strs(items: Union[Sequence[Union[Enum, str]], Type[Enum]]):
|
||||
str_items = []
|
||||
for item in items:
|
||||
if isinstance(item, Enum):
|
||||
str_items.append(str(item.value))
|
||||
else:
|
||||
str_items.append(str(item))
|
||||
return str_items
|
||||
|
||||
|
||||
def get_all_documents_by_type(bucket: Bucket, *, doc_type: str, skip=0, limit=100):
|
||||
query_str = f"SELECT *, META().id as id FROM {COUCHBASE_BUCKET_NAME} WHERE type = $type LIMIT $limit OFFSET $skip;"
|
||||
q = N1QLQuery(
|
||||
query_str, bucket=COUCHBASE_BUCKET_NAME, type=doc_type, limit=limit, skip=skip
|
||||
)
|
||||
q.consistency = CONSISTENCY_REQUEST
|
||||
result = bucket.n1ql_query(q)
|
||||
return result
|
||||
|
||||
|
||||
def get_documents_by_keys(
|
||||
bucket: Bucket, *, keys: List[str], doc_model=Type[BaseModel]
|
||||
):
|
||||
results = bucket.get_multi(keys, quiet=True)
|
||||
docs = []
|
||||
for result in results.values():
|
||||
doc = doc_model(**result.value)
|
||||
docs.append(doc)
|
||||
return docs
|
||||
|
||||
|
||||
def results_to_model(results_from_couchbase: list, *, doc_model: Type[BaseModel]):
|
||||
items = []
|
||||
for doc in results_from_couchbase:
|
||||
data = doc[COUCHBASE_BUCKET_NAME]
|
||||
doc = doc_model(**data)
|
||||
items.append(doc)
|
||||
return items
|
||||
|
||||
|
||||
def search_results_to_model(
|
||||
results_from_couchbase: list, *, doc_model: Type[BaseModel]
|
||||
):
|
||||
items = []
|
||||
for doc in results_from_couchbase:
|
||||
data = doc.get("fields")
|
||||
if not data:
|
||||
continue
|
||||
data_nones = {}
|
||||
for key, value in data.items():
|
||||
field: Field = doc_model.__fields__[key]
|
||||
if not value:
|
||||
value = None
|
||||
elif field.shape in {Shape.LIST, Shape.SET, Shape.TUPLE} and not isinstance(
|
||||
value, list
|
||||
):
|
||||
value = [value]
|
||||
data_nones[key] = value
|
||||
doc = doc_model(**data_nones)
|
||||
items.append(doc)
|
||||
return items
|
||||
|
||||
|
||||
def get_docs(
|
||||
bucket: Bucket, *, doc_type: str, doc_model=Type[BaseModel], skip=0, limit=100
|
||||
):
|
||||
doc_results = get_all_documents_by_type(
|
||||
bucket, doc_type=doc_type, skip=skip, limit=limit
|
||||
)
|
||||
return results_to_model(doc_results, doc_model=doc_model)
|
||||
|
||||
|
||||
def get_doc(bucket: Bucket, *, doc_id: str, doc_model: Type[BaseModel]):
|
||||
result = bucket.get(doc_id, quiet=True)
|
||||
if not result.value:
|
||||
return None
|
||||
model = doc_model(**result.value)
|
||||
return model
|
||||
|
||||
|
||||
def search_docs_get_doc_ids(
|
||||
bucket: Bucket,
|
||||
*,
|
||||
query_string: str,
|
||||
index_name: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
):
|
||||
query = QueryStringQuery(query_string)
|
||||
hits = bucket.search(index_name, query, skip=skip, limit=limit)
|
||||
doc_ids = []
|
||||
for hit in hits:
|
||||
doc_ids.append(hit["id"])
|
||||
return doc_ids
|
||||
|
||||
|
||||
def search_get_results(
|
||||
bucket: Bucket,
|
||||
*,
|
||||
query_string: str,
|
||||
index_name: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
):
|
||||
if query_string:
|
||||
query = QueryStringQuery(query_string)
|
||||
else:
|
||||
query = MatchAllQuery()
|
||||
hits = bucket.search(index_name, query, fields=["*"], skip=skip, limit=limit)
|
||||
docs = []
|
||||
for hit in hits:
|
||||
docs.append(hit)
|
||||
return docs
|
||||
|
||||
|
||||
def search_get_results_by_type(
|
||||
bucket: Bucket,
|
||||
*,
|
||||
query_string: str,
|
||||
index_name: str,
|
||||
doc_type: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
):
|
||||
type_filter = f"type:{doc_type}"
|
||||
if not query_string:
|
||||
query_string = type_filter
|
||||
if query_string and type_filter not in query_string:
|
||||
query_string += f" {type_filter}"
|
||||
query = QueryStringQuery(query_string)
|
||||
hits = bucket.search(index_name, query, fields=["*"], skip=skip, limit=limit)
|
||||
docs = []
|
||||
for hit in hits:
|
||||
docs.append(hit)
|
||||
return docs
|
||||
|
||||
|
||||
def search_docs(
|
||||
bucket: Bucket,
|
||||
*,
|
||||
query_string: str,
|
||||
index_name: str,
|
||||
doc_model: Type[BaseModel],
|
||||
skip=0,
|
||||
limit=100,
|
||||
):
|
||||
keys = search_docs_get_doc_ids(
|
||||
bucket=bucket,
|
||||
query_string=query_string,
|
||||
index_name=index_name,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
if not keys:
|
||||
return []
|
||||
doc_results = get_documents_by_keys(bucket=bucket, keys=keys, doc_model=doc_model)
|
||||
return doc_results
|
||||
|
||||
|
||||
def search_results(
|
||||
bucket: Bucket,
|
||||
*,
|
||||
query_string: str,
|
||||
index_name: str,
|
||||
doc_model: Type[BaseModel],
|
||||
skip=0,
|
||||
limit=100,
|
||||
):
|
||||
doc_results = search_get_results(
|
||||
bucket=bucket,
|
||||
query_string=query_string,
|
||||
index_name=index_name,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
return search_results_to_model(doc_results, doc_model=doc_model)
|
||||
|
||||
|
||||
def search_results_by_type(
|
||||
bucket: Bucket,
|
||||
*,
|
||||
query_string: str,
|
||||
index_name: str,
|
||||
doc_type: str,
|
||||
doc_model: Type[BaseModel],
|
||||
skip=0,
|
||||
limit=100,
|
||||
):
|
||||
doc_results = search_get_results_by_type(
|
||||
bucket=bucket,
|
||||
query_string=query_string,
|
||||
index_name=index_name,
|
||||
doc_type=doc_type,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
return search_results_to_model(doc_results, doc_model=doc_model)
|
||||
@@ -1,5 +1,4 @@
|
||||
# Import all the models, so that Base has them before being
|
||||
# imported by Alembic
|
||||
from app.db.base_class import Base # noqa
|
||||
from app.models.role import Role # noqa
|
||||
from app.models.user import User # noqa
|
||||
from app.db_models.user import User # noqa
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import json
|
||||
from pathlib import Path, PurePath
|
||||
from typing import Any, Dict
|
||||
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
from app.core.config import (
|
||||
COUCHBASE_FULL_TEXT_INDEX_DEFINITIONS_DIR,
|
||||
COUCHBASE_PASSWORD,
|
||||
COUCHBASE_USER,
|
||||
)
|
||||
|
||||
|
||||
def get_index(
|
||||
index_name: str,
|
||||
*,
|
||||
username: str = COUCHBASE_USER,
|
||||
password: str = COUCHBASE_PASSWORD,
|
||||
host="couchbase",
|
||||
port="8094",
|
||||
):
|
||||
full_text_url = f"http://{host}:{port}"
|
||||
index_url = f"{full_text_url}/api/index/{index_name}"
|
||||
auth = HTTPBasicAuth(username, password)
|
||||
response = requests.get(index_url, auth=auth)
|
||||
if response.status_code == 400:
|
||||
content = response.json()
|
||||
error = content.get("error")
|
||||
if error == "rest_auth: preparePerms, err: index not found":
|
||||
return None
|
||||
raise ValueError(error)
|
||||
elif response.status_code == 200:
|
||||
content = response.json()
|
||||
assert (
|
||||
content.get("status") == "ok"
|
||||
), "Expected a status OK communicating with Full Text Search"
|
||||
index_def = content.get("indexDef")
|
||||
return index_def
|
||||
raise ValueError(response.text)
|
||||
|
||||
|
||||
def create_index(
|
||||
index_definition: Dict[str, Any],
|
||||
*,
|
||||
reset_uuids=True,
|
||||
username: str = COUCHBASE_USER,
|
||||
password: str = COUCHBASE_PASSWORD,
|
||||
host="couchbase",
|
||||
port="8094",
|
||||
):
|
||||
index_name = index_definition.get("name")
|
||||
assert index_name, "An index name is required as key in an index definition"
|
||||
if reset_uuids:
|
||||
index_definition.update({"uuid": "", "sourceUUID": ""})
|
||||
full_text_url = f"http://{host}:{port}"
|
||||
index_url = f"{full_text_url}/api/index/{index_name}"
|
||||
auth = HTTPBasicAuth(username, password)
|
||||
response = requests.put(index_url, auth=auth, json=index_definition)
|
||||
content = response.json()
|
||||
if response.status_code == 400:
|
||||
error = content.get("error")
|
||||
if (
|
||||
"cannot create index because an index with the same name already exists:"
|
||||
in error
|
||||
):
|
||||
raise ValueError(error)
|
||||
else:
|
||||
raise ValueError(error)
|
||||
elif response.status_code == 200:
|
||||
assert (
|
||||
content.get("status") == "ok"
|
||||
), "Expected a status OK communicating with Full Text Search"
|
||||
return True
|
||||
raise ValueError(response.text)
|
||||
|
||||
|
||||
def ensure_create_full_text_indexes(
|
||||
index_dir=COUCHBASE_FULL_TEXT_INDEX_DEFINITIONS_DIR,
|
||||
username: str = COUCHBASE_USER,
|
||||
password: str = COUCHBASE_PASSWORD,
|
||||
host="couchbase",
|
||||
port="8094",
|
||||
):
|
||||
file_path: PurePath
|
||||
for file_path in Path(index_dir).iterdir():
|
||||
if file_path.name.endswith(".json"):
|
||||
with open(file_path) as f:
|
||||
index_definition = json.load(f)
|
||||
name = index_definition.get("name")
|
||||
assert name, "A full text search index definition must have a name field"
|
||||
current_index = get_index(
|
||||
index_name=name,
|
||||
username=username,
|
||||
password=password,
|
||||
host=host,
|
||||
port=port,
|
||||
)
|
||||
if not current_index:
|
||||
assert create_index(
|
||||
index_definition=index_definition,
|
||||
username=username,
|
||||
password=password,
|
||||
host=host,
|
||||
port=port,
|
||||
), "Full Text Search index could not be created"
|
||||
@@ -1,11 +1,6 @@
|
||||
from app.core import config
|
||||
from app.db.utils import (
|
||||
assign_role_to_user,
|
||||
create_role,
|
||||
create_user,
|
||||
get_role_by_name,
|
||||
get_user_by_username,
|
||||
)
|
||||
from app.crud import user as crud_user
|
||||
from app.models.user import UserInCreate
|
||||
|
||||
|
||||
def init_db(db_session):
|
||||
@@ -14,16 +9,11 @@ def init_db(db_session):
|
||||
# the tables uncommenting the next line
|
||||
# Base.metadata.create_all(bind=engine)
|
||||
|
||||
role = get_role_by_name("default", db_session)
|
||||
if not role:
|
||||
role = create_role("default", db_session)
|
||||
|
||||
user = get_user_by_username(config.FIRST_SUPERUSER, db_session)
|
||||
user = crud_user.get_by_email(db_session, email=config.FIRST_SUPERUSER)
|
||||
if not user:
|
||||
user = create_user(
|
||||
db_session,
|
||||
config.FIRST_SUPERUSER,
|
||||
config.FIRST_SUPERUSER_PASSWORD,
|
||||
user_in = UserInCreate(
|
||||
email=config.FIRST_SUPERUSER,
|
||||
password=config.FIRST_SUPERUSER_PASSWORD,
|
||||
is_superuser=True,
|
||||
)
|
||||
assign_role_to_user(role, user, db_session)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
from app.core import config
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from app.core import config
|
||||
|
||||
engine = create_engine(config.SQLALCHEMY_DATABASE_URI, convert_unicode=True)
|
||||
db_session = scoped_session(
|
||||
sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
)
|
||||
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
@@ -1,11 +0,0 @@
|
||||
# Import installed packages
|
||||
# Import app code
|
||||
from app.db.base_class import Base
|
||||
from sqlalchemy import Column, ForeignKey, Integer, Table
|
||||
|
||||
users_roles = Table(
|
||||
"users_roles",
|
||||
Base.metadata,
|
||||
Column("user_id", Integer, ForeignKey("user.id")),
|
||||
Column("role_id", Integer, ForeignKey("role.id")),
|
||||
)
|
||||
@@ -1,19 +0,0 @@
|
||||
# Import standard library packages
|
||||
from datetime import datetime
|
||||
|
||||
# Import app code
|
||||
from app.db.base_class import Base
|
||||
from app.models.base_relations import users_roles
|
||||
|
||||
# Import installed packages
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
class Role(Base):
|
||||
# Own properties
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow(), index=True)
|
||||
name = Column(String, index=True)
|
||||
# Relationships
|
||||
users = relationship("User", secondary=users_roles, back_populates="roles")
|
||||
@@ -1,29 +1,12 @@
|
||||
# Import standard library packages
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
# Typings, for autocompletion (VS Code with Python plug-in)
|
||||
from typing import List # noqa
|
||||
|
||||
# Import app code
|
||||
from app.db.base_class import Base
|
||||
from app.models.base_relations import users_roles
|
||||
|
||||
# Import installed packages
|
||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
class User(Base):
|
||||
# Own properties
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow(), index=True)
|
||||
first_name = Column(String, index=True)
|
||||
last_name = Column(String, index=True)
|
||||
full_name = Column(String, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
password = Column(String)
|
||||
hashed_password = Column(String)
|
||||
is_active = Column(Boolean(), default=True)
|
||||
is_superuser = Column(Boolean(), default=False)
|
||||
# Relationships
|
||||
roles = relationship(
|
||||
"Role", secondary=users_roles, back_populates="users"
|
||||
) # type: List[role.Role]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import logging
|
||||
|
||||
from app.db.init_db import init_db
|
||||
from app.db.session import db_session
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def init():
|
||||
init_db(db_session)
|
||||
|
||||
|
||||
def main():
|
||||
logger.info("Creating initial data")
|
||||
init()
|
||||
logger.info("Initial data created")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,17 +1,19 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.api.api_v1.api import api_router
|
||||
from app.core.config import API_V1_STR, BACKEND_CORS_ORIGINS, PROJECT_NAME
|
||||
from app.core import config
|
||||
from app.db.session import Session
|
||||
|
||||
app = FastAPI(title=PROJECT_NAME, openapi_url="/api/v1/openapi.json")
|
||||
app = FastAPI(title=config.PROJECT_NAME, openapi_url="/api/v1/openapi.json")
|
||||
|
||||
# CORS
|
||||
origins = []
|
||||
|
||||
# Set all CORS enabled origins
|
||||
if BACKEND_CORS_ORIGINS:
|
||||
origins_raw = BACKEND_CORS_ORIGINS.split(",")
|
||||
if config.BACKEND_CORS_ORIGINS:
|
||||
origins_raw = config.BACKEND_CORS_ORIGINS.split(",")
|
||||
for origin in origins_raw:
|
||||
use_origin = origin.strip()
|
||||
origins.append(use_origin)
|
||||
@@ -23,4 +25,12 @@ if BACKEND_CORS_ORIGINS:
|
||||
allow_headers=["*"],
|
||||
),
|
||||
|
||||
app.include_router(api_router, prefix=API_V1_STR)
|
||||
app.include_router(api_router, prefix=config.API_V1_STR)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def db_session_middleware(request: Request, call_next):
|
||||
request.state.db = Session()
|
||||
response = await call_next(request)
|
||||
request.state.db.close()
|
||||
return response
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
USERPROFILE_DOC_TYPE = "userprofile"
|
||||
@@ -1,14 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.config import ROLE_SUPERUSER
|
||||
|
||||
|
||||
class RoleEnum(Enum):
|
||||
superuser = ROLE_SUPERUSER
|
||||
|
||||
|
||||
class Roles(BaseModel):
|
||||
roles: List[RoleEnum]
|
||||
@@ -7,4 +7,4 @@ class Token(BaseModel):
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
username: str = None
|
||||
user_id: int = None
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
from typing import List, Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.models.config import USERPROFILE_DOC_TYPE
|
||||
from app.models.role import RoleEnum
|
||||
|
||||
|
||||
# Shared properties
|
||||
class UserBase(BaseModel):
|
||||
email: Optional[str] = None
|
||||
admin_roles: Optional[List[Union[str, RoleEnum]]] = None
|
||||
admin_channels: Optional[List[Union[str, RoleEnum]]] = None
|
||||
disabled: Optional[bool] = None
|
||||
is_active: Optional[bool] = True
|
||||
is_superuser: Optional[bool] = False
|
||||
full_name: Optional[str] = None
|
||||
|
||||
|
||||
class UserBaseInDB(UserBase):
|
||||
username: str
|
||||
full_name: Optional[str] = None
|
||||
id: int = None
|
||||
|
||||
|
||||
# Properties to receive via API on creation
|
||||
class UserInCreate(UserBaseInDB):
|
||||
email: str
|
||||
password: str
|
||||
admin_roles: List[Union[str, RoleEnum]] = []
|
||||
admin_channels: List[Union[str, RoleEnum]] = []
|
||||
disabled: bool = False
|
||||
|
||||
|
||||
# Properties to receive via API on update
|
||||
@@ -39,10 +33,4 @@ class User(UserBaseInDB):
|
||||
|
||||
# Additional properties stored in DB
|
||||
class UserInDB(UserBaseInDB):
|
||||
type: str = USERPROFILE_DOC_TYPE
|
||||
hashed_password: str
|
||||
|
||||
|
||||
class UserSyncIn(UserBase):
|
||||
name: str
|
||||
password: Optional[str] = None
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "users",
|
||||
"type": "fulltext-alias",
|
||||
"params": {
|
||||
"targets": {
|
||||
"users_01": {}
|
||||
}
|
||||
},
|
||||
"sourceType": "nil",
|
||||
"sourceName": "",
|
||||
"sourceUUID": "",
|
||||
"sourceParams": null,
|
||||
"planParams": {},
|
||||
"uuid": ""
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
{
|
||||
"name": "users_01",
|
||||
"type": "fulltext-index",
|
||||
"params": {
|
||||
"doc_config": {
|
||||
"docid_prefix_delim": "",
|
||||
"docid_regexp": "",
|
||||
"mode": "type_field",
|
||||
"type_field": "type"
|
||||
},
|
||||
"mapping": {
|
||||
"analysis": {
|
||||
"analyzers": {
|
||||
"userprofile": {
|
||||
"token_filters": [
|
||||
"apostrophe",
|
||||
"to_lower"
|
||||
],
|
||||
"tokenizer": "unicode",
|
||||
"type": "custom"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default_analyzer": "standard",
|
||||
"default_datetime_parser": "dateTimeOptional",
|
||||
"default_field": "_all",
|
||||
"default_mapping": {
|
||||
"dynamic": true,
|
||||
"enabled": false
|
||||
},
|
||||
"default_type": "_default",
|
||||
"docvalues_dynamic": true,
|
||||
"index_dynamic": true,
|
||||
"store_dynamic": false,
|
||||
"type_field": "_type",
|
||||
"types": {
|
||||
"userprofile": {
|
||||
"dynamic": false,
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"type": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"analyzer": "keyword",
|
||||
"store": false,
|
||||
"index": true,
|
||||
"include_term_vectors": false,
|
||||
"include_in_all": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"admin_channels": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"analyzer": "keyword",
|
||||
"include_in_all": true,
|
||||
"include_term_vectors": true,
|
||||
"index": true,
|
||||
"name": "admin_channels",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"admin_roles": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"analyzer": "keyword",
|
||||
"include_in_all": true,
|
||||
"include_term_vectors": true,
|
||||
"index": true,
|
||||
"name": "admin_roles",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"disabled": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"include_in_all": true,
|
||||
"include_term_vectors": true,
|
||||
"index": true,
|
||||
"name": "disabled",
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"email": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"analyzer": "keyword",
|
||||
"include_in_all": true,
|
||||
"include_term_vectors": true,
|
||||
"index": true,
|
||||
"name": "email",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"full_name": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"analyzer": "standard",
|
||||
"include_in_all": true,
|
||||
"include_term_vectors": true,
|
||||
"index": true,
|
||||
"name": "full_name",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"username": {
|
||||
"enabled": true,
|
||||
"dynamic": false,
|
||||
"fields": [
|
||||
{
|
||||
"analyzer": "keyword",
|
||||
"include_in_all": true,
|
||||
"include_term_vectors": true,
|
||||
"index": true,
|
||||
"name": "username",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"indexType": "scorch",
|
||||
"kvStoreName": ""
|
||||
}
|
||||
},
|
||||
"sourceType": "couchbase",
|
||||
"sourceName": "app",
|
||||
"sourceUUID": "",
|
||||
"sourceParams": {},
|
||||
"planParams": {
|
||||
"maxPartitionsPerPIndex": 171,
|
||||
"numReplicas": 0
|
||||
},
|
||||
"uuid": ""
|
||||
}
|
||||
@@ -27,4 +27,4 @@ def test_use_access_token(superuser_token_headers):
|
||||
)
|
||||
result = r.json()
|
||||
assert r.status_code == 200
|
||||
assert "username" in result
|
||||
assert "email" in result
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import requests
|
||||
|
||||
from app.core import config
|
||||
from app.crud.user import get_user, upsert_user
|
||||
from app.db.database import get_default_bucket
|
||||
from app.crud import user as crud_user
|
||||
from app.db.session import db_session
|
||||
from app.models.user import UserInCreate
|
||||
from app.tests.utils.user import user_authentication_headers
|
||||
from app.tests.utils.utils import get_server_api, random_lower_string
|
||||
@@ -15,16 +15,16 @@ def test_get_users_superuser_me(superuser_token_headers):
|
||||
)
|
||||
current_user = r.json()
|
||||
assert current_user
|
||||
assert current_user["disabled"] is False
|
||||
assert "superuser" in current_user["admin_roles"]
|
||||
assert current_user["username"] == config.FIRST_SUPERUSER
|
||||
assert current_user["is_active"] is True
|
||||
assert current_user["is_superuser"]
|
||||
assert current_user["email"] == config.FIRST_SUPERUSER
|
||||
|
||||
|
||||
def test_create_user_new_email(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
username = random_lower_string()
|
||||
password = random_lower_string()
|
||||
data = {"username": username, "password": password}
|
||||
data = {"email": username, "password": password}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/users/",
|
||||
headers=superuser_token_headers,
|
||||
@@ -32,26 +32,25 @@ def test_create_user_new_email(superuser_token_headers):
|
||||
)
|
||||
assert 200 <= r.status_code < 300
|
||||
created_user = r.json()
|
||||
bucket = get_default_bucket()
|
||||
user = get_user(bucket, username)
|
||||
assert user.username == created_user["username"]
|
||||
user = crud_user.get_by_email(db_session, email=username)
|
||||
assert user.email == created_user["email"]
|
||||
|
||||
|
||||
def test_get_existing_user(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
username = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=username, email=username, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
user_in = UserInCreate(email=username, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
user_id = user.id
|
||||
r = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/users/{username}",
|
||||
f"{server_api}{config.API_V1_STR}/users/{user_id}",
|
||||
headers=superuser_token_headers,
|
||||
)
|
||||
assert 200 <= r.status_code < 300
|
||||
api_user = r.json()
|
||||
user = get_user(bucket, username)
|
||||
assert user.username == api_user["username"]
|
||||
user = crud_user.get_by_email(db_session, email=username)
|
||||
assert user.email == api_user["email"]
|
||||
|
||||
|
||||
def test_create_user_existing_username(superuser_token_headers):
|
||||
@@ -59,10 +58,9 @@ def test_create_user_existing_username(superuser_token_headers):
|
||||
username = random_lower_string()
|
||||
# username = email
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=username, email=username, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
data = {"username": username, "password": password}
|
||||
user_in = UserInCreate(email=username, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
data = {"email": username, "password": password}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/users/",
|
||||
headers=superuser_token_headers,
|
||||
@@ -77,11 +75,10 @@ def test_create_user_by_normal_user():
|
||||
server_api = get_server_api()
|
||||
username = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=username, email=username, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
user_in = UserInCreate(email=username, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
user_token_headers = user_authentication_headers(server_api, username, password)
|
||||
data = {"username": username, "password": password}
|
||||
data = {"email": username, "password": password}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/users/", headers=user_token_headers, json=data
|
||||
)
|
||||
@@ -92,14 +89,13 @@ def test_retrieve_users(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
username = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=username, email=username, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
user_in = UserInCreate(email=username, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
|
||||
username2 = random_lower_string()
|
||||
password2 = random_lower_string()
|
||||
user_in2 = UserInCreate(username=username2, email=username2, password=password2)
|
||||
user2 = upsert_user(bucket, user_in, persist_to=1)
|
||||
user_in2 = UserInCreate(email=username2, password=password2)
|
||||
user2 = crud_user.create(db_session, user_in=user_in2)
|
||||
|
||||
r = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/users/", headers=superuser_token_headers
|
||||
@@ -108,5 +104,4 @@ def test_retrieve_users(superuser_token_headers):
|
||||
|
||||
assert len(all_users) > 1
|
||||
for user in all_users:
|
||||
assert "username" in user
|
||||
assert "admin_roles" in user
|
||||
assert "email" in user
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
from app.crud.user import get_user_doc_id
|
||||
|
||||
|
||||
def test_get_user_id():
|
||||
username = "johndoe@example.com"
|
||||
user_id = get_user_doc_id(username)
|
||||
assert user_id == "userprofile::johndoe@example.com"
|
||||
@@ -1,14 +1,7 @@
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from app.crud.user import (
|
||||
authenticate_user,
|
||||
check_if_user_is_active,
|
||||
check_if_user_is_superuser,
|
||||
get_user,
|
||||
upsert_user,
|
||||
)
|
||||
from app.db.database import get_default_bucket
|
||||
from app.models.role import RoleEnum
|
||||
from app.crud import user as crud_user
|
||||
from app.db.session import db_session
|
||||
from app.models.user import UserInCreate
|
||||
from app.tests.utils.utils import random_lower_string
|
||||
|
||||
@@ -16,90 +9,75 @@ from app.tests.utils.utils import random_lower_string
|
||||
def test_create_user():
|
||||
email = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=email, email=email, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
assert hasattr(user, "username")
|
||||
assert user.username == email
|
||||
user_in = UserInCreate(email=email, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
assert user.email == email
|
||||
assert hasattr(user, "hashed_password")
|
||||
assert hasattr(user, "type")
|
||||
assert user.type == "userprofile"
|
||||
|
||||
|
||||
def test_authenticate_user():
|
||||
email = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=email, email=email, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
authenticated_user = authenticate_user(bucket, email, password)
|
||||
user_in = UserInCreate(email=email, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
authenticated_user = crud_user.authenticate(
|
||||
db_session, email=email, password=password
|
||||
)
|
||||
assert authenticated_user
|
||||
assert user.username == authenticated_user.username
|
||||
assert user.email == authenticated_user.email
|
||||
|
||||
|
||||
def test_not_authenticate_user():
|
||||
email = random_lower_string()
|
||||
password = random_lower_string()
|
||||
bucket = get_default_bucket()
|
||||
user = authenticate_user(bucket, email, password)
|
||||
user = crud_user.authenticate(db_session, email=email, password=password)
|
||||
assert user is False
|
||||
|
||||
|
||||
def test_check_if_user_is_active():
|
||||
email = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=email, email=email, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
is_active = check_if_user_is_active(user)
|
||||
user_in = UserInCreate(email=email, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
is_active = crud_user.is_active(user)
|
||||
assert is_active is True
|
||||
|
||||
|
||||
def test_check_if_user_is_active_inactive():
|
||||
email = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(
|
||||
username=email, email=email, password=password, disabled=True
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
is_active = check_if_user_is_active(user)
|
||||
assert is_active is False
|
||||
user_in = UserInCreate(email=email, password=password, disabled=True)
|
||||
print(user_in)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
print(user)
|
||||
is_active = crud_user.is_active(user)
|
||||
print(is_active)
|
||||
assert is_active
|
||||
|
||||
|
||||
def test_check_if_user_is_superuser():
|
||||
email = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(
|
||||
username=email, email=email, password=password, admin_roles=[RoleEnum.superuser]
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
is_superuser = check_if_user_is_superuser(user)
|
||||
user_in = UserInCreate(email=email, password=password, is_superuser=True)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
is_superuser = crud_user.is_superuser(user)
|
||||
assert is_superuser is True
|
||||
|
||||
|
||||
def test_check_if_user_is_superuser_normal_user():
|
||||
username = random_lower_string()
|
||||
password = random_lower_string()
|
||||
user_in = UserInCreate(username=username, email=username, password=password)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
is_superuser = check_if_user_is_superuser(user)
|
||||
user_in = UserInCreate(email=username, password=password)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
is_superuser = crud_user.is_superuser(user)
|
||||
assert is_superuser is False
|
||||
|
||||
|
||||
def test_get_user():
|
||||
password = random_lower_string()
|
||||
username = random_lower_string()
|
||||
user_in = UserInCreate(
|
||||
username=username,
|
||||
email=username,
|
||||
password=password,
|
||||
admin_roles=[RoleEnum.superuser],
|
||||
)
|
||||
bucket = get_default_bucket()
|
||||
user = upsert_user(bucket, user_in, persist_to=1)
|
||||
user_2 = get_user(bucket, username)
|
||||
assert user.username == user_2.username
|
||||
user_in = UserInCreate(email=username, password=password, is_superuser=True)
|
||||
user = crud_user.create(db_session, user_in=user_in)
|
||||
user_2 = crud_user.get(db_session, user_id=user.id)
|
||||
assert user.email == user_2.email
|
||||
assert jsonable_encoder(user) == jsonable_encoder(user_2)
|
||||
|
||||
@@ -2,8 +2,8 @@ import logging
|
||||
|
||||
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from app.db.external_session import db_session
|
||||
from app.tests.api.api_v1.token.test_token import test_get_access_token
|
||||
from app.db.session import db_session
|
||||
from app.tests.api.api_v1.test_token import test_get_access_token
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,96 +1,86 @@
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
from typing import Optional
|
||||
|
||||
import emails
|
||||
import jwt
|
||||
from emails.template import JinjaTemplate
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
|
||||
from app.core.config import (
|
||||
EMAIL_RESET_TOKEN_EXPIRE_HOURS,
|
||||
EMAIL_TEMPLATES_DIR,
|
||||
EMAILS_ENABLED,
|
||||
EMAILS_FROM_EMAIL,
|
||||
EMAILS_FROM_NAME,
|
||||
PROJECT_NAME,
|
||||
SECRET_KEY,
|
||||
SERVER_HOST,
|
||||
SMTP_HOST,
|
||||
SMTP_PASSWORD,
|
||||
SMTP_PORT,
|
||||
SMTP_TLS,
|
||||
SMTP_USER,
|
||||
)
|
||||
from app.core import config
|
||||
|
||||
password_reset_jwt_subject = "preset"
|
||||
|
||||
|
||||
def send_email(email_to: str, subject_template="", html_template="", environment={}):
|
||||
assert EMAILS_ENABLED, "no provided configuration for email variables"
|
||||
assert config.EMAILS_ENABLED, "no provided configuration for email variables"
|
||||
message = emails.Message(
|
||||
subject=JinjaTemplate(subject_template),
|
||||
html=JinjaTemplate(html_template),
|
||||
mail_from=(EMAILS_FROM_NAME, EMAILS_FROM_EMAIL),
|
||||
mail_from=(config.EMAILS_FROM_NAME, config.EMAILS_FROM_EMAIL),
|
||||
)
|
||||
smtp_options = {"host": SMTP_HOST, "port": SMTP_PORT}
|
||||
if SMTP_TLS:
|
||||
smtp_options = {"host": config.SMTP_HOST, "port": config.SMTP_PORT}
|
||||
if config.SMTP_TLS:
|
||||
smtp_options["tls"] = True
|
||||
if SMTP_USER:
|
||||
smtp_options["user"] = SMTP_USER
|
||||
if SMTP_PASSWORD:
|
||||
smtp_options["password"] = SMTP_PASSWORD
|
||||
if config.SMTP_USER:
|
||||
smtp_options["user"] = config.SMTP_USER
|
||||
if config.SMTP_PASSWORD:
|
||||
smtp_options["password"] = config.SMTP_PASSWORD
|
||||
response = message.send(to=email_to, render=environment, smtp=smtp_options)
|
||||
logging.info(f"send email result: {response}")
|
||||
|
||||
|
||||
def send_test_email(email_to: str):
|
||||
subject = f"{PROJECT_NAME} - Test email"
|
||||
with open(Path(EMAIL_TEMPLATES_DIR) / "test_email.html") as f:
|
||||
project_name = config.PROJECT_NAME
|
||||
subject = f"{project_name} - Test email"
|
||||
with open(Path(config.EMAIL_TEMPLATES_DIR) / "test_email.html") as f:
|
||||
template_str = f.read()
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject_template=subject,
|
||||
html_template=template_str,
|
||||
environment={"project_name": PROJECT_NAME, "email": email_to},
|
||||
environment={"project_name": config.PROJECT_NAME, "email": email_to},
|
||||
)
|
||||
|
||||
|
||||
def send_reset_password_email(email_to: str, username: str, token: str):
|
||||
subject = f"{PROJECT_NAME} - Password recovery for user {username}"
|
||||
with open(Path(EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
|
||||
def send_reset_password_email(email_to: str, email: str, token: str):
|
||||
project_name = config.PROJECT_NAME
|
||||
subject = f"{project_name} - Password recovery for user {email}"
|
||||
with open(Path(config.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
|
||||
template_str = f.read()
|
||||
if hasattr(token, "decode"):
|
||||
use_token = token.decode()
|
||||
else:
|
||||
use_token = token
|
||||
link = f"{SERVER_HOST}/reset-password?token={use_token}"
|
||||
server_host = config.SERVER_HOST
|
||||
link = f"{server_host}/reset-password?token={use_token}"
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject_template=subject,
|
||||
html_template=template_str,
|
||||
environment={
|
||||
"project_name": PROJECT_NAME,
|
||||
"username": username,
|
||||
"project_name": config.PROJECT_NAME,
|
||||
"username": email,
|
||||
"email": email_to,
|
||||
"valid_hours": EMAIL_RESET_TOKEN_EXPIRE_HOURS,
|
||||
"valid_hours": config.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
|
||||
"link": link,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def send_new_account_email(email_to: str, username: str, password: str):
|
||||
subject = f"{PROJECT_NAME} - New acccount for user {username}"
|
||||
with open(Path(EMAIL_TEMPLATES_DIR) / "new_account.html") as f:
|
||||
project_name = config.PROJECT_NAME
|
||||
subject = f"{project_name} - New acccount for user {username}"
|
||||
with open(Path(config.EMAIL_TEMPLATES_DIR) / "new_account.html") as f:
|
||||
template_str = f.read()
|
||||
link = f"{SERVER_HOST}"
|
||||
link = config.SERVER_HOST
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject_template=subject,
|
||||
html_template=template_str,
|
||||
environment={
|
||||
"project_name": PROJECT_NAME,
|
||||
"project_name": config.PROJECT_NAME,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"email": email_to,
|
||||
@@ -99,28 +89,23 @@ def send_new_account_email(email_to: str, username: str, password: str):
|
||||
)
|
||||
|
||||
|
||||
def generate_password_reset_token(username):
|
||||
delta = timedelta(hours=EMAIL_RESET_TOKEN_EXPIRE_HOURS)
|
||||
def generate_password_reset_token(email):
|
||||
delta = timedelta(hours=config.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
|
||||
now = datetime.utcnow()
|
||||
expires = now + delta
|
||||
exp = expires.timestamp()
|
||||
encoded_jwt = jwt.encode(
|
||||
{
|
||||
"exp": exp,
|
||||
"nbf": now,
|
||||
"sub": password_reset_jwt_subject,
|
||||
"username": username,
|
||||
},
|
||||
SECRET_KEY,
|
||||
{"exp": exp, "nbf": now, "sub": password_reset_jwt_subject, "email": email},
|
||||
config.SECRET_KEY,
|
||||
algorithm="HS256",
|
||||
)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def verify_password_reset_token(token) -> Union[str, bool]:
|
||||
def verify_password_reset_token(token) -> Optional[str]:
|
||||
try:
|
||||
decoded_token = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
||||
decoded_token = jwt.decode(token, config.SECRET_KEY, algorithms=["HS256"])
|
||||
assert decoded_token["sub"] == password_reset_jwt_subject
|
||||
return decoded_token["username"]
|
||||
return decoded_token["email"]
|
||||
except InvalidTokenError:
|
||||
return False
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
# Import standard library modules
|
||||
|
||||
|
||||
# Import installed packages
|
||||
from raven import Client
|
||||
|
||||
from app.core import config
|
||||
from app.core.celery_app import celery_app
|
||||
|
||||
# Import app code
|
||||
# Absolute imports for Hydrogen (Jupyter Kernel) compatibility
|
||||
from app.core.config import SENTRY_DSN
|
||||
|
||||
client_sentry = Client(SENTRY_DSN)
|
||||
client_sentry = Client(config.SENTRY_DSN)
|
||||
|
||||
|
||||
@celery_app.task(acks_late=True)
|
||||
|
||||
Reference in New Issue
Block a user