fix: use HMAC-SHA256 with secret key for API token hashing

This commit is contained in:
Stefano Bertelli 2026-04-03 00:51:38 -05:00
parent 496334137d
commit 604446f8ca
2 changed files with 12 additions and 8 deletions

View file

@ -1,8 +1,6 @@
"""Tests for REST API endpoints and token auth.""" """Tests for REST API endpoints and token auth."""
import hashlib from wiregui.auth.api_token import _token_hmac, generate_api_token, resolve_bearer_token
from wiregui.auth.api_token import generate_api_token, resolve_bearer_token
from wiregui.auth.passwords import hash_password from wiregui.auth.passwords import hash_password
from wiregui.models.api_token import ApiToken from wiregui.models.api_token import ApiToken
from wiregui.models.user import User from wiregui.models.user import User
@ -15,7 +13,7 @@ from wiregui.utils.time import utcnow
def test_generate_api_token(): def test_generate_api_token():
plaintext, token_hash = generate_api_token() plaintext, token_hash = generate_api_token()
assert len(plaintext) > 20 assert len(plaintext) > 20
assert token_hash == hashlib.sha512(plaintext.encode()).hexdigest() assert token_hash == _token_hmac(plaintext)
def test_generate_api_token_unique(): def test_generate_api_token_unique():

View file

@ -1,27 +1,33 @@
"""API token authentication — Bearer token via Authorization header.""" """API token authentication — Bearer token via Authorization header."""
import hashlib import hmac
import secrets import secrets
from loguru import logger from loguru import logger
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select from sqlmodel import select
from wiregui.config import get_settings
from wiregui.models.api_token import ApiToken from wiregui.models.api_token import ApiToken
from wiregui.models.user import User from wiregui.models.user import User
from wiregui.utils.time import utcnow from wiregui.utils.time import utcnow
def _token_hmac(token: str) -> str:
"""Compute a keyed HMAC-SHA256 digest of an API token."""
key = get_settings().secret_key.encode()
return hmac.new(key, token.encode(), "sha256").hexdigest()
def generate_api_token() -> tuple[str, str]: def generate_api_token() -> tuple[str, str]:
"""Generate a new API token. Returns (plaintext_token, token_hash).""" """Generate a new API token. Returns (plaintext_token, token_hash)."""
plaintext = secrets.token_urlsafe(32) plaintext = secrets.token_urlsafe(32)
token_hash = hashlib.sha512(plaintext.encode()).hexdigest() return plaintext, _token_hmac(plaintext)
return plaintext, token_hash
async def resolve_bearer_token(session: AsyncSession, token: str) -> User | None: async def resolve_bearer_token(session: AsyncSession, token: str) -> User | None:
"""Look up a Bearer token and return the associated user, or None.""" """Look up a Bearer token and return the associated user, or None."""
token_hash = hashlib.sha512(token.encode()).hexdigest() token_hash = _token_hmac(token)
result = await session.execute( result = await session.execute(
select(ApiToken).where(ApiToken.token_hash == token_hash) select(ApiToken).where(ApiToken.token_hash == token_hash)
) )