Permission names are <resource>:<action>, replacing the catch-all admin permission
CI / full-gate (push) Successful in 2m43s
CI / full-gate (push) Successful in 2m43s
This commit is contained in:
@@ -83,7 +83,7 @@ test("an expired session JWT is silently re-minted while Kratos lives, then clea
|
||||
const claims1 = jwtClaims(jwt1);
|
||||
expect(claims1.email).toBe(ADMIN_EMAIL);
|
||||
expect(claims1.sub, "sub is the Kratos identity id").toBeTruthy();
|
||||
expect(claims1.permissions, "permissions are projected from Keto").toContain("admin");
|
||||
expect(claims1.permissions, "permissions are projected from Keto").toContain("users:read");
|
||||
|
||||
// 2. Token timeout → refresh: once the 8s TTL lapses, the next request re-mints a fresh JWT.
|
||||
const jwt2Line = await awaitJwtSetCookie(session, jwt1);
|
||||
@@ -91,7 +91,7 @@ test("an expired session JWT is silently re-minted while Kratos lives, then clea
|
||||
expect(jwt2, "a different token was minted").not.toBe(jwt1);
|
||||
const claims2 = jwtClaims(jwt2);
|
||||
expect(claims2.exp, "the new token expires later").toBeGreaterThan(claims1.exp);
|
||||
expect(claims2.permissions, "re-mint re-reads permissions from Keto").toContain("admin");
|
||||
expect(claims2.permissions, "re-mint re-reads permissions from Keto").toContain("users:read");
|
||||
|
||||
// 3. Kill the Kratos session: now the lapsed token cannot refresh — the cookie is cleared.
|
||||
const revoke = await fetch(`${KRATOS_ADMIN}/admin/identities/${claims1.sub}/sessions`, { method: "DELETE" });
|
||||
|
||||
@@ -30,6 +30,13 @@ services:
|
||||
timeout: 4s
|
||||
retries: 30
|
||||
|
||||
# This stack mounts no plugins, so nothing declares a permission for the bootstrap to seed — and
|
||||
# the suite asserts that Keto's grants reach the JWT claim. Name one explicitly so there is
|
||||
# something to project.
|
||||
bootstrap:
|
||||
environment:
|
||||
ADMIN_PERMISSIONS: users:read
|
||||
|
||||
# Shorten the session→JWT TTL and expose a network-resolvable base_url (ory/kratos/e2e.yml),
|
||||
# merged after the base config.
|
||||
kratos:
|
||||
|
||||
@@ -102,8 +102,9 @@ test.describe.serial("authenticated admin journey", () => {
|
||||
});
|
||||
|
||||
test("menu filters by permission: an admin sees the gated Admin section + the plugin", async () => {
|
||||
// The signed-in admin holds admin + scheduling:read/write, so both gated sections are present
|
||||
// in the menu (collapsed by default → assert they're in the DOM, not necessarily visible).
|
||||
// The signed-in admin holds every permission the two mounted plugins declare (the bootstrap
|
||||
// seeds exactly those), so both gated sections are present in the menu (collapsed by default →
|
||||
// assert they're in the DOM, not necessarily visible).
|
||||
await page.goto("/dashboard");
|
||||
await expect(page.locator('.sidebar a[href="/admin/users"]')).toHaveCount(1);
|
||||
await expect(page.locator('.sidebar a[href="/scheduling/shifts"]')).toHaveCount(1);
|
||||
@@ -146,7 +147,7 @@ test.describe.serial("authenticated admin journey", () => {
|
||||
await expect(page).toHaveURL(/\/admin\/groups(\?|\/|$)/);
|
||||
await expect(page.locator("main")).toContainText(group);
|
||||
|
||||
const permission = `e2e-permission-${suffix}`;
|
||||
const permission = `e2e-${suffix}:read`; // permission names are <resource>:<action>; the form refuses a bare word
|
||||
await page.goto("/admin/permissions/new");
|
||||
await page.fill('input[name="name"]', permission);
|
||||
await page.locator('select[name="member"]').selectOption({ index: 1 });
|
||||
|
||||
Reference in New Issue
Block a user