211 lines
6.4 KiB
Python
211 lines
6.4 KiB
Python
import json
|
|
import uuid
|
|
from datetime import timedelta
|
|
from datetime import timezone, datetime
|
|
from fastapi.exceptions import HTTPException
|
|
from fastapi import APIRouter as FastAPI, Depends, Request
|
|
|
|
|
|
from config import (
|
|
LIMITER,
|
|
SMTP_HOST,
|
|
SMTP_PORT,
|
|
SMTP_USER,
|
|
SMTP_PASSWORD,
|
|
SMTP_USE_TLS,
|
|
SMTP_TIMEOUT,
|
|
SMTP_FROM_USER,
|
|
SMTP_TEMPLATES_DIRECTORY,
|
|
PLATFORM_NAME,
|
|
HAS_HTTPS,
|
|
HOST,
|
|
PORT,
|
|
EMAIL_VERIFICATION_EXPIRATION,
|
|
UNVERIFIED_MANAGER,
|
|
)
|
|
from responses import (
|
|
Response as APIResponse,
|
|
UnprocessableEntity,
|
|
BadRequest,
|
|
NotFound,
|
|
InternalServerError,
|
|
)
|
|
from orm.users import (
|
|
User,
|
|
UserModel,
|
|
)
|
|
from orm.email_verification import EmailVerification, EmailVerificationType
|
|
from util.email import send_email
|
|
|
|
|
|
router = FastAPI()
|
|
|
|
|
|
@router.get(
|
|
"/user/verifyEmail/{verification_id}",
|
|
tags=["Users"],
|
|
status_code=200,
|
|
responses={
|
|
200: {"model": APIResponse},
|
|
404: {"model": NotFound},
|
|
422: {"model": UnprocessableEntity},
|
|
},
|
|
)
|
|
@LIMITER.limit("3/second")
|
|
async def verify_email(
|
|
request: Request,
|
|
verification_id: str,
|
|
user: UserModel = Depends(UNVERIFIED_MANAGER),
|
|
):
|
|
"""
|
|
Verifies a user's email address. Endpoint is
|
|
limited to 3 hits per second
|
|
"""
|
|
|
|
if not (
|
|
verification := await EmailVerification.select(*EmailVerification.all_columns())
|
|
.where(EmailVerification.id == verification_id)
|
|
.first()
|
|
):
|
|
raise HTTPException(status_code=404, detail="Verification ID is invalid")
|
|
elif not verification["pending"]:
|
|
raise HTTPException(status_code=400, detail="Email is already verified")
|
|
elif datetime.now().astimezone(timezone.utc) - verification[
|
|
"creation_date"
|
|
].astimezone(timezone.utc) > timedelta(seconds=EMAIL_VERIFICATION_EXPIRATION):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Verification window has expired. Try again",
|
|
)
|
|
else:
|
|
await EmailVerification.update({EmailVerification.pending: False}).where(
|
|
EmailVerification.user == user.public_id
|
|
)
|
|
await User.update({User.email_verified: True}).where(
|
|
User.public_id == user.public_id
|
|
)
|
|
return APIResponse(status_code=200, msg="Verification successful")
|
|
|
|
|
|
@router.get(
|
|
"/user/changeEmail/{verification_id}",
|
|
tags=["Users"],
|
|
status_code=200,
|
|
responses={
|
|
200: {"model": APIResponse},
|
|
400: {"model": BadRequest},
|
|
422: {"model": UnprocessableEntity},
|
|
404: {"model": NotFound},
|
|
},
|
|
)
|
|
@LIMITER.limit("3/second")
|
|
async def change_email(
|
|
request: Request,
|
|
verification_id: str,
|
|
user: UserModel = Depends(UNVERIFIED_MANAGER),
|
|
):
|
|
"""
|
|
Modifies a user's email address.
|
|
Endpoint is limited to 3 hits per second
|
|
"""
|
|
|
|
if not (
|
|
verification := await EmailVerification.select(*EmailVerification.all_columns())
|
|
.where(EmailVerification.id == verification_id)
|
|
.first()
|
|
):
|
|
raise HTTPException(status_code=404, detail="Request ID is invalid")
|
|
elif not verification["pending"]:
|
|
raise HTTPException(status_code=400, detail="This link has already been used")
|
|
elif datetime.now().astimezone(timezone.utc) - verification[
|
|
"creation_date"
|
|
].astimezone(timezone.utc) > timedelta(seconds=EMAIL_VERIFICATION_EXPIRATION):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Verification window has expired. Try again",
|
|
)
|
|
else:
|
|
# Note how we don't update based on the verification ID:
|
|
# this way, multiple pending email verification requests
|
|
# are all cleared at once
|
|
await EmailVerification.update({EmailVerification.pending: False}).where(
|
|
EmailVerification.user == user.public_id
|
|
and EmailVerification.kind == EmailVerificationType.CHANGE_EMAIL
|
|
)
|
|
await User.update(
|
|
{
|
|
User.email_address: verification["data"].decode(),
|
|
User.email_verified: False,
|
|
}
|
|
).where(User.public_id == user.public_id)
|
|
return APIResponse(status_code=200, msg="Email updated")
|
|
|
|
|
|
@router.put(
|
|
"user/resendMail",
|
|
tags=["Users"],
|
|
status_code=200,
|
|
responses={
|
|
200: {"model": APIResponse},
|
|
400: {"model": BadRequest},
|
|
422: {"model": UnprocessableEntity},
|
|
500: {"model": InternalServerError},
|
|
},
|
|
)
|
|
@LIMITER.limit("6/minute")
|
|
async def resend_email(request: Request, user: UserModel = Depends(UNVERIFIED_MANAGER)):
|
|
"""
|
|
Resends the verification email to the user if the previous has expired.
|
|
Endpoint is limited to 6 hits per minute
|
|
"""
|
|
|
|
if user.email_verified:
|
|
raise HTTPException(status_code=400, detail="Email is already verified")
|
|
email_template = SMTP_TEMPLATES_DIRECTORY / f"{user.locale}.json"
|
|
try:
|
|
email_template.resolve(strict=True)
|
|
except FileNotFoundError:
|
|
email_template = SMTP_TEMPLATES_DIRECTORY / "en_US.json"
|
|
with email_template.open() as f:
|
|
email_message = json.load(f)["signup"]
|
|
verification_id = uuid.uuid4()
|
|
if await send_email(
|
|
SMTP_HOST,
|
|
SMTP_PORT,
|
|
email_message["content"].format(
|
|
first_name=user.first_name,
|
|
last_name=user.last_name,
|
|
username=user.username,
|
|
email=user.email_address,
|
|
link=f"http{'s' if HAS_HTTPS else ''}://{HOST}"
|
|
f"{'' if PORT == 443 and HAS_HTTPS or PORT == 80 else f':{PORT}'}/user/verifyEmail/{verification_id}",
|
|
platformName=PLATFORM_NAME,
|
|
),
|
|
SMTP_TIMEOUT,
|
|
SMTP_FROM_USER,
|
|
user.email_address,
|
|
email_message["subject"].format(
|
|
first_name=user.first_name,
|
|
last_name=user.last_name,
|
|
username=user.username,
|
|
email=user.email_address,
|
|
platformName=PLATFORM_NAME,
|
|
),
|
|
SMTP_USER,
|
|
SMTP_PASSWORD,
|
|
use_tls=SMTP_USE_TLS,
|
|
):
|
|
await EmailVerification.update(
|
|
{
|
|
EmailVerification.id: verification_id,
|
|
EmailVerification.creation_date: datetime.now(),
|
|
}
|
|
)
|
|
return APIResponse(status_code=200, msg="Success")
|
|
else:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="An error occurred while trying to resend the email,"
|
|
" please try again later",
|
|
)
|