From 25283eba9bf0dfa30aed77d1b7d627f7dd725133 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Mon, 9 Feb 2026 16:42:12 +0000 Subject: [PATCH] Change database name to (rp-id).paskiadb (previously paskia.jsonl). Validate the rp-id in passkey init. --- .gitignore | 2 +- paskia/db/operations.py | 8 +++----- paskia/globals.py | 4 ++-- paskia/sansio.py | 17 +++++++++++++++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index a2a17cf..3fe319d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ dist/ *.lock package-lock.json paskia.sqlite -paskia.jsonl +*.paskiadb /paskia/frontend-build /paskia/_version.py coverage-html/ diff --git a/paskia/db/operations.py b/paskia/db/operations.py index 7988044..539578c 100644 --- a/paskia/db/operations.py +++ b/paskia/db/operations.py @@ -17,7 +17,6 @@ import uuid7 from paskia.config import SESSION_LIFETIME from paskia.db.jsonl import ( - DB_PATH_DEFAULT, JsonlStore, ) from paskia.db.structs import ( @@ -42,15 +41,14 @@ _db._store = _store _initialized = False -async def init(*args, **kwargs): +async def init(rp_id: str = "localhost", *args, **kwargs): """Load database from JSONL file.""" global _db, _initialized if _initialized: _logger.debug("Database already initialized, skipping reload") return - db_path = os.environ.get("PASKIA_DB", DB_PATH_DEFAULT) - if db_path.startswith("json:"): - db_path = db_path[5:] + default_path = f"{rp_id}.paskiadb" + db_path = os.environ.get("PASKIA_DB", default_path) await _store.load(db_path) _db = _store.db _initialized = True diff --git a/paskia/globals.py b/paskia/globals.py index d2c4061..137ad23 100644 --- a/paskia/globals.py +++ b/paskia/globals.py @@ -42,7 +42,7 @@ async def init( Database configuration: Set PASKIA_DB environment variable to specify the JSONL database file path. - Default: paskia.jsonl + Default: {rp_id}.paskiadb """ # Initialize passkey instance with provided parameters @@ -53,7 +53,7 @@ async def init( ) # Initialize database - await db.init() + await db.init(rp_id=rp_id) # Initialize remote auth manager await remoteauth.init() diff --git a/paskia/sansio.py b/paskia/sansio.py index dcf43ac..8890726 100644 --- a/paskia/sansio.py +++ b/paskia/sansio.py @@ -8,6 +8,7 @@ This module provides a unified interface for WebAuthn operations including: """ import json +import re from urllib.parse import urlparse from uuid import UUID @@ -62,6 +63,7 @@ class Passkey: ValueError: If any origin domain doesn't match or isn't a subdomain of rp_id. """ self.rp_id = rp_id + self._validate_rp_id(rp_id) self.rp_name = rp_name or rp_id self.allowed_origins: set[str] | None = None if origins: @@ -75,6 +77,21 @@ class Passkey: COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_256, ] + def _validate_rp_id(self, rp_id: str) -> None: + """Validate that rp_id is a valid domain name.""" + if not rp_id: + raise ValueError("rp_id cannot be empty") + # Allow localhost, or domain-like strings + if rp_id == "localhost": + return + # Regex for valid domain: letters, digits, hyphens, dots, but not starting/ending with hyphen, etc. + # Simplified: alphanumeric, dots, hyphens + if not re.match( + r"^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$", + rp_id, + ): + raise ValueError(f"rp_id '{rp_id}' is not a valid domain name") + def _validate_origin(self, origin: str, rp_id: str) -> None: """Validate an origin URL against the rp_id.""" hostname = urlparse(origin).hostname