@estiva-app/identity 0.1.0 → 0.1.1
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/CHANGELOG.md +26 -0
- package/README.md +11 -4
- package/dist/client.d.ts +32 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +25 -19
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/client.ts +49 -18
- package/src/index.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @estiva-app/identity
|
|
2
2
|
|
|
3
|
+
## 0.1.1 — 2026-08-28
|
|
4
|
+
|
|
5
|
+
**No wire behaviour**, and no break: `pendingStore` is optional and defaults to
|
|
6
|
+
`storage`, so a consumer on `^0.1.0` needs no change. Additive, so a PATCH —
|
|
7
|
+
ADR 0002 §4b puts the break on MINOR within `0.x`.
|
|
8
|
+
|
|
9
|
+
- **`pendingStore`**, a second storage seam. Wiring Ship — the second consumer —
|
|
10
|
+
found that the package assumed **one** store because it was extracted from
|
|
11
|
+
Peek, which uses one. Ship uses two, with different lifetimes on purpose: its
|
|
12
|
+
*session* lives in `localStorage` so it outlives a tab, and its *in-flight PKCE
|
|
13
|
+
credentials* live in `sessionStorage` because the flow starts and ends in one
|
|
14
|
+
tab, which is also why the silent-attempt guard belongs there.
|
|
15
|
+
|
|
16
|
+
This is the exact "a library pulled from one app comes out shaped like that
|
|
17
|
+
app" failure SHA-4 names, found by the mechanism SHA-4 prescribes for finding
|
|
18
|
+
it. The ticket said wiring Ship was the point rather than a formality; it was
|
|
19
|
+
right, and this is what it caught.
|
|
20
|
+
|
|
21
|
+
`clearSession` now spans both stores — the token from `storage`, the shell
|
|
22
|
+
reason from `pendingStore` — while still leaving the verifier, state and
|
|
23
|
+
returnTo alone. PEEK-167's separation had to survive the split, or the split
|
|
24
|
+
would have reintroduced the bug the separation exists to prevent. A test
|
|
25
|
+
asserts exactly that, and a control asserts the single-store default still
|
|
26
|
+
behaves as Peek expects, so none of it can pass on a package that quietly
|
|
27
|
+
ignores the new parameter.
|
|
28
|
+
|
|
3
29
|
## 0.1.0 — 2026-08-27
|
|
4
30
|
|
|
5
31
|
First release. SHA-4, extracted during REW-2.
|
package/README.md
CHANGED
|
@@ -38,13 +38,20 @@ package come out Peek-shaped, which is the whole risk SHA-4 names.
|
|
|
38
38
|
| --- | --- | --- |
|
|
39
39
|
| `clientId` | `estiva-peek` | `estiva-ship` |
|
|
40
40
|
| `redirectUri` | a fixed `/auth/callback` | its current pathname — Ship routes on `location.hash` |
|
|
41
|
-
| `storage` | `sessionStorage`,
|
|
41
|
+
| `storage` — the session | `sessionStorage`, dies with the tab | `localStorage`, outlives a tab |
|
|
42
|
+
| `pendingStore` — in-flight PKCE credentials | defaults to `storage` | `sessionStorage`, because the flow starts and ends in one tab |
|
|
42
43
|
| `keyPrefix` | `peek.estivaId` | `ship.estiva-id` |
|
|
43
44
|
| `navigate` | the one genuinely untestable act, so it is observable | |
|
|
44
45
|
|
|
45
|
-
**Do not unify
|
|
46
|
-
and belong in `localStorage`; an access token does not. Different
|
|
47
|
-
different homes.
|
|
46
|
+
**Do not unify the two stores.** NIP-RS read-state slots (CRO-4) must survive a
|
|
47
|
+
restart and belong in `localStorage`; an access token does not. Different
|
|
48
|
+
lifetimes, different homes.
|
|
49
|
+
|
|
50
|
+
`pendingStore` exists because wiring the second consumer found the package
|
|
51
|
+
assuming one store — it was extracted from Peek, which uses one. That is the "a
|
|
52
|
+
library pulled from one app comes out shaped like that app" failure SHA-4 names,
|
|
53
|
+
caught by the mechanism SHA-4 prescribes. Worth knowing as evidence that the
|
|
54
|
+
second-consumer rule earns its keep rather than being ceremony.
|
|
48
55
|
|
|
49
56
|
An app instantiates once and re-exports its own names, so call sites do not churn:
|
|
50
57
|
|
package/dist/client.d.ts
CHANGED
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
* `app_credentials` with separate `redirectUris` and separate kind ceilings.
|
|
39
39
|
* - **`redirectUri`** — Peek registers a fixed `/auth/callback`; Ship routes on
|
|
40
40
|
* `location.hash` and registers its current pathname. Neither is more correct.
|
|
41
|
-
* - **`storage`**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* - **`storage`** and **`pendingStore`** — two stores, because Ship needs two.
|
|
42
|
+
* Peek keeps everything in `sessionStorage`, which dies with the tab and keeps
|
|
43
|
+
* a bearer token off a shared machine's disk. Ship keeps its *session* in
|
|
44
|
+
* `localStorage` so it outlives a tab, and its *in-flight PKCE credentials* in
|
|
45
|
+
* `sessionStorage` because the flow starts and ends in one tab. The package
|
|
46
|
+
* assumed one store until the second consumer was wired — see `pendingStore`.
|
|
47
|
+
* **Do not unify them**: NIP-RS slots (CRO-4) must survive a restart and belong
|
|
48
|
+
* in `localStorage`, while an access token does not.
|
|
46
49
|
* - **`keyPrefix`** — so two apps on one origin cannot read each other's keys.
|
|
47
50
|
* - **`navigate`** — the one genuinely untestable act. Injected so a test can
|
|
48
51
|
* observe where the flow *would* go.
|
|
@@ -68,8 +71,31 @@ export interface EstivaIdConfig {
|
|
|
68
71
|
* **exactly** — a trailing slash is a refused sign-in.
|
|
69
72
|
*/
|
|
70
73
|
redirectUri: () => string;
|
|
71
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Where the **session** lives — the token and nothing else.
|
|
76
|
+
*
|
|
77
|
+
* Peek uses `sessionStorage`: it dies with the tab, which keeps a bearer token
|
|
78
|
+
* off a shared machine's disk. Ship uses `localStorage` so a session survives
|
|
79
|
+
* a tab close. Returns null outside a browser.
|
|
80
|
+
*/
|
|
72
81
|
storage: () => KeyValueStore | null;
|
|
82
|
+
/**
|
|
83
|
+
* Where an **in-flight sign-in's** single-use credentials live — the verifier,
|
|
84
|
+
* the state, the returnTo, the shell reason and the silent-attempt guard.
|
|
85
|
+
*
|
|
86
|
+
* Defaults to {@link storage}, which is what Peek wants: one store for both.
|
|
87
|
+
*
|
|
88
|
+
* **Ship needs them separated, and that is why this exists.** Its session
|
|
89
|
+
* belongs in `localStorage` so it outlives a tab, while the PKCE credentials
|
|
90
|
+
* are per-tab by nature — the flow starts and ends in one tab, and the guard's
|
|
91
|
+
* whole point is that a genuinely new tab is entitled to a fresh attempt.
|
|
92
|
+
*
|
|
93
|
+
* This parameter was added by wiring the second consumer. The package had
|
|
94
|
+
* assumed one store because it was extracted from Peek, which uses one: the
|
|
95
|
+
* exact "comes out shaped like the app it came from" failure SHA-4 names, found
|
|
96
|
+
* by the mechanism SHA-4 prescribes for finding it.
|
|
97
|
+
*/
|
|
98
|
+
pendingStore?: () => KeyValueStore | null;
|
|
73
99
|
/** Namespaces this app's keys, e.g. `peek.estivaId`. */
|
|
74
100
|
keyPrefix: string;
|
|
75
101
|
/** Defaults to the runtime's `location.assign`-equivalent via `navigate`. */
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAyB,MAAM,YAAY,CAAA;AAuBxF,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACpE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAA;AAIvE,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IACnC;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IACzC,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,qBAAa,WAAY,SAAQ,KAAK;CAAG;AASzC,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACpD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChF,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACrF,kBAAkB,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACrD,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;IACrC,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAA;IAChD,UAAU,EAAE,MAAM,OAAO,CAAA;IACzB,kBAAkB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IAC5C,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IACjC,gBAAgB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;IAC/C,eAAe,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;CAC1C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAiXrE"}
|
package/dist/client.js
CHANGED
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
* `app_credentials` with separate `redirectUris` and separate kind ceilings.
|
|
39
39
|
* - **`redirectUri`** — Peek registers a fixed `/auth/callback`; Ship routes on
|
|
40
40
|
* `location.hash` and registers its current pathname. Neither is more correct.
|
|
41
|
-
* - **`storage`**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* - **`storage`** and **`pendingStore`** — two stores, because Ship needs two.
|
|
42
|
+
* Peek keeps everything in `sessionStorage`, which dies with the tab and keeps
|
|
43
|
+
* a bearer token off a shared machine's disk. Ship keeps its *session* in
|
|
44
|
+
* `localStorage` so it outlives a tab, and its *in-flight PKCE credentials* in
|
|
45
|
+
* `sessionStorage` because the flow starts and ends in one tab. The package
|
|
46
|
+
* assumed one store until the second consumer was wired — see `pendingStore`.
|
|
47
|
+
* **Do not unify them**: NIP-RS slots (CRO-4) must survive a restart and belong
|
|
48
|
+
* in `localStorage`, while an access token does not.
|
|
46
49
|
* - **`keyPrefix`** — so two apps on one origin cannot read each other's keys.
|
|
47
50
|
* - **`navigate`** — the one genuinely untestable act. Injected so a test can
|
|
48
51
|
* observe where the flow *would* go.
|
|
@@ -52,6 +55,8 @@ export class SignInError extends Error {
|
|
|
52
55
|
}
|
|
53
56
|
export function createEstivaId(config) {
|
|
54
57
|
const now = config.now ?? (() => Date.now());
|
|
58
|
+
/** In-flight credentials, which may live somewhere shorter than the session. */
|
|
59
|
+
const pending = config.pendingStore ?? config.storage;
|
|
55
60
|
const send = () => config.fetch ?? fetch;
|
|
56
61
|
const go = (url) => {
|
|
57
62
|
if (!config.navigate)
|
|
@@ -126,11 +131,11 @@ export function createEstivaId(config) {
|
|
|
126
131
|
* Anything that wants both says so, and only `beginSignOut` does.
|
|
127
132
|
*/
|
|
128
133
|
const clearSession = () => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
config.storage()?.removeItem(TOKEN_KEY);
|
|
135
|
+
// The reason lives with the in-flight credentials, which may be a different
|
|
136
|
+
// store — see `pendingStore`. Still cleared here: a reason outliving the
|
|
137
|
+
// session it described is a lie, and that was true before the split.
|
|
138
|
+
pending()?.removeItem(REASON_KEY);
|
|
134
139
|
};
|
|
135
140
|
/**
|
|
136
141
|
* Abandon a sign-in that has not come back.
|
|
@@ -141,18 +146,18 @@ export function createEstivaId(config) {
|
|
|
141
146
|
* different challenge. Destroying a live one is the expensive direction.
|
|
142
147
|
*/
|
|
143
148
|
const clearPendingSignIn = () => {
|
|
144
|
-
const s =
|
|
149
|
+
const s = pending();
|
|
145
150
|
if (!s)
|
|
146
151
|
return;
|
|
147
152
|
for (const k of [VERIFIER_KEY, STATE_KEY, RETURN_KEY])
|
|
148
153
|
s.removeItem(k);
|
|
149
154
|
};
|
|
150
155
|
const stashShellReason = (reason) => {
|
|
151
|
-
|
|
156
|
+
pending()?.setItem(REASON_KEY, reason);
|
|
152
157
|
};
|
|
153
158
|
/** Read once. Single-use: a stale reason outliving its cause is a lie. */
|
|
154
159
|
const takeShellReason = () => {
|
|
155
|
-
const s =
|
|
160
|
+
const s = pending();
|
|
156
161
|
const raw = s?.getItem(REASON_KEY);
|
|
157
162
|
if (raw)
|
|
158
163
|
s?.removeItem(REASON_KEY);
|
|
@@ -166,7 +171,7 @@ export function createEstivaId(config) {
|
|
|
166
171
|
* puts people on a default screen instead of the link they opened (PEEK-123).
|
|
167
172
|
*/
|
|
168
173
|
const takeReturnTo = () => {
|
|
169
|
-
const s =
|
|
174
|
+
const s = pending();
|
|
170
175
|
const raw = s?.getItem(RETURN_KEY);
|
|
171
176
|
if (raw)
|
|
172
177
|
s?.removeItem(RETURN_KEY);
|
|
@@ -202,7 +207,7 @@ export function createEstivaId(config) {
|
|
|
202
207
|
* code that just arrived belongs to anything this tab did.
|
|
203
208
|
*/
|
|
204
209
|
hasPendingSignIn: () => {
|
|
205
|
-
const s =
|
|
210
|
+
const s = pending();
|
|
206
211
|
return Boolean(s?.getItem(VERIFIER_KEY) && s.getItem(STATE_KEY));
|
|
207
212
|
},
|
|
208
213
|
/**
|
|
@@ -214,7 +219,7 @@ export function createEstivaId(config) {
|
|
|
214
219
|
* compared.
|
|
215
220
|
*/
|
|
216
221
|
async beginSignIn(returnTo, { silent = false } = {}) {
|
|
217
|
-
const s =
|
|
222
|
+
const s = pending();
|
|
218
223
|
if (!s)
|
|
219
224
|
throw new Error('sign-in requires a browser');
|
|
220
225
|
const verifier = randomUrlSafe();
|
|
@@ -268,8 +273,9 @@ export function createEstivaId(config) {
|
|
|
268
273
|
* on this side of the exchange.
|
|
269
274
|
*/
|
|
270
275
|
async completeSignIn(search) {
|
|
271
|
-
const s =
|
|
272
|
-
|
|
276
|
+
const s = pending();
|
|
277
|
+
const sessionStore = config.storage();
|
|
278
|
+
if (!s || !sessionStore)
|
|
273
279
|
throw new SignInError('sign-in requires a browser');
|
|
274
280
|
const params = new URLSearchParams(search);
|
|
275
281
|
const error = params.get('error');
|
|
@@ -316,7 +322,7 @@ export function createEstivaId(config) {
|
|
|
316
322
|
if (!body.access_token || !body.pubkey)
|
|
317
323
|
throw new SignInError('Estiva ID returned no token.');
|
|
318
324
|
const token = tokenFrom(body, body.pubkey);
|
|
319
|
-
|
|
325
|
+
sessionStore.setItem(TOKEN_KEY, JSON.stringify(token));
|
|
320
326
|
// The attempt worked, so the next boot is entitled to a fresh silent one.
|
|
321
327
|
clearGuard(s);
|
|
322
328
|
return { token, returnTo };
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* The thing that made it possible was not cleverness — it was that a browser API
|
|
47
47
|
* an app must inject is also a browser API a test can observe.
|
|
48
48
|
*/
|
|
49
|
-
export declare const IDENTITY_VERSION = "0.1.
|
|
49
|
+
export declare const IDENTITY_VERSION = "0.1.1";
|
|
50
50
|
export { type KeyValueStore, type ShellPhase, type ShellReason, type ShellState, type BootObservation, type BootAction, type ShellEvent, isRecoverable, SHELL_COPY, CONTINUE_LABEL, RETRY_LABEL, PROBE_TIMEOUT_MS, GUARD_TTL_MS, GUARD_KEY, decideBoot, reasonForCallbackError, readGuard, markGuard, clearGuard, logShellEvent, } from './shell.js';
|
|
51
51
|
export { type EstivaIdConfig, type EstivaIdClient, type StoredToken, type FetchLike, SignInError, createEstivaId, } from './client.js';
|
|
52
52
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* The thing that made it possible was not cleverness — it was that a browser API
|
|
47
47
|
* an app must inject is also a browser API a test can observe.
|
|
48
48
|
*/
|
|
49
|
-
export const IDENTITY_VERSION = '0.1.
|
|
49
|
+
export const IDENTITY_VERSION = '0.1.1';
|
|
50
50
|
export { isRecoverable, SHELL_COPY, CONTINUE_LABEL, RETRY_LABEL, PROBE_TIMEOUT_MS, GUARD_TTL_MS, GUARD_KEY, decideBoot, reasonForCallbackError, readGuard, markGuard, clearGuard, logShellEvent, } from './shell.js';
|
|
51
51
|
export { SignInError, createEstivaId, } from './client.js';
|
|
52
52
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estiva-app/identity",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Estiva ID sign-in: authorization code + PKCE, token storage, silent renewal, sign-out, and the auth-shell state machine. Knows nothing about any app's data layer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/client.ts
CHANGED
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
* `app_credentials` with separate `redirectUris` and separate kind ceilings.
|
|
39
39
|
* - **`redirectUri`** — Peek registers a fixed `/auth/callback`; Ship routes on
|
|
40
40
|
* `location.hash` and registers its current pathname. Neither is more correct.
|
|
41
|
-
* - **`storage`**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* - **`storage`** and **`pendingStore`** — two stores, because Ship needs two.
|
|
42
|
+
* Peek keeps everything in `sessionStorage`, which dies with the tab and keeps
|
|
43
|
+
* a bearer token off a shared machine's disk. Ship keeps its *session* in
|
|
44
|
+
* `localStorage` so it outlives a tab, and its *in-flight PKCE credentials* in
|
|
45
|
+
* `sessionStorage` because the flow starts and ends in one tab. The package
|
|
46
|
+
* assumed one store until the second consumer was wired — see `pendingStore`.
|
|
47
|
+
* **Do not unify them**: NIP-RS slots (CRO-4) must survive a restart and belong
|
|
48
|
+
* in `localStorage`, while an access token does not.
|
|
46
49
|
* - **`keyPrefix`** — so two apps on one origin cannot read each other's keys.
|
|
47
50
|
* - **`navigate`** — the one genuinely untestable act. Injected so a test can
|
|
48
51
|
* observe where the flow *would* go.
|
|
@@ -88,8 +91,31 @@ export interface EstivaIdConfig {
|
|
|
88
91
|
* **exactly** — a trailing slash is a refused sign-in.
|
|
89
92
|
*/
|
|
90
93
|
redirectUri: () => string
|
|
91
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Where the **session** lives — the token and nothing else.
|
|
96
|
+
*
|
|
97
|
+
* Peek uses `sessionStorage`: it dies with the tab, which keeps a bearer token
|
|
98
|
+
* off a shared machine's disk. Ship uses `localStorage` so a session survives
|
|
99
|
+
* a tab close. Returns null outside a browser.
|
|
100
|
+
*/
|
|
92
101
|
storage: () => KeyValueStore | null
|
|
102
|
+
/**
|
|
103
|
+
* Where an **in-flight sign-in's** single-use credentials live — the verifier,
|
|
104
|
+
* the state, the returnTo, the shell reason and the silent-attempt guard.
|
|
105
|
+
*
|
|
106
|
+
* Defaults to {@link storage}, which is what Peek wants: one store for both.
|
|
107
|
+
*
|
|
108
|
+
* **Ship needs them separated, and that is why this exists.** Its session
|
|
109
|
+
* belongs in `localStorage` so it outlives a tab, while the PKCE credentials
|
|
110
|
+
* are per-tab by nature — the flow starts and ends in one tab, and the guard's
|
|
111
|
+
* whole point is that a genuinely new tab is entitled to a fresh attempt.
|
|
112
|
+
*
|
|
113
|
+
* This parameter was added by wiring the second consumer. The package had
|
|
114
|
+
* assumed one store because it was extracted from Peek, which uses one: the
|
|
115
|
+
* exact "comes out shaped like the app it came from" failure SHA-4 names, found
|
|
116
|
+
* by the mechanism SHA-4 prescribes for finding it.
|
|
117
|
+
*/
|
|
118
|
+
pendingStore?: () => KeyValueStore | null
|
|
93
119
|
/** Namespaces this app's keys, e.g. `peek.estivaId`. */
|
|
94
120
|
keyPrefix: string
|
|
95
121
|
/** Defaults to the runtime's `location.assign`-equivalent via `navigate`. */
|
|
@@ -147,6 +173,8 @@ export interface EstivaIdClient {
|
|
|
147
173
|
|
|
148
174
|
export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
149
175
|
const now = config.now ?? (() => Date.now())
|
|
176
|
+
/** In-flight credentials, which may live somewhere shorter than the session. */
|
|
177
|
+
const pending = config.pendingStore ?? config.storage
|
|
150
178
|
const send = () => config.fetch ?? fetch
|
|
151
179
|
const go = (url: string) => {
|
|
152
180
|
if (!config.navigate) throw new Error('createEstivaId: navigate is required to leave for Estiva ID')
|
|
@@ -225,9 +253,11 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
225
253
|
* Anything that wants both says so, and only `beginSignOut` does.
|
|
226
254
|
*/
|
|
227
255
|
const clearSession = (): void => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
256
|
+
config.storage()?.removeItem(TOKEN_KEY)
|
|
257
|
+
// The reason lives with the in-flight credentials, which may be a different
|
|
258
|
+
// store — see `pendingStore`. Still cleared here: a reason outliving the
|
|
259
|
+
// session it described is a lie, and that was true before the split.
|
|
260
|
+
pending()?.removeItem(REASON_KEY)
|
|
231
261
|
}
|
|
232
262
|
|
|
233
263
|
/**
|
|
@@ -239,18 +269,18 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
239
269
|
* different challenge. Destroying a live one is the expensive direction.
|
|
240
270
|
*/
|
|
241
271
|
const clearPendingSignIn = (): void => {
|
|
242
|
-
const s =
|
|
272
|
+
const s = pending()
|
|
243
273
|
if (!s) return
|
|
244
274
|
for (const k of [VERIFIER_KEY, STATE_KEY, RETURN_KEY]) s.removeItem(k)
|
|
245
275
|
}
|
|
246
276
|
|
|
247
277
|
const stashShellReason = (reason: ShellReason): void => {
|
|
248
|
-
|
|
278
|
+
pending()?.setItem(REASON_KEY, reason)
|
|
249
279
|
}
|
|
250
280
|
|
|
251
281
|
/** Read once. Single-use: a stale reason outliving its cause is a lie. */
|
|
252
282
|
const takeShellReason = (): ShellReason | null => {
|
|
253
|
-
const s =
|
|
283
|
+
const s = pending()
|
|
254
284
|
const raw = s?.getItem(REASON_KEY)
|
|
255
285
|
if (raw) s?.removeItem(REASON_KEY)
|
|
256
286
|
return (raw as ShellReason | null) ?? null
|
|
@@ -264,7 +294,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
264
294
|
* puts people on a default screen instead of the link they opened (PEEK-123).
|
|
265
295
|
*/
|
|
266
296
|
const takeReturnTo = (): string | null => {
|
|
267
|
-
const s =
|
|
297
|
+
const s = pending()
|
|
268
298
|
const raw = s?.getItem(RETURN_KEY)
|
|
269
299
|
if (raw) s?.removeItem(RETURN_KEY)
|
|
270
300
|
return raw ?? null
|
|
@@ -303,7 +333,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
303
333
|
* code that just arrived belongs to anything this tab did.
|
|
304
334
|
*/
|
|
305
335
|
hasPendingSignIn: () => {
|
|
306
|
-
const s =
|
|
336
|
+
const s = pending()
|
|
307
337
|
return Boolean(s?.getItem(VERIFIER_KEY) && s.getItem(STATE_KEY))
|
|
308
338
|
},
|
|
309
339
|
|
|
@@ -316,7 +346,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
316
346
|
* compared.
|
|
317
347
|
*/
|
|
318
348
|
async beginSignIn(returnTo: string, { silent = false }: { silent?: boolean } = {}): Promise<void> {
|
|
319
|
-
const s =
|
|
349
|
+
const s = pending()
|
|
320
350
|
if (!s) throw new Error('sign-in requires a browser')
|
|
321
351
|
|
|
322
352
|
const verifier = randomUrlSafe()
|
|
@@ -373,8 +403,9 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
373
403
|
* on this side of the exchange.
|
|
374
404
|
*/
|
|
375
405
|
async completeSignIn(search: string): Promise<{ token: StoredToken; returnTo: string }> {
|
|
376
|
-
const s =
|
|
377
|
-
|
|
406
|
+
const s = pending()
|
|
407
|
+
const sessionStore = config.storage()
|
|
408
|
+
if (!s || !sessionStore) throw new SignInError('sign-in requires a browser')
|
|
378
409
|
|
|
379
410
|
const params = new URLSearchParams(search)
|
|
380
411
|
const error = params.get('error')
|
|
@@ -427,7 +458,7 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
427
458
|
if (!body.access_token || !body.pubkey) throw new SignInError('Estiva ID returned no token.')
|
|
428
459
|
|
|
429
460
|
const token = tokenFrom(body, body.pubkey)
|
|
430
|
-
|
|
461
|
+
sessionStore.setItem(TOKEN_KEY, JSON.stringify(token))
|
|
431
462
|
// The attempt worked, so the next boot is entitled to a fresh silent one.
|
|
432
463
|
clearGuard(s)
|
|
433
464
|
return { token, returnTo }
|
package/src/index.ts
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* The thing that made it possible was not cleverness — it was that a browser API
|
|
47
47
|
* an app must inject is also a browser API a test can observe.
|
|
48
48
|
*/
|
|
49
|
-
export const IDENTITY_VERSION = '0.1.
|
|
49
|
+
export const IDENTITY_VERSION = '0.1.1'
|
|
50
50
|
|
|
51
51
|
export {
|
|
52
52
|
type KeyValueStore,
|