deer-flow/frontend/tests/e2e-real-backend/auth-disabled-contract.spec.ts
hataa c55f242451
feat(authz): surface effective route permissions on GET /auth/me (Phase 4, #4063) (#5228)
* feat(authz): surface effective route permissions on GET /auth/me (Phase 4, #4063)

GET /api/v1/auth/me now returns the effective route permissions alongside
the user identity, so the frontend can hide actions the caller's role
cannot perform (RFC #4063 Phase 4).

The value reuses the AuthContext that AuthMiddleware already resolves per
request (including PAT-scope intersection and internal-caller semantics),
so /me adds zero extra provider evaluations; a middleware-less composition
falls back to the same resolution _authenticate uses.

Credential-creation responses (register/initialize) leave the field None:
they are public paths where the middleware does not run, and resolving
there would introduce fresh on-loop config loads on those routes.

* test(e2e): expect /auth/me permissions in auth-disabled contract

PR #5228 adds the effective route permissions to GET /auth/me, so the
strict toEqual against the bare AUTH_DISABLED_USER object no longer
holds: the received payload carries six extra keys (the permissions
array). Extend the expected payload with the full registered permission
set in _ALL_PERMISSIONS order — with authorization disabled the gateway
grants exactly that static list, so the pin stays deterministic.

The runtime frontend is unaffected (auth-disabled SSR never calls /me,
and userSchema strips unknown keys); only this contract pin needed the
new field.

* refactor(authz): public resolve_route_permissions_for_request wrapper

Address review nits on the middleware-less fallback: the router reached
into the private authz._is_internal_caller, so expose a thin public
wrapper pairing resolve_route_permissions with the internal-caller
heuristics, and use it from both _authenticate and the /me fallback so
the two cannot drift apart. Also drop an unused tmp_path parameter from
test_auth_disabled_me_includes_default_admin_permissions (_setup_auth
provisions its own tmp directory).

No behavior change: the wrapper delegates to the exact pair of calls the
fallback made before.
2026-09-08 16:33:05 +08:00

34 lines
1.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { AUTH_DISABLED_USER } from "../../src/core/auth/auth-disabled-user";
const APP =
process.env.E2E_APP_URL ??
`http://localhost:${process.env.E2E_FRONTEND_PORT ?? "3000"}`;
// /me also returns the caller's effective route permissions (RFC #4063
// Phase 4). Auth-disabled mode grants the full registered set, in the order
// of backend _ALL_PERMISSIONS (backend/app/gateway/authz.py).
const AUTH_DISABLED_PERMISSIONS = [
"threads:read",
"threads:write",
"threads:delete",
"runs:create",
"runs:read",
"runs:cancel",
];
test.describe("auth-disabled contract (real backend)", () => {
test("gateway /auth/me returns the frontend synthetic user without a cookie", async ({
context,
}) => {
const resp = await context.request.get(`${APP}/api/v1/auth/me`);
expect(resp.status(), await resp.text()).toBe(200);
await expect(resp.json()).resolves.toEqual({
...AUTH_DISABLED_USER,
permissions: AUTH_DISABLED_PERMISSIONS,
});
});
});