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
This commit is contained in:
parent
95c0ddc4c6
commit
a8b7eace48
26 changed files with 1229 additions and 129 deletions
13
crawler/models/passkey_credential.py
Normal file
13
crawler/models/passkey_credential.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from datetime import datetime
|
||||
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
|
||||
class PasskeyCredential(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
credential_id: str = Field(index=True, unique=True)
|
||||
public_key: str
|
||||
sign_count: int = Field(default=0)
|
||||
transports: str | None = Field(default=None) # JSON-encoded list
|
||||
user_id: int = Field(foreign_key="user.id", index=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import EmailStr
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
|
|
@ -5,4 +7,5 @@ from sqlmodel import SQLModel, Field
|
|||
class User(SQLModel, table=True):
|
||||
id: int = Field(primary_key=True)
|
||||
email: EmailStr = Field(index=True, unique=True)
|
||||
password: str = Field(nullable=False)
|
||||
password: str | None = Field(default=None, nullable=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue