@payload-auth-link/react 0.1.0 → 0.2.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/README.md +45 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +7 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +22 -8
- package/dist/provider.js.map +1 -1
- package/dist/session.d.ts +13 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +39 -1
- package/dist/session.js.map +1 -1
- package/package.json +15 -3
- package/src/index.ts +2 -1
- package/src/provider.tsx +30 -9
- package/src/session.ts +50 -0
- package/dist/__tests__/components.test.d.ts +0 -2
- package/dist/__tests__/components.test.d.ts.map +0 -1
- package/dist/__tests__/components.test.js +0 -163
- package/dist/__tests__/components.test.js.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -10
- package/dist/__tests__/index.test.js.map +0 -1
- package/dist/__tests__/provider.test.d.ts +0 -2
- package/dist/__tests__/provider.test.d.ts.map +0 -1
- package/dist/__tests__/provider.test.js +0 -177
- package/dist/__tests__/provider.test.js.map +0 -1
- package/dist/__tests__/session.test.d.ts +0 -2
- package/dist/__tests__/session.test.d.ts.map +0 -1
- package/dist/__tests__/session.test.js +0 -70
- package/dist/__tests__/session.test.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @payload-auth-link/react
|
|
2
|
+
|
|
3
|
+
Provider-agnostic React UI for payload-auth-link: a session context plus unstyled sign-in, sign-out, and user-menu components. It has no Next.js or Payload imports, so it works under any framework adapter.
|
|
4
|
+
|
|
5
|
+
Part of [payload-auth-link](https://github.com/jellologic/payload-auth-link): an auth plugin for Payload CMS with swappable identity providers (Better Auth, Auth.js) and first-class Next.js and TanStack Start adapters.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @payload-auth-link/react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies: `react` ^19.0.0.
|
|
14
|
+
|
|
15
|
+
ESM-only, Node 20.9 or later.
|
|
16
|
+
|
|
17
|
+
## What it provides
|
|
18
|
+
|
|
19
|
+
- `AuthLinkProvider`, `useAuthLink`, `useHasRoles`: session context (`user`, anonymous `guest`, `mfaRequired` step-up signal), hydrated from the server through `initialUser`
|
|
20
|
+
- `SignInButton`, `SignOutButton`, `UserMenu`: unstyled primitives
|
|
21
|
+
- `RequireRoles`: hides UI by role (never use it to gate content; enforce on the server)
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { AuthLinkProvider, UserMenu } from '@payload-auth-link/react'
|
|
27
|
+
|
|
28
|
+
export function Layout({ user, children }) {
|
|
29
|
+
return (
|
|
30
|
+
<AuthLinkProvider initialUser={user}>
|
|
31
|
+
<UserMenu signOutRedirectTo="/" />
|
|
32
|
+
{children}
|
|
33
|
+
</AuthLinkProvider>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Docs
|
|
39
|
+
|
|
40
|
+
- [Getting started](https://github.com/jellologic/payload-auth-link/blob/main/docs/getting-started.md)
|
|
41
|
+
- [Compatibility](https://github.com/jellologic/payload-auth-link/blob/main/docs/compatibility.md) · [Troubleshooting](https://github.com/jellologic/payload-auth-link/blob/main/docs/troubleshooting.md) · [Changelog](./CHANGELOG.md)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ export { RequireRoles, SignInButton, SignOutButton, UserMenu } from './component
|
|
|
8
8
|
export type { RequireRolesProps, SignInButtonProps, SignOutButtonProps, UserMenuProps, } from './components.js';
|
|
9
9
|
export { AuthLinkProvider, useAuthLink, useHasRoles } from './provider.js';
|
|
10
10
|
export type { AuthLinkContextValue, AuthLinkProviderProps, AuthLinkStatus } from './provider.js';
|
|
11
|
-
export { fetchSessionUser, revokeSession } from './session.js';
|
|
11
|
+
export { fetchSession, fetchSessionUser, revokeSession } from './session.js';
|
|
12
|
+
export type { SessionSnapshot } from './session.js';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,WAAW,6BAA6B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACrF,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAChG,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,WAAW,6BAA6B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACrF,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAChG,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
export const packageName = '@payload-auth-link/react';
|
|
7
7
|
export { RequireRoles, SignInButton, SignOutButton, UserMenu } from './components.js';
|
|
8
8
|
export { AuthLinkProvider, useAuthLink, useHasRoles } from './provider.js';
|
|
9
|
-
export { fetchSessionUser, revokeSession } from './session.js';
|
|
9
|
+
export { fetchSession, fetchSessionUser, revokeSession } from './session.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,0BAA0B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAOrF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE1E,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,0BAA0B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAOrF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE1E,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type HttpFetch, type SessionUser } from '@payload-auth-link/core';
|
|
2
|
+
import { type HttpFetch, type SessionGuest, type SessionUser } from '@payload-auth-link/core';
|
|
3
3
|
/** Session state: unknown until the first load settles. */
|
|
4
4
|
export type AuthLinkStatus = 'loading' | 'authenticated' | 'anonymous';
|
|
5
5
|
/** What `useAuthLink` returns: user, status, refresh, honest sign-out. */
|
|
6
6
|
export type AuthLinkContextValue = {
|
|
7
7
|
readonly user: SessionUser | null;
|
|
8
|
+
/**
|
|
9
|
+
* The anonymous guest principal (CAP-10), present instead of a
|
|
10
|
+
* user while the provider session is anonymous. Never carries
|
|
11
|
+
* roles or access — a stable key for anonymous app state only.
|
|
12
|
+
*/
|
|
13
|
+
readonly guest: SessionGuest | null;
|
|
8
14
|
readonly status: AuthLinkStatus;
|
|
9
15
|
/** Re-read the session endpoint (call after sign-in or role changes). */
|
|
10
16
|
readonly refresh: () => Promise<void>;
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAA;AAGhC,2DAA2D;AAC3D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,eAAe,GAAG,WAAW,CAAA;AAEtE,0EAA0E;AAC1E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CACzC,CAAA;AAID,uEAAuE;AACvE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7C,wDAAwD;IACxD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAA;CACtD,CAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAA;AAGhC,2DAA2D;AAC3D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,eAAe,GAAG,WAAW,CAAA;AAEtE,0EAA0E;AAC1E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CACzC,CAAA;AAID,uEAAuE;AACvE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7C,wDAAwD;IACxD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAA;CACtD,CAAA;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,eAA4C,EAC5C,SAAiB,EACjB,WAAuB,GACxB,EAAE,qBAAqB,GAAG,SAAS,CAiEnC;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,oBAAoB,CAMlD;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAMhE"}
|
package/dist/provider.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
4
4
|
import { defaultSessionEndpointPath, } from '@payload-auth-link/core';
|
|
5
|
-
import {
|
|
5
|
+
import { fetchSession, revokeSession } from './session.js';
|
|
6
6
|
const AuthLinkContext = createContext(null);
|
|
7
7
|
/** Value identity for hydration snapshots (fresh objects, same user). */
|
|
8
8
|
function snapshotKey(user) {
|
|
@@ -12,7 +12,17 @@ function snapshotKey(user) {
|
|
|
12
12
|
if (user === null) {
|
|
13
13
|
return 'null';
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
// PORT-4 facts are part of the value: a re-run loader reporting a
|
|
16
|
+
// new actor/assurance/tenant for the same user must still sync.
|
|
17
|
+
return JSON.stringify([
|
|
18
|
+
user.id,
|
|
19
|
+
user.collection,
|
|
20
|
+
user.email,
|
|
21
|
+
[...user.roles].sort(),
|
|
22
|
+
user.assurance ?? null,
|
|
23
|
+
user.actor ?? null,
|
|
24
|
+
user.organizationId ?? null,
|
|
25
|
+
]);
|
|
16
26
|
}
|
|
17
27
|
function statusFor(user) {
|
|
18
28
|
return user === null ? 'anonymous' : 'authenticated';
|
|
@@ -28,6 +38,7 @@ function statusFor(user) {
|
|
|
28
38
|
*/
|
|
29
39
|
export function AuthLinkProvider({ children, sessionEndpoint = defaultSessionEndpointPath, fetchImpl = fetch, initialUser = undefined, }) {
|
|
30
40
|
const [user, setUser] = useState(initialUser ?? null);
|
|
41
|
+
const [guest, setGuest] = useState(null);
|
|
31
42
|
const [status, setStatus] = useState(initialUser === undefined ? 'loading' : statusFor(initialUser));
|
|
32
43
|
// React's "adjust state when a prop changes" pattern (during render,
|
|
33
44
|
// not in an effect): no stale frame, no set-state-in-effect.
|
|
@@ -40,16 +51,18 @@ export function AuthLinkProvider({ children, sessionEndpoint = defaultSessionEnd
|
|
|
40
51
|
setStatus(statusFor(initialUser));
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
|
-
const loadSession = useCallback(() =>
|
|
54
|
+
const loadSession = useCallback(() => fetchSession(sessionEndpoint, fetchImpl), [sessionEndpoint, fetchImpl]);
|
|
44
55
|
const refresh = useCallback(async () => {
|
|
45
56
|
const current = await loadSession();
|
|
46
|
-
setUser(current);
|
|
47
|
-
|
|
57
|
+
setUser(current.user);
|
|
58
|
+
setGuest(current.guest);
|
|
59
|
+
setStatus(current.user === null ? 'anonymous' : 'authenticated');
|
|
48
60
|
}, [loadSession]);
|
|
49
61
|
const signOut = useCallback(async () => {
|
|
50
62
|
const revoked = await revokeSession(sessionEndpoint, fetchImpl);
|
|
51
63
|
if (revoked) {
|
|
52
64
|
setUser(null);
|
|
65
|
+
setGuest(null);
|
|
53
66
|
setStatus('anonymous');
|
|
54
67
|
}
|
|
55
68
|
return revoked;
|
|
@@ -64,15 +77,16 @@ export function AuthLinkProvider({ children, sessionEndpoint = defaultSessionEnd
|
|
|
64
77
|
void (async () => {
|
|
65
78
|
const current = await loadSession();
|
|
66
79
|
if (!cancelled) {
|
|
67
|
-
setUser(current);
|
|
68
|
-
|
|
80
|
+
setUser(current.user);
|
|
81
|
+
setGuest(current.guest);
|
|
82
|
+
setStatus(current.user === null ? 'anonymous' : 'authenticated');
|
|
69
83
|
}
|
|
70
84
|
})();
|
|
71
85
|
return () => {
|
|
72
86
|
cancelled = true;
|
|
73
87
|
};
|
|
74
88
|
}, [initialUser, loadSession]);
|
|
75
|
-
const value = useMemo(() => ({ user, status, refresh, signOut }), [user, status, refresh, signOut]);
|
|
89
|
+
const value = useMemo(() => ({ user, guest, status, refresh, signOut }), [user, guest, status, refresh, signOut]);
|
|
76
90
|
return _jsx(AuthLinkContext.Provider, { value: value, children: children });
|
|
77
91
|
}
|
|
78
92
|
/** Read the auth context. Throws outside `AuthLinkProvider` — fail loud. */
|
package/dist/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EACL,aAAa,EACb,WAAW,EACX,UAAU,EACV,SAAS,EACT,OAAO,EACP,QAAQ,GAET,MAAM,OAAO,CAAA;AACd,OAAO,EACL,0BAA0B,
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HttpFetch, type SessionUser } from '@payload-auth-link/core';
|
|
1
|
+
import { type HttpFetch, type SessionGuest, type SessionUser } from '@payload-auth-link/core';
|
|
2
2
|
/**
|
|
3
3
|
* Read the current session user from the plugin's session endpoint
|
|
4
4
|
* (`{ user }` envelope). Fails closed: transport errors, non-2xx
|
|
@@ -6,6 +6,18 @@ import { type HttpFetch, type SessionUser } from '@payload-auth-link/core';
|
|
|
6
6
|
* never an exception and never a partial user.
|
|
7
7
|
*/
|
|
8
8
|
export declare function fetchSessionUser(sessionEndpoint?: string, fetchImpl?: HttpFetch): Promise<SessionUser | null>;
|
|
9
|
+
/** Both halves of one session-endpoint read (CAP-10). */
|
|
10
|
+
export type SessionSnapshot = {
|
|
11
|
+
readonly user: SessionUser | null;
|
|
12
|
+
readonly guest: SessionGuest | null;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Read user AND guest from one session-endpoint response. The body
|
|
16
|
+
* is parsed exactly once; a guest body yields `user: null` plus the
|
|
17
|
+
* guest, a user body the reverse. Fails closed like
|
|
18
|
+
* {@link fetchSessionUser}.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchSession(sessionEndpoint?: string, fetchImpl?: HttpFetch): Promise<SessionSnapshot>;
|
|
9
21
|
/**
|
|
10
22
|
* Revoke the current session through the plugin's session endpoint.
|
|
11
23
|
* Sends the CSRF header the endpoint requires. Returns `true` when the
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAA;AAEhC;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,eAAe,GAAE,MAAmC,EACpD,SAAS,GAAE,SAAiB,GAC3B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAe7B;AAED,yDAAyD;AACzD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;CACpC,CAAA;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,eAAe,GAAE,MAAmC,EACpD,SAAS,GAAE,SAAiB,GAC3B,OAAO,CAAC,eAAe,CAAC,CA6B1B;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,eAAe,GAAE,MAAmC,EACpD,SAAS,GAAE,SAAiB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAYlB"}
|
package/dist/session.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { csrfHeaderName, defaultSessionEndpointPath, parseSessionResponse, } from '@payload-auth-link/core';
|
|
1
|
+
import { csrfHeaderName, defaultSessionEndpointPath, isRecord, parseSessionGuest, parseSessionResponse, parseSessionUser, } from '@payload-auth-link/core';
|
|
2
2
|
/**
|
|
3
3
|
* Read the current session user from the plugin's session endpoint
|
|
4
4
|
* (`{ user }` envelope). Fails closed: transport errors, non-2xx
|
|
@@ -22,6 +22,44 @@ export async function fetchSessionUser(sessionEndpoint = defaultSessionEndpointP
|
|
|
22
22
|
}
|
|
23
23
|
return parseSessionResponse(response);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Read user AND guest from one session-endpoint response. The body
|
|
27
|
+
* is parsed exactly once; a guest body yields `user: null` plus the
|
|
28
|
+
* guest, a user body the reverse. Fails closed like
|
|
29
|
+
* {@link fetchSessionUser}.
|
|
30
|
+
*/
|
|
31
|
+
export async function fetchSession(sessionEndpoint = defaultSessionEndpointPath, fetchImpl = fetch) {
|
|
32
|
+
const empty = { user: null, guest: null };
|
|
33
|
+
let response;
|
|
34
|
+
try {
|
|
35
|
+
response = await fetchImpl(sessionEndpoint, {
|
|
36
|
+
method: 'GET',
|
|
37
|
+
credentials: 'same-origin',
|
|
38
|
+
cache: 'no-store',
|
|
39
|
+
headers: { accept: 'application/json' },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return empty;
|
|
44
|
+
}
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
return empty;
|
|
47
|
+
}
|
|
48
|
+
let body;
|
|
49
|
+
try {
|
|
50
|
+
body = await response.json();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return empty;
|
|
54
|
+
}
|
|
55
|
+
if (!isRecord(body)) {
|
|
56
|
+
return empty;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
user: parseSessionUser(body.user),
|
|
60
|
+
guest: body.user === null ? parseSessionGuest(body.guest) : null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
25
63
|
/**
|
|
26
64
|
* Revoke the current session through the plugin's session endpoint.
|
|
27
65
|
* Sends the CSRF header the endpoint requires. Returns `true` when the
|
package/dist/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,oBAAoB,
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,QAAQ,EACR,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,GAIjB,MAAM,yBAAyB,CAAA;AAEhC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,kBAA0B,0BAA0B,EACpD,YAAuB,KAAK;IAE5B,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,SAAS,CAAC,eAAe,EAAE;YAC1C,MAAM,EAAE,KAAK;YACb,WAAW,EAAE,aAAa;YAC1B,0DAA0D;YAC1D,mDAAmD;YACnD,KAAK,EAAE,UAAU;YACjB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,oBAAoB,CAAC,QAAQ,CAAC,CAAA;AACvC,CAAC;AAQD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,kBAA0B,0BAA0B,EACpD,YAAuB,KAAK;IAE5B,MAAM,KAAK,GAAoB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAC1D,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,SAAS,CAAC,eAAe,EAAE;YAC1C,MAAM,EAAE,KAAK;YACb,WAAW,EAAE,aAAa;YAC1B,KAAK,EAAE,UAAU;YACjB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO;QACL,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;KACjE,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,kBAA0B,0BAA0B,EACpD,YAAuB,KAAK;IAE5B,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,SAAS,CAAC,eAAe,EAAE;YAC1C,MAAM,EAAE,QAAQ;YAChB,WAAW,EAAE,aAAa;YAC1B,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,GAAG,EAAE;SACnC,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAA;AAC/C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payload-auth-link/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "payload-auth-link React UI: provider-agnostic sign-in components and user menu.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jellologic/payload-auth-link.git",
|
|
9
|
+
"directory": "packages/react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/jellologic/payload-auth-link/tree/main/packages/react#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jellologic/payload-auth-link/issues"
|
|
14
|
+
},
|
|
6
15
|
"type": "module",
|
|
7
16
|
"sideEffects": false,
|
|
8
17
|
"exports": {
|
|
@@ -17,7 +26,10 @@
|
|
|
17
26
|
"dist",
|
|
18
27
|
"!dist/*.tsbuildinfo",
|
|
19
28
|
"src",
|
|
20
|
-
"!src/**/__tests__"
|
|
29
|
+
"!src/**/__tests__",
|
|
30
|
+
"!dist/**/__tests__",
|
|
31
|
+
"!dist/**/__fixtures__",
|
|
32
|
+
"!src/**/__fixtures__"
|
|
21
33
|
],
|
|
22
34
|
"scripts": {
|
|
23
35
|
"build": "tsc -b",
|
|
@@ -25,7 +37,7 @@
|
|
|
25
37
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
26
38
|
},
|
|
27
39
|
"dependencies": {
|
|
28
|
-
"@payload-auth-link/core": "0.
|
|
40
|
+
"@payload-auth-link/core": "0.2.0"
|
|
29
41
|
},
|
|
30
42
|
"peerDependencies": {
|
|
31
43
|
"react": "^19.0.0"
|
package/src/index.ts
CHANGED
|
@@ -14,4 +14,5 @@ export type {
|
|
|
14
14
|
} from './components.js'
|
|
15
15
|
export { AuthLinkProvider, useAuthLink, useHasRoles } from './provider.js'
|
|
16
16
|
export type { AuthLinkContextValue, AuthLinkProviderProps, AuthLinkStatus } from './provider.js'
|
|
17
|
-
export { fetchSessionUser, revokeSession } from './session.js'
|
|
17
|
+
export { fetchSession, fetchSessionUser, revokeSession } from './session.js'
|
|
18
|
+
export type { SessionSnapshot } from './session.js'
|
package/src/provider.tsx
CHANGED
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
defaultSessionEndpointPath,
|
|
14
14
|
type HttpFetch,
|
|
15
|
+
type SessionGuest,
|
|
15
16
|
type SessionUser,
|
|
16
17
|
} from '@payload-auth-link/core'
|
|
17
|
-
import {
|
|
18
|
+
import { fetchSession, revokeSession } from './session.js'
|
|
18
19
|
|
|
19
20
|
/** Session state: unknown until the first load settles. */
|
|
20
21
|
export type AuthLinkStatus = 'loading' | 'authenticated' | 'anonymous'
|
|
@@ -22,6 +23,12 @@ export type AuthLinkStatus = 'loading' | 'authenticated' | 'anonymous'
|
|
|
22
23
|
/** What `useAuthLink` returns: user, status, refresh, honest sign-out. */
|
|
23
24
|
export type AuthLinkContextValue = {
|
|
24
25
|
readonly user: SessionUser | null
|
|
26
|
+
/**
|
|
27
|
+
* The anonymous guest principal (CAP-10), present instead of a
|
|
28
|
+
* user while the provider session is anonymous. Never carries
|
|
29
|
+
* roles or access — a stable key for anonymous app state only.
|
|
30
|
+
*/
|
|
31
|
+
readonly guest: SessionGuest | null
|
|
25
32
|
readonly status: AuthLinkStatus
|
|
26
33
|
/** Re-read the session endpoint (call after sign-in or role changes). */
|
|
27
34
|
readonly refresh: () => Promise<void>
|
|
@@ -58,7 +65,17 @@ function snapshotKey(user: SessionUser | null | undefined): string {
|
|
|
58
65
|
if (user === null) {
|
|
59
66
|
return 'null'
|
|
60
67
|
}
|
|
61
|
-
|
|
68
|
+
// PORT-4 facts are part of the value: a re-run loader reporting a
|
|
69
|
+
// new actor/assurance/tenant for the same user must still sync.
|
|
70
|
+
return JSON.stringify([
|
|
71
|
+
user.id,
|
|
72
|
+
user.collection,
|
|
73
|
+
user.email,
|
|
74
|
+
[...user.roles].sort(),
|
|
75
|
+
user.assurance ?? null,
|
|
76
|
+
user.actor ?? null,
|
|
77
|
+
user.organizationId ?? null,
|
|
78
|
+
])
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
function statusFor(user: SessionUser | null): AuthLinkStatus {
|
|
@@ -81,6 +98,7 @@ export function AuthLinkProvider({
|
|
|
81
98
|
initialUser = undefined,
|
|
82
99
|
}: AuthLinkProviderProps): ReactNode {
|
|
83
100
|
const [user, setUser] = useState<SessionUser | null>(initialUser ?? null)
|
|
101
|
+
const [guest, setGuest] = useState<SessionGuest | null>(null)
|
|
84
102
|
const [status, setStatus] = useState<AuthLinkStatus>(
|
|
85
103
|
initialUser === undefined ? 'loading' : statusFor(initialUser)
|
|
86
104
|
)
|
|
@@ -97,20 +115,22 @@ export function AuthLinkProvider({
|
|
|
97
115
|
}
|
|
98
116
|
|
|
99
117
|
const loadSession = useCallback(
|
|
100
|
-
() =>
|
|
118
|
+
() => fetchSession(sessionEndpoint, fetchImpl),
|
|
101
119
|
[sessionEndpoint, fetchImpl]
|
|
102
120
|
)
|
|
103
121
|
|
|
104
122
|
const refresh = useCallback(async () => {
|
|
105
123
|
const current = await loadSession()
|
|
106
|
-
setUser(current)
|
|
107
|
-
|
|
124
|
+
setUser(current.user)
|
|
125
|
+
setGuest(current.guest)
|
|
126
|
+
setStatus(current.user === null ? 'anonymous' : 'authenticated')
|
|
108
127
|
}, [loadSession])
|
|
109
128
|
|
|
110
129
|
const signOut = useCallback(async () => {
|
|
111
130
|
const revoked = await revokeSession(sessionEndpoint, fetchImpl)
|
|
112
131
|
if (revoked) {
|
|
113
132
|
setUser(null)
|
|
133
|
+
setGuest(null)
|
|
114
134
|
setStatus('anonymous')
|
|
115
135
|
}
|
|
116
136
|
return revoked
|
|
@@ -126,8 +146,9 @@ export function AuthLinkProvider({
|
|
|
126
146
|
void (async () => {
|
|
127
147
|
const current = await loadSession()
|
|
128
148
|
if (!cancelled) {
|
|
129
|
-
setUser(current)
|
|
130
|
-
|
|
149
|
+
setUser(current.user)
|
|
150
|
+
setGuest(current.guest)
|
|
151
|
+
setStatus(current.user === null ? 'anonymous' : 'authenticated')
|
|
131
152
|
}
|
|
132
153
|
})()
|
|
133
154
|
return () => {
|
|
@@ -136,8 +157,8 @@ export function AuthLinkProvider({
|
|
|
136
157
|
}, [initialUser, loadSession])
|
|
137
158
|
|
|
138
159
|
const value = useMemo<AuthLinkContextValue>(
|
|
139
|
-
() => ({ user, status, refresh, signOut }),
|
|
140
|
-
[user, status, refresh, signOut]
|
|
160
|
+
() => ({ user, guest, status, refresh, signOut }),
|
|
161
|
+
[user, guest, status, refresh, signOut]
|
|
141
162
|
)
|
|
142
163
|
return <AuthLinkContext.Provider value={value}>{children}</AuthLinkContext.Provider>
|
|
143
164
|
}
|
package/src/session.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
csrfHeaderName,
|
|
3
3
|
defaultSessionEndpointPath,
|
|
4
|
+
isRecord,
|
|
5
|
+
parseSessionGuest,
|
|
4
6
|
parseSessionResponse,
|
|
7
|
+
parseSessionUser,
|
|
5
8
|
type HttpFetch,
|
|
9
|
+
type SessionGuest,
|
|
6
10
|
type SessionUser,
|
|
7
11
|
} from '@payload-auth-link/core'
|
|
8
12
|
|
|
@@ -32,6 +36,52 @@ export async function fetchSessionUser(
|
|
|
32
36
|
return parseSessionResponse(response)
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/** Both halves of one session-endpoint read (CAP-10). */
|
|
40
|
+
export type SessionSnapshot = {
|
|
41
|
+
readonly user: SessionUser | null
|
|
42
|
+
readonly guest: SessionGuest | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read user AND guest from one session-endpoint response. The body
|
|
47
|
+
* is parsed exactly once; a guest body yields `user: null` plus the
|
|
48
|
+
* guest, a user body the reverse. Fails closed like
|
|
49
|
+
* {@link fetchSessionUser}.
|
|
50
|
+
*/
|
|
51
|
+
export async function fetchSession(
|
|
52
|
+
sessionEndpoint: string = defaultSessionEndpointPath,
|
|
53
|
+
fetchImpl: HttpFetch = fetch
|
|
54
|
+
): Promise<SessionSnapshot> {
|
|
55
|
+
const empty: SessionSnapshot = { user: null, guest: null }
|
|
56
|
+
let response: Response
|
|
57
|
+
try {
|
|
58
|
+
response = await fetchImpl(sessionEndpoint, {
|
|
59
|
+
method: 'GET',
|
|
60
|
+
credentials: 'same-origin',
|
|
61
|
+
cache: 'no-store',
|
|
62
|
+
headers: { accept: 'application/json' },
|
|
63
|
+
})
|
|
64
|
+
} catch {
|
|
65
|
+
return empty
|
|
66
|
+
}
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
return empty
|
|
69
|
+
}
|
|
70
|
+
let body: unknown
|
|
71
|
+
try {
|
|
72
|
+
body = await response.json()
|
|
73
|
+
} catch {
|
|
74
|
+
return empty
|
|
75
|
+
}
|
|
76
|
+
if (!isRecord(body)) {
|
|
77
|
+
return empty
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
user: parseSessionUser(body.user),
|
|
81
|
+
guest: body.user === null ? parseSessionGuest(body.guest) : null,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
35
85
|
/**
|
|
36
86
|
* Revoke the current session through the plugin's session endpoint.
|
|
37
87
|
* Sends the CSRF header the endpoint requires. Returns `true` when the
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"components.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/components.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,163 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"components.test.js","sourceRoot":"","sources":["../../src/__tests__/components.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,177 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/__tests__/provider.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,70 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.test.js","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":"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"}
|