mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-19 23:54:16 +02:00
163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
import logging
|
|
import os
|
|
import uuid
|
|
from typing import Optional
|
|
|
|
from fastapi import Depends, Request
|
|
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models
|
|
from fastapi_users.authentication import (
|
|
AuthenticationBackend,
|
|
BearerTransport,
|
|
CookieTransport,
|
|
JWTStrategy,
|
|
)
|
|
from fastapi_users.db import SQLAlchemyUserDatabase
|
|
from httpx_oauth.clients.openid import OpenID
|
|
from fastapi.responses import RedirectResponse, Response
|
|
from starlette import status
|
|
|
|
from media_manager.auth.config import AuthConfig, OpenIdConfig, EmailConfig
|
|
from media_manager.auth.db import User, get_user_db
|
|
from media_manager.auth.schemas import UserUpdate
|
|
from media_manager.config import BasicConfig
|
|
|
|
import smtplib
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
config = AuthConfig()
|
|
SECRET = config.token_secret
|
|
LIFETIME = config.session_lifetime
|
|
|
|
if (
|
|
os.getenv("OPENID_ENABLED") is not None
|
|
and os.getenv("OPENID_ENABLED").upper() == "TRUE"
|
|
):
|
|
openid_config = OpenIdConfig()
|
|
openid_client = OpenID(
|
|
base_scopes=["openid", "email", "profile"],
|
|
client_id=openid_config.client_id,
|
|
client_secret=openid_config.client_secret,
|
|
name=openid_config.name,
|
|
openid_configuration_endpoint=openid_config.configuration_endpoint,
|
|
)
|
|
openid_client.base_scopes = ["openid", "email", "profile"]
|
|
else:
|
|
openid_client = None
|
|
|
|
|
|
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
|
reset_password_token_secret = SECRET
|
|
verification_token_secret = SECRET
|
|
|
|
async def on_after_register(self, user: User, request: Optional[Request] = None):
|
|
log.info(f"User {user.id} has registered.")
|
|
if user.email in config.admin_email:
|
|
updated_user = UserUpdate(is_superuser=True, is_verified=True)
|
|
await self.update(user=user, user_update=updated_user)
|
|
|
|
async def on_after_forgot_password(
|
|
self, user: User, token: str, request: Optional[Request] = None
|
|
):
|
|
link = f"{BasicConfig().FRONTEND_URL}login/reset-password?token={token}"
|
|
log.info(f"User {user.id} has forgot their password. Reset Link: {link}")
|
|
if not config.email_password_resets:
|
|
log.info("Email password resets are disabled, not sending email.")
|
|
return
|
|
|
|
email_conf = EmailConfig()
|
|
subject = "MediaManager - Password Reset Request"
|
|
html = f"""\
|
|
<html>
|
|
<body>
|
|
<p>Hi {user.email},
|
|
<br>
|
|
<br>
|
|
if you forgot your password, <a href="{link}">reset you password here</a>.<br>
|
|
If you did not request a password reset, you can ignore this email.</p>
|
|
<br>
|
|
<br>
|
|
If the link does not work, copy the following link into your browser: {link}<br>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
message = MIMEMultipart()
|
|
message["From"] = email_conf.from_email
|
|
message["To"] = user.email
|
|
message["Subject"] = subject
|
|
message.attach(MIMEText(html, "html"))
|
|
|
|
with smtplib.SMTP(email_conf.smtp_host, email_conf.smtp_port) as server:
|
|
if email_conf.use_tls:
|
|
server.starttls()
|
|
server.login(email_conf.smtp_user, email_conf.smtp_password)
|
|
server.sendmail(email_conf.from_email, user.email, message.as_string())
|
|
log.info(f"Sent password reset email to {user.email}")
|
|
|
|
async def on_after_reset_password(
|
|
self, user: User, request: Optional[Request] = None
|
|
):
|
|
log.info(f"User {user.id} has reset their password.")
|
|
|
|
async def on_after_request_verify(
|
|
self, user: User, token: str, request: Optional[Request] = None
|
|
):
|
|
log.info(
|
|
f"Verification requested for user {user.id}. Verification token: {token}"
|
|
)
|
|
|
|
async def on_after_verify(self, user: User, request: Optional[Request] = None):
|
|
log.info(f"User {user.id} has been verified")
|
|
|
|
|
|
async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
|
|
yield UserManager(user_db)
|
|
|
|
|
|
def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:
|
|
return JWTStrategy(secret=SECRET, lifetime_seconds=LIFETIME)
|
|
|
|
|
|
# needed because the default CookieTransport does not redirect after login,
|
|
# thus the user would be stuck on the OAuth Providers "redirecting" page
|
|
class RedirectingCookieTransport(CookieTransport):
|
|
async def get_login_response(self, token: str) -> Response:
|
|
response = RedirectResponse(
|
|
str(BasicConfig().FRONTEND_URL) + "dashboard",
|
|
status_code=status.HTTP_302_FOUND,
|
|
)
|
|
return self._set_login_cookie(response, token)
|
|
|
|
|
|
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
|
cookie_transport = CookieTransport(cookie_max_age=LIFETIME)
|
|
openid_cookie_transport = RedirectingCookieTransport(cookie_max_age=LIFETIME)
|
|
|
|
bearer_auth_backend = AuthenticationBackend(
|
|
name="jwt",
|
|
transport=bearer_transport,
|
|
get_strategy=get_jwt_strategy,
|
|
)
|
|
cookie_auth_backend = AuthenticationBackend(
|
|
name="cookie",
|
|
transport=cookie_transport,
|
|
get_strategy=get_jwt_strategy,
|
|
)
|
|
openid_cookie_auth_backend = AuthenticationBackend(
|
|
name="cookie",
|
|
transport=openid_cookie_transport,
|
|
get_strategy=get_jwt_strategy,
|
|
)
|
|
|
|
fastapi_users = FastAPIUsers[User, uuid.UUID](
|
|
get_user_manager, [bearer_auth_backend, cookie_auth_backend]
|
|
)
|
|
|
|
current_active_user = fastapi_users.current_user(active=True, verified=True)
|
|
current_superuser = fastapi_users.current_user(
|
|
active=True, verified=True, superuser=True
|
|
)
|