Identify sessions by session key - a hash that can be safely shared.
This commit is contained in:
@@ -24,6 +24,11 @@ const terminatingSessions = ref({})
|
||||
const hoveredCredentialUuid = ref(null)
|
||||
const hoveredSession = ref(null)
|
||||
|
||||
// Convert credentials dict to array with uuid attached as 'credential'
|
||||
const credentials = computed(() =>
|
||||
Object.entries(props.userDetail?.credentials || {}).map(([uuid, c]) => ({ ...c, credential: uuid }))
|
||||
)
|
||||
|
||||
// Template refs for navigation
|
||||
const userInfoRef = ref(null)
|
||||
const regActionsRef = ref(null)
|
||||
@@ -218,7 +223,7 @@ defineExpose({ focusFirstElement })
|
||||
<div class="section-body">
|
||||
<CredentialList
|
||||
ref="credentialListRef"
|
||||
:credentials="userDetail.credentials ? Object.values(userDetail.credentials) : []"
|
||||
:credentials="credentials"
|
||||
:aaguid-info="userDetail.aaguid_info"
|
||||
:allow-delete="true"
|
||||
:hovered-credential-uuid="hoveredCredentialUuid"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="section-body">
|
||||
<CredentialList
|
||||
ref="credentialList"
|
||||
:credentials="authStore.userInfo?.credentials ? Object.values(authStore.userInfo.credentials) : []"
|
||||
:credentials="credentials"
|
||||
:aaguid-info="authStore.userInfo?.aaguid_info || {}"
|
||||
:loading="authStore.isLoading"
|
||||
:hovered-credential-uuid="hoveredCredentialUuid"
|
||||
@@ -369,7 +369,9 @@ const isAdmin = computed(() => {
|
||||
return perms?.includes('auth:admin') || perms?.includes('auth:org:admin')
|
||||
})
|
||||
const hasMultipleSessions = computed(() => Object.keys(sessions.value).length > 1)
|
||||
const credentials = computed(() => authStore.userInfo?.credentials ? Object.values(authStore.userInfo.credentials) : [])
|
||||
const credentials = computed(() =>
|
||||
Object.entries(authStore.userInfo?.credentials || {}).map(([uuid, c]) => ({ ...c, credential: uuid }))
|
||||
)
|
||||
const useWideLayout = computed(() => {
|
||||
// Check if any single site has more than 8 sessions
|
||||
const groups = {}
|
||||
|
||||
@@ -11,8 +11,8 @@ import secrets
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from uuid import UUID
|
||||
|
||||
import uuid7
|
||||
import base64url
|
||||
import uuid7
|
||||
|
||||
from paskia.config import SESSION_LIFETIME
|
||||
from paskia.db.jsonl import (
|
||||
@@ -474,7 +474,7 @@ def set_session_host(
|
||||
|
||||
|
||||
def delete_session(
|
||||
key: bytes, *, ctx: SessionContext | None = None, action: str = "delete_session"
|
||||
key: str, *, ctx: SessionContext | None = None, action: str = "delete_session"
|
||||
) -> None:
|
||||
"""Delete a session.
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import secrets
|
||||
from datetime import UTC, datetime
|
||||
from uuid import UUID
|
||||
|
||||
import base64url
|
||||
import msgspec
|
||||
import uuid7
|
||||
import base64url
|
||||
|
||||
from paskia import db
|
||||
from paskia.util import hostutil
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
from uuid import UUID
|
||||
|
||||
import base64url
|
||||
from fastapi import Body, FastAPI, HTTPException, Query, Request, Response
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from uuid import UUID
|
||||
|
||||
import base64url
|
||||
from fastapi import (
|
||||
Body,
|
||||
FastAPI,
|
||||
|
||||
@@ -9,12 +9,10 @@ import asyncio
|
||||
import logging
|
||||
from uuid import UUID
|
||||
|
||||
import base64url
|
||||
import httpx
|
||||
|
||||
from paskia import db
|
||||
from paskia.util import oidjwt
|
||||
from paskia.util.crypto import hash_secret
|
||||
from paskia.util.hostutil import _load_config
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -30,7 +28,7 @@ def _issuer() -> str:
|
||||
|
||||
|
||||
def _collect_oidc_sessions(
|
||||
session_keys: list[bytes],
|
||||
session_keys: list[str],
|
||||
) -> list[tuple[str, str, UUID, UUID | None]]:
|
||||
"""Collect (backchannel_logout_uri, sid, client_uuid, user_uuid) for OIDC sessions.
|
||||
|
||||
@@ -46,7 +44,7 @@ def _collect_oidc_sessions(
|
||||
client = data.oidc.clients.get(session.client_uuid)
|
||||
if not client or not client.backchannel_logout_uri:
|
||||
continue
|
||||
sid = base64url.enc(hash_secret("oidc", session.key))
|
||||
sid = session.key
|
||||
notifications.append(
|
||||
(client.backchannel_logout_uri, sid, session.client_uuid, session.user_uuid)
|
||||
)
|
||||
@@ -104,7 +102,7 @@ async def notify(
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
|
||||
def schedule_notifications(session_keys: list[bytes]) -> None:
|
||||
def schedule_notifications(session_keys: list[str]) -> None:
|
||||
"""Collect OIDC info from sessions (before deletion) and schedule async notifications.
|
||||
|
||||
Must be called BEFORE the sessions are deleted. The actual HTTP requests
|
||||
|
||||
+4
-3
@@ -19,6 +19,7 @@ from collections.abc import AsyncGenerator
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from uuid import UUID
|
||||
|
||||
import base64url
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
@@ -251,10 +252,10 @@ def create_test_session(
|
||||
ip: str = "127.0.0.1",
|
||||
user_agent: str = "pytest",
|
||||
duration: timedelta | None = None,
|
||||
) -> tuple[bytes, str]:
|
||||
) -> tuple[str, str]:
|
||||
"""Create a test session. Returns (key, token) tuple.
|
||||
|
||||
- key: bytes used for session lookup (base64url encode for URLs)
|
||||
- key: str used for session lookup (base64url encoded)
|
||||
- token: stored in cookie/sent to client
|
||||
"""
|
||||
if duration is None:
|
||||
@@ -267,7 +268,7 @@ def create_test_session(
|
||||
|
||||
# Generate token and derive key
|
||||
token = secrets.token_urlsafe(12)
|
||||
key = hash_secret("cookie", token)
|
||||
key = base64url.enc(hash_secret("cookie", token))
|
||||
|
||||
session = Session.create(
|
||||
user=user_uuid,
|
||||
|
||||
+3
-3
@@ -1300,7 +1300,7 @@ class TestAdminSessions:
|
||||
)
|
||||
|
||||
response = await client.delete(
|
||||
f"/auth/api/admin/users/{test_user.uuid}/sessions/{base64url.enc(extra_db_key)}",
|
||||
f"/auth/api/admin/users/{test_user.uuid}/sessions/{extra_db_key}",
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
@@ -1364,9 +1364,9 @@ class TestAdminSessions:
|
||||
f"/auth/api/admin/users/{test_user.uuid}/sessions/invalid!!id",
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert "Invalid session ID format" in data["detail"]
|
||||
assert "Session not found" in data["detail"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_session_not_found(
|
||||
|
||||
Reference in New Issue
Block a user