feat(sessions): show session origin in admin panel + test
This commit is contained in:
@@ -144,6 +144,41 @@ HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.email" == "{{email}}"
|
||||
jsonpath "$.username" == "{{username}}"
|
||||
[Captures]
|
||||
admin_user_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 7b — SessionOrigin stamping regression. Every login handler
|
||||
# records HOW the session was minted; the admin panel
|
||||
# surfaces that. This step proves TWO origins land
|
||||
# correctly on the same account:
|
||||
# * `password` — from Steps 1-2 legacy /api/auth/login
|
||||
# * `magic_link` — from Step 6 magic-link redemption
|
||||
# A missing/drifted stamp (e.g. a handler forgetting to
|
||||
# pass the SessionOrigin arg after a refactor) would
|
||||
# surface here as `unknown` instead of the expected value.
|
||||
#
|
||||
# `include_revoked=true` because Step 1 and Step 2 both
|
||||
# create sessions and the second may have rotated the
|
||||
# first out — we want ALL of admin's sessions in-frame.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/admin/sessions?user_id={{admin_user_id}}&include_revoked=true
|
||||
Authorization: Bearer {{alice_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
# `body contains` rather than a jsonpath collection predicate because
|
||||
# Hurl unwraps single-element `[*]` results to scalars — see the same
|
||||
# pattern in tests/oidc/oidc.hurl Step 8b for the full reasoning.
|
||||
body contains "\"origin\":\"password\""
|
||||
body contains "\"origin\":\"magic_link\""
|
||||
# `access_token_expiry_secs` also served for the SPA's revoke-lag
|
||||
# notice. Belt-and-braces with tests/oidc/oidc.hurl Step 8b (same
|
||||
# handler, both suites verify the field ships so a shape change
|
||||
# would fail at least one of them).
|
||||
jsonpath "$.access_token_expiry_secs" isInteger
|
||||
jsonpath "$.access_token_expiry_secs" > 0
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -267,6 +267,12 @@ HTTP 200
|
||||
[Captures]
|
||||
refreshed_access_token: jsonpath "$.access_token"
|
||||
refreshed_refresh_token: jsonpath "$.refresh_token"
|
||||
# Also capture the ROTATED csrf token so Step 8c (logout POST) can
|
||||
# thread it into `X-CSRF-Token`. The refresh handler rotates all
|
||||
# three cookies including csrf — using `initial_csrf_token` here
|
||||
# would 403 at the CSRF middleware because it no longer matches
|
||||
# the (freshly-rotated) `oxicloud_csrf` cookie on the browser.
|
||||
refreshed_csrf_token: cookie "oxicloud_csrf"
|
||||
[Asserts]
|
||||
jsonpath "$.user.username" == "oidc_user"
|
||||
jsonpath "$.access_token" isString
|
||||
@@ -293,6 +299,79 @@ HTTP 200
|
||||
jsonpath "$.username" == "oidc_user"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 8b — Admin sessions panel exposes `origin = "oidc"` and
|
||||
# NEVER leaks the raw IdP `sid`. The oidc_user was JIT-
|
||||
# provisioned admin by its `groups` claim (see Step 6),
|
||||
# so /api/admin/sessions is reachable with the current
|
||||
# cookies. Also asserts `access_token_expiry_secs` is
|
||||
# served so the SPA can render the revoke-lag notice.
|
||||
#
|
||||
# Regressions this pins:
|
||||
# * origin column drops back to "unknown" on refresh
|
||||
# (would break the panel filter for OIDC-only view);
|
||||
# * DTO reintroduces `oidc_sid` (the anti-leak fix from
|
||||
# dto_never_leaks_oidc_sid in session_dto.rs);
|
||||
# * handler stops publishing the TTL (would silently
|
||||
# kill the revoke-lag notice on the admin page).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/admin/sessions?limit=25
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
# At least our own OIDC session is here. `body contains` rather than a
|
||||
# jsonpath collection predicate because Hurl unwraps single-element
|
||||
# `$.sessions[*].origin` results to a scalar, and the deprecated
|
||||
# `includes` didn't handle that consistently either. Distinctive-enough
|
||||
# substring — a false positive would need `"origin":"oidc"` to appear
|
||||
# elsewhere in the SessionSummaryDto shape, which by construction it
|
||||
# doesn't.
|
||||
body contains "\"origin\":\"oidc\""
|
||||
# TTL surfaces for the SPA's revoke-lag notice.
|
||||
jsonpath "$.access_token_expiry_secs" isInteger
|
||||
jsonpath "$.access_token_expiry_secs" > 0
|
||||
# The raw IdP sid must never appear in the wire shape — not the key,
|
||||
# not even a prefix (see dto_never_leaks_oidc_sid unit test).
|
||||
body not contains "oidc_sid"
|
||||
# id_token is a JWT — three base64-url parts joined by `.`. A leak
|
||||
# would materialise as a long dotted token in the response body.
|
||||
# The fake IdP's issuer URL is a durable substring of every id_token
|
||||
# claim payload, so absence of that URL is a cheap "no id_token
|
||||
# leaked" proof.
|
||||
body not contains "{{oidc_issuer}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 8c — RP-initiated logout. Server MUST return
|
||||
# `post_logout_url` on the /api/auth/logout response
|
||||
# because the session's `oidc_id_token` is populated
|
||||
# (both at OIDC-callback INSERT time AND — critically
|
||||
# — after the Step 7 refresh which used to drop it).
|
||||
# This is the regression that made "logout after OIDC
|
||||
# login → refresh → logout" go through the local-only
|
||||
# path, leaving the IdP session live.
|
||||
#
|
||||
# The post_logout_url is the IdP's `end_session_endpoint`
|
||||
# carrying `id_token_hint` + `post_logout_redirect_uri`.
|
||||
# We assert its shape rather than following it (Step 9
|
||||
# does a fresh OIDC login anyway).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/logout
|
||||
X-CSRF-Token: {{refreshed_csrf_token}}
|
||||
Content-Type: application/json
|
||||
{}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.post_logout_url" exists
|
||||
jsonpath "$.post_logout_url" matches "id_token_hint="
|
||||
jsonpath "$.post_logout_url" matches "post_logout_redirect_uri="
|
||||
# Cookie-clearing is covered by other tests
|
||||
# (`auth_session_lifecycle.hurl`); the point of THIS step is the
|
||||
# `post_logout_url` shape — proving the id_token carried across the
|
||||
# Step 7 refresh, which the bug we fixed used to drop.
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 9 — Existing-user re-login. A second pass through the same
|
||||
# OIDC `sub` MUST resolve back to the SAME local user
|
||||
|
||||
Reference in New Issue
Block a user