@cellarnode/auth 0.14.0 → 0.15.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 +72 -2
- package/dist/auth-api.d.ts.map +1 -1
- package/dist/auth-api.js +44 -5
- package/dist/auth-api.js.map +1 -1
- package/dist/auth-client.d.ts.map +1 -1
- package/dist/auth-client.js +61 -17
- package/dist/auth-client.js.map +1 -1
- package/dist/auth-store.d.ts +2 -2
- package/dist/auth-store.d.ts.map +1 -1
- package/dist/auth-store.js +491 -230
- package/dist/auth-store.js.map +1 -1
- package/dist/auth-transport.d.ts +5 -0
- package/dist/auth-transport.d.ts.map +1 -0
- package/dist/auth-transport.js +45 -0
- package/dist/auth-transport.js.map +1 -0
- package/dist/auth-user.d.ts +7 -0
- package/dist/auth-user.d.ts.map +1 -0
- package/dist/auth-user.js +73 -0
- package/dist/auth-user.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/react/login-form.js +1 -1
- package/dist/react/login-form.js.map +1 -1
- package/dist/session-continuity.d.ts +13 -0
- package/dist/session-continuity.d.ts.map +1 -0
- package/dist/session-continuity.js +107 -0
- package/dist/session-continuity.js.map +1 -0
- package/dist/types.d.ts +67 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,14 @@ npm install @cellarnode/auth
|
|
|
13
13
|
### Core (framework-agnostic)
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
canReplaySession,
|
|
18
|
+
captureSessionContinuity,
|
|
19
|
+
createAuthApi,
|
|
20
|
+
createAuthClient,
|
|
21
|
+
createAuthStore,
|
|
22
|
+
resolveSessionForReplay,
|
|
23
|
+
} from "@cellarnode/auth";
|
|
17
24
|
|
|
18
25
|
const authStore = createAuthStore({
|
|
19
26
|
baseUrl: "http://localhost:4000",
|
|
@@ -28,6 +35,59 @@ const authClient = createAuthClient({
|
|
|
28
35
|
const authApi = createAuthApi({ client: authClient, store: authStore });
|
|
29
36
|
```
|
|
30
37
|
|
|
38
|
+
All package-owned requests require HTTPS. HTTP works automatically only for
|
|
39
|
+
exact loopback hosts `localhost`, `127.0.0.1`, and `[::1]` during local
|
|
40
|
+
development. Invalid URLs, userinfo, other schemes, non-loopback HTTP, and
|
|
41
|
+
request paths that escape the configured origin or base path fail before
|
|
42
|
+
network transport. Redirects are rejected so credentials cannot follow a
|
|
43
|
+
cross-origin or HTTPS-to-HTTP redirect.
|
|
44
|
+
|
|
45
|
+
Resolve token and authoritative identity together before enabling protected
|
|
46
|
+
work. `unavailable` preserves credentials while authority getters fail closed.
|
|
47
|
+
Caller abort returns `superseded` without cancelling shared adoption.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const session = await authStore.resolveSession({ refresh: true, signal });
|
|
51
|
+
if (session.status === "ready") {
|
|
52
|
+
// session.token and validated session.user are from one guarded generation
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`ensureAccessToken()` remains a legacy credential-only wrapper. It may return a
|
|
57
|
+
retained token while authority is transiently `unavailable`; authority getters
|
|
58
|
+
still fail closed. Never use its token as proof that user or organisation
|
|
59
|
+
authority is ready. Use `resolveSession()` and require `status === "ready"` for
|
|
60
|
+
authorization decisions.
|
|
61
|
+
|
|
62
|
+
`onSessionStateChange` immediately reports current state. A synchronous
|
|
63
|
+
`resolving` notification precedes credential or authority changes, allowing
|
|
64
|
+
consumers to suspend writes and clear captured tenant queries first.
|
|
65
|
+
`getSessionState()` returns the same guarded state as a defensive snapshot;
|
|
66
|
+
continuity capture requires that snapshot to remain `ready`. Internal refresh
|
|
67
|
+
and identity requests time out after 10 seconds by default; configure
|
|
68
|
+
`resolutionTimeoutMs` on `createAuthStore` when needed.
|
|
69
|
+
|
|
70
|
+
Authenticated transports that retry after 401 must capture continuity before
|
|
71
|
+
their first request and replay only for same validated user and organisation:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const before = captureSessionContinuity(authStore);
|
|
75
|
+
if (!before) throw new Error("Session authority unavailable");
|
|
76
|
+
const response = await fetch(url, init);
|
|
77
|
+
if (response.status === 401) {
|
|
78
|
+
const after = await resolveSessionForReplay(authStore, before, {
|
|
79
|
+
signal: init.signal,
|
|
80
|
+
});
|
|
81
|
+
if (canReplaySession(before, after, authStore)) {
|
|
82
|
+
// retry once with after.token
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`resolveSessionForReplay` centralizes stale-401 handling. It never refreshes a
|
|
88
|
+
replacement session. A ready rotated token can be reused once only when
|
|
89
|
+
validated user and organisation still match captured request authority.
|
|
90
|
+
|
|
31
91
|
### React Components
|
|
32
92
|
|
|
33
93
|
```tsx
|
|
@@ -84,7 +144,7 @@ Add this to your CSS file so Tailwind picks up utility classes from the package:
|
|
|
84
144
|
|
|
85
145
|
## Exports
|
|
86
146
|
|
|
87
|
-
- `@cellarnode/auth` — Core: `createAuthStore`, `createAuthClient`, `createAuthApi`, `validateUserType`, `hasEntitlement`, `extractAccessToken`, types
|
|
147
|
+
- `@cellarnode/auth` — Core: `createAuthStore`, `createAuthClient`, `createAuthApi`, `captureSessionContinuity`, `resolveSessionForReplay`, `canReplaySession`, `validateUserType`, `hasEntitlement`, `extractAccessToken`, `AuthError`, session-resolution types, and `DevLoginResult`
|
|
88
148
|
- `@cellarnode/auth/react` — React: `LoginForm`, `RegisterForm`, `UnauthorizedPage`, `SquircleShift`, `InputOTP` (+ `Group` / `Slot` / `Separator`)
|
|
89
149
|
|
|
90
150
|
`DevSignInBypass`, `DEV_LOGIN_EMAIL_STORAGE_KEY`, `readDevLoginEmail` and
|
|
@@ -93,6 +153,16 @@ Add this to your CSS file so Tailwind picks up utility classes from the package:
|
|
|
93
153
|
and an exported symbol carries no gate — a consumer importing it could render the
|
|
94
154
|
bypass UI, or write a sign-in address to `localStorage`, from a production build.
|
|
95
155
|
|
|
156
|
+
## Publishing
|
|
157
|
+
|
|
158
|
+
Published to npm (public access) via **npm Trusted Publishing (OIDC)** —
|
|
159
|
+
`.github/workflows/publish.yml` publishes automatically on a merge to `main` that changes
|
|
160
|
+
`package.json`'s version. There is no long-lived npm token in the publish job, and nobody runs
|
|
161
|
+
`npm publish` by hand. Provenance is generated automatically (public repo + public package).
|
|
162
|
+
|
|
163
|
+
Bump `version` in `package.json` and land it through a normal PR — merging to `main` is what
|
|
164
|
+
triggers the publish job, gated on the version having actually changed.
|
|
165
|
+
|
|
96
166
|
## License
|
|
97
167
|
|
|
98
168
|
MIT
|
package/dist/auth-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-api.d.ts","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,SAAS,
|
|
1
|
+
{"version":3,"file":"auth-api.d.ts","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,SAAS,EAIV,MAAM,YAAY,CAAC;AAIpB,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CAClB,GAAG,OAAO,CAoHV"}
|
package/dist/auth-api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthError } from "./types.js";
|
|
2
2
|
import { extractAccessToken } from "./extract-token.js";
|
|
3
|
+
import { parseAuthUser, parseVerifyOtpUser } from "./auth-user.js";
|
|
3
4
|
export function createAuthApi(config) {
|
|
4
5
|
const { client, store } = config;
|
|
5
6
|
return {
|
|
@@ -27,26 +28,64 @@ export function createAuthApi(config) {
|
|
|
27
28
|
if (!token) {
|
|
28
29
|
throw new AuthError(500, "TOKEN_EXTRACTION_FAILED", "No access token found in verify-otp response");
|
|
29
30
|
}
|
|
31
|
+
const user = parseVerifyOtpUser(raw.user);
|
|
32
|
+
if (!user) {
|
|
33
|
+
throw new AuthError(500, "OTP_USER_INVALID", "Invalid user in verify-otp response");
|
|
34
|
+
}
|
|
30
35
|
const expiresIn = typeof raw.expiresIn === "number" ? raw.expiresIn : 900;
|
|
31
36
|
store.setAccessToken(token, expiresIn);
|
|
32
37
|
return {
|
|
33
38
|
accessToken: token,
|
|
34
39
|
expiresIn,
|
|
35
|
-
user
|
|
40
|
+
user,
|
|
36
41
|
};
|
|
37
42
|
},
|
|
38
43
|
async logout() {
|
|
39
|
-
|
|
44
|
+
const token = store.getAccessToken();
|
|
45
|
+
await client.fetch("/auth/logout", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
skipAuth: true,
|
|
48
|
+
...(token
|
|
49
|
+
? { headers: { Authorization: `Bearer ${token}` } }
|
|
50
|
+
: {}),
|
|
51
|
+
});
|
|
40
52
|
},
|
|
41
53
|
async getMe(token) {
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
const currentToken = store.getAccessToken();
|
|
55
|
+
const explicitCurrentToken = token !== undefined && token === currentToken;
|
|
56
|
+
if (store.resolveSession && token === undefined) {
|
|
57
|
+
const resolution = await store.resolveSession();
|
|
58
|
+
if (resolution.status === "ready")
|
|
59
|
+
return resolution.user;
|
|
60
|
+
if (resolution.status === "unauthorized") {
|
|
61
|
+
throw new AuthError(401, "UNAUTHORIZED", "Session is unauthorized");
|
|
62
|
+
}
|
|
63
|
+
if (resolution.status === "superseded") {
|
|
64
|
+
throw new AuthError(409, "SESSION_SUPERSEDED", "Session was superseded");
|
|
65
|
+
}
|
|
66
|
+
throw new AuthError(503, "AUTHORITY_UNAVAILABLE", "Session identity is unavailable");
|
|
67
|
+
}
|
|
68
|
+
const raw = token !== undefined
|
|
69
|
+
? await client.fetch("/auth/me", {
|
|
44
70
|
method: "GET",
|
|
45
71
|
skipAuth: true,
|
|
46
72
|
headers: { Authorization: `Bearer ${token}` },
|
|
73
|
+
})
|
|
74
|
+
: await client.fetch("/auth/me", {
|
|
75
|
+
method: "GET",
|
|
76
|
+
skipAuth: true,
|
|
77
|
+
...(currentToken
|
|
78
|
+
? { headers: { Authorization: `Bearer ${currentToken}` } }
|
|
79
|
+
: {}),
|
|
47
80
|
});
|
|
81
|
+
if (explicitCurrentToken && store.getAccessToken() !== token) {
|
|
82
|
+
throw new AuthError(409, "SESSION_SUPERSEDED", "Session was superseded");
|
|
83
|
+
}
|
|
84
|
+
const user = parseAuthUser(raw);
|
|
85
|
+
if (!user) {
|
|
86
|
+
throw new AuthError(503, "AUTHORITY_UNAVAILABLE", "Session identity is unavailable");
|
|
48
87
|
}
|
|
49
|
-
return
|
|
88
|
+
return user;
|
|
50
89
|
},
|
|
51
90
|
};
|
|
52
91
|
}
|
package/dist/auth-api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-api.js","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-api.js","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAQlF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CA8JrE"}
|
package/dist/auth-client.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AuthError } from "./types.js";
|
|
2
|
+
import { canReplaySession, captureSessionContinuity, resolveSessionForReplay, } from "./session-continuity.js";
|
|
3
|
+
import { fetchAuthRequest } from "./auth-transport.js";
|
|
2
4
|
export function createAuthClient(config) {
|
|
3
5
|
const { baseUrl, store, onAuthFailure } = config;
|
|
4
6
|
async function parseErrorResponse(res) {
|
|
@@ -20,17 +22,32 @@ export function createAuthClient(config) {
|
|
|
20
22
|
async fetch(path, options) {
|
|
21
23
|
const { skipAuth, ...init } = options ?? {};
|
|
22
24
|
const headers = new Headers(init.headers);
|
|
25
|
+
const continuity = skipAuth ? null : captureSessionContinuity(store);
|
|
23
26
|
if (!skipAuth) {
|
|
24
27
|
const token = store.getAccessToken();
|
|
25
|
-
if (
|
|
26
|
-
|
|
28
|
+
if (!continuity) {
|
|
29
|
+
throw token
|
|
30
|
+
? new AuthError(503, "SESSION_UNAVAILABLE", "Session identity is unavailable")
|
|
31
|
+
: new AuthError(401, "UNAUTHORIZED", "Session is unauthorized");
|
|
27
32
|
}
|
|
33
|
+
headers.set("Authorization", `Bearer ${continuity.token}`);
|
|
28
34
|
}
|
|
29
35
|
if (!headers.has("Content-Type") && init.body) {
|
|
30
36
|
headers.set("Content-Type", "application/json");
|
|
31
37
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
38
|
+
const retryWithToken = async (token) => {
|
|
39
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
40
|
+
const retryRes = await fetchAuthRequest(baseUrl, path, {
|
|
41
|
+
...init,
|
|
42
|
+
headers,
|
|
43
|
+
credentials: "include",
|
|
44
|
+
});
|
|
45
|
+
if (retryRes.ok)
|
|
46
|
+
return (await retryRes.json());
|
|
47
|
+
const retryError = await parseErrorResponse(retryRes);
|
|
48
|
+
throw new AuthError(retryRes.status, retryError.code, retryError.error, retryError.remainingAttempts);
|
|
49
|
+
};
|
|
50
|
+
const res = await fetchAuthRequest(baseUrl, path, {
|
|
34
51
|
...init,
|
|
35
52
|
headers,
|
|
36
53
|
credentials: "include",
|
|
@@ -38,23 +55,50 @@ export function createAuthClient(config) {
|
|
|
38
55
|
if (res.ok) {
|
|
39
56
|
return (await res.json());
|
|
40
57
|
}
|
|
41
|
-
// 401:
|
|
58
|
+
// 401: refresh once. Replay only when validated user + tenant continuity
|
|
59
|
+
// matches the authority captured before original transport.
|
|
42
60
|
if (res.status === 401 && !skipAuth) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
if (!store.resolveSession) {
|
|
62
|
+
const current = captureSessionContinuity(store);
|
|
63
|
+
if (!continuity ||
|
|
64
|
+
!current ||
|
|
65
|
+
current.token !== continuity.token ||
|
|
66
|
+
current.userId !== continuity.userId ||
|
|
67
|
+
current.orgId !== continuity.orgId) {
|
|
68
|
+
throw new AuthError(409, "SESSION_SUPERSEDED", "Request session was superseded");
|
|
69
|
+
}
|
|
70
|
+
store.clearAccessToken();
|
|
71
|
+
if (store.getAccessToken() === null)
|
|
72
|
+
onAuthFailure?.();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const resolution = await resolveSessionForReplay(store, continuity, {
|
|
76
|
+
signal: init.signal ?? undefined,
|
|
50
77
|
});
|
|
51
|
-
if (
|
|
52
|
-
|
|
78
|
+
if (resolution.status === "ready" &&
|
|
79
|
+
canReplaySession(continuity, resolution, store)) {
|
|
80
|
+
return retryWithToken(resolution.token);
|
|
81
|
+
}
|
|
82
|
+
if (resolution.status === "unavailable") {
|
|
83
|
+
throw new AuthError(503, "SESSION_UNAVAILABLE", "Session identity is unavailable");
|
|
84
|
+
}
|
|
85
|
+
if (resolution.status === "superseded") {
|
|
86
|
+
if (init.signal?.aborted) {
|
|
87
|
+
throw new DOMException("Request aborted", "AbortError");
|
|
88
|
+
}
|
|
89
|
+
throw new AuthError(409, "SESSION_SUPERSEDED", "Request session was superseded");
|
|
90
|
+
}
|
|
91
|
+
if (resolution.status === "ready") {
|
|
92
|
+
const currentState = store.getSessionState?.();
|
|
93
|
+
if ((currentState?.status === "resolving" ||
|
|
94
|
+
currentState?.status === "unavailable") &&
|
|
95
|
+
currentState.token === resolution.token) {
|
|
96
|
+
throw new AuthError(503, "SESSION_UNAVAILABLE", "Session identity is unavailable");
|
|
97
|
+
}
|
|
98
|
+
throw new AuthError(409, "SESSION_CONTINUITY_CHANGED", "Request session authority changed");
|
|
53
99
|
}
|
|
100
|
+
onAuthFailure?.();
|
|
54
101
|
}
|
|
55
|
-
// Refresh failed or retry failed
|
|
56
|
-
store.clearAccessToken();
|
|
57
|
-
onAuthFailure?.();
|
|
58
102
|
}
|
|
59
103
|
const errBody = await parseErrorResponse(res);
|
|
60
104
|
throw new AuthError(res.status, errBody.code, errBody.error, errBody.remainingAttempts);
|
package/dist/auth-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-client.js","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-client.js","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"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"}
|
package/dist/auth-store.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function createAuthStore(config: AuthStoreConfig):
|
|
1
|
+
import type { AuthStoreConfig, ConcreteAuthStore } from "./types.js";
|
|
2
|
+
export declare function createAuthStore(config: AuthStoreConfig): ConcreteAuthStore;
|
|
3
3
|
//# sourceMappingURL=auth-store.d.ts.map
|
package/dist/auth-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-store.d.ts","sourceRoot":"","sources":["../src/auth-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-store.d.ts","sourceRoot":"","sources":["../src/auth-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,eAAe,EAEf,iBAAiB,EASlB,MAAM,YAAY,CAAC;AAsDpB,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAsvB1E"}
|