Refactoring done, bugs gone.

This commit is contained in:
2025-07-04 06:46:05 +00:00
parent a6e80ffcb5
commit 3f373a9a8a
5 changed files with 213 additions and 176 deletions
+34 -18
View File
@@ -5,19 +5,19 @@ This module provides a simple WebAuthn implementation that:
- Uses WebSocket for real-time communication
- Supports Resident Keys (discoverable credentials) for passwordless authentication
- Maintains challenges locally per connection
- Uses SQLite database for persistent storage of users and credentials
- Enables true passwordless authentication where users don't need to enter a username
- Uses async SQLite database for persistent storage of users and credentials
- Enables true passwordless authentication where users don't need to enter a user_name
"""
from pathlib import Path
import db
import uuid7
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from passkeyauth.passkey import Passkey
from .db import Credential, db
from .passkey import Passkey
STATIC_DIR = Path(__file__).parent.parent / "static"
@@ -30,6 +30,12 @@ passkey = Passkey(
app = FastAPI(title="Passkey Auth")
@app.on_event("startup")
async def startup_event():
"""Initialize database on startup."""
await db.init_database()
@app.websocket("/ws/new_user_registration")
async def websocket_register_new(ws: WebSocket):
"""Register a new user and with a new passkey credential."""
@@ -38,40 +44,53 @@ async def websocket_register_new(ws: WebSocket):
form = await ws.receive_json()
user_id = uuid7.create().bytes
user_name = form["user_name"]
await register_chat(ws, user_id, username)
# Generate registration options and handle registration
credential, verified = await register_chat(ws, user_id, user_name)
# Store the user in the database
await db.create_user(user_name, user_id)
await db.create_user(user_id, user_name)
await db.store_credential(
Credential(
credential_id=credential.raw_id,
user_id=user_id,
aaguid=b"", # verified.aaguid,
public_key=verified.credential_public_key,
sign_count=verified.sign_count,
)
)
await ws.send_json({"status": "success", "user_id": user_id.hex()})
except WebSocketDisconnect:
pass
async def register_chat(ws: WebSocket, user_id: bytes, username: str):
async def register_chat(ws: WebSocket, user_id: bytes, user_name: str):
"""Generate registration options and send them to the client."""
options, challenge = passkey.reg_generate_options(
user_id=user_id,
username=username,
user_name=user_name,
)
await ws.send_text(options)
await ws.send_json(options)
# Wait for the client to use his authenticator to register
credential = passkey.reg_credential(await ws.receive_json())
passkey.reg_verify(credential, challenge)
verified_registration = passkey.reg_verify(credential, challenge)
return credential, verified_registration
@app.websocket("/ws/authenticate")
async def websocket_authenticate(ws: WebSocket):
await ws.accept()
try:
options = passkey.auth_generate_options()
options, challenge = await passkey.auth_generate_options()
await ws.send_json(options)
# Wait for the client to use his authenticator to authenticate
credential = passkey.auth_credential(await ws.receive_json())
# Fetch from the database by credential ID
stored_cred = await db.fetch_credential(credential.raw_id)
# Verify the credential matches the stored data, that is also updated
passkey.auth_verify(credential, stored_cred)
# Update the credential in the database
stored_cred = await db.get_credential_by_id(credential.raw_id)
# Verify the credential matches the stored data
_ = await passkey.auth_verify(credential, challenge, stored_cred)
await db.update_credential(stored_cred)
await ws.send_json({"status": "success"})
except WebSocketDisconnect:
pass
@@ -99,8 +118,5 @@ def main():
)
# Initialize database on startup
db.init_database()
if __name__ == "__main__":
main()