@icp-sdk/auth 8.0.3 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/client/app-delegation-source.d.ts +65 -0
- package/dist/esm/client/app-delegation-source.js +58 -0
- package/dist/esm/client/app-delegation-source.js.map +1 -0
- package/dist/esm/client/app-lock.d.ts +63 -0
- package/dist/esm/client/app-lock.js +92 -0
- package/dist/esm/client/app-lock.js.map +1 -0
- package/dist/esm/client/auth-client.d.ts +270 -53
- package/dist/esm/client/auth-client.js +1017 -414
- package/dist/esm/client/auth-client.js.map +1 -1
- package/dist/esm/client/base64.d.ts +8 -0
- package/dist/esm/client/base64.js +28 -0
- package/dist/esm/client/base64.js.map +1 -0
- package/dist/esm/client/cookie-state-storage.d.ts +59 -0
- package/dist/esm/client/cookie-state-storage.js +245 -0
- package/dist/esm/client/cookie-state-storage.js.map +1 -0
- package/dist/esm/client/credential-storage.d.ts +48 -0
- package/dist/esm/client/credential-storage.js +2 -0
- package/dist/esm/client/credential-storage.js.map +1 -0
- package/dist/esm/client/db.d.ts +6 -1
- package/dist/esm/client/db.js +56 -15
- package/dist/esm/client/db.js.map +1 -1
- package/dist/esm/client/foreground-refresh.d.ts +35 -0
- package/dist/esm/client/foreground-refresh.js +83 -0
- package/dist/esm/client/foreground-refresh.js.map +1 -0
- package/dist/esm/client/idb-credential-storage.d.ts +25 -0
- package/dist/esm/client/idb-credential-storage.js +69 -0
- package/dist/esm/client/idb-credential-storage.js.map +1 -0
- package/dist/esm/client/index.d.ts +14 -3
- package/dist/esm/client/index.js +13 -3
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/local-credential-storage.d.ts +26 -0
- package/dist/esm/client/local-credential-storage.js +59 -0
- package/dist/esm/client/local-credential-storage.js.map +1 -0
- package/dist/esm/client/memory-credential-storage.d.ts +22 -0
- package/dist/esm/client/memory-credential-storage.js +35 -0
- package/dist/esm/client/memory-credential-storage.js.map +1 -0
- package/dist/esm/client/session-delegation.d.ts +40 -0
- package/dist/esm/client/session-delegation.js +100 -0
- package/dist/esm/client/session-delegation.js.map +1 -0
- package/dist/esm/client/session-identity.d.ts +97 -0
- package/dist/esm/client/session-identity.js +392 -0
- package/dist/esm/client/session-identity.js.map +1 -0
- package/dist/esm/client/session-minter.d.ts +94 -0
- package/dist/esm/client/session-minter.js +178 -0
- package/dist/esm/client/session-minter.js.map +1 -0
- package/dist/esm/client/shared-memory-credential-storage.d.ts +44 -0
- package/dist/esm/client/shared-memory-credential-storage.js +243 -0
- package/dist/esm/client/shared-memory-credential-storage.js.map +1 -0
- package/dist/esm/client/slots.d.ts +59 -0
- package/dist/esm/client/slots.js +67 -0
- package/dist/esm/client/slots.js.map +1 -0
- package/dist/esm/client/sso.d.ts +33 -0
- package/dist/esm/client/sso.js +120 -0
- package/dist/esm/client/sso.js.map +1 -0
- package/dist/esm/client/state-storage.d.ts +135 -0
- package/dist/esm/client/state-storage.js +178 -0
- package/dist/esm/client/state-storage.js.map +1 -0
- package/package.json +5 -4
- package/src/client/app-delegation-source.ts +86 -0
- package/src/client/app-lock.ts +120 -0
- package/src/client/auth-client.ts +1300 -487
- package/src/client/base64.ts +28 -0
- package/src/client/cookie-state-storage.ts +277 -0
- package/src/client/credential-storage.ts +56 -0
- package/src/client/db.ts +56 -12
- package/src/client/foreground-refresh.ts +92 -0
- package/src/client/idb-credential-storage.ts +82 -0
- package/src/client/index.ts +24 -8
- package/src/client/local-credential-storage.ts +68 -0
- package/src/client/memory-credential-storage.ts +41 -0
- package/src/client/session-delegation.ts +138 -0
- package/src/client/session-identity.ts +510 -0
- package/src/client/session-minter.ts +277 -0
- package/src/client/shared-memory-credential-storage.ts +303 -0
- package/src/client/slots.ts +78 -0
- package/src/client/sso.ts +122 -0
- package/src/client/state-storage.ts +284 -0
- package/dist/esm/client/idle-manager.d.ts +0 -83
- package/dist/esm/client/idle-manager.js +0 -100
- package/dist/esm/client/idle-manager.js.map +0 -1
- package/dist/esm/client/storage.d.ts +0 -51
- package/dist/esm/client/storage.js +0 -90
- package/dist/esm/client/storage.js.map +0 -1
- package/src/client/idle-manager.ts +0 -162
- package/src/client/storage.ts +0 -114
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { DerEncodedPublicKey } from '@icp-sdk/core/agent';
|
|
2
|
+
import type { DelegationChain } from '@icp-sdk/core/identity';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when the canister has no session behind the caller: revoked, expired,
|
|
6
|
+
* pruned, or never one at all.
|
|
7
|
+
*
|
|
8
|
+
* This is the one terminal outcome. Everything else a mint can fail with, a
|
|
9
|
+
* transport error or an internal canister error, leaves the session alone and is
|
|
10
|
+
* worth retrying, so only this type may end a session.
|
|
11
|
+
*/
|
|
12
|
+
export class SessionGoneError extends Error {
|
|
13
|
+
constructor(message = 'The session behind this identity no longer exists') {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'SessionGoneError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Thrown when the session is one this library cannot act for, however healthy it
|
|
21
|
+
* is.
|
|
22
|
+
*
|
|
23
|
+
* The third outcome, and the reason it is not either of the others: the session
|
|
24
|
+
* is neither gone nor worth retrying. Retrying returns the same answer forever,
|
|
25
|
+
* and reporting it as gone would retract a record for a sign-in that is alive,
|
|
26
|
+
* signing every tab on the domain out of it.
|
|
27
|
+
*/
|
|
28
|
+
export class SessionUnsupportedError extends Error {
|
|
29
|
+
constructor(message: string) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'SessionUnsupportedError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Thrown when a mint answers for an account the caller was not expecting.
|
|
37
|
+
*
|
|
38
|
+
* The session is healthy and the mint succeeded; it is the pairing that is
|
|
39
|
+
* wrong. So it is neither gone nor retryable — asking again returns the same
|
|
40
|
+
* account — and the caller's own record is what has to give way.
|
|
41
|
+
*/
|
|
42
|
+
export class AccountMismatchError extends Error {
|
|
43
|
+
constructor() {
|
|
44
|
+
super('The minted delegation is not for the account this origin acts as');
|
|
45
|
+
this.name = 'AccountMismatchError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Where {@link SessionIdentity} gets an app delegation from.
|
|
51
|
+
*
|
|
52
|
+
* The identity decides *when* to mint; this decides *how*. Splitting them is
|
|
53
|
+
* what lets the timing be tested without a replica, and what keeps the identity
|
|
54
|
+
* free of any agent or canister id.
|
|
55
|
+
*/
|
|
56
|
+
export interface AppDelegationSource {
|
|
57
|
+
/**
|
|
58
|
+
* Mint a delegation from the session to `appPublicKey`.
|
|
59
|
+
*
|
|
60
|
+
* Three outcomes. {@link SessionGoneError} where the session no longer
|
|
61
|
+
* exists, {@link SessionUnsupportedError} where it exists but this library
|
|
62
|
+
* cannot act for it, and anything else where the attempt may be worth
|
|
63
|
+
* repeating.
|
|
64
|
+
*/
|
|
65
|
+
mint(appPublicKey: DerEncodedPublicKey): Promise<DelegationChain>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Whether two DER-encoded keys are the same bytes. */
|
|
69
|
+
export const sameKey = (a: Uint8Array, b: Uint8Array): boolean =>
|
|
70
|
+
a.length === b.length && a.every((byte, i) => byte === b[i]);
|
|
71
|
+
|
|
72
|
+
/** The leaf of a chain: the key it authorises. */
|
|
73
|
+
export const delegatesTo = (chain: DelegationChain): Uint8Array | undefined =>
|
|
74
|
+
chain.delegations[chain.delegations.length - 1]?.delegation.pubkey;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether a chain's leaf authorises `keyDer` — the key that would sign with it.
|
|
78
|
+
*
|
|
79
|
+
* A chain paired with a key it does not authorise signs nothing the replica will
|
|
80
|
+
* accept, and fails with a signature error that points at neither half. Every
|
|
81
|
+
* place that holds both halves checks this.
|
|
82
|
+
*/
|
|
83
|
+
export const chainAuthorisesKey = (chain: DelegationChain, keyDer: Uint8Array): boolean => {
|
|
84
|
+
const leaf = delegatesTo(chain);
|
|
85
|
+
return leaf !== undefined && sameKey(new Uint8Array(leaf), new Uint8Array(keyDer));
|
|
86
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `mint` while holding a named lock, where the environment has one.
|
|
3
|
+
*
|
|
4
|
+
* The lock is the library coordinating with itself rather than anything a store
|
|
5
|
+
* provides, so a store answers only whether another tab can read it and needs to
|
|
6
|
+
* know nothing about the Web Locks API.
|
|
7
|
+
*
|
|
8
|
+
* Where there is no lock — an older browser, or a store no other tab reads —
|
|
9
|
+
* `mint` runs straight away and every tab mints for itself. That costs calls and
|
|
10
|
+
* never correctness: coordination may only ever suppress a mint, never be
|
|
11
|
+
* required for one.
|
|
12
|
+
*
|
|
13
|
+
* `mint` is handed a signal that fires when the lock is taken away by
|
|
14
|
+
* {@link stealMintLock}. It is the only thing a mint can act on, because a call
|
|
15
|
+
* already sent cannot be recalled: the mint runs to completion and the signal is
|
|
16
|
+
* what tells it to throw away the result rather than store it.
|
|
17
|
+
* @param name - The lock to hold, or `null` to run without one.
|
|
18
|
+
* @param mint - The work to serialise, taking the signal described above.
|
|
19
|
+
*/
|
|
20
|
+
export function withMintLock<T>(
|
|
21
|
+
name: string | null,
|
|
22
|
+
mint: (stolen: AbortSignal) => Promise<T>,
|
|
23
|
+
): Promise<T> {
|
|
24
|
+
const locks = globalThis.navigator?.locks;
|
|
25
|
+
if (name === null || locks === undefined) {
|
|
26
|
+
// Nothing can take a lock that was never held, so the signal never fires.
|
|
27
|
+
return mint(new AbortController().signal);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const stolen = new AbortController();
|
|
31
|
+
const held = locks.request(name, () => mint(stolen.signal)) as Promise<T>;
|
|
32
|
+
|
|
33
|
+
// A steal releases this tab's lock and rejects this promise, in this document.
|
|
34
|
+
// That rejection is the only notice another tab's sign-out can deliver, so it
|
|
35
|
+
// is turned into the signal `mint` was given rather than being surfaced: the
|
|
36
|
+
// mint decides what to do about it, and the caller sees whatever it returns.
|
|
37
|
+
held.catch(() => stolen.abort());
|
|
38
|
+
return held;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Takes the lock away from whoever holds it, without waiting for them.
|
|
43
|
+
*
|
|
44
|
+
* What signing out does. Queueing would make a sign-out the user asked for wait
|
|
45
|
+
* on a canister call in another tab, and a mint that finished afterwards would
|
|
46
|
+
* write a credential into the slot the sign-out had just cleared.
|
|
47
|
+
* @param name - The lock to take, or `null` where none is held.
|
|
48
|
+
*/
|
|
49
|
+
export async function stealMintLock(name: string | null): Promise<void> {
|
|
50
|
+
const locks = globalThis.navigator?.locks;
|
|
51
|
+
if (name === null || locks === undefined) return;
|
|
52
|
+
|
|
53
|
+
// Granted at once, and released at once: holding it would only make the caller
|
|
54
|
+
// the next thing a queued mint waits for.
|
|
55
|
+
await locks.request(name, { steal: true }, () => undefined).catch(() => undefined);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A lock this holder keeps until it gives it up, and a signal for losing it.
|
|
60
|
+
*
|
|
61
|
+
* Unlike {@link withMintLock}, what needs guarding here is not one call but a
|
|
62
|
+
* span — a ceremony is several calls and then several writes — so the lock is
|
|
63
|
+
* held across it and released by the holder rather than by a callback returning.
|
|
64
|
+
*/
|
|
65
|
+
export interface HeldLock {
|
|
66
|
+
/** Fires when someone else takes this lock away. */
|
|
67
|
+
readonly stolen: AbortSignal;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gives the lock up.
|
|
71
|
+
*
|
|
72
|
+
* Idempotent, and safe to call after it was stolen: releasing a lock this
|
|
73
|
+
* holder no longer has does nothing.
|
|
74
|
+
*/
|
|
75
|
+
release(): void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Takes a lock away from whoever holds it and keeps it until released.
|
|
80
|
+
*
|
|
81
|
+
* Taken rather than queued for, like {@link stealMintLock} and for the same
|
|
82
|
+
* reason: the newer intent is the one to honour. A user who presses sign in
|
|
83
|
+
* again means the second press, and an origin has one signer window, so the two
|
|
84
|
+
* cannot both proceed anyway. The incumbent finds out through {@link
|
|
85
|
+
* HeldLock.stolen} and throws its result away instead of writing it.
|
|
86
|
+
*
|
|
87
|
+
* Where there is no lock — an older browser, or a caller that passes `null`
|
|
88
|
+
* because nothing else could be holding one — the result never reports a steal
|
|
89
|
+
* and releasing does nothing. That costs coordination, never correctness:
|
|
90
|
+
* coordination may only ever suppress work, never be required for it.
|
|
91
|
+
* @param name - The lock to take, or `null` to take none.
|
|
92
|
+
*/
|
|
93
|
+
export function stealLock(name: string | null): HeldLock {
|
|
94
|
+
const stolen = new AbortController();
|
|
95
|
+
const locks = globalThis.navigator?.locks;
|
|
96
|
+
if (name === null || locks === undefined) {
|
|
97
|
+
return { stolen: stolen.signal, release: () => {} };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The callback holds the lock for as long as what it returns is pending, so
|
|
101
|
+
// this is what `release` settles. A steal rejects the `request` promise in this
|
|
102
|
+
// document, which is the only notice the platform gives.
|
|
103
|
+
let release: () => void = () => {};
|
|
104
|
+
const held = new Promise<void>((resolve) => {
|
|
105
|
+
release = resolve;
|
|
106
|
+
});
|
|
107
|
+
void locks
|
|
108
|
+
.request(name, { steal: true }, () => held)
|
|
109
|
+
.catch((error: unknown) => {
|
|
110
|
+
// Only a steal, which arrives as an `AbortError`. A request can fail for
|
|
111
|
+
// reasons that are nothing to do with another holder — a frame going away
|
|
112
|
+
// takes its callback with it — and reporting one of those as a steal would
|
|
113
|
+
// tell the caller its work was superseded when nothing had superseded it.
|
|
114
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
115
|
+
stolen.abort();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return { stolen: stolen.signal, release: () => release() };
|
|
120
|
+
}
|