@estiva-app/identity 0.1.2 → 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/CHANGELOG.md +101 -0
- package/README.md +29 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -6
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/signer.d.ts +135 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +114 -0
- package/dist/signer.js.map +1 -0
- package/package.json +9 -3
- package/src/client.ts +57 -6
- package/src/index.ts +10 -1
- package/src/signer.ts +231 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
# @estiva-app/identity
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-09-07
|
|
4
|
+
|
|
5
|
+
**MINOR because it adds a peer dependency**, which is a break for a consumer
|
|
6
|
+
that does not already have `@estiva-app/protocol` — ADR 0002 §4b puts the break
|
|
7
|
+
on MINOR within `0.x`. Both consumers already have it, and per §5 both had an
|
|
8
|
+
open upgrade PR before this published.
|
|
9
|
+
|
|
10
|
+
**`POST /sign` lives here now.** SHA-3 left `estivaIdSigner` behind in the apps
|
|
11
|
+
deliberately — "it knows about Estiva ID, which is this ticket" — and SHA-4's
|
|
12
|
+
handoff comment called it "the one thing left duplicated between ship and
|
|
13
|
+
estiva-agent, byte-identical, and it is yours to remove". This is that.
|
|
14
|
+
|
|
15
|
+
- **New: `signViaEstivaId(unsigned, { base, token, expectedPubkey })`** — one
|
|
16
|
+
round trip. What Peek uses, because Peek signs per request with a token it
|
|
17
|
+
reads fresh each time.
|
|
18
|
+
|
|
19
|
+
- **New: `estivaIdSigner({ base, pubkey, token, renew })`** — a `Signer` over the
|
|
20
|
+
same round trip, holding a live bearer and renewing once on a `401`. What Ship
|
|
21
|
+
uses, because Ship builds one signer at module scope for the life of the page.
|
|
22
|
+
Neither shape can be expressed as the other without one of them getting worse,
|
|
23
|
+
so both are here over one implementation.
|
|
24
|
+
|
|
25
|
+
- **`SignerTokenExpired`** travels with them: "obtain a new token" and "this
|
|
26
|
+
event was refused" call for different things from the caller, and the status
|
|
27
|
+
code is the only thing that separates them.
|
|
28
|
+
|
|
29
|
+
### The guard the three copies had already drifted on
|
|
30
|
+
|
|
31
|
+
`/sign` **signs as the token's subject regardless of what it is handed.** So a
|
|
32
|
+
token that belongs to somebody else does not produce an error — it produces
|
|
33
|
+
**HTTP 200 carrying a valid event authored by that other person**, and the
|
|
34
|
+
symptom arrives much later as a comment attributed to a colleague, with nothing
|
|
35
|
+
in any log tying it back.
|
|
36
|
+
|
|
37
|
+
SHA-4's Traps section says both apps must keep the `expectedPubkey` guard. **Only
|
|
38
|
+
Peek's did.** Ship's `estivaIdSigner` — the signer every issue, comment, status
|
|
39
|
+
change and assignment in Ship goes through — never had a pubkey comparison at
|
|
40
|
+
all. That is precisely the drift three copies of one call produce, and the app
|
|
41
|
+
that could least afford it was the one missing it.
|
|
42
|
+
|
|
43
|
+
Here the guard is not optional on the `estivaIdSigner` path: it is supplied from
|
|
44
|
+
the `pubkey` the signer was built with, whether or not the caller thought about
|
|
45
|
+
it. Two tests cover it and a control asserts a wrong-author refusal does **not**
|
|
46
|
+
trigger a renewal — spending a single-use refresh token on an error a new token
|
|
47
|
+
cannot fix is how one bad signature becomes a revoked chain.
|
|
48
|
+
|
|
49
|
+
### What stayed in the apps, and why
|
|
50
|
+
|
|
51
|
+
`localSigner` (a per-browser anonymous key) and `nip07Signer` (a browser
|
|
52
|
+
extension) are still duplicated between `ship` and `estiva-agent`, held by
|
|
53
|
+
`scripts/conformance.test.ts`. Neither knows anything about Estiva ID, which is
|
|
54
|
+
the line this package draws, so neither moved. Worth its own ticket rather than
|
|
55
|
+
a quiet widening of this one.
|
|
56
|
+
|
|
57
|
+
### The seam property still holds
|
|
58
|
+
|
|
59
|
+
`@estiva-app/protocol` is a **peer** dependency, following `@estiva-app/interop`.
|
|
60
|
+
Nothing here calls a protocol function — only its types are used — so the
|
|
61
|
+
complete list of imports in the shipped JavaScript is still `./client.js`,
|
|
62
|
+
`./shell.js` and now `./signer.js`. Only the `.d.ts` names protocol.
|
|
63
|
+
|
|
64
|
+
## 0.1.3 — 2026-09-07
|
|
65
|
+
|
|
66
|
+
**Fixes a dead end reported from production.** No API change; `expiresAt` is now
|
|
67
|
+
derived differently, so a consumer needs no code change to get the fix.
|
|
68
|
+
|
|
69
|
+
`expires_in` is a **duration**, so acting on it means adding it to the local
|
|
70
|
+
clock. The service decides with the token's own `exp` claim, against **its**
|
|
71
|
+
clock. Those two answers agree only for as long as the clocks do.
|
|
72
|
+
|
|
73
|
+
When they diverge in the unsafe direction — a browser clock ahead of the
|
|
74
|
+
service, which is what a machine waking from a night's sleep readily produces —
|
|
75
|
+
the app holds a token it believes is live and every server refuses. And it does
|
|
76
|
+
not merely retry badly, it cannot recover at all:
|
|
77
|
+
|
|
78
|
+
- `POST /sign` answers `401 Invalid token: "exp" claim timestamp check failed`.
|
|
79
|
+
- `validToken()` keeps returning that same token, because by its own arithmetic
|
|
80
|
+
the token is fine.
|
|
81
|
+
- So every caller that asks gets the dead token again — including the one Convex
|
|
82
|
+
asks — and nothing ever renews.
|
|
83
|
+
- Peek's shell then sees a live token plus an unauthenticated verdict and calls
|
|
84
|
+
it `token_rejected`, whose copy says a configuration problem that signing in
|
|
85
|
+
again will not fix. It was an expiry, and signing in again was exactly what
|
|
86
|
+
fixed it. Clearing site data was the only way out.
|
|
87
|
+
|
|
88
|
+
**Both bounds are now computed and the earlier wins.** `exp` is what the service
|
|
89
|
+
will actually check, so it is the one that matters; `expires_in` is kept because
|
|
90
|
+
it covers a token with no readable `exp`, and a clock running *behind* the
|
|
91
|
+
service's, where `exp` alone would be the more generous of the two. The 30s
|
|
92
|
+
hold-back applies to whichever wins.
|
|
93
|
+
|
|
94
|
+
Reading the claim is not verification — that is the resource server's job and it
|
|
95
|
+
holds the key. This reads one number out of a payload the app already has, so
|
|
96
|
+
that "is this token still good" is answered by the same fact the service will
|
|
97
|
+
answer it with. A payload that will not parse yields no bound and the token is
|
|
98
|
+
still held: a token this cannot read is still a token.
|
|
99
|
+
|
|
100
|
+
5 new tests, 59 to 64, including two controls that the `expires_in` fallback is
|
|
101
|
+
untouched. Reverting the clamp fails exactly the two tests that depend on it.
|
|
102
|
+
|
|
103
|
+
|
|
3
104
|
## 0.1.2 — 2026-09-07
|
|
4
105
|
|
|
5
106
|
**No wire behaviour, no break.** `scheduleRenewal` is new and the guard on
|
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ renewal, sign-out, and the auth-shell state machine.
|
|
|
7
7
|
npm install @estiva-app/identity
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Nothing from outside the `@estiva-app` foundation, no data layer, and no UI.
|
|
11
|
+
CI enforces all three.
|
|
11
12
|
|
|
12
13
|
## The seam this package exists for
|
|
13
14
|
|
|
@@ -68,6 +69,33 @@ const client = createEstivaId({
|
|
|
68
69
|
export const { validToken, beginSignIn, completeSignIn, beginSignOut } = client
|
|
69
70
|
```
|
|
70
71
|
|
|
72
|
+
## Signing is asking, and the answer must be checked
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// Peek: one round trip, a token read fresh each time
|
|
76
|
+
const event = await signViaEstivaId(unsigned, { base, token, expectedPubkey: myPubkey })
|
|
77
|
+
|
|
78
|
+
// Ship: a protocol Signer, built once, renewing itself
|
|
79
|
+
const signer = estivaIdSigner({ base, pubkey, token, renew: client.refreshAccessToken })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
An app publishes as a person without ever seeing their secret. `@estiva-app/protocol`
|
|
83
|
+
owns how an event gets its signature; this package owns *who is allowed to ask*.
|
|
84
|
+
|
|
85
|
+
**`/sign` signs as the token's subject regardless of what it is handed.** So a
|
|
86
|
+
token belonging to somebody else is not an error — it is **HTTP 200 with a valid
|
|
87
|
+
event authored by that other person**. `expectedPubkey` is a check, not a
|
|
88
|
+
request, and `estivaIdSigner` supplies it unconditionally from the pubkey it was
|
|
89
|
+
built with. There were three copies of this round trip before SHA-4 and only one
|
|
90
|
+
of them checked; the one that did not was Ship's, through which every write in
|
|
91
|
+
Ship passes.
|
|
92
|
+
|
|
93
|
+
A `401` throws `SignerTokenExpired` rather than a generic failure, and
|
|
94
|
+
`estivaIdSigner` renews **once** and retries. Never twice: a second `401` after
|
|
95
|
+
a successful renewal is a token the service will not accept, and retrying that
|
|
96
|
+
is how one refused signature becomes a hot loop. Anything that is not an expiry
|
|
97
|
+
is rethrown untouched, so a wrong-author refusal never spends a refresh token.
|
|
98
|
+
|
|
71
99
|
## Renewal is scheduled, not reactive
|
|
72
100
|
|
|
73
101
|
```ts
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -86,18 +86,68 @@ export function createEstivaId(config) {
|
|
|
86
86
|
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
|
|
87
87
|
return base64Url(new Uint8Array(digest));
|
|
88
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* The `exp` claim, in epoch ms, or null if the token does not carry a readable one.
|
|
91
|
+
*
|
|
92
|
+
* Not verification — that is the resource server's job and it holds the key.
|
|
93
|
+
* This reads one number out of a payload the app already holds, for one
|
|
94
|
+
* purpose: so that "is this token still good" is answered by the same fact the
|
|
95
|
+
* service will answer it with.
|
|
96
|
+
*
|
|
97
|
+
* Parse failures return null rather than throwing. A token this cannot read is
|
|
98
|
+
* still a token, and refusing to hold one because its middle segment is not
|
|
99
|
+
* base64url would be a worse failure than the one this prevents.
|
|
100
|
+
*/
|
|
101
|
+
const expFromJwt = (accessToken) => {
|
|
102
|
+
const payload = accessToken.split('.')[1];
|
|
103
|
+
if (!payload)
|
|
104
|
+
return null;
|
|
105
|
+
try {
|
|
106
|
+
const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
107
|
+
const exp = JSON.parse(json).exp;
|
|
108
|
+
return typeof exp === 'number' && Number.isFinite(exp) ? exp * 1000 : null;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
89
114
|
/**
|
|
90
115
|
* Shape a token response into what is kept.
|
|
91
116
|
*
|
|
92
117
|
* The expiry is deliberately pessimistic by 30s, so a token is never presented
|
|
93
118
|
* in the window where it is technically alive and about to not be.
|
|
119
|
+
*
|
|
120
|
+
* ## Why `exp` and not just `expires_in`
|
|
121
|
+
*
|
|
122
|
+
* `expires_in` describes a *duration*, so acting on it means adding it to the
|
|
123
|
+
* local clock — and the service decides using the token's own `exp` against
|
|
124
|
+
* *its* clock. Those two answers agree only while the clocks do.
|
|
125
|
+
*
|
|
126
|
+
* When they diverge in the unsafe direction the app believes a dead token is
|
|
127
|
+
* live, and the failure is genuinely nasty: `POST /sign` answers
|
|
128
|
+
* `401 Invalid token: "exp" claim timestamp check failed`, while `validToken`
|
|
129
|
+
* keeps returning that same token to every caller that asks — including the
|
|
130
|
+
* one Convex asks — so nothing ever renews and nothing ever recovers. Peek got
|
|
131
|
+
* into exactly this state overnight on a machine that had slept, and the only
|
|
132
|
+
* way out was clearing site data and signing in again.
|
|
133
|
+
*
|
|
134
|
+
* So both bounds are computed and **the earlier wins**. `exp` is what the
|
|
135
|
+
* service will actually check; `expires_in` still covers a token with no
|
|
136
|
+
* readable `exp`, and a clock that is *behind* the service's, where `exp`
|
|
137
|
+
* alone would be the more generous of the two.
|
|
94
138
|
*/
|
|
95
|
-
const tokenFrom = (body, pubkey) =>
|
|
96
|
-
accessToken
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
139
|
+
const tokenFrom = (body, pubkey) => {
|
|
140
|
+
const accessToken = body.access_token;
|
|
141
|
+
const fromDuration = now() + Math.max(0, (body.expires_in ?? 600) - 30) * 1000;
|
|
142
|
+
const exp = expFromJwt(accessToken);
|
|
143
|
+
const fromClaim = exp === null ? null : exp - 30_000;
|
|
144
|
+
return {
|
|
145
|
+
accessToken,
|
|
146
|
+
expiresAt: fromClaim === null ? fromDuration : Math.min(fromDuration, fromClaim),
|
|
147
|
+
pubkey,
|
|
148
|
+
...(body.refresh_token ? { refreshToken: body.refresh_token } : {}),
|
|
149
|
+
};
|
|
150
|
+
};
|
|
101
151
|
const storedToken = () => {
|
|
102
152
|
const raw = config.storage()?.getItem(TOKEN_KEY);
|
|
103
153
|
if (!raw)
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
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,8 @@
|
|
|
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.
|
|
49
|
+
export declare const IDENTITY_VERSION = "0.2.0";
|
|
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, type RenewalHandlers, type RenewalSchedule, SignInError, createEstivaId, } from './client.js';
|
|
52
|
+
export { type SignViaEstivaIdOptions, type EstivaIdSignerOptions, type SignFetchLike, SignerTokenExpired, signViaEstivaId, estivaIdSigner, } from './signer.js';
|
|
52
53
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,kBAAkB,EAClB,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,8 @@
|
|
|
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.
|
|
49
|
+
export const IDENTITY_VERSION = '0.2.0';
|
|
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
|
+
export { SignerTokenExpired, signViaEstivaId, estivaIdSigner, } from './signer.js';
|
|
52
53
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC,OAAO,EAQL,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,OAAO,EAOL,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC,OAAO,EAQL,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,UAAU,EACV,sBAAsB,EACtB,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,OAAO,EAOL,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EAIL,kBAAkB,EAClB,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAA"}
|
package/dist/signer.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signing by asking Estiva ID, holding no key at all.
|
|
3
|
+
*
|
|
4
|
+
* The point of the whole seam: an app publishes as a person without ever seeing
|
|
5
|
+
* their secret. `POST /sign` takes a bearer token and an unsigned event and
|
|
6
|
+
* returns a signed one.
|
|
7
|
+
*
|
|
8
|
+
* ## Why this is identity's and not protocol's
|
|
9
|
+
*
|
|
10
|
+
* `@estiva-app/protocol` owns how an event gets its signature — the id
|
|
11
|
+
* computation, the serialization, the bytes. It owns the {@link Signer} seam and
|
|
12
|
+
* the raw-secret-key implementation of it. What it deliberately does not own is
|
|
13
|
+
* *who is allowed to ask*: the token, its renewal, and the round trip to an
|
|
14
|
+
* identity service. That is this package, and `ship/lib/nostr/signer.ts` has
|
|
15
|
+
* said so in a header comment since SHA-3 — "it is `@estiva-app/identity`'s
|
|
16
|
+
* remit — SHA-4. Until then this file is the app's copy."
|
|
17
|
+
*
|
|
18
|
+
* There were three copies until this file existed:
|
|
19
|
+
*
|
|
20
|
+
* | where | what |
|
|
21
|
+
* | --- | --- |
|
|
22
|
+
* | `ship/lib/nostr/signer.ts` | `estivaIdSigner` |
|
|
23
|
+
* | `estiva-agent/lib/nostr/signer.ts` | byte-identical to it |
|
|
24
|
+
* | `peek/src/nostr/bridge.ts` | `signViaEstivaId`, a third |
|
|
25
|
+
*
|
|
26
|
+
* Three implementations of the one call that decides who authored an event, and
|
|
27
|
+
* they had already drifted on the thing that matters most — see the guard below.
|
|
28
|
+
*
|
|
29
|
+
* ## Two exports over one round trip
|
|
30
|
+
*
|
|
31
|
+
* The consumers genuinely want different shapes, and this is not the same kind
|
|
32
|
+
* of difference as `storage` or `clientId`. Peek signs *per request* with a token
|
|
33
|
+
* it reads fresh each time (`signViaEstivaId`); Ship builds a `Signer` **once**,
|
|
34
|
+
* at module scope, which then holds a live bearer for the life of the page and
|
|
35
|
+
* renews it on a `401`. Neither can be expressed as the other without one of
|
|
36
|
+
* them getting worse, so both are here, over one implementation of the round
|
|
37
|
+
* trip and one copy of the guard.
|
|
38
|
+
*
|
|
39
|
+
* `@estiva-app/protocol` is a **peer** dependency, following `@estiva-app/interop`.
|
|
40
|
+
* Nothing here calls a protocol function — only its types are used — so the
|
|
41
|
+
* emitted `.js` imports nothing, and `dist/` keeps the property SHA-4's seam was
|
|
42
|
+
* verified by: the complete list of imports in the shipped JavaScript is
|
|
43
|
+
* `./client.js` and `./shell.js`.
|
|
44
|
+
*/
|
|
45
|
+
import type { SignedEvent, Signer, UnsignedEvent } from '@estiva-app/protocol';
|
|
46
|
+
/**
|
|
47
|
+
* `fetch`, again, and for the same reason as in `client.ts` — `lib.es2022` has
|
|
48
|
+
* no such global, and this package compiles with `types: []` and no `lib: dom`
|
|
49
|
+
* so one published `.d.ts` works in every consumer (ADR 0002 §4a).
|
|
50
|
+
*
|
|
51
|
+
* Note this module needs a *slightly* wider `fetch` than `client.ts` does: it
|
|
52
|
+
* reads `text()` rather than `json()`, because `/sign`'s error bodies are not
|
|
53
|
+
* reliably JSON and the raw text is the only thing worth showing a person.
|
|
54
|
+
*/
|
|
55
|
+
declare const fetch: (url: string, init: {
|
|
56
|
+
method: string;
|
|
57
|
+
headers: Record<string, string>;
|
|
58
|
+
body: string;
|
|
59
|
+
}) => Promise<{
|
|
60
|
+
ok: boolean;
|
|
61
|
+
status: number;
|
|
62
|
+
text(): Promise<string>;
|
|
63
|
+
}>;
|
|
64
|
+
/** Just the part of `fetch` this module uses. A parameter as well as a global. */
|
|
65
|
+
export type SignFetchLike = typeof fetch;
|
|
66
|
+
/** Distinguishes "sign in again" from "this event was refused". */
|
|
67
|
+
export declare class SignerTokenExpired extends Error {
|
|
68
|
+
constructor();
|
|
69
|
+
}
|
|
70
|
+
export interface SignViaEstivaIdOptions {
|
|
71
|
+
/** Origin of the identity service, e.g. `https://id.estiva.app`. */
|
|
72
|
+
base: string;
|
|
73
|
+
/** A bearer token from `POST /token`, audience-bound to this app. */
|
|
74
|
+
token: string;
|
|
75
|
+
/**
|
|
76
|
+
* The pubkey the returned event **must** be authored by.
|
|
77
|
+
*
|
|
78
|
+
* Not a request. `/sign` ignores any pubkey it is handed and signs as the
|
|
79
|
+
* token's subject, always — so this is a *check*, and the reason one is needed
|
|
80
|
+
* at all is that a mismatch comes back as **HTTP 200 with a perfectly valid
|
|
81
|
+
* event authored by somebody else**. Without it the failure is invisible at
|
|
82
|
+
* the call site and surfaces later as a message attributed to the wrong
|
|
83
|
+
* person, which is not a bug anybody can debug from the symptom.
|
|
84
|
+
*
|
|
85
|
+
* Optional only because `estivaIdSigner` always supplies it and a caller with
|
|
86
|
+
* genuinely no expectation should be able to say so out loud. Supply it.
|
|
87
|
+
*/
|
|
88
|
+
expectedPubkey?: string;
|
|
89
|
+
fetch?: SignFetchLike;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* One `POST /sign`.
|
|
93
|
+
*
|
|
94
|
+
* **`/sign` refuses `kind:0` for every app, always.** Profiles belong to the
|
|
95
|
+
* identity service (PEEK-31); a refusal here is the policy working rather than
|
|
96
|
+
* something to route around.
|
|
97
|
+
*
|
|
98
|
+
* A `401` is thrown as {@link SignerTokenExpired} rather than a generic failure,
|
|
99
|
+
* because "obtain a new token" and "this event was refused" call for completely
|
|
100
|
+
* different responses from the caller and the status code is the only thing that
|
|
101
|
+
* distinguishes them.
|
|
102
|
+
*/
|
|
103
|
+
export declare function signViaEstivaId(unsigned: UnsignedEvent, options: SignViaEstivaIdOptions): Promise<SignedEvent>;
|
|
104
|
+
export interface EstivaIdSignerOptions {
|
|
105
|
+
/** Origin of the identity service, e.g. `https://id.estiva.app`. */
|
|
106
|
+
base: string;
|
|
107
|
+
/** The pubkey the token was issued for — `sub` in the JWT. */
|
|
108
|
+
pubkey: string;
|
|
109
|
+
/** A bearer token from `POST /token`, audience-bound to this app. */
|
|
110
|
+
token: string;
|
|
111
|
+
/**
|
|
112
|
+
* Renew the token, once, when `/sign` says it has expired (PEEK-108).
|
|
113
|
+
*
|
|
114
|
+
* Optional: without it a `401` surfaces as {@link SignerTokenExpired} for the
|
|
115
|
+
* caller to deal with, exactly as before. With it, an expiry is invisible —
|
|
116
|
+
* which matters more than it used to, because access tokens live ten minutes
|
|
117
|
+
* rather than an hour, so a session of any length crosses one.
|
|
118
|
+
*
|
|
119
|
+
* Returning `null` means renewal failed and the expiry is real.
|
|
120
|
+
*
|
|
121
|
+
* Pass `client.refreshAccessToken` and nothing else: it coalesces concurrent
|
|
122
|
+
* callers, and a refresh token is single-use with a replay read as theft.
|
|
123
|
+
*/
|
|
124
|
+
renew?: () => Promise<string | null>;
|
|
125
|
+
fetch?: SignFetchLike;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A {@link Signer} that signs by asking Estiva ID.
|
|
129
|
+
*
|
|
130
|
+
* Holds the live bearer, so a renewal replaces it for every later signature
|
|
131
|
+
* rather than being rediscovered on each one.
|
|
132
|
+
*/
|
|
133
|
+
export declare function estivaIdSigner({ base, pubkey, token, renew, fetch: send }: EstivaIdSignerOptions): Signer;
|
|
134
|
+
export {};
|
|
135
|
+
//# sourceMappingURL=signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAE9E;;;;;;;;GAQG;AACH,OAAO,CAAC,MAAM,KAAK,EAAE,CACnB,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,MAAM,CAAC,CAAA;CAAE,CAAC,CAAA;AAEtE,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,OAAO,KAAK,CAAA;AAExC,mEAAmE;AACnE,qBAAa,kBAAmB,SAAQ,KAAK;;CAK5C;AAED,MAAM,WAAW,sBAAsB;IACrC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAkDtB;AAED,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACpC,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,qBAAqB,GAAG,MAAM,CAwCzG"}
|
package/dist/signer.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** Distinguishes "sign in again" from "this event was refused". */
|
|
2
|
+
export class SignerTokenExpired extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super('The signing token has expired');
|
|
5
|
+
this.name = 'SignerTokenExpired';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* One `POST /sign`.
|
|
10
|
+
*
|
|
11
|
+
* **`/sign` refuses `kind:0` for every app, always.** Profiles belong to the
|
|
12
|
+
* identity service (PEEK-31); a refusal here is the policy working rather than
|
|
13
|
+
* something to route around.
|
|
14
|
+
*
|
|
15
|
+
* A `401` is thrown as {@link SignerTokenExpired} rather than a generic failure,
|
|
16
|
+
* because "obtain a new token" and "this event was refused" call for completely
|
|
17
|
+
* different responses from the caller and the status code is the only thing that
|
|
18
|
+
* distinguishes them.
|
|
19
|
+
*/
|
|
20
|
+
export async function signViaEstivaId(unsigned, options) {
|
|
21
|
+
const send = options.fetch ?? fetch;
|
|
22
|
+
const origin = options.base.replace(/\/+$/, '');
|
|
23
|
+
const response = await send(`${origin}/sign`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'content-type': 'application/json',
|
|
27
|
+
authorization: `Bearer ${options.token}`,
|
|
28
|
+
},
|
|
29
|
+
/*
|
|
30
|
+
Only the four fields that go into the signature input. The unsigned event
|
|
31
|
+
may carry a `pubkey` — Peek's NIP-98 path passes an empty one on purpose —
|
|
32
|
+
and sending it would imply `/sign` honours it, which it does not.
|
|
33
|
+
*/
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
event: {
|
|
36
|
+
kind: unsigned.kind,
|
|
37
|
+
created_at: unsigned.created_at,
|
|
38
|
+
tags: unsigned.tags,
|
|
39
|
+
content: unsigned.content,
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
const text = await response.text();
|
|
44
|
+
if (response.status === 401)
|
|
45
|
+
throw new SignerTokenExpired();
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
let reason = text.slice(0, 200);
|
|
48
|
+
try {
|
|
49
|
+
const body = JSON.parse(text);
|
|
50
|
+
reason = body.error?.message ?? reason;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Keep the raw text. `/sign`'s error bodies are not reliably JSON, and the
|
|
54
|
+
// raw text is more use to a person than "unparseable response".
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Estiva ID refused to sign kind:${unsigned.kind} — ${reason}`);
|
|
57
|
+
}
|
|
58
|
+
const event = JSON.parse(text).event;
|
|
59
|
+
if (!event)
|
|
60
|
+
throw new Error('POST /sign returned no event');
|
|
61
|
+
if (options.expectedPubkey && event.pubkey !== options.expectedPubkey) {
|
|
62
|
+
throw new Error(`POST /sign returned an event authored by ${event.pubkey}, expected ${options.expectedPubkey}. ` +
|
|
63
|
+
'That token belongs to a different identity — refusing to publish under the wrong author.');
|
|
64
|
+
}
|
|
65
|
+
return event;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A {@link Signer} that signs by asking Estiva ID.
|
|
69
|
+
*
|
|
70
|
+
* Holds the live bearer, so a renewal replaces it for every later signature
|
|
71
|
+
* rather than being rediscovered on each one.
|
|
72
|
+
*/
|
|
73
|
+
export function estivaIdSigner({ base, pubkey, token, renew, fetch: send }) {
|
|
74
|
+
let bearer = token;
|
|
75
|
+
return {
|
|
76
|
+
pubkey,
|
|
77
|
+
kind: 'estiva-id',
|
|
78
|
+
async sign(unsigned) {
|
|
79
|
+
const once = (auth) => signViaEstivaId(unsigned, {
|
|
80
|
+
base,
|
|
81
|
+
token: auth,
|
|
82
|
+
// The guard, unconditionally. Ship's copy of this signer never had it
|
|
83
|
+
// and Peek's `signViaEstivaId` did, which is exactly the drift three
|
|
84
|
+
// copies produce: the app that publishes every issue, comment and
|
|
85
|
+
// status change was the one that could not tell it had signed as
|
|
86
|
+
// somebody else. SHA-4's Traps section says both must keep it.
|
|
87
|
+
expectedPubkey: pubkey,
|
|
88
|
+
...(send ? { fetch: send } : {}),
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
return await once(bearer);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
/*
|
|
95
|
+
Renew once and retry, never twice.
|
|
96
|
+
|
|
97
|
+
A second 401 after a successful renewal is not an expiry — it is a
|
|
98
|
+
token the service will not accept, and retrying that in a loop is how
|
|
99
|
+
one refused signature becomes a hot loop against the identity service.
|
|
100
|
+
Anything that is not an expiry is rethrown untouched, so a wrong-author
|
|
101
|
+
refusal is never mistaken for something a new token would fix.
|
|
102
|
+
*/
|
|
103
|
+
if (!(error instanceof SignerTokenExpired) || !renew)
|
|
104
|
+
throw error;
|
|
105
|
+
const renewed = await renew();
|
|
106
|
+
if (!renewed)
|
|
107
|
+
throw error;
|
|
108
|
+
bearer = renewed;
|
|
109
|
+
return await once(bearer);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estiva-app/identity",
|
|
3
|
-
"version": "0.
|
|
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.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Estiva ID sign-in: authorization code + PKCE, token storage, silent renewal, sign-out, signing via POST /sign, and the auth-shell state machine. Knows nothing about any app's data layer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -35,11 +35,13 @@
|
|
|
35
35
|
"keywords": [
|
|
36
36
|
"estiva",
|
|
37
37
|
"estiva-id",
|
|
38
|
+
"nostr",
|
|
38
39
|
"oauth",
|
|
39
40
|
"pkce",
|
|
40
|
-
"
|
|
41
|
+
"signer"
|
|
41
42
|
],
|
|
42
43
|
"scripts": {
|
|
44
|
+
"prebuild": "npm run build --workspace @estiva-app/protocol",
|
|
43
45
|
"build": "tsc -p tsconfig.json",
|
|
44
46
|
"test": "npm run build && node --test test/*.test.mjs test/*.test.ts",
|
|
45
47
|
"typecheck": "npm run build && tsc -p tsconfig.test.json",
|
|
@@ -47,7 +49,11 @@
|
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {},
|
|
49
51
|
"devDependencies": {
|
|
52
|
+
"@estiva-app/protocol": ">=0.14.0",
|
|
50
53
|
"@types/node": "^24.13.3",
|
|
51
54
|
"typescript": "~5.9.3"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@estiva-app/protocol": ">=0.14.0"
|
|
52
58
|
}
|
|
53
59
|
}
|
package/src/client.ts
CHANGED
|
@@ -67,6 +67,8 @@ declare const crypto: {
|
|
|
67
67
|
subtle: { digest(algorithm: string, data: Uint8Array): Promise<ArrayBuffer> }
|
|
68
68
|
}
|
|
69
69
|
declare const btoa: (binary: string) => string
|
|
70
|
+
/** Reading the `exp` claim out of a token the app already holds. */
|
|
71
|
+
declare const atob: (base64: string) => string
|
|
70
72
|
declare const TextEncoder: { new (): { encode(input: string): Uint8Array } }
|
|
71
73
|
declare const URL: {
|
|
72
74
|
new (url: string, base?: string): { searchParams: { set(k: string, v: string): void }; toString(): string }
|
|
@@ -251,18 +253,67 @@ export function createEstivaId(config: EstivaIdConfig): EstivaIdClient {
|
|
|
251
253
|
return base64Url(new Uint8Array(digest))
|
|
252
254
|
}
|
|
253
255
|
|
|
256
|
+
/**
|
|
257
|
+
* The `exp` claim, in epoch ms, or null if the token does not carry a readable one.
|
|
258
|
+
*
|
|
259
|
+
* Not verification — that is the resource server's job and it holds the key.
|
|
260
|
+
* This reads one number out of a payload the app already holds, for one
|
|
261
|
+
* purpose: so that "is this token still good" is answered by the same fact the
|
|
262
|
+
* service will answer it with.
|
|
263
|
+
*
|
|
264
|
+
* Parse failures return null rather than throwing. A token this cannot read is
|
|
265
|
+
* still a token, and refusing to hold one because its middle segment is not
|
|
266
|
+
* base64url would be a worse failure than the one this prevents.
|
|
267
|
+
*/
|
|
268
|
+
const expFromJwt = (accessToken: string): number | null => {
|
|
269
|
+
const payload = accessToken.split('.')[1]
|
|
270
|
+
if (!payload) return null
|
|
271
|
+
try {
|
|
272
|
+
const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'))
|
|
273
|
+
const exp = (JSON.parse(json) as { exp?: unknown }).exp
|
|
274
|
+
return typeof exp === 'number' && Number.isFinite(exp) ? exp * 1000 : null
|
|
275
|
+
} catch {
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
254
280
|
/**
|
|
255
281
|
* Shape a token response into what is kept.
|
|
256
282
|
*
|
|
257
283
|
* The expiry is deliberately pessimistic by 30s, so a token is never presented
|
|
258
284
|
* in the window where it is technically alive and about to not be.
|
|
285
|
+
*
|
|
286
|
+
* ## Why `exp` and not just `expires_in`
|
|
287
|
+
*
|
|
288
|
+
* `expires_in` describes a *duration*, so acting on it means adding it to the
|
|
289
|
+
* local clock — and the service decides using the token's own `exp` against
|
|
290
|
+
* *its* clock. Those two answers agree only while the clocks do.
|
|
291
|
+
*
|
|
292
|
+
* When they diverge in the unsafe direction the app believes a dead token is
|
|
293
|
+
* live, and the failure is genuinely nasty: `POST /sign` answers
|
|
294
|
+
* `401 Invalid token: "exp" claim timestamp check failed`, while `validToken`
|
|
295
|
+
* keeps returning that same token to every caller that asks — including the
|
|
296
|
+
* one Convex asks — so nothing ever renews and nothing ever recovers. Peek got
|
|
297
|
+
* into exactly this state overnight on a machine that had slept, and the only
|
|
298
|
+
* way out was clearing site data and signing in again.
|
|
299
|
+
*
|
|
300
|
+
* So both bounds are computed and **the earlier wins**. `exp` is what the
|
|
301
|
+
* service will actually check; `expires_in` still covers a token with no
|
|
302
|
+
* readable `exp`, and a clock that is *behind* the service's, where `exp`
|
|
303
|
+
* alone would be the more generous of the two.
|
|
259
304
|
*/
|
|
260
|
-
const tokenFrom = (body: TokenResponse, pubkey: string): StoredToken =>
|
|
261
|
-
accessToken
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
305
|
+
const tokenFrom = (body: TokenResponse, pubkey: string): StoredToken => {
|
|
306
|
+
const accessToken = body.access_token as string
|
|
307
|
+
const fromDuration = now() + Math.max(0, (body.expires_in ?? 600) - 30) * 1000
|
|
308
|
+
const exp = expFromJwt(accessToken)
|
|
309
|
+
const fromClaim = exp === null ? null : exp - 30_000
|
|
310
|
+
return {
|
|
311
|
+
accessToken,
|
|
312
|
+
expiresAt: fromClaim === null ? fromDuration : Math.min(fromDuration, fromClaim),
|
|
313
|
+
pubkey,
|
|
314
|
+
...(body.refresh_token ? { refreshToken: body.refresh_token } : {}),
|
|
315
|
+
}
|
|
316
|
+
}
|
|
266
317
|
|
|
267
318
|
const storedToken = (): StoredToken | null => {
|
|
268
319
|
const raw = config.storage()?.getItem(TOKEN_KEY)
|
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.
|
|
49
|
+
export const IDENTITY_VERSION = '0.2.0'
|
|
50
50
|
|
|
51
51
|
export {
|
|
52
52
|
type KeyValueStore,
|
|
@@ -81,3 +81,12 @@ export {
|
|
|
81
81
|
SignInError,
|
|
82
82
|
createEstivaId,
|
|
83
83
|
} from './client.js'
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
type SignViaEstivaIdOptions,
|
|
87
|
+
type EstivaIdSignerOptions,
|
|
88
|
+
type SignFetchLike,
|
|
89
|
+
SignerTokenExpired,
|
|
90
|
+
signViaEstivaId,
|
|
91
|
+
estivaIdSigner,
|
|
92
|
+
} from './signer.js'
|
package/src/signer.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signing by asking Estiva ID, holding no key at all.
|
|
3
|
+
*
|
|
4
|
+
* The point of the whole seam: an app publishes as a person without ever seeing
|
|
5
|
+
* their secret. `POST /sign` takes a bearer token and an unsigned event and
|
|
6
|
+
* returns a signed one.
|
|
7
|
+
*
|
|
8
|
+
* ## Why this is identity's and not protocol's
|
|
9
|
+
*
|
|
10
|
+
* `@estiva-app/protocol` owns how an event gets its signature — the id
|
|
11
|
+
* computation, the serialization, the bytes. It owns the {@link Signer} seam and
|
|
12
|
+
* the raw-secret-key implementation of it. What it deliberately does not own is
|
|
13
|
+
* *who is allowed to ask*: the token, its renewal, and the round trip to an
|
|
14
|
+
* identity service. That is this package, and `ship/lib/nostr/signer.ts` has
|
|
15
|
+
* said so in a header comment since SHA-3 — "it is `@estiva-app/identity`'s
|
|
16
|
+
* remit — SHA-4. Until then this file is the app's copy."
|
|
17
|
+
*
|
|
18
|
+
* There were three copies until this file existed:
|
|
19
|
+
*
|
|
20
|
+
* | where | what |
|
|
21
|
+
* | --- | --- |
|
|
22
|
+
* | `ship/lib/nostr/signer.ts` | `estivaIdSigner` |
|
|
23
|
+
* | `estiva-agent/lib/nostr/signer.ts` | byte-identical to it |
|
|
24
|
+
* | `peek/src/nostr/bridge.ts` | `signViaEstivaId`, a third |
|
|
25
|
+
*
|
|
26
|
+
* Three implementations of the one call that decides who authored an event, and
|
|
27
|
+
* they had already drifted on the thing that matters most — see the guard below.
|
|
28
|
+
*
|
|
29
|
+
* ## Two exports over one round trip
|
|
30
|
+
*
|
|
31
|
+
* The consumers genuinely want different shapes, and this is not the same kind
|
|
32
|
+
* of difference as `storage` or `clientId`. Peek signs *per request* with a token
|
|
33
|
+
* it reads fresh each time (`signViaEstivaId`); Ship builds a `Signer` **once**,
|
|
34
|
+
* at module scope, which then holds a live bearer for the life of the page and
|
|
35
|
+
* renews it on a `401`. Neither can be expressed as the other without one of
|
|
36
|
+
* them getting worse, so both are here, over one implementation of the round
|
|
37
|
+
* trip and one copy of the guard.
|
|
38
|
+
*
|
|
39
|
+
* `@estiva-app/protocol` is a **peer** dependency, following `@estiva-app/interop`.
|
|
40
|
+
* Nothing here calls a protocol function — only its types are used — so the
|
|
41
|
+
* emitted `.js` imports nothing, and `dist/` keeps the property SHA-4's seam was
|
|
42
|
+
* verified by: the complete list of imports in the shipped JavaScript is
|
|
43
|
+
* `./client.js` and `./shell.js`.
|
|
44
|
+
*/
|
|
45
|
+
import type { SignedEvent, Signer, UnsignedEvent } from '@estiva-app/protocol'
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `fetch`, again, and for the same reason as in `client.ts` — `lib.es2022` has
|
|
49
|
+
* no such global, and this package compiles with `types: []` and no `lib: dom`
|
|
50
|
+
* so one published `.d.ts` works in every consumer (ADR 0002 §4a).
|
|
51
|
+
*
|
|
52
|
+
* Note this module needs a *slightly* wider `fetch` than `client.ts` does: it
|
|
53
|
+
* reads `text()` rather than `json()`, because `/sign`'s error bodies are not
|
|
54
|
+
* reliably JSON and the raw text is the only thing worth showing a person.
|
|
55
|
+
*/
|
|
56
|
+
declare const fetch: (
|
|
57
|
+
url: string,
|
|
58
|
+
init: { method: string; headers: Record<string, string>; body: string },
|
|
59
|
+
) => Promise<{ ok: boolean; status: number; text(): Promise<string> }>
|
|
60
|
+
|
|
61
|
+
/** Just the part of `fetch` this module uses. A parameter as well as a global. */
|
|
62
|
+
export type SignFetchLike = typeof fetch
|
|
63
|
+
|
|
64
|
+
/** Distinguishes "sign in again" from "this event was refused". */
|
|
65
|
+
export class SignerTokenExpired extends Error {
|
|
66
|
+
constructor() {
|
|
67
|
+
super('The signing token has expired')
|
|
68
|
+
this.name = 'SignerTokenExpired'
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SignViaEstivaIdOptions {
|
|
73
|
+
/** Origin of the identity service, e.g. `https://id.estiva.app`. */
|
|
74
|
+
base: string
|
|
75
|
+
/** A bearer token from `POST /token`, audience-bound to this app. */
|
|
76
|
+
token: string
|
|
77
|
+
/**
|
|
78
|
+
* The pubkey the returned event **must** be authored by.
|
|
79
|
+
*
|
|
80
|
+
* Not a request. `/sign` ignores any pubkey it is handed and signs as the
|
|
81
|
+
* token's subject, always — so this is a *check*, and the reason one is needed
|
|
82
|
+
* at all is that a mismatch comes back as **HTTP 200 with a perfectly valid
|
|
83
|
+
* event authored by somebody else**. Without it the failure is invisible at
|
|
84
|
+
* the call site and surfaces later as a message attributed to the wrong
|
|
85
|
+
* person, which is not a bug anybody can debug from the symptom.
|
|
86
|
+
*
|
|
87
|
+
* Optional only because `estivaIdSigner` always supplies it and a caller with
|
|
88
|
+
* genuinely no expectation should be able to say so out loud. Supply it.
|
|
89
|
+
*/
|
|
90
|
+
expectedPubkey?: string
|
|
91
|
+
fetch?: SignFetchLike
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* One `POST /sign`.
|
|
96
|
+
*
|
|
97
|
+
* **`/sign` refuses `kind:0` for every app, always.** Profiles belong to the
|
|
98
|
+
* identity service (PEEK-31); a refusal here is the policy working rather than
|
|
99
|
+
* something to route around.
|
|
100
|
+
*
|
|
101
|
+
* A `401` is thrown as {@link SignerTokenExpired} rather than a generic failure,
|
|
102
|
+
* because "obtain a new token" and "this event was refused" call for completely
|
|
103
|
+
* different responses from the caller and the status code is the only thing that
|
|
104
|
+
* distinguishes them.
|
|
105
|
+
*/
|
|
106
|
+
export async function signViaEstivaId(
|
|
107
|
+
unsigned: UnsignedEvent,
|
|
108
|
+
options: SignViaEstivaIdOptions,
|
|
109
|
+
): Promise<SignedEvent> {
|
|
110
|
+
const send = options.fetch ?? fetch
|
|
111
|
+
const origin = options.base.replace(/\/+$/, '')
|
|
112
|
+
|
|
113
|
+
const response = await send(`${origin}/sign`, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'content-type': 'application/json',
|
|
117
|
+
authorization: `Bearer ${options.token}`,
|
|
118
|
+
},
|
|
119
|
+
/*
|
|
120
|
+
Only the four fields that go into the signature input. The unsigned event
|
|
121
|
+
may carry a `pubkey` — Peek's NIP-98 path passes an empty one on purpose —
|
|
122
|
+
and sending it would imply `/sign` honours it, which it does not.
|
|
123
|
+
*/
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
event: {
|
|
126
|
+
kind: unsigned.kind,
|
|
127
|
+
created_at: unsigned.created_at,
|
|
128
|
+
tags: unsigned.tags,
|
|
129
|
+
content: unsigned.content,
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const text = await response.text()
|
|
135
|
+
if (response.status === 401) throw new SignerTokenExpired()
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
let reason = text.slice(0, 200)
|
|
138
|
+
try {
|
|
139
|
+
const body = JSON.parse(text) as { error?: { message?: string } }
|
|
140
|
+
reason = body.error?.message ?? reason
|
|
141
|
+
} catch {
|
|
142
|
+
// Keep the raw text. `/sign`'s error bodies are not reliably JSON, and the
|
|
143
|
+
// raw text is more use to a person than "unparseable response".
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`Estiva ID refused to sign kind:${unsigned.kind} — ${reason}`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const event = (JSON.parse(text) as { event?: SignedEvent }).event
|
|
149
|
+
if (!event) throw new Error('POST /sign returned no event')
|
|
150
|
+
|
|
151
|
+
if (options.expectedPubkey && event.pubkey !== options.expectedPubkey) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`POST /sign returned an event authored by ${event.pubkey}, expected ${options.expectedPubkey}. ` +
|
|
154
|
+
'That token belongs to a different identity — refusing to publish under the wrong author.',
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return event
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface EstivaIdSignerOptions {
|
|
162
|
+
/** Origin of the identity service, e.g. `https://id.estiva.app`. */
|
|
163
|
+
base: string
|
|
164
|
+
/** The pubkey the token was issued for — `sub` in the JWT. */
|
|
165
|
+
pubkey: string
|
|
166
|
+
/** A bearer token from `POST /token`, audience-bound to this app. */
|
|
167
|
+
token: string
|
|
168
|
+
/**
|
|
169
|
+
* Renew the token, once, when `/sign` says it has expired (PEEK-108).
|
|
170
|
+
*
|
|
171
|
+
* Optional: without it a `401` surfaces as {@link SignerTokenExpired} for the
|
|
172
|
+
* caller to deal with, exactly as before. With it, an expiry is invisible —
|
|
173
|
+
* which matters more than it used to, because access tokens live ten minutes
|
|
174
|
+
* rather than an hour, so a session of any length crosses one.
|
|
175
|
+
*
|
|
176
|
+
* Returning `null` means renewal failed and the expiry is real.
|
|
177
|
+
*
|
|
178
|
+
* Pass `client.refreshAccessToken` and nothing else: it coalesces concurrent
|
|
179
|
+
* callers, and a refresh token is single-use with a replay read as theft.
|
|
180
|
+
*/
|
|
181
|
+
renew?: () => Promise<string | null>
|
|
182
|
+
fetch?: SignFetchLike
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A {@link Signer} that signs by asking Estiva ID.
|
|
187
|
+
*
|
|
188
|
+
* Holds the live bearer, so a renewal replaces it for every later signature
|
|
189
|
+
* rather than being rediscovered on each one.
|
|
190
|
+
*/
|
|
191
|
+
export function estivaIdSigner({ base, pubkey, token, renew, fetch: send }: EstivaIdSignerOptions): Signer {
|
|
192
|
+
let bearer = token
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
pubkey,
|
|
196
|
+
kind: 'estiva-id',
|
|
197
|
+
async sign(unsigned) {
|
|
198
|
+
const once = (auth: string) =>
|
|
199
|
+
signViaEstivaId(unsigned, {
|
|
200
|
+
base,
|
|
201
|
+
token: auth,
|
|
202
|
+
// The guard, unconditionally. Ship's copy of this signer never had it
|
|
203
|
+
// and Peek's `signViaEstivaId` did, which is exactly the drift three
|
|
204
|
+
// copies produce: the app that publishes every issue, comment and
|
|
205
|
+
// status change was the one that could not tell it had signed as
|
|
206
|
+
// somebody else. SHA-4's Traps section says both must keep it.
|
|
207
|
+
expectedPubkey: pubkey,
|
|
208
|
+
...(send ? { fetch: send } : {}),
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
return await once(bearer)
|
|
213
|
+
} catch (error) {
|
|
214
|
+
/*
|
|
215
|
+
Renew once and retry, never twice.
|
|
216
|
+
|
|
217
|
+
A second 401 after a successful renewal is not an expiry — it is a
|
|
218
|
+
token the service will not accept, and retrying that in a loop is how
|
|
219
|
+
one refused signature becomes a hot loop against the identity service.
|
|
220
|
+
Anything that is not an expiry is rethrown untouched, so a wrong-author
|
|
221
|
+
refusal is never mistaken for something a new token would fix.
|
|
222
|
+
*/
|
|
223
|
+
if (!(error instanceof SignerTokenExpired) || !renew) throw error
|
|
224
|
+
const renewed = await renew()
|
|
225
|
+
if (!renewed) throw error
|
|
226
|
+
bearer = renewed
|
|
227
|
+
return await once(bearer)
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
}
|