feat: add SQLiteMagicLinkRepository with tests

This commit is contained in:
Johan Lundberg 2026-02-13 13:53:14 +01:00
parent bbe0dac8cb
commit 9f4914a922
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
2 changed files with 154 additions and 1 deletions

View file

@ -2,7 +2,7 @@ from datetime import UTC, datetime
import aiosqlite
from fastapi_oidc_op.models import PasswordCredential, User, WebAuthnCredential
from fastapi_oidc_op.models import MagicLink, PasswordCredential, User, WebAuthnCredential
from fastapi_oidc_op.store.exceptions import DuplicateError
@ -238,3 +238,54 @@ class SQLiteCredentialRepository:
cursor = await self._db.execute("DELETE FROM password_credentials WHERE user_id = ?", (user_id,))
await self._db.commit()
return cursor.rowcount > 0
class SQLiteMagicLinkRepository:
def __init__(self, db: aiosqlite.Connection) -> None:
self._db = db
def _row_to_magic_link(self, row: aiosqlite.Row) -> MagicLink:
return MagicLink(
token=row["token"],
username=row["username"],
expires_at=datetime.fromisoformat(row["expires_at"]),
used=bool(row["used"]),
created_by=row["created_by"],
note=row["note"],
)
async def create(self, link: MagicLink) -> MagicLink:
try:
await self._db.execute(
"INSERT INTO magic_links (token, username, expires_at, used, created_by, note) VALUES (?, ?, ?, ?, ?, ?)",
(
link.token,
link.username,
link.expires_at.isoformat(),
int(link.used),
link.created_by,
link.note,
),
)
await self._db.commit()
except aiosqlite.IntegrityError as e:
raise DuplicateError(str(e)) from e
return link
async def get_by_token(self, token: str) -> MagicLink | None:
async with self._db.execute("SELECT * FROM magic_links WHERE token = ?", (token,)) as cursor:
row = await cursor.fetchone()
if row is None:
return None
return self._row_to_magic_link(row)
async def mark_used(self, token: str) -> bool:
cursor = await self._db.execute("UPDATE magic_links SET used = 1 WHERE token = ?", (token,))
await self._db.commit()
return cursor.rowcount > 0
async def delete_expired(self) -> int:
now = datetime.now(UTC).isoformat()
cursor = await self._db.execute("DELETE FROM magic_links WHERE expires_at < ? AND used = 0", (now,))
await self._db.commit()
return cursor.rowcount