This commit is contained in:
2026-07-17 12:51:47 +09:00
parent 38af2aab9f
commit 4ac681bd10
30 changed files with 1340 additions and 803 deletions
+26
View File
@@ -12,6 +12,8 @@ from fastapi.responses import Response
from config.config_db import get_db_pool
from config.config_system import (
DEVICE_TOKEN_COOKIE_NAME,
EMAIL_REVERIFY_DAYS,
PASSWORD_BCRYPT_ROUNDS,
SESSION_COOKIE_NAME,
SESSION_COOKIE_SECURE,
@@ -39,6 +41,30 @@ def hash_user_agent(user_agent: str) -> str:
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
def generate_device_token() -> str:
return secrets.token_urlsafe(32)
def hash_device_token(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
def get_device_token_cookie(request: Request) -> str | None:
return request.cookies.get(DEVICE_TOKEN_COOKIE_NAME)
def set_device_token_cookie(response: Response, token: str) -> None:
response.set_cookie(
key=DEVICE_TOKEN_COOKIE_NAME,
value=token,
max_age=EMAIL_REVERIFY_DAYS * 24 * 60 * 60,
secure=SESSION_COOKIE_SECURE,
httponly=True,
samesite="lax",
path="/",
)
def set_session_cookie(response: Response, session_id: str) -> None:
response.set_cookie(
key=SESSION_COOKIE_NAME,
+36 -11
View File
@@ -9,7 +9,7 @@ from typing import Any
import aiomysql
from config.config_db import get_db_pool
from config.config_system import OTP_VALID_MINUTES
from config.config_system import EMAIL_REVERIFY_DAYS, OTP_VALID_MINUTES
def _company_code() -> str:
@@ -152,8 +152,7 @@ async def complete_registration(user_id: int, is_master: bool) -> None:
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
await cursor.execute(
"""UPDATE users SET status = 'NO_COMPANY', last_email_verified_at = CURRENT_TIMESTAMP,
auth_expires_at = DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 3 MONTH)
"""UPDATE users SET status = 'NO_COMPANY', last_email_verified_at = CURRENT_TIMESTAMP
WHERE id = %s""",
(user_id,),
)
@@ -226,23 +225,49 @@ async def clear_login_failures(user_id: int) -> None:
await connection.commit()
async def has_known_browser(user_id: int, user_agent_hash: str) -> bool:
async def has_trusted_device(user_id: int, token_hash: str) -> bool:
valid_after = datetime.utcnow() - timedelta(days=EMAIL_REVERIFY_DAYS)
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
await cursor.execute(
"SELECT 1 FROM trusted_devices WHERE user_id = %s AND user_agent_hash = %s LIMIT 1",
(user_id, user_agent_hash),
"""SELECT id FROM trusted_devices
WHERE user_id = %s AND token_hash = %s AND last_used_at >= %s LIMIT 1""",
(user_id, token_hash, valid_after),
)
return await cursor.fetchone() is not None
row = await cursor.fetchone()
if not row:
return False
await cursor.execute(
"UPDATE trusted_devices SET last_used_at = CURRENT_TIMESTAMP WHERE id = %s",
(row[0],),
)
await connection.commit()
return True
async def trust_browser(user_id: int, user_agent_hash: str) -> None:
async def trust_device(
user_id: int,
token_hash: str,
user_agent_hash: str,
previous_token_hash: str | None = None,
) -> None:
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
if previous_token_hash:
await cursor.execute(
"""UPDATE trusted_devices
SET token_hash = %s, user_agent_hash = %s,
verified_at = CURRENT_TIMESTAMP, last_used_at = CURRENT_TIMESTAMP
WHERE user_id = %s AND token_hash = %s""",
(token_hash, user_agent_hash, user_id, previous_token_hash),
)
if cursor.rowcount:
await connection.commit()
return
await cursor.execute(
"""INSERT INTO trusted_devices (user_id, user_agent_hash)
VALUES (%s, %s) ON DUPLICATE KEY UPDATE last_used_at = CURRENT_TIMESTAMP""",
(user_id, user_agent_hash),
"""INSERT INTO trusted_devices (user_id, user_agent_hash, token_hash)
VALUES (%s, %s, %s)""",
(user_id, user_agent_hash, token_hash),
)
await connection.commit()