Added email functionality
Build LinkLog Development Image / development-image (push) Successful in 10s

This commit is contained in:
Olaf
2026-08-25 22:27:45 +02:00
parent bd78e3b86e
commit 07e520e03f
13 changed files with 228 additions and 8 deletions
+16 -5
View File
@@ -10,6 +10,9 @@ from pydantic import BaseModel
from backend.app.api.dependencies import require_admin
from backend.app.database import get_connection, hash_password
from backend.app.services.link_service import delete_label
from backend.app.services.email_service import send_verification_email, smtp_configured
from backend.app.services.email_verification import create_verification_token
from backend.app.core.config import settings
router = APIRouter()
@@ -39,6 +42,7 @@ def public_user(row):
'avatar_url': row['avatar_url'],
'bio': row['bio'],
'created_at': row['created_at'],
'email_verified': bool(row['email_verified']),
}
@@ -46,7 +50,7 @@ def public_user(row):
def list_users(_: dict = Depends(require_admin)):
with get_connection() as conn:
rows = conn.execute(
'SELECT id, username, email, is_admin, avatar_url, bio, created_at FROM users ORDER BY username'
'SELECT id, username, email, is_admin, avatar_url, bio, created_at, email_verified FROM users ORDER BY username'
).fetchall()
return [public_user(row) for row in rows]
@@ -62,8 +66,8 @@ def create_user(payload: AdminUserCreate, _: dict = Depends(require_admin)):
try:
cursor = conn.execute(
'''
INSERT INTO users (id, username, email, password_hash, is_admin)
VALUES (?, ?, ?, ?, ?)
INSERT INTO users (id, username, email, password_hash, is_admin, email_verified)
VALUES (?, ?, ?, ?, ?, 0)
''',
(str(uuid4()), username, email, hash_password(payload.password), int(payload.is_admin)),
)
@@ -74,8 +78,15 @@ def create_user(payload: AdminUserCreate, _: dict = Depends(require_admin)):
raise
row = conn.execute(
'SELECT id, username, email, is_admin, avatar_url, bio, created_at FROM users WHERE rowid = last_insert_rowid()'
'SELECT id, username, email, is_admin, avatar_url, bio, created_at, email_verified FROM users WHERE rowid = last_insert_rowid()'
).fetchone()
token = create_verification_token(row['id'])
verification_url = f'{settings.public_url}/api/auth/verify-email?token={token}'
if smtp_configured():
try:
send_verification_email(row['email'], row['username'], verification_url)
except Exception as error:
raise HTTPException(status_code=503, detail=f'User created but verification email could not be sent: {error}') from error
return public_user(row)
@@ -109,7 +120,7 @@ def update_user_privileges(
)
conn.commit()
row = conn.execute(
'SELECT id, username, email, is_admin, avatar_url, bio, created_at FROM users WHERE id = ?',
'SELECT id, username, email, is_admin, avatar_url, bio, created_at, email_verified FROM users WHERE id = ?',
(user_id,),
).fetchone()
return public_user(row)
+10
View File
@@ -8,6 +8,7 @@ from pydantic import BaseModel
from backend.app.database import get_connection, init_db
from backend.app.services.auth_service import authenticate_user
from backend.app.services.email_verification import verify_email
from backend.app.services.token_service import issue_token, revoke_token, validate_token
router = APIRouter()
@@ -25,6 +26,8 @@ def login(payload: LoginRequest):
user = authenticate_user(payload.username, payload.password)
if user is None:
raise HTTPException(status_code=401, detail='Invalid username or password')
if not user['email_verified']:
raise HTTPException(status_code=403, detail='Email address is not verified')
token_data = issue_token(user['id'], user['username'])
return {
@@ -36,6 +39,13 @@ def login(payload: LoginRequest):
}
@router.get('/verify-email')
def verify_email_address(token: str):
if not verify_email(token):
raise HTTPException(status_code=400, detail='Verification link is invalid or expired')
return {'status': 'verified', 'message': 'Email address verified. You can now sign in.'}
@router.post('/logout')
def logout(payload: dict):
token = payload.get('token')