Fix actor fields and transactions for API operations as they are recorded to DB.
This commit is contained in:
@@ -136,7 +136,6 @@ async def admin_create_org(
|
||||
auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all
|
||||
)
|
||||
from ..db import Org as OrgDC # local import to avoid cycles
|
||||
from ..db import Role as RoleDC # local import to avoid cycles
|
||||
|
||||
actor = str(ctx.user.uuid)
|
||||
org_uuid = uuid4()
|
||||
@@ -145,17 +144,6 @@ async def admin_create_org(
|
||||
org = OrgDC(uuid=org_uuid, display_name=display_name, permissions=permissions)
|
||||
db.create_organization(org, actor=actor)
|
||||
|
||||
# Automatically create Administration role with org admin permission
|
||||
# The auth:org:admin permission is automatically created/enabled by create_organization
|
||||
role_uuid = uuid4()
|
||||
admin_role = RoleDC(
|
||||
uuid=role_uuid,
|
||||
org_uuid=org_uuid,
|
||||
display_name="Administration",
|
||||
permissions=["auth:org:admin"],
|
||||
)
|
||||
db.create_role(admin_role, actor=actor)
|
||||
|
||||
return {"uuid": str(org_uuid)}
|
||||
|
||||
|
||||
|
||||
@@ -289,11 +289,11 @@ async def api_logout(request: Request, response: Response, auth=AUTH_COOKIE):
|
||||
if not auth:
|
||||
return {"message": "Already logged out"}
|
||||
try:
|
||||
await get_session(auth, host=request.headers.get("host"))
|
||||
s = await get_session(auth, host=request.headers.get("host"))
|
||||
except ValueError:
|
||||
return {"message": "Already logged out"}
|
||||
with suppress(Exception):
|
||||
db.delete_session(session_key(auth))
|
||||
db.delete_session(session_key(auth), actor=str(s.user_uuid))
|
||||
session.clear_session_cookie(response)
|
||||
return {"message": "Logged out successfully"}
|
||||
|
||||
|
||||
+20
-13
@@ -16,7 +16,6 @@ import base64url
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
|
||||
from paskia import db, remoteauth
|
||||
from paskia.authsession import create_session
|
||||
from paskia.fastapi.session import infodict
|
||||
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
|
||||
from paskia.globals import passkey
|
||||
@@ -334,9 +333,6 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
||||
credential, webauthn_challenge, stored_cred, origin
|
||||
)
|
||||
|
||||
# Update credential last_used
|
||||
db.login(stored_cred.user_uuid, stored_cred)
|
||||
|
||||
# Create a session for the REQUESTING device
|
||||
assert stored_cred.uuid is not None
|
||||
|
||||
@@ -346,7 +342,7 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
||||
if request.action == "register":
|
||||
# For registration, create a reset token for device addition
|
||||
from paskia.authsession import expires
|
||||
from paskia.util import tokens
|
||||
from paskia.util import hostutil, tokens
|
||||
|
||||
token_str = passphrase.generate()
|
||||
expiry = expires()
|
||||
@@ -355,25 +351,36 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
||||
key=tokens.reset_key(token_str),
|
||||
expiry=expiry,
|
||||
token_type="device addition",
|
||||
actor=str(stored_cred.user_uuid),
|
||||
)
|
||||
reset_token = token_str
|
||||
# Also create a session so the device is logged in?
|
||||
# User requested: "We can make the flow always create a new session, but make additional tokens for other possibilities."
|
||||
session_token = await create_session(
|
||||
# Also create a session so the device is logged in
|
||||
session_token = passphrase.generate()
|
||||
normalized_host = hostutil.normalize_host(request.host)
|
||||
db.login(
|
||||
user_uuid=stored_cred.user_uuid,
|
||||
credential_uuid=stored_cred.uuid,
|
||||
host=request.host,
|
||||
credential=stored_cred,
|
||||
session_key=tokens.session_key(session_token),
|
||||
host=normalized_host,
|
||||
ip=request.ip,
|
||||
user_agent=request.user_agent,
|
||||
expiry=expires(),
|
||||
)
|
||||
else:
|
||||
# Default login action
|
||||
session_token = await create_session(
|
||||
from paskia.authsession import expires
|
||||
from paskia.util import hostutil, tokens
|
||||
|
||||
session_token = passphrase.generate()
|
||||
normalized_host = hostutil.normalize_host(request.host)
|
||||
db.login(
|
||||
user_uuid=stored_cred.user_uuid,
|
||||
credential_uuid=stored_cred.uuid,
|
||||
host=request.host,
|
||||
credential=stored_cred,
|
||||
session_key=tokens.session_key(session_token),
|
||||
host=normalized_host,
|
||||
ip=request.ip,
|
||||
user_agent=request.user_agent,
|
||||
expiry=expires(),
|
||||
)
|
||||
|
||||
# Complete the remote auth request (notifies the waiting device)
|
||||
|
||||
@@ -55,7 +55,7 @@ async def user_update_display_name(
|
||||
raise HTTPException(status_code=400, detail="display_name required")
|
||||
if len(new_name) > 64:
|
||||
raise HTTPException(status_code=400, detail="display_name too long")
|
||||
db.update_user_display_name(s.user_uuid, new_name)
|
||||
db.update_user_display_name(s.user_uuid, new_name, actor=str(s.user_uuid))
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ async def api_logout_all(request: Request, response: Response, auth=AUTH_COOKIE)
|
||||
raise authz.AuthException(
|
||||
status_code=401, detail="Session expired", mode="login"
|
||||
)
|
||||
db.delete_sessions_for_user(s.user_uuid)
|
||||
db.delete_sessions_for_user(s.user_uuid, actor=str(s.user_uuid))
|
||||
session.clear_session_cookie(response)
|
||||
return {"message": "Logged out from all hosts"}
|
||||
|
||||
@@ -103,7 +103,7 @@ async def api_delete_session(
|
||||
if not target_session or target_session.user_uuid != current_session.user_uuid:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
db.delete_session(target_key)
|
||||
db.delete_session(target_key, actor=str(current_session.user_uuid))
|
||||
current_terminated = target_key == session_key(auth)
|
||||
if current_terminated:
|
||||
session.clear_session_cookie(response) # explicit because 200
|
||||
@@ -149,6 +149,7 @@ async def api_create_link(
|
||||
key=tokens.reset_key(token),
|
||||
expiry=expiry,
|
||||
token_type="device addition",
|
||||
actor=str(s.user_uuid),
|
||||
)
|
||||
url = hostutil.reset_link_url(token)
|
||||
return {
|
||||
|
||||
+18
-8
@@ -3,12 +3,12 @@ from uuid import UUID
|
||||
from fastapi import FastAPI, WebSocket
|
||||
|
||||
from paskia import db
|
||||
from paskia.authsession import create_session, get_reset, get_session
|
||||
from paskia.authsession import expires, get_reset, get_session
|
||||
from paskia.fastapi import authz, remote
|
||||
from paskia.fastapi.session import AUTH_COOKIE, infodict
|
||||
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
|
||||
from paskia.globals import passkey
|
||||
from paskia.util import passphrase
|
||||
from paskia.util import hostutil, passphrase
|
||||
from paskia.util.tokens import create_token, session_key
|
||||
|
||||
# Create a FastAPI subapp for WebSocket endpoints
|
||||
@@ -89,6 +89,7 @@ async def websocket_register_add(
|
||||
host=host,
|
||||
ip=metadata.get("ip"),
|
||||
user_agent=metadata.get("user_agent"),
|
||||
actor=str(user_uuid),
|
||||
)
|
||||
auth = token
|
||||
|
||||
@@ -140,18 +141,27 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
|
||||
|
||||
# Verify the credential matches the stored data
|
||||
passkey.instance.auth_verify(credential, challenge, stored_cred, origin)
|
||||
# Update both credential and user's last_seen timestamp
|
||||
db.login(stored_cred.user_uuid, stored_cred)
|
||||
|
||||
# Create a session token for the authenticated user
|
||||
# Create session and update user/credential in a single transaction
|
||||
assert stored_cred.uuid is not None
|
||||
metadata = infodict(ws, "auth")
|
||||
token = await create_session(
|
||||
token = create_token()
|
||||
normalized_host = hostutil.normalize_host(host)
|
||||
if not normalized_host:
|
||||
raise ValueError("Host required for session creation")
|
||||
hostname = normalized_host.split(":")[0]
|
||||
rp_id = passkey.instance.rp_id
|
||||
if not (hostname == rp_id or hostname.endswith(f".{rp_id}")):
|
||||
raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
|
||||
|
||||
db.login(
|
||||
user_uuid=stored_cred.user_uuid,
|
||||
credential_uuid=stored_cred.uuid,
|
||||
host=host,
|
||||
credential=stored_cred,
|
||||
session_key=session_key(token),
|
||||
host=normalized_host,
|
||||
ip=metadata.get("ip") or "",
|
||||
user_agent=metadata.get("user_agent") or "",
|
||||
expiry=expires(),
|
||||
)
|
||||
|
||||
await ws.send_json(
|
||||
|
||||
Reference in New Issue
Block a user