Make rpid.paskiadb a folder containing the database and the files in one. Migrates existing old format rpid.paskiadb file to main.db.
This commit is contained in:
@@ -66,7 +66,7 @@ paskia [options]
|
||||
| --auth-host *url* | Dedicated authentication site, e.g. **auth.example.com** | Use **/auth/** path on each site |
|
||||
| --save | Save current options to database | (only --rp-id required on further invocations) |
|
||||
|
||||
To clear a stored setting, pass an empty value like `--auth-host=`. The database is stored in `{rp-id}.paskiadb` in current directory. This can be overridden by environment `PASKIA_DB` if needed.
|
||||
To clear a stored setting, pass an empty value like `--auth-host=`. The database is stored in `{rp-id}.paskiadb` folder in current directory. This can be overridden by environment `PASKIA_DB` if needed.
|
||||
|
||||
## Tutorial: From Local Testing to Production
|
||||
|
||||
|
||||
+3
-2
@@ -9,6 +9,7 @@ from fastapi_vue.hostutil import parse_endpoints
|
||||
|
||||
from paskia._version import __version__
|
||||
from paskia.db.jsonl import load_readonly
|
||||
from paskia.db.paths import db_file_path
|
||||
from paskia.util import startupbox
|
||||
from paskia.util.hostutil import (
|
||||
normalize_auth_host_and_origins,
|
||||
@@ -75,9 +76,9 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load stored config (read-only, no writes, no global state)
|
||||
db_path = os.environ.get("PASKIA_DB", f"{args.rp_id}.paskiadb")
|
||||
db_path = db_file_path(rp_id=args.rp_id, create_root=True)
|
||||
try:
|
||||
config = load_readonly(db_path, rp_id=args.rp_id).config
|
||||
config = load_readonly(str(db_path), rp_id=args.rp_id).config
|
||||
except SystemExit as e:
|
||||
print(f"🛑 Paskia {__version__} could not load")
|
||||
sys.exit(str(e))
|
||||
|
||||
@@ -3,13 +3,13 @@ Database lifecycle: initialization and maintenance.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import paskia.db.operations as _ops
|
||||
from paskia import oidc_notify
|
||||
from paskia.authsession import EXPIRES
|
||||
from paskia.db.jsonl import JsonlStore
|
||||
from paskia.db.paths import db_file_path
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,9 +19,9 @@ async def init(rp_id: str, *args, **kwargs):
|
||||
if _ops._db._store:
|
||||
_logger.debug("Database already initialized, skipping reload")
|
||||
return
|
||||
db_path = os.environ.get("PASKIA_DB", f"{rp_id}.paskiadb")
|
||||
store = JsonlStore(_ops._db, db_path)
|
||||
await store.load(db_path, rp_id=rp_id)
|
||||
db_path = db_file_path(rp_id=rp_id, create_root=True)
|
||||
store = JsonlStore(_ops._db, str(db_path))
|
||||
await store.load(str(db_path), rp_id=rp_id)
|
||||
_ops._db = store.db
|
||||
_ops._db._store = store
|
||||
# Request a snapshot after successful startup
|
||||
|
||||
@@ -460,9 +460,7 @@ def update_session(
|
||||
s.validated = validated
|
||||
|
||||
|
||||
def set_session_host(
|
||||
key: str, host: str, *, ctx: SessionContext | None = None
|
||||
) -> None:
|
||||
def set_session_host(key: str, host: str, *, ctx: SessionContext | None = None) -> None:
|
||||
"""Set the host for a session (first-time binding)."""
|
||||
update_session(key, host=host, ctx=ctx)
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ from paskia.fastapi import authz, session, user
|
||||
from paskia.fastapi.response import MsgspecResponse
|
||||
from paskia.fastapi.session import AUTH_COOKIE, AUTH_COOKIE_NAME, get_client_ip
|
||||
from paskia.globals import passkey as global_passkey
|
||||
from paskia.util.crypto import hash_secret
|
||||
from paskia.util import hostutil, htmlutil, passphrase, permutil, userinfo
|
||||
from paskia.util.apistructs import (
|
||||
ApiCheckUserResponse,
|
||||
@@ -33,6 +32,7 @@ from paskia.util.apistructs import (
|
||||
ApiUserContext,
|
||||
ApiValidateResponse,
|
||||
)
|
||||
from paskia.util.crypto import hash_secret
|
||||
|
||||
bearer_auth = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
@@ -134,7 +134,9 @@ def format_access_log(
|
||||
|
||||
# Format: "IP STATUS METHOD host path [extra] TIMING"
|
||||
extra_str = f" {_TIMING}{extra}{_RESET}" if extra else ""
|
||||
return f"{ip} {status_str} {method_str} {host_str}{path_str}{extra_str} {timing_str}"
|
||||
return (
|
||||
f"{ip} {status_str} {method_str} {host_str}{path_str}{extra_str} {timing_str}"
|
||||
)
|
||||
|
||||
|
||||
# WebSocket connection counter (mod 100)
|
||||
|
||||
@@ -109,11 +109,17 @@ async def authenticate_and_login(
|
||||
cred, new_sign_count = await authenticate_chat(ws, credential_ids)
|
||||
|
||||
# Use overrides if provided, otherwise use websocket metadata
|
||||
login_host = hostutil.normalize_host(session_host) if session_host is not None else normalized_host
|
||||
login_host = (
|
||||
hostutil.normalize_host(session_host)
|
||||
if session_host is not None
|
||||
else normalized_host
|
||||
)
|
||||
if not login_host:
|
||||
raise ValueError("Host required for session creation")
|
||||
login_ip = session_ip if session_ip is not None else metadata["ip"]
|
||||
login_user_agent = session_user_agent if session_user_agent is not None else metadata["user_agent"]
|
||||
login_user_agent = (
|
||||
session_user_agent if session_user_agent is not None else metadata["user_agent"]
|
||||
)
|
||||
|
||||
# Create session and update user/credential
|
||||
secret = db.login(
|
||||
|
||||
@@ -4,12 +4,12 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import HTTPException, UploadFile
|
||||
|
||||
from paskia.db.paths import users_root_path
|
||||
from paskia.util import hostutil
|
||||
|
||||
MAX_UPLOAD_BYTES = 10 * 1024 * 1024
|
||||
@@ -17,15 +17,12 @@ MAX_UPLOAD_BYTES = 10 * 1024 * 1024
|
||||
|
||||
def media_root() -> Path:
|
||||
"""Return the filesystem root for auxiliary media files."""
|
||||
db_path = Path(os.environ.get("PASKIA_DB", "localhost.paskiadb")).resolve()
|
||||
db_name = db_path.name
|
||||
hostname = db_name.removesuffix(".paskiadb") or db_path.stem
|
||||
return db_path.parent / f"{hostname}.data"
|
||||
return users_root_path(create_root=True)
|
||||
|
||||
|
||||
def avatars_root() -> Path:
|
||||
"""Return the filesystem root for stored avatar images."""
|
||||
return media_root() / "user"
|
||||
return media_root()
|
||||
|
||||
|
||||
def avatar_path(user_uuid: UUID) -> Path:
|
||||
|
||||
+31
-6
@@ -15,6 +15,7 @@ from urllib.parse import urlsplit
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from paskia.db.paths import db_file_path, users_root_path
|
||||
from tests.conftest import auth_headers, create_test_image_bytes
|
||||
|
||||
|
||||
@@ -178,6 +179,13 @@ class TestUserAvatar:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
info = await client.get(
|
||||
"/auth/api/user-info",
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert info.status_code == 200
|
||||
assert info.json()["user"].get("avatar_url") is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_regular_user_cannot_upload_another_users_avatar(
|
||||
self,
|
||||
@@ -194,12 +202,29 @@ class TestUserAvatar:
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
info = await client.get(
|
||||
"/auth/api/user-info",
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert info.status_code == 200
|
||||
assert info.json()["user"].get("avatar_url") is None
|
||||
|
||||
def test_paskia_db_legacy_file_is_migrated_to_root_dir(tmp_path, monkeypatch):
|
||||
legacy_path = tmp_path / "legacy.paskiadb"
|
||||
legacy_bytes = b'{"v":0}\n'
|
||||
legacy_path.write_bytes(legacy_bytes)
|
||||
|
||||
monkeypatch.setenv("PASKIA_DB", str(legacy_path))
|
||||
|
||||
db_path = db_file_path(create_root=True)
|
||||
|
||||
assert legacy_path.is_dir()
|
||||
assert db_path == legacy_path / "main.db"
|
||||
assert db_path.read_bytes() == legacy_bytes
|
||||
|
||||
|
||||
def test_paskia_db_root_uses_users_directory(tmp_path, monkeypatch):
|
||||
root_path = tmp_path / "instance-root"
|
||||
monkeypatch.setenv("PASKIA_DB", str(root_path))
|
||||
|
||||
users_path = users_root_path(create_root=True)
|
||||
|
||||
assert users_path == root_path / "users"
|
||||
assert users_path.parent == root_path
|
||||
|
||||
|
||||
class TestUserLogoutAll:
|
||||
|
||||
Reference in New Issue
Block a user