fix(security): store only a hash of magic-link tokens
Magic-link tokens were persisted in plaintext, so a database read disclosed usable login/invite tokens. The service now hashes tokens (HMAC-SHA256 when a pepper is configured, else SHA-256 of the high-entropy token) and persists only the hash; the raw token is exposed solely in the registration URL and is re-attached to objects returned to callers. Refs: porchlight-42h Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cdde3e3754
commit
91a2277664
3 changed files with 65 additions and 62 deletions
|
|
@ -42,6 +42,18 @@ async def test_create_returns_magic_link(service: MagicLinkService) -> None:
|
|||
assert link.expires_at > datetime.now(UTC)
|
||||
|
||||
|
||||
async def test_token_stored_hashed_not_plaintext(service: MagicLinkService, repo: SQLiteMagicLinkRepository) -> None:
|
||||
# The raw token handed to the user must never be the value persisted in
|
||||
# the store; a DB read must not yield a usable token.
|
||||
link = await service.create(username="alice")
|
||||
raw = link.token
|
||||
assert await repo.get_by_token(raw) is None
|
||||
# ...but the raw token must still validate through the service.
|
||||
validated = await service.validate(raw)
|
||||
assert validated is not None
|
||||
assert validated.username == "alice"
|
||||
|
||||
|
||||
async def test_create_generates_unique_tokens(service: MagicLinkService) -> None:
|
||||
link1 = await service.create(username="alice")
|
||||
link2 = await service.create(username="bob")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue