Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions verify/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ the `Authorization` header, and no cookies are set. The access token is accepted
the pair, and a used refresh token answers `401` after the reuse window. Logout ends the
session.

**Guard.** `/api/me` answers `401` with no session or an altered access cookie, and the
signed-in user's id with a valid one.
**Guard.** `/api/me` answers `401` with no session, an altered access cookie, or the
sign-in flow's ephemeral cookie presented as the access cookie, and the signed-in user's
id with a valid one.

**Errors.** An API validation failure keeps its `400` and body (`error:
"invalid_request"` with `details.issues`). No session answers `401` with an `error`
Expand Down
26 changes: 26 additions & 0 deletions verify/harness/adapter/protectedEndpoint.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { request as playwrightRequest } from '@playwright/test';

import { cookieSignup, currentUserId } from '../lib/conformanceFlows';
import { cookieNamed } from '../lib/cookies';
import { expect, test } from '../lib/fixtures';

// GET /api/me is the reference app's own route behind the adapter's guard, so these
Expand Down Expand Up @@ -38,4 +39,29 @@ test.describe('protected app endpoint (adapter, cookies)', () => {
await browser.dispose();
}
});

// Every adapter cookie can be signed with the same secret, and the sign-in flow
// hands out the ephemeral cookie before any factor is proven. It must never pass
// for a session.
test('refuses the sign-in flow cookie presented as the session cookie', async ({
adapterActor,
adapterUrl,
}) => {
await cookieSignup(adapterActor.ctx, adapterActor.email);
const login = await adapterActor.ctx.post('/auth/login', {
data: { identifier: adapterActor.email },
});
const ephemeral = cookieNamed(login, 'seamless-ephemeral');
expect(ephemeral, 'login set the ephemeral cookie').toBeDefined();

const browser = await playwrightRequest.newContext({
baseURL: adapterUrl,
extraHTTPHeaders: { cookie: `seamless-access=${ephemeral!.value}` },
});
try {
expect((await browser.get('/api/me')).status()).toBe(401);
} finally {
await browser.dispose();
}
});
});
Loading