@payload-auth-link/react 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/components.test.d.ts +2 -0
- package/dist/__tests__/components.test.d.ts.map +1 -0
- package/dist/__tests__/components.test.js +163 -0
- package/dist/__tests__/components.test.js.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +10 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/__tests__/provider.test.d.ts +2 -0
- package/dist/__tests__/provider.test.d.ts.map +1 -0
- package/dist/__tests__/provider.test.js +177 -0
- package/dist/__tests__/provider.test.js.map +1 -0
- package/dist/__tests__/session.test.d.ts +2 -0
- package/dist/__tests__/session.test.d.ts.map +1 -0
- package/dist/__tests__/session.test.js +70 -0
- package/dist/__tests__/session.test.js.map +1 -0
- package/dist/components.d.ts +54 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +49 -0
- package/dist/components.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +46 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +94 -0
- package/dist/provider.js.map +1 -0
- package/dist/session.d.ts +17 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +46 -0
- package/dist/session.js.map +1 -0
- package/package.json +41 -4
- package/src/components.tsx +127 -0
- package/src/index.ts +17 -0
- package/src/provider.tsx +161 -0
- package/src/session.ts +57 -0
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/components.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
// RTL's auto-cleanup keys off a global afterEach, which Vitest does not
|
|
6
|
+
// provide with `globals` off — clean up explicitly between renders.
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
cleanup();
|
|
9
|
+
});
|
|
10
|
+
import { AuthLinkProvider } from '../provider.js';
|
|
11
|
+
import { RequireRoles, SignInButton, SignOutButton, UserMenu } from '../components.js';
|
|
12
|
+
const user = {
|
|
13
|
+
id: 'user-1',
|
|
14
|
+
collection: 'users',
|
|
15
|
+
email: 'user@example.com',
|
|
16
|
+
roles: ['member', 'admin'],
|
|
17
|
+
};
|
|
18
|
+
function jsonResponse(body, status = 200) {
|
|
19
|
+
return new Response(JSON.stringify(body), {
|
|
20
|
+
status,
|
|
21
|
+
headers: { 'content-type': 'application/json' },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function stubFetch(handler) {
|
|
25
|
+
return vi.fn((input, init) => {
|
|
26
|
+
const result = handler(input, init);
|
|
27
|
+
return result instanceof Error ? Promise.reject(result) : Promise.resolve(result);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function withinProvider(children, options) {
|
|
31
|
+
const fetchImpl = options?.fetchImpl ?? stubFetch(() => jsonResponse({ user }));
|
|
32
|
+
if (options && 'initialUser' in options) {
|
|
33
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: options.initialUser, children: children }));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: children }));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Deterministic settling for negative assertions: await every response
|
|
40
|
+
* the stub handed out, then drain the microtask queue (bounded) inside
|
|
41
|
+
* `act` so React flushes whatever those resolutions scheduled. No
|
|
42
|
+
* wall-clock sleep, so the test cannot pass or fail on machine load.
|
|
43
|
+
*/
|
|
44
|
+
async function settle(fetchImpl) {
|
|
45
|
+
await act(async () => {
|
|
46
|
+
await Promise.allSettled(fetchImpl.mock.results.map((result) => result.value));
|
|
47
|
+
for (let tick = 0; tick < 50; tick += 1) {
|
|
48
|
+
await Promise.resolve();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
describe('SignInButton', () => {
|
|
53
|
+
it('links to the sign-in page by default', () => {
|
|
54
|
+
render(_jsx(SignInButton, {}));
|
|
55
|
+
const link = screen.getByText('Sign in');
|
|
56
|
+
expect(link.tagName).toBe('A');
|
|
57
|
+
expect(link.getAttribute('href')).toBe('/sign-in');
|
|
58
|
+
});
|
|
59
|
+
it('honors href, className, and children', () => {
|
|
60
|
+
render(_jsx(SignInButton, { href: "/login", className: "btn", children: "Log in" }));
|
|
61
|
+
const link = screen.getByText('Log in');
|
|
62
|
+
expect(link.getAttribute('href')).toBe('/login');
|
|
63
|
+
expect(link.getAttribute('class')).toBe('btn');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('SignOutButton', () => {
|
|
67
|
+
it('signs out, notifies, and navigates on success', async () => {
|
|
68
|
+
const fetchImpl = stubFetch(() => jsonResponse({ ok: true }));
|
|
69
|
+
const navigate = vi.fn();
|
|
70
|
+
const onSignedOut = vi.fn();
|
|
71
|
+
withinProvider(_jsx(SignOutButton, { redirectTo: "/", navigate: navigate, onSignedOut: onSignedOut }), {
|
|
72
|
+
fetchImpl,
|
|
73
|
+
initialUser: user,
|
|
74
|
+
});
|
|
75
|
+
fireEvent.click(screen.getByText('Sign out'));
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
expect(onSignedOut).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
expect(navigate).toHaveBeenCalledWith('/');
|
|
80
|
+
expect(fetchImpl.mock.calls.some(([, init]) => init?.method === 'DELETE')).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it('stays put when revocation fails', async () => {
|
|
83
|
+
const fetchImpl = stubFetch(() => jsonResponse({}, 500));
|
|
84
|
+
const navigate = vi.fn();
|
|
85
|
+
const onSignedOut = vi.fn();
|
|
86
|
+
withinProvider(_jsx(SignOutButton, { redirectTo: "/", navigate: navigate, onSignedOut: onSignedOut }), {
|
|
87
|
+
fetchImpl,
|
|
88
|
+
initialUser: user,
|
|
89
|
+
});
|
|
90
|
+
fireEvent.click(screen.getByText('Sign out'));
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(fetchImpl.mock.calls.some(([, init]) => init?.method === 'DELETE')).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
// Let the failed DELETE settle; nothing must happen afterwards.
|
|
95
|
+
await settle(fetchImpl);
|
|
96
|
+
expect(onSignedOut).not.toHaveBeenCalled();
|
|
97
|
+
expect(navigate).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('SignOutButton redirect safety (SEC-12)', () => {
|
|
101
|
+
it.each([
|
|
102
|
+
['https://evil.example/phish', '/'],
|
|
103
|
+
['//evil.example', '/'],
|
|
104
|
+
['/api/users', '/'],
|
|
105
|
+
['/sign-in', '/sign-in'],
|
|
106
|
+
['/goodbye?x=1', '/goodbye?x=1'],
|
|
107
|
+
])('navigates %s as %s', async (redirectTo, expected) => {
|
|
108
|
+
const fetchImpl = stubFetch(() => jsonResponse({ ok: true }));
|
|
109
|
+
const navigate = vi.fn();
|
|
110
|
+
withinProvider(_jsx(SignOutButton, { redirectTo: redirectTo, navigate: navigate }), {
|
|
111
|
+
fetchImpl,
|
|
112
|
+
initialUser: user,
|
|
113
|
+
});
|
|
114
|
+
fireEvent.click(screen.getByText('Sign out'));
|
|
115
|
+
await waitFor(() => {
|
|
116
|
+
expect(navigate).toHaveBeenCalledTimes(1);
|
|
117
|
+
});
|
|
118
|
+
expect(navigate).toHaveBeenCalledWith(expected);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('UserMenu', () => {
|
|
122
|
+
it('renders nothing while loading', async () => {
|
|
123
|
+
const pending = [];
|
|
124
|
+
const fetchImpl = vi.fn(async () => new Promise((resolve) => {
|
|
125
|
+
pending.push(resolve);
|
|
126
|
+
}));
|
|
127
|
+
const { container } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(UserMenu, {}) }));
|
|
128
|
+
expect(container.textContent).toBe('');
|
|
129
|
+
pending[0]?.(jsonResponse({ user }));
|
|
130
|
+
// Settle inside the test so no state update lands after unmount.
|
|
131
|
+
await screen.findByText('user@example.com');
|
|
132
|
+
});
|
|
133
|
+
it('offers sign-in when anonymous', async () => {
|
|
134
|
+
const fetchImpl = stubFetch(() => jsonResponse({}, 401));
|
|
135
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(UserMenu, { signInHref: "/login" }) }));
|
|
136
|
+
const link = await screen.findByText('Sign in');
|
|
137
|
+
expect(link.getAttribute('href')).toBe('/login');
|
|
138
|
+
});
|
|
139
|
+
it('shows the email and signs out when authenticated', async () => {
|
|
140
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
141
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(UserMenu, {}) }));
|
|
142
|
+
expect(await screen.findByText('user@example.com')).toBeDefined();
|
|
143
|
+
expect(screen.getByText('Sign out')).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('RequireRoles', () => {
|
|
147
|
+
it('gates on every listed role', async () => {
|
|
148
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
149
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(RequireRoles, { roles: ['admin'], fallback: _jsx("span", { children: "denied" }), children: _jsx("span", { children: "granted" }) }) }));
|
|
150
|
+
expect(await screen.findByText('granted')).toBeDefined();
|
|
151
|
+
});
|
|
152
|
+
it('fails closed while loading and when denied', () => {
|
|
153
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
154
|
+
const { container } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(RequireRoles, { roles: ['owner'], fallback: _jsx("span", { children: "denied" }), children: _jsx("span", { children: "granted" }) }) }));
|
|
155
|
+
expect(container.textContent).toBe('denied');
|
|
156
|
+
});
|
|
157
|
+
it('renders nothing by default when denied', () => {
|
|
158
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
159
|
+
const { container } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(RequireRoles, { roles: ['owner'], children: _jsx("span", { children: "granted" }) }) }));
|
|
160
|
+
expect(container.textContent).toBe('');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=components.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.test.js","sourceRoot":"","sources":["../../src/__tests__/components.test.tsx"],"names":[],"mappings":";AAAA,gCAAgC;AAChC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAEzF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE5D,wEAAwE;AACxE,oEAAoE;AACpE,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,EAAE,CAAA;AACX,CAAC,CAAC,CAAA;AAEF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEtF,MAAM,IAAI,GAAgB;IACxB,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,OAAO;IACnB,KAAK,EAAE,kBAAkB;IACzB,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,SAAS,YAAY,CAAC,IAAa,EAAE,MAAM,GAAG,GAAG;IAC/C,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,OAAgE;IACjF,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,KAAa,EAAE,IAAkB,EAAqB,EAAE;QACpE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QACnC,OAAO,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACnF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,QAAmB,EACnB,OAAwF;IAExF,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC/E,IAAI,OAAO,IAAI,aAAa,IAAI,OAAO,EAAE,CAAC;QACxC,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,YACrE,QAAQ,GACQ,CACpB,CAAA;QACD,OAAM;IACR,CAAC;IACD,MAAM,CAAC,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YAAG,QAAQ,GAAoB,CAAC,CAAA;AAC/E,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,MAAM,CAAC,SAAuC;IAC3D,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAgB,CAAC,CAAC,CAAA;QACzF,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;QACzB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,KAAC,YAAY,KAAG,CAAC,CAAA;QACxB,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACxC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC9B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CACJ,KAAC,YAAY,IAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,KAAK,uBAE5B,CAChB,CAAA;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QACvC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAChD,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QAC3B,cAAc,CAAC,KAAC,aAAa,IAAC,UAAU,EAAC,GAAG,EAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAI,EAAE;YAC7F,SAAS;YACT,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,WAAW,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAA;QAC1C,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QACxD,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QAC3B,cAAc,CAAC,KAAC,aAAa,IAAC,UAAU,EAAC,GAAG,EAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAI,EAAE;YAC7F,SAAS;YACT,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvF,CAAC,CAAC,CAAA;QACF,gEAAgE;QAChE,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;QACvB,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IACzC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,4BAA4B,EAAE,GAAG,CAAC;QACnC,CAAC,gBAAgB,EAAE,GAAG,CAAC;QACvB,CAAC,YAAY,EAAE,GAAG,CAAC;QACnB,CAAC,UAAU,EAAE,UAAU,CAAC;QACxB,CAAC,cAAc,EAAE,cAAc,CAAC;KACjC,CAAC,CAAC,oBAAoB,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;QACtD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,cAAc,CAAC,KAAC,aAAa,IAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAI,EAAE;YAC5E,SAAS;YACT,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,OAAO,GAAqC,EAAE,CAAA;QACpD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CACrB,KAAK,IAAuB,EAAE,CAC5B,IAAI,OAAO,CAAW,CAAC,OAAO,EAAE,EAAE;YAChC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvB,CAAC,CAAC,CACL,CAAA;QACD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,QAAQ,KAAG,GACK,CACpB,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACtC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACpC,iEAAiE;QACjE,MAAM,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QACxD,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,QAAQ,IAAC,UAAU,EAAC,QAAQ,GAAG,GACf,CACpB,CAAA;QACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,QAAQ,KAAG,GACK,CACpB,CAAA;QACD,MAAM,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IACpD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,YAAY,IAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,oCAAmB,YAC3D,qCAAoB,GACP,GACE,CACpB,CAAA;QACD,MAAM,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,YACvD,KAAC,YAAY,IAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,oCAAmB,YAC3D,qCAAoB,GACP,GACE,CACpB,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,YACvD,KAAC,YAAY,IAAC,KAAK,EAAE,CAAC,OAAO,CAAC,YAC5B,qCAAoB,GACP,GACE,CACpB,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { fetchSessionUser } from '../index.js';
|
|
3
|
+
describe('react entrypoint', () => {
|
|
4
|
+
it('serves the session reader contract through the barrel', async () => {
|
|
5
|
+
const fetchImpl = vi.fn(() => Promise.resolve(new Response('nope', { status: 401 })));
|
|
6
|
+
await expect(fetchSessionUser('/api/auth-link/session', fetchImpl)).resolves.toBeNull();
|
|
7
|
+
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9C,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;QACrF,MAAM,MAAM,CAAC,gBAAgB,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAA;QACvF,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { AuthLinkProvider, useAuthLink, useHasRoles } from '../provider.js';
|
|
6
|
+
// RTL's auto-cleanup keys off a global afterEach, which Vitest does not
|
|
7
|
+
// provide with `globals` off — clean up explicitly between renders.
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
cleanup();
|
|
10
|
+
});
|
|
11
|
+
const user = {
|
|
12
|
+
id: 'user-1',
|
|
13
|
+
collection: 'users',
|
|
14
|
+
email: 'user@example.com',
|
|
15
|
+
roles: ['member', 'admin'],
|
|
16
|
+
};
|
|
17
|
+
function jsonResponse(body, status = 200) {
|
|
18
|
+
return new Response(JSON.stringify(body), {
|
|
19
|
+
status,
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function stubFetch(handler) {
|
|
24
|
+
return vi.fn((input, init) => {
|
|
25
|
+
const result = handler(input, init);
|
|
26
|
+
return result instanceof Error ? Promise.reject(result) : Promise.resolve(result);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function StatusProbe() {
|
|
30
|
+
const { user: current, status } = useAuthLink();
|
|
31
|
+
return (_jsxs("div", { children: [_jsx("span", { "data-testid": "status", children: status }), _jsx("span", { "data-testid": "email", children: current?.email ?? 'none' })] }));
|
|
32
|
+
}
|
|
33
|
+
function RolesProbe({ roles }) {
|
|
34
|
+
return _jsx("span", { "data-testid": "has-roles", children: useHasRoles(...roles) ? 'yes' : 'no' });
|
|
35
|
+
}
|
|
36
|
+
describe('AuthLinkProvider', () => {
|
|
37
|
+
it('loads the session on mount', async () => {
|
|
38
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
39
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(StatusProbe, {}) }));
|
|
40
|
+
expect(screen.getByTestId('status').textContent).toBe('loading');
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
43
|
+
});
|
|
44
|
+
expect(screen.getByTestId('email').textContent).toBe('user@example.com');
|
|
45
|
+
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
46
|
+
});
|
|
47
|
+
it('goes anonymous when no session exists', async () => {
|
|
48
|
+
const fetchImpl = stubFetch(() => jsonResponse({ error: 'nope' }, 401));
|
|
49
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(StatusProbe, {}) }));
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it('hydrates from initialUser without fetching', () => {
|
|
55
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
56
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(StatusProbe, {}) }));
|
|
57
|
+
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
58
|
+
expect(screen.getByTestId('email').textContent).toBe('user@example.com');
|
|
59
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
it('hydrates anonymous from a null initialUser without fetching', () => {
|
|
62
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
63
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(StatusProbe, {}) }));
|
|
64
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
65
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
it('syncs a later initialUser into context (PAR-10)', () => {
|
|
68
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
69
|
+
const other = { ...user, id: 'user-2', email: 'other@example.com' };
|
|
70
|
+
const { rerender } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(StatusProbe, {}) }));
|
|
71
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
72
|
+
rerender(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(StatusProbe, {}) }));
|
|
73
|
+
expect(screen.getByTestId('email').textContent).toBe('user@example.com');
|
|
74
|
+
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
75
|
+
rerender(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: other, children: _jsx(StatusProbe, {}) }));
|
|
76
|
+
expect(screen.getByTestId('email').textContent).toBe('other@example.com');
|
|
77
|
+
rerender(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(StatusProbe, {}) }));
|
|
78
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
79
|
+
// Refresh stays explicit: syncing a snapshot never fetches.
|
|
80
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
it('keeps a client sign-out when re-rendered with an equal fresh snapshot', async () => {
|
|
83
|
+
const fetchImpl = stubFetch(() => jsonResponse({ ok: true }));
|
|
84
|
+
function SignOutProbe() {
|
|
85
|
+
const { signOut } = useAuthLink();
|
|
86
|
+
return (_jsx("button", { type: "button", onClick: () => void signOut(), children: "out" }));
|
|
87
|
+
}
|
|
88
|
+
const tree = (snapshot) => (_jsxs(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: snapshot, children: [_jsx(StatusProbe, {}), _jsx(SignOutProbe, {})] }));
|
|
89
|
+
const { rerender } = render(tree(user));
|
|
90
|
+
fireEvent.click(screen.getByText('out'));
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
93
|
+
});
|
|
94
|
+
rerender(tree({ ...user, roles: [...user.roles] }));
|
|
95
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
96
|
+
});
|
|
97
|
+
it('refreshes on demand', async () => {
|
|
98
|
+
let current = null;
|
|
99
|
+
const fetchImpl = stubFetch(() => current ? jsonResponse({ user: current }) : jsonResponse({}, 401));
|
|
100
|
+
function RefreshProbe() {
|
|
101
|
+
const { refresh } = useAuthLink();
|
|
102
|
+
return (_jsx("button", { type: "button", onClick: () => {
|
|
103
|
+
void refresh();
|
|
104
|
+
}, children: "refresh" }));
|
|
105
|
+
}
|
|
106
|
+
render(_jsxs(AuthLinkProvider, { fetchImpl: fetchImpl, children: [_jsx(StatusProbe, {}), _jsx(RefreshProbe, {})] }));
|
|
107
|
+
await waitFor(() => {
|
|
108
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
109
|
+
});
|
|
110
|
+
current = user;
|
|
111
|
+
fireEvent.click(screen.getByText('refresh'));
|
|
112
|
+
await waitFor(() => {
|
|
113
|
+
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('signOut clears state on success and keeps it on failure', async () => {
|
|
117
|
+
let failRevoke = false;
|
|
118
|
+
const outcomes = [];
|
|
119
|
+
const fetchImpl = stubFetch((_input, init) => {
|
|
120
|
+
if (init?.method === 'DELETE') {
|
|
121
|
+
return failRevoke ? jsonResponse({}, 500) : jsonResponse({ ok: true });
|
|
122
|
+
}
|
|
123
|
+
return jsonResponse({ user });
|
|
124
|
+
});
|
|
125
|
+
function SignOutProbe() {
|
|
126
|
+
const { signOut } = useAuthLink();
|
|
127
|
+
return (_jsx("button", { type: "button", onClick: () => {
|
|
128
|
+
void signOut().then((ok) => outcomes.push(ok));
|
|
129
|
+
}, children: "sign out" }));
|
|
130
|
+
}
|
|
131
|
+
render(_jsxs(AuthLinkProvider, { fetchImpl: fetchImpl, children: [_jsx(StatusProbe, {}), _jsx(SignOutProbe, {})] }));
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
134
|
+
});
|
|
135
|
+
failRevoke = true;
|
|
136
|
+
fireEvent.click(screen.getByText('sign out'));
|
|
137
|
+
await waitFor(() => {
|
|
138
|
+
expect(outcomes).toEqual([false]);
|
|
139
|
+
});
|
|
140
|
+
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
141
|
+
failRevoke = false;
|
|
142
|
+
fireEvent.click(screen.getByText('sign out'));
|
|
143
|
+
await waitFor(() => {
|
|
144
|
+
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
145
|
+
});
|
|
146
|
+
expect(outcomes).toEqual([false, true]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('useAuthLink', () => {
|
|
150
|
+
it('throws outside the provider', () => {
|
|
151
|
+
const silenced = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
152
|
+
try {
|
|
153
|
+
expect(() => render(_jsx(StatusProbe, {}))).toThrow('useAuthLink must be used within AuthLinkProvider');
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
silenced.mockRestore();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('useHasRoles', () => {
|
|
161
|
+
it('passes when every listed role is held', () => {
|
|
162
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
163
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(RolesProbe, { roles: ['admin', 'member'] }) }));
|
|
164
|
+
expect(screen.getByTestId('has-roles').textContent).toBe('yes');
|
|
165
|
+
});
|
|
166
|
+
it('fails when any listed role is missing', () => {
|
|
167
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
168
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(RolesProbe, { roles: ['admin', 'owner'] }) }));
|
|
169
|
+
expect(screen.getByTestId('has-roles').textContent).toBe('no');
|
|
170
|
+
});
|
|
171
|
+
it('fails closed when anonymous', () => {
|
|
172
|
+
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
173
|
+
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(RolesProbe, { roles: ['admin'] }) }));
|
|
174
|
+
expect(screen.getByTestId('has-roles').textContent).toBe('no');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
//# sourceMappingURL=provider.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/__tests__/provider.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { fetchSessionUser, revokeSession } from '../session.js';
|
|
3
|
+
const user = { id: 'user-1', collection: 'users', email: 'user@example.com', roles: ['member'] };
|
|
4
|
+
function jsonResponse(body, status = 200) {
|
|
5
|
+
return new Response(JSON.stringify(body), {
|
|
6
|
+
status,
|
|
7
|
+
headers: { 'content-type': 'application/json' },
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function stubFetch(response) {
|
|
11
|
+
const fetchImpl = vi.fn();
|
|
12
|
+
if (response instanceof Error) {
|
|
13
|
+
fetchImpl.mockRejectedValue(response);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
fetchImpl.mockResolvedValue(response);
|
|
17
|
+
}
|
|
18
|
+
return fetchImpl;
|
|
19
|
+
}
|
|
20
|
+
describe('fetchSessionUser', () => {
|
|
21
|
+
it('reads the user from the default endpoint', async () => {
|
|
22
|
+
const fetchImpl = stubFetch(jsonResponse({ user }));
|
|
23
|
+
const result = await fetchSessionUser(undefined, fetchImpl);
|
|
24
|
+
expect(result).toEqual(user);
|
|
25
|
+
expect(fetchImpl).toHaveBeenCalledWith('/api/auth-link/session', {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
credentials: 'same-origin',
|
|
28
|
+
cache: 'no-store',
|
|
29
|
+
headers: { accept: 'application/json' },
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it('honors a custom endpoint', async () => {
|
|
33
|
+
const fetchImpl = stubFetch(jsonResponse({ user }));
|
|
34
|
+
await fetchSessionUser('/custom/session', fetchImpl);
|
|
35
|
+
expect(fetchImpl).toHaveBeenCalledWith('/custom/session', expect.objectContaining({ method: 'GET' }));
|
|
36
|
+
});
|
|
37
|
+
it('fails closed on transport and protocol failures', async () => {
|
|
38
|
+
for (const response of [
|
|
39
|
+
stubFetch(new Error('down')),
|
|
40
|
+
stubFetch(jsonResponse({ error: 'nope' }, 401)),
|
|
41
|
+
stubFetch(jsonResponse({ error: 'nope' }, 500)),
|
|
42
|
+
stubFetch(new Response('not-json{', { status: 200 })),
|
|
43
|
+
stubFetch(jsonResponse([user])),
|
|
44
|
+
stubFetch(jsonResponse({})),
|
|
45
|
+
stubFetch(jsonResponse({ user: { ...user, email: 'bad' } })),
|
|
46
|
+
]) {
|
|
47
|
+
await expect(fetchSessionUser(undefined, response)).resolves.toBeNull();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('revokeSession', () => {
|
|
52
|
+
it('sends a CSRF-proofed DELETE to the default endpoint', async () => {
|
|
53
|
+
const fetchImpl = stubFetch(jsonResponse({ ok: true }));
|
|
54
|
+
await expect(revokeSession(undefined, fetchImpl)).resolves.toBe(true);
|
|
55
|
+
expect(fetchImpl).toHaveBeenCalledWith('/api/auth-link/session', {
|
|
56
|
+
method: 'DELETE',
|
|
57
|
+
credentials: 'same-origin',
|
|
58
|
+
headers: { 'x-auth-link-csrf': '1' },
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it('treats already-signed-out as success (idempotent logout)', async () => {
|
|
62
|
+
await expect(revokeSession(undefined, stubFetch(jsonResponse({}, 401)))).resolves.toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it('reports failure instead of lying', async () => {
|
|
65
|
+
await expect(revokeSession(undefined, stubFetch(new Error('down')))).resolves.toBe(false);
|
|
66
|
+
await expect(revokeSession(undefined, stubFetch(jsonResponse({}, 500)))).resolves.toBe(false);
|
|
67
|
+
await expect(revokeSession(undefined, stubFetch(jsonResponse({}, 403)))).resolves.toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.test.js","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/** {@link SignInButton} wiring; unstyled by design. */
|
|
3
|
+
export type SignInButtonProps = {
|
|
4
|
+
/** Where the sign-in page lives. Defaults to `/sign-in`. */
|
|
5
|
+
readonly href?: string | undefined;
|
|
6
|
+
readonly className?: string | undefined;
|
|
7
|
+
readonly children?: ReactNode | undefined;
|
|
8
|
+
};
|
|
9
|
+
/** Unstyled link to the builder's sign-in page. */
|
|
10
|
+
export declare function SignInButton({ href, className, children, }: SignInButtonProps): ReactNode;
|
|
11
|
+
/** {@link SignOutButton} wiring; navigation is injectable. */
|
|
12
|
+
export type SignOutButtonProps = {
|
|
13
|
+
readonly className?: string | undefined;
|
|
14
|
+
readonly children?: ReactNode | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Where to go after a successful sign-out. Stays put by default.
|
|
17
|
+
* Narrowed by `safeRedirectPath` (an off-origin or API destination
|
|
18
|
+
* falls back to `/`), so a prop fed from a query param cannot turn
|
|
19
|
+
* sign-out into an open redirect.
|
|
20
|
+
*/
|
|
21
|
+
readonly redirectTo?: string | undefined;
|
|
22
|
+
/** Navigation injection for tests; defaults to a full page load. */
|
|
23
|
+
readonly navigate?: ((url: string) => void) | undefined;
|
|
24
|
+
/** Called after a successful sign-out, before navigation. */
|
|
25
|
+
readonly onSignedOut?: (() => void) | undefined;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Unstyled sign-out button. Revokes through the provider; on failure it
|
|
29
|
+
* does nothing (the session is still live, so navigating away would lie).
|
|
30
|
+
*/
|
|
31
|
+
export declare function SignOutButton({ className, children, redirectTo, navigate, onSignedOut, }: SignOutButtonProps): ReactNode;
|
|
32
|
+
/** {@link UserMenu} wiring; renders nothing while loading. */
|
|
33
|
+
export type UserMenuProps = {
|
|
34
|
+
readonly className?: string | undefined;
|
|
35
|
+
/** Sign-in page for the anonymous state. Defaults to `/sign-in`. */
|
|
36
|
+
readonly signInHref?: string | undefined;
|
|
37
|
+
/** Where to go after sign-out. Stays put by default. */
|
|
38
|
+
readonly signOutRedirectTo?: string | undefined;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Minimal session menu: nothing while loading, a sign-in link when
|
|
42
|
+
* anonymous, the email plus a sign-out button when authenticated.
|
|
43
|
+
*/
|
|
44
|
+
export declare function UserMenu({ className, signInHref, signOutRedirectTo, }: UserMenuProps): ReactNode;
|
|
45
|
+
/** {@link RequireRoles} wiring: all roles required, fail closed. */
|
|
46
|
+
export type RequireRolesProps = {
|
|
47
|
+
/** Every listed role is required (fail closed while loading). */
|
|
48
|
+
readonly roles: readonly string[];
|
|
49
|
+
readonly children: ReactNode;
|
|
50
|
+
readonly fallback?: ReactNode | undefined;
|
|
51
|
+
};
|
|
52
|
+
/** Render-gate for role-restricted UI. Hides — it never authorizes. */
|
|
53
|
+
export declare function RequireRoles({ roles, children, fallback }: RequireRolesProps): ReactNode;
|
|
54
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,uDAAuD;AACvD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAC1C,CAAA;AAED,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,EAC3B,IAAiB,EACjB,SAAS,EACT,QAAoB,GACrB,EAAE,iBAAiB,GAAG,SAAS,CAM/B;AAED,8DAA8D;AAC9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACvD,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;CAChD,CAAA;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,QAAqB,EACrB,UAAU,EACV,QAEC,EACD,WAAW,GACZ,EAAE,kBAAkB,GAAG,SAAS,CAuBhC;AAED,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,oEAAoE;IACpE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,wDAAwD;IACxD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAChD,CAAA;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,UAAuB,EACvB,iBAAiB,GAClB,EAAE,aAAa,GAAG,SAAS,CAc3B;AAED,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAC1C,CAAA;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAe,EAAE,EAAE,iBAAiB,GAAG,SAAS,CAG/F"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { safeRedirectPath } from '@payload-auth-link/core';
|
|
4
|
+
import { useAuthLink, useHasRoles } from './provider.js';
|
|
5
|
+
/** Unstyled link to the builder's sign-in page. */
|
|
6
|
+
export function SignInButton({ href = '/sign-in', className, children = 'Sign in', }) {
|
|
7
|
+
return (_jsx("a", { href: href, className: className, "data-auth-link": "sign-in", children: children }));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Unstyled sign-out button. Revokes through the provider; on failure it
|
|
11
|
+
* does nothing (the session is still live, so navigating away would lie).
|
|
12
|
+
*/
|
|
13
|
+
export function SignOutButton({ className, children = 'Sign out', redirectTo, navigate = (url) => {
|
|
14
|
+
window.location.assign(url);
|
|
15
|
+
}, onSignedOut, }) {
|
|
16
|
+
const { signOut } = useAuthLink();
|
|
17
|
+
return (_jsx("button", { type: "button", className: className, "data-auth-link": "sign-out", onClick: () => {
|
|
18
|
+
void (async () => {
|
|
19
|
+
if (await signOut()) {
|
|
20
|
+
onSignedOut?.();
|
|
21
|
+
if (redirectTo !== undefined) {
|
|
22
|
+
// Sign-in is a fine post-sign-out destination: disable
|
|
23
|
+
// that rule, keep the off-origin and API ones.
|
|
24
|
+
navigate(safeRedirectPath(redirectTo, '/', { signInPath: null }));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
28
|
+
}, children: children }));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Minimal session menu: nothing while loading, a sign-in link when
|
|
32
|
+
* anonymous, the email plus a sign-out button when authenticated.
|
|
33
|
+
*/
|
|
34
|
+
export function UserMenu({ className, signInHref = '/sign-in', signOutRedirectTo, }) {
|
|
35
|
+
const { user, status } = useAuthLink();
|
|
36
|
+
if (status === 'loading') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (!user) {
|
|
40
|
+
return _jsx(SignInButton, { href: signInHref });
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs("div", { className: className, "data-auth-link": "user-menu", "data-auth-status": status, children: [_jsx("span", { "data-auth-link": "user-email", children: user.email }), ' ', _jsx(SignOutButton, { redirectTo: signOutRedirectTo })] }));
|
|
43
|
+
}
|
|
44
|
+
/** Render-gate for role-restricted UI. Hides — it never authorizes. */
|
|
45
|
+
export function RequireRoles({ roles, children, fallback = null }) {
|
|
46
|
+
const allowed = useHasRoles(...roles);
|
|
47
|
+
return allowed ? children : fallback;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAUxD,mDAAmD;AACnD,MAAM,UAAU,YAAY,CAAC,EAC3B,IAAI,GAAG,UAAU,EACjB,SAAS,EACT,QAAQ,GAAG,SAAS,GACF;IAClB,OAAO,CACL,YAAG,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,oBAAiB,SAAS,YAC1D,QAAQ,GACP,CACL,CAAA;AACH,CAAC;AAmBD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,SAAS,EACT,QAAQ,GAAG,UAAU,EACrB,UAAU,EACV,QAAQ,GAAG,CAAC,GAAW,EAAQ,EAAE;IAC/B,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;AAC7B,CAAC,EACD,WAAW,GACQ;IACnB,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,EAAE,CAAA;IACjC,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,SAAS,oBACL,UAAU,EACzB,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,IAAI,MAAM,OAAO,EAAE,EAAE,CAAC;oBACpB,WAAW,EAAE,EAAE,CAAA;oBACf,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;wBAC7B,uDAAuD;wBACvD,+CAA+C;wBAC/C,QAAQ,CAAC,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;oBACnE,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,EAAE,CAAA;QACN,CAAC,YAEA,QAAQ,GACF,CACV,CAAA;AACH,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,EACvB,SAAS,EACT,UAAU,GAAG,UAAU,EACvB,iBAAiB,GACH;IACd,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAC,YAAY,IAAC,IAAI,EAAE,UAAU,GAAI,CAAA;IAC3C,CAAC;IACD,OAAO,CACL,eAAK,SAAS,EAAE,SAAS,oBAAiB,WAAW,sBAAmB,MAAM,aAC5E,iCAAqB,YAAY,YAAE,IAAI,CAAC,KAAK,GAAQ,EAAC,GAAG,EACzD,KAAC,aAAa,IAAC,UAAU,EAAE,iBAAiB,GAAI,IAC5C,CACP,CAAA;AACH,CAAC;AAUD,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,EAAqB;IAClF,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,KAAK,CAAC,CAAA;IACrC,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;AACtC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic React UI: session context, sign-in components, user
|
|
3
|
+
* menu, role gates. No Next, no Payload imports — framework work lives in
|
|
4
|
+
* `@payload-auth-link/next`, server work in `@payload-auth-link/plugin`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const packageName = "@payload-auth-link/react";
|
|
7
|
+
export { RequireRoles, SignInButton, SignOutButton, UserMenu } from './components.js';
|
|
8
|
+
export type { RequireRolesProps, SignInButtonProps, SignOutButtonProps, UserMenuProps, } from './components.js';
|
|
9
|
+
export { AuthLinkProvider, useAuthLink, useHasRoles } from './provider.js';
|
|
10
|
+
export type { AuthLinkContextValue, AuthLinkProviderProps, AuthLinkStatus } from './provider.js';
|
|
11
|
+
export { fetchSessionUser, revokeSession } from './session.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|