wrongmove/crawler/repositories/user_repository.py
Viktor Barzin a8b7eace48
Add passkey (WebAuthn) authentication with self-registration
Enable users to sign up and sign in using passkeys (biometrics/security
keys) without needing a manually-created Authentik account. The existing
SSO login remains as an alternative.

Backend:
- Add WebAuthn registration/authentication endpoints via py-webauthn
- Issue HS256 JWTs for passkey users, with Redis-backed challenge storage
- Dual JWT verification in auth middleware (issuer-based routing: passkey
  HS256 vs Authentik RS256)
- PasskeyCredential model + migration making user.password nullable
- UserRepository with full CRUD for users and credentials

Frontend:
- AuthUser type abstraction unifying OIDC and passkey users
- Passkey service using @simplewebauthn/browser for WebAuthn ceremonies
- LoginModal redesigned with Sign In / Sign Up tabs
- Type migration from oidc-client-ts User to AuthUser across all services
  and components
2026-02-07 00:34:47 +00:00

62 lines
2.3 KiB
Python

from models.passkey_credential import PasskeyCredential
from models.user import User
from sqlalchemy import Engine
from sqlmodel import Session, select
class UserRepository:
engine: Engine
def __init__(self, engine: Engine) -> None:
self.engine = engine
def get_user_by_email(self, email: str) -> User | None:
with Session(self.engine) as session:
statement = select(User).where(User.email == email)
return session.exec(statement).first()
def get_user_by_id(self, user_id: int) -> User | None:
with Session(self.engine) as session:
return session.get(User, user_id)
def create_user(self, email: str) -> User:
with Session(self.engine) as session:
user = User(email=email) # type: ignore[call-arg]
session.add(user)
session.commit()
session.refresh(user)
return user
def get_credentials_for_user(self, user_id: int) -> list[PasskeyCredential]:
with Session(self.engine) as session:
statement = select(PasskeyCredential).where(
PasskeyCredential.user_id == user_id
)
return list(session.exec(statement).all())
def get_credential_by_id(self, credential_id: str) -> PasskeyCredential | None:
with Session(self.engine) as session:
statement = select(PasskeyCredential).where(
PasskeyCredential.credential_id == credential_id
)
return session.exec(statement).first()
def save_credential(self, credential: PasskeyCredential) -> PasskeyCredential:
with Session(self.engine) as session:
session.add(credential)
session.commit()
session.refresh(credential)
return credential
def update_credential_sign_count(
self, credential_id: str, new_sign_count: int
) -> None:
with Session(self.engine) as session:
statement = select(PasskeyCredential).where(
PasskeyCredential.credential_id == credential_id
)
credential = session.exec(statement).first()
if credential:
credential.sign_count = new_sign_count
session.add(credential)
session.commit()