fix: let a read-only account sign out
A React server action POSTs to the URL of the page it lives on, so the middleware's method check over /admin refused every form on the site to a viewer — including the sign-out button, which surfaced as "an unexpected response was received from the server". Gating pages by HTTP method was the wrong instrument: at the edge there is no way to tell a form that changes the shop's hours from one that ends a session. The method check now applies to /api/admin only, and page-level writes are authorised inside the actions themselves, where the intent is actually known. lib/auth/actions.ts carries that check and returns an error rather than throwing, since "you do not have permission" is a normal outcome and not a crash. The edge decision moves into a pure function with a regression test for this exact case. These rules are short, but they are the only thing in front of the administration and one of them has now been got wrong once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { decideAccess } from './access';
|
||||
|
||||
describe('decideAccess', () => {
|
||||
it('sends an anonymous browser to the sign-in screen', () => {
|
||||
expect(decideAccess({ pathname: '/admin/horaires', method: 'GET', role: undefined })).toEqual({
|
||||
kind: 'redirect',
|
||||
});
|
||||
});
|
||||
|
||||
it('answers an anonymous fetch with 401 rather than an HTML page', () => {
|
||||
expect(decideAccess({ pathname: '/api/admin/messages', method: 'GET', role: undefined })).toEqual(
|
||||
{ kind: 'deny', status: 401, error: 'Non authentifié' },
|
||||
);
|
||||
});
|
||||
|
||||
it('lets a read-only account read pages and API routes', () => {
|
||||
expect(decideAccess({ pathname: '/admin', method: 'GET', role: 'viewer' })).toEqual({
|
||||
kind: 'allow',
|
||||
});
|
||||
expect(decideAccess({ pathname: '/api/admin/messages', method: 'GET', role: 'viewer' })).toEqual(
|
||||
{ kind: 'allow' },
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses a write to the API from a read-only account', () => {
|
||||
expect(decideAccess({ pathname: '/api/admin/messages', method: 'POST', role: 'viewer' })).toEqual(
|
||||
{ kind: 'deny', status: 403, error: 'Compte en lecture seule' },
|
||||
);
|
||||
});
|
||||
|
||||
it('allows a write to the API from an administrator', () => {
|
||||
expect(decideAccess({ pathname: '/api/admin/messages', method: 'DELETE', role: 'admin' })).toEqual(
|
||||
{ kind: 'allow' },
|
||||
);
|
||||
});
|
||||
|
||||
it('lets a read-only account POST to a page', () => {
|
||||
// Regression: a React server action POSTs to the URL of the page it lives
|
||||
// on. Gating pages by method refused every form to a viewer, sign-out
|
||||
// included, which showed up as "an unexpected response was received from
|
||||
// the server". Page writes are checked inside the action instead.
|
||||
expect(decideAccess({ pathname: '/admin', method: 'POST', role: 'viewer' })).toEqual({
|
||||
kind: 'allow',
|
||||
});
|
||||
expect(decideAccess({ pathname: '/admin/horaires', method: 'POST', role: 'viewer' })).toEqual({
|
||||
kind: 'allow',
|
||||
});
|
||||
});
|
||||
|
||||
it('still requires a session before a page POST', () => {
|
||||
expect(decideAccess({ pathname: '/admin', method: 'POST', role: undefined })).toEqual({
|
||||
kind: 'redirect',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user