Initial config with email service test
Build LinkLog Development Image / development-image (push) Successful in 11s

This commit is contained in:
Olaf
2026-08-25 23:23:28 +02:00
parent defe7a83a9
commit 102d8e533c
20 changed files with 482 additions and 34 deletions
+6
View File
@@ -19,3 +19,9 @@ def authenticate_user(username: str, password: str):
(username, password_hash),
).fetchone()
return dict(row) if row else None
def find_user(username: str):
with get_connection() as conn:
row = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
return dict(row) if row else None
+11 -1
View File
@@ -70,4 +70,14 @@ def send_verification_email(email: str, username: str, verification_url: str) ->
def send_test_email(email: str) -> None:
send_message(email, 'LinkLog SMTP test', 'This is a test message from LinkLog. SMTP is configured correctly.\n')
send_message(email, 'LinkLog SMTP test', 'This is a test message from LinkLog. SMTP is configured correctly.\n')
def send_password_reset_email(email: str, username: str, reset_url: str) -> None:
send_message(
email,
'Reset your LinkLog password',
f'Hello {username},\n\n'
f'Reset your LinkLog password by opening this link:\n{reset_url}\n\n'
f'This link expires in {settings.password_reset_expiry_hours} hours.\n',
)
+48
View File
@@ -0,0 +1,48 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timedelta, timezone
from hashlib import sha256
from secrets import token_urlsafe
from uuid import uuid4
from backend.app.core.config import settings
from backend.app.database import get_connection, hash_password
def hash_reset_token(token: str) -> str:
return sha256(token.encode('utf-8')).hexdigest()
def create_reset_token(user_id: str) -> str:
token = token_urlsafe(32)
expires_at = datetime.now(timezone.utc) + timedelta(hours=settings.password_reset_expiry_hours)
with get_connection() as conn:
conn.execute('DELETE FROM password_reset_tokens WHERE user_id = ?', (user_id,))
conn.execute(
'''INSERT INTO password_reset_tokens
(id, user_id, token_hash, expires_at) VALUES (?, ?, ?, ?)''',
(str(uuid4()), user_id, hash_reset_token(token), expires_at.isoformat()),
)
conn.commit()
return token
def reset_password(token: str, password: str) -> bool:
now = datetime.now(timezone.utc).isoformat()
with get_connection() as conn:
row = conn.execute(
'''SELECT user_id FROM password_reset_tokens
WHERE token_hash = ? AND expires_at > ?''',
(hash_reset_token(token), now),
).fetchone()
if row is None:
return False
conn.execute(
'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
(hash_password(password), row['user_id']),
)
conn.execute('DELETE FROM password_reset_tokens WHERE user_id = ?', (row['user_id'],))
conn.execute('DELETE FROM tokens WHERE user_id = ?', (row['user_id'],))
conn.commit()
return True