DAN-58 Email validation (#39)

This commit is contained in:
Yuhong Sun
2023-05-13 14:05:21 -07:00
committed by GitHub
parent 6ed86ed369
commit fb9c3e530b
5 changed files with 87 additions and 24 deletions

View File

@@ -1,10 +0,0 @@
import os
DISABLE_AUTH = os.environ.get("DISABLE_AUTH", "").lower() == "true"
SECRET = os.environ.get("SECRET", "")
SESSION_EXPIRE_TIME_SECONDS = int(os.environ.get("SESSION_EXPIRE_TIME_SECONDS", 3600))
ENABLE_OAUTH = os.environ.get("ENABLE_OAUTH", "False").lower() != "false"
GOOGLE_OAUTH_CLIENT_ID = os.environ.get("GOOGLE_OAUTH_CLIENT_ID", "")
GOOGLE_OAUTH_CLIENT_SECRET = os.environ.get("GOOGLE_OAUTH_CLIENT_SECRET", "")

View File

@@ -1,19 +1,30 @@
import smtplib
import uuid import uuid
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Optional from typing import Optional
from danswer.auth.configs import DISABLE_AUTH
from danswer.auth.configs import GOOGLE_OAUTH_CLIENT_ID
from danswer.auth.configs import GOOGLE_OAUTH_CLIENT_SECRET
from danswer.auth.configs import SECRET
from danswer.auth.configs import SESSION_EXPIRE_TIME_SECONDS
from danswer.auth.schemas import UserCreate from danswer.auth.schemas import UserCreate
from danswer.auth.schemas import UserRole from danswer.auth.schemas import UserRole
from danswer.configs.app_configs import DISABLE_AUTH
from danswer.configs.app_configs import GOOGLE_OAUTH_CLIENT_ID
from danswer.configs.app_configs import GOOGLE_OAUTH_CLIENT_SECRET
from danswer.configs.app_configs import REQUIRE_EMAIL_VERIFICATION
from danswer.configs.app_configs import SECRET
from danswer.configs.app_configs import SESSION_EXPIRE_TIME_SECONDS
from danswer.configs.app_configs import SMTP_PASS
from danswer.configs.app_configs import SMTP_PORT
from danswer.configs.app_configs import SMTP_SERVER
from danswer.configs.app_configs import SMTP_USER
from danswer.configs.app_configs import VALID_EMAIL_DOMAIN
from danswer.configs.app_configs import WEB_DOMAIN
from danswer.db.auth import get_access_token_db from danswer.db.auth import get_access_token_db
from danswer.db.auth import get_user_count from danswer.db.auth import get_user_count
from danswer.db.auth import get_user_db from danswer.db.auth import get_user_db
from danswer.db.models import AccessToken from danswer.db.models import AccessToken
from danswer.db.models import User from danswer.db.models import User
from danswer.utils.logging import setup_logger
from fastapi import Depends from fastapi import Depends
from fastapi import HTTPException from fastapi import HTTPException
from fastapi import Request from fastapi import Request
@@ -30,6 +41,27 @@ from fastapi_users.authentication.strategy.db import DatabaseStrategy
from fastapi_users.db import SQLAlchemyUserDatabase from fastapi_users.db import SQLAlchemyUserDatabase
from httpx_oauth.clients.google import GoogleOAuth2 from httpx_oauth.clients.google import GoogleOAuth2
logger = setup_logger()
def send_user_verification_email(user_email: str, token: str) -> None:
msg = MIMEMultipart()
msg["Subject"] = "Danswer Email Verification"
msg["From"] = "no-reply@danswer.dev"
msg["To"] = user_email
link = f"{WEB_DOMAIN}/verify-email?token={token}"
body = MIMEText(f"Click the following link to verify your email address: {link}")
msg.attach(body)
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as s:
s.starttls()
# If credentials fails with gmail, check (You need an app password, not just the basic email password)
# https://support.google.com/accounts/answer/185833?sjid=8512343437447396151-NA
s.login(SMTP_USER, SMTP_PASS)
s.send_message(msg)
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
reset_password_token_secret = SECRET reset_password_token_secret = SECRET
@@ -52,17 +84,34 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
async def on_after_register( async def on_after_register(
self, user: User, request: Optional[Request] = None self, user: User, request: Optional[Request] = None
) -> None: ) -> None:
print(f"User {user.id} has registered.") logger.info(f"User {user.id} has registered.")
async def on_after_forgot_password( async def on_after_forgot_password(
self, user: User, token: str, request: Optional[Request] = None self, user: User, token: str, request: Optional[Request] = None
) -> None: ) -> None:
print(f"User {user.id} has forgot their password. Reset token: {token}") logger.info(f"User {user.id} has forgot their password. Reset token: {token}")
async def on_after_request_verify( async def on_after_request_verify(
self, user: User, token: str, request: Optional[Request] = None self, user: User, token: str, request: Optional[Request] = None
) -> None: ) -> None:
print(f"Verification requested for user {user.id}. Verification token: {token}") if VALID_EMAIL_DOMAIN:
if user.email.count("@") != 1:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email is not valid",
)
domain = user.email.split("@")[-1]
if domain != VALID_EMAIL_DOMAIN:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email domain is not valid",
)
logger.info(
f"Verification requested for user {user.id}. Verification token: {token}"
)
send_user_verification_email(user.email, token)
async def get_user_manager( async def get_user_manager(
@@ -92,7 +141,9 @@ google_oauth_client = GoogleOAuth2(GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_S
fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])
current_active_user = fastapi_users.current_user(active=True, optional=DISABLE_AUTH) current_active_user = fastapi_users.current_user(
active=True, verified=REQUIRE_EMAIL_VERIFICATION, optional=DISABLE_AUTH
)
def current_admin_user(user: User = Depends(current_active_user)) -> User | None: def current_admin_user(user: User = Depends(current_active_user)) -> User | None:

View File

@@ -23,6 +23,27 @@ BLURB_LENGTH = 200 # Characters. Blurbs will be truncated at the first punctuat
WEB_DOMAIN = os.environ.get("WEB_DOMAIN", "http://localhost:3000") WEB_DOMAIN = os.environ.get("WEB_DOMAIN", "http://localhost:3000")
#####
# Auth Configs
#####
DISABLE_AUTH = os.environ.get("DISABLE_AUTH", "").lower() == "true"
REQUIRE_EMAIL_VERIFICATION = (
os.environ.get("REQUIRE_EMAIL_VERIFICATION", "").lower() == "true"
)
SMTP_SERVER = os.environ.get("SMTP_SERVER", "smtp.gmail.com")
SMTP_PORT = int(os.environ.get("SMTP_PORT", "587"))
SMTP_USER = os.environ.get("SMTP_USER", "your-email@gmail.com")
SMTP_PASS = os.environ.get("SMTP_PASS", "your-gmail-password")
SECRET = os.environ.get("SECRET", "")
SESSION_EXPIRE_TIME_SECONDS = int(os.environ.get("SESSION_EXPIRE_TIME_SECONDS", 3600))
VALID_EMAIL_DOMAIN = os.environ.get("VALID_EMAIL_DOMAIN", "")
# OAuth Login Flow
ENABLE_OAUTH = os.environ.get("ENABLE_OAUTH", "").lower() != "false"
GOOGLE_OAUTH_CLIENT_ID = os.environ.get("GOOGLE_OAUTH_CLIENT_ID", "")
GOOGLE_OAUTH_CLIENT_SECRET = os.environ.get("GOOGLE_OAUTH_CLIENT_SECRET", "")
##### #####
# Vector DB Configs # Vector DB Configs
##### #####
@@ -47,6 +68,7 @@ GOOGLE_DRIVE_INCLUDE_SHARED = False
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", "") GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", "")
##### #####
# Query Configs # Query Configs
##### #####

View File

@@ -1,6 +1,4 @@
import uvicorn import uvicorn
from danswer.auth.configs import ENABLE_OAUTH
from danswer.auth.configs import SECRET
from danswer.auth.schemas import UserCreate from danswer.auth.schemas import UserCreate
from danswer.auth.schemas import UserRead from danswer.auth.schemas import UserRead
from danswer.auth.schemas import UserUpdate from danswer.auth.schemas import UserUpdate
@@ -9,6 +7,8 @@ from danswer.auth.users import fastapi_users
from danswer.auth.users import google_oauth_client from danswer.auth.users import google_oauth_client
from danswer.configs.app_configs import APP_HOST from danswer.configs.app_configs import APP_HOST
from danswer.configs.app_configs import APP_PORT from danswer.configs.app_configs import APP_PORT
from danswer.configs.app_configs import ENABLE_OAUTH
from danswer.configs.app_configs import SECRET
from danswer.configs.app_configs import WEB_DOMAIN from danswer.configs.app_configs import WEB_DOMAIN
from danswer.server.admin import router as admin_router from danswer.server.admin import router as admin_router
from danswer.server.event_loading import router as event_processing_router from danswer.server.event_loading import router as event_processing_router

View File

@@ -116,7 +116,7 @@ if __name__ == "__main__":
) )
parser.add_argument( parser.add_argument(
"--slack-export-dir", "--slack-export-dir",
default="/Users/chrisweaver/Downloads/test-slack-export", default="~/Downloads/test-slack-export",
) )
parser.add_argument( parser.add_argument(
"--qdrant-collection", "--qdrant-collection",
@@ -133,6 +133,6 @@ if __name__ == "__main__":
recreate_collection(args.qdrant_collection) recreate_collection(args.qdrant_collection)
# load_slack_batch(args.slack_export_dir, args.qdrant_collection) # load_slack_batch(args.slack_export_dir, args.qdrant_collection)
# load_web_batch(args.website_url, args.qdrant_collection) load_web_batch(args.website_url, args.qdrant_collection)
# load_google_drive_batch(args.qdrant_collection) # load_google_drive_batch(args.qdrant_collection)
load_github_batch(args.github_owner, args.github_repo, args.qdrant_collection) # load_github_batch(args.github_owner, args.github_repo, args.qdrant_collection)