@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.
Files changed (85) hide show
  1. package/dist/esm/client/app-delegation-source.d.ts +65 -0
  2. package/dist/esm/client/app-delegation-source.js +58 -0
  3. package/dist/esm/client/app-delegation-source.js.map +1 -0
  4. package/dist/esm/client/app-lock.d.ts +63 -0
  5. package/dist/esm/client/app-lock.js +92 -0
  6. package/dist/esm/client/app-lock.js.map +1 -0
  7. package/dist/esm/client/auth-client.d.ts +270 -53
  8. package/dist/esm/client/auth-client.js +1017 -414
  9. package/dist/esm/client/auth-client.js.map +1 -1
  10. package/dist/esm/client/base64.d.ts +8 -0
  11. package/dist/esm/client/base64.js +28 -0
  12. package/dist/esm/client/base64.js.map +1 -0
  13. package/dist/esm/client/cookie-state-storage.d.ts +59 -0
  14. package/dist/esm/client/cookie-state-storage.js +245 -0
  15. package/dist/esm/client/cookie-state-storage.js.map +1 -0
  16. package/dist/esm/client/credential-storage.d.ts +48 -0
  17. package/dist/esm/client/credential-storage.js +2 -0
  18. package/dist/esm/client/credential-storage.js.map +1 -0
  19. package/dist/esm/client/db.d.ts +6 -1
  20. package/dist/esm/client/db.js +56 -15
  21. package/dist/esm/client/db.js.map +1 -1
  22. package/dist/esm/client/foreground-refresh.d.ts +35 -0
  23. package/dist/esm/client/foreground-refresh.js +83 -0
  24. package/dist/esm/client/foreground-refresh.js.map +1 -0
  25. package/dist/esm/client/idb-credential-storage.d.ts +25 -0
  26. package/dist/esm/client/idb-credential-storage.js +69 -0
  27. package/dist/esm/client/idb-credential-storage.js.map +1 -0
  28. package/dist/esm/client/index.d.ts +14 -3
  29. package/dist/esm/client/index.js +13 -3
  30. package/dist/esm/client/index.js.map +1 -1
  31. package/dist/esm/client/local-credential-storage.d.ts +26 -0
  32. package/dist/esm/client/local-credential-storage.js +59 -0
  33. package/dist/esm/client/local-credential-storage.js.map +1 -0
  34. package/dist/esm/client/memory-credential-storage.d.ts +22 -0
  35. package/dist/esm/client/memory-credential-storage.js +35 -0
  36. package/dist/esm/client/memory-credential-storage.js.map +1 -0
  37. package/dist/esm/client/session-delegation.d.ts +40 -0
  38. package/dist/esm/client/session-delegation.js +100 -0
  39. package/dist/esm/client/session-delegation.js.map +1 -0
  40. package/dist/esm/client/session-identity.d.ts +97 -0
  41. package/dist/esm/client/session-identity.js +392 -0
  42. package/dist/esm/client/session-identity.js.map +1 -0
  43. package/dist/esm/client/session-minter.d.ts +94 -0
  44. package/dist/esm/client/session-minter.js +178 -0
  45. package/dist/esm/client/session-minter.js.map +1 -0
  46. package/dist/esm/client/shared-memory-credential-storage.d.ts +44 -0
  47. package/dist/esm/client/shared-memory-credential-storage.js +243 -0
  48. package/dist/esm/client/shared-memory-credential-storage.js.map +1 -0
  49. package/dist/esm/client/slots.d.ts +59 -0
  50. package/dist/esm/client/slots.js +67 -0
  51. package/dist/esm/client/slots.js.map +1 -0
  52. package/dist/esm/client/sso.d.ts +33 -0
  53. package/dist/esm/client/sso.js +120 -0
  54. package/dist/esm/client/sso.js.map +1 -0
  55. package/dist/esm/client/state-storage.d.ts +135 -0
  56. package/dist/esm/client/state-storage.js +178 -0
  57. package/dist/esm/client/state-storage.js.map +1 -0
  58. package/package.json +5 -4
  59. package/src/client/app-delegation-source.ts +86 -0
  60. package/src/client/app-lock.ts +120 -0
  61. package/src/client/auth-client.ts +1300 -487
  62. package/src/client/base64.ts +28 -0
  63. package/src/client/cookie-state-storage.ts +277 -0
  64. package/src/client/credential-storage.ts +56 -0
  65. package/src/client/db.ts +56 -12
  66. package/src/client/foreground-refresh.ts +92 -0
  67. package/src/client/idb-credential-storage.ts +82 -0
  68. package/src/client/index.ts +24 -8
  69. package/src/client/local-credential-storage.ts +68 -0
  70. package/src/client/memory-credential-storage.ts +41 -0
  71. package/src/client/session-delegation.ts +138 -0
  72. package/src/client/session-identity.ts +510 -0
  73. package/src/client/session-minter.ts +277 -0
  74. package/src/client/shared-memory-credential-storage.ts +303 -0
  75. package/src/client/slots.ts +78 -0
  76. package/src/client/sso.ts +122 -0
  77. package/src/client/state-storage.ts +284 -0
  78. package/dist/esm/client/idle-manager.d.ts +0 -83
  79. package/dist/esm/client/idle-manager.js +0 -100
  80. package/dist/esm/client/idle-manager.js.map +0 -1
  81. package/dist/esm/client/storage.d.ts +0 -51
  82. package/dist/esm/client/storage.js +0 -90
  83. package/dist/esm/client/storage.js.map +0 -1
  84. package/src/client/idle-manager.ts +0 -162
  85. package/src/client/storage.ts +0 -114
@@ -2,13 +2,29 @@
2
2
  * @module api/client
3
3
  */
4
4
 
5
+ export { type AppDelegationSource, SessionGoneError } from './app-delegation-source.js';
5
6
  export * from './auth-client.js';
6
- export { type DBCreateOptions, IdbKeyVal } from './db.js';
7
- export * from './idle-manager.js';
8
7
  export {
9
- type AuthClientStorage,
10
- IdbStorage,
11
- KEY_STORAGE_DELEGATION,
12
- KEY_STORAGE_KEY,
13
- LocalStorage,
14
- } from './storage.js';
8
+ CookieStateStorage,
9
+ type CookieStateStorageOptions,
10
+ } from './cookie-state-storage.js';
11
+ export type { Credential, CredentialStorage } from './credential-storage.js';
12
+ export { DB_VERSION, type DBCreateOptions, IdbKeyVal } from './db.js';
13
+ export { watchActivity, watchForeground } from './foreground-refresh.js';
14
+ export { IdbCredentialStorage } from './idb-credential-storage.js';
15
+ export { LocalCredentialStorage } from './local-credential-storage.js';
16
+ export { MemoryCredentialStorage } from './memory-credential-storage.js';
17
+ export { InteractionRequiredError, requestSessionDelegation } from './session-delegation.js';
18
+ export { SessionIdentity, type SessionIdentityOptions } from './session-identity.js';
19
+ export { SessionMinter, type SessionMinterOptions } from './session-minter.js';
20
+ export {
21
+ DEFAULT_SHARED_MEMORY_NAME,
22
+ SharedMemoryCredentialStorage,
23
+ } from './shared-memory-credential-storage.js';
24
+ export { isValidSsoDomain } from './sso.js';
25
+ export {
26
+ LocalStateStorage,
27
+ MemoryStateStorage,
28
+ type SessionState,
29
+ type StateStorage,
30
+ } from './state-storage.js';
@@ -0,0 +1,68 @@
1
+ import { DelegationChain, Ed25519KeyIdentity } from '@icp-sdk/core/identity';
2
+ import type { Credential, CredentialStorage } from './credential-storage.js';
3
+
4
+ /** What one slot holds: the key as JSON, and the chain as JSON. */
5
+ interface StoredCredential {
6
+ key: string;
7
+ chain?: string;
8
+ }
9
+
10
+ /**
11
+ * Credentials in `localStorage`, for where IndexedDB is unavailable.
12
+ *
13
+ * `localStorage` holds strings, so the key has to be one: this generates an
14
+ * Ed25519 identity, whose private bytes serialise, rather than the
15
+ * non-extractable key {@link IdbCredentialStorage} uses. That is the trade the
16
+ * medium imposes, and it is why generation belongs to the store.
17
+ * @see implements {@link CredentialStorage}
18
+ */
19
+ export class LocalCredentialStorage implements CredentialStorage<Ed25519KeyIdentity> {
20
+ public readonly shared = true;
21
+ public readonly durable = true;
22
+
23
+ /**
24
+ * @param prefix - Prepended to every slot, so two clients under one origin can
25
+ * be kept apart.
26
+ */
27
+ constructor(public readonly prefix = 'ic-') {}
28
+
29
+ public create(): Promise<Ed25519KeyIdentity> {
30
+ return Promise.resolve(Ed25519KeyIdentity.generate());
31
+ }
32
+
33
+ public get(slot: string): Promise<Credential<Ed25519KeyIdentity> | null> {
34
+ const raw = this.#localStorage().getItem(this.prefix + slot);
35
+ if (raw === null) return Promise.resolve(null);
36
+
37
+ try {
38
+ const stored = JSON.parse(raw) as StoredCredential;
39
+ const identity = Ed25519KeyIdentity.fromJSON(stored.key);
40
+ const chain = stored.chain === undefined ? undefined : DelegationChain.fromJSON(stored.chain);
41
+ return Promise.resolve({ identity, chain });
42
+ } catch {
43
+ return Promise.resolve(null);
44
+ }
45
+ }
46
+
47
+ public set(slot: string, credential: Credential<Ed25519KeyIdentity>): Promise<void> {
48
+ const stored: StoredCredential = {
49
+ key: JSON.stringify(credential.identity.toJSON()),
50
+ chain: credential.chain && JSON.stringify(credential.chain.toJSON()),
51
+ };
52
+ this.#localStorage().setItem(this.prefix + slot, JSON.stringify(stored));
53
+ return Promise.resolve();
54
+ }
55
+
56
+ public remove(slot: string): Promise<void> {
57
+ this.#localStorage().removeItem(this.prefix + slot);
58
+ return Promise.resolve();
59
+ }
60
+
61
+ #localStorage(): Storage {
62
+ const ls = globalThis.localStorage;
63
+ if (!ls) {
64
+ throw new Error('Could not find local storage.');
65
+ }
66
+ return ls;
67
+ }
68
+ }
@@ -0,0 +1,41 @@
1
+ import { ECDSAKeyIdentity } from '@icp-sdk/core/identity';
2
+ import type { Credential, CredentialStorage } from './credential-storage.js';
3
+
4
+ /**
5
+ * Credentials held for the lifetime of the instance and shared with nothing.
6
+ *
7
+ * For tests and for environments with neither IndexedDB nor `localStorage`. A
8
+ * reload starts from nothing, and other tabs see nothing, so every tab of an
9
+ * origin is alone.
10
+ *
11
+ * Records are handed back as they were given rather than round-tripped through
12
+ * an encoding, which is what lets this hold a non-extractable key at all.
13
+ * @see implements {@link CredentialStorage}
14
+ */
15
+ export class MemoryCredentialStorage implements CredentialStorage<ECDSAKeyIdentity> {
16
+ public readonly shared = false;
17
+ public readonly durable = false;
18
+
19
+ // On the instance and never on the module: two clients on one page must not
20
+ // see each other's slots, and no state may carry between tests in one process.
21
+ #credentials = new Map<string, Credential<ECDSAKeyIdentity>>();
22
+
23
+ public create(): Promise<ECDSAKeyIdentity> {
24
+ // Nothing is serialised here, so the key never has to be readable.
25
+ return ECDSAKeyIdentity.generate({ extractable: false });
26
+ }
27
+
28
+ public get(slot: string): Promise<Credential<ECDSAKeyIdentity> | null> {
29
+ return Promise.resolve(this.#credentials.get(slot) ?? null);
30
+ }
31
+
32
+ public set(slot: string, credential: Credential<ECDSAKeyIdentity>): Promise<void> {
33
+ this.#credentials.set(slot, credential);
34
+ return Promise.resolve();
35
+ }
36
+
37
+ public remove(slot: string): Promise<void> {
38
+ this.#credentials.delete(slot);
39
+ return Promise.resolve();
40
+ }
41
+ }
@@ -0,0 +1,138 @@
1
+ import type { DerEncodedPublicKey } from '@icp-sdk/core/agent';
2
+ import { Delegation, DelegationChain } from '@icp-sdk/core/identity';
3
+ import { Principal } from '@icp-sdk/core/principal';
4
+ import type { Signer } from '@icp-sdk/signer';
5
+ import { fromBase64, toBase64 } from './base64.js';
6
+
7
+ /**
8
+ * The method Internet Identity answers with a session rather than a long-lived
9
+ * delegation. Named for its provider because a JSON-RPC method travels over a
10
+ * transport shared with other signers, unlike a canister method.
11
+ */
12
+ const SESSION_DELEGATION_METHOD = 'ii_session_delegation';
13
+
14
+ /**
15
+ * The code Internet Identity denies a `prompt: 'none'` request with.
16
+ *
17
+ * In ICRC-25's 3xxx user-action range, which is what lets a caller tell a request
18
+ * that needs a ceremony apart from a transport or protocol failure.
19
+ */
20
+ const INTERACTION_REQUIRED_CODE = 3002;
21
+
22
+ /**
23
+ * Internet Identity had nothing to answer a silent request from.
24
+ *
25
+ * The one denial that is not a failure: it says a ceremony is needed, so the
26
+ * caller signs in interactively rather than retrying. It is also the only
27
+ * dependable evidence that a shared record is stale rather than replaced, which
28
+ * is what lets an origin retract a record it did not write.
29
+ *
30
+ * Thrown only for a request that asked not to render anything. An interactive
31
+ * sign-in that the user abandons is a different outcome and is not this.
32
+ */
33
+ export class InteractionRequiredError extends Error {
34
+ /**
35
+ * Why, where Internet Identity said: `login_required` where it holds no
36
+ * session, `account_selection_required` where it holds more than one and the
37
+ * request named none.
38
+ *
39
+ * A caller may word its prompt from this and does not have to branch on it, so
40
+ * it is deliberately a string rather than a union — a value this library does
41
+ * not know is not a reason to fail.
42
+ */
43
+ readonly reason: string | undefined;
44
+
45
+ constructor(message: string, reason?: string) {
46
+ super(message);
47
+ this.name = 'InteractionRequiredError';
48
+ this.reason = reason;
49
+ }
50
+ }
51
+
52
+ interface SessionDelegationResult {
53
+ publicKey: string;
54
+ signerDelegation: {
55
+ delegation: { pubkey: string; expiration: string; targets?: string[] };
56
+ signature: string;
57
+ }[];
58
+ }
59
+
60
+ const looksLikeResult = (value: unknown): value is SessionDelegationResult => {
61
+ const result = value as SessionDelegationResult | undefined;
62
+ return (
63
+ typeof result?.publicKey === 'string' &&
64
+ Array.isArray(result?.signerDelegation) &&
65
+ // An empty list builds a chain whose earliest expiry is 0, so it reads as a
66
+ // session that expired at the epoch rather than as a bad response.
67
+ result.signerDelegation.length > 0
68
+ );
69
+ };
70
+
71
+ /**
72
+ * Asks the identity provider for a session, and returns the chain it signed.
73
+ *
74
+ * The chain is restricted to the Internet Identity canister, so it is not
75
+ * something an application can call its own canisters with. It is what app
76
+ * delegations are minted from.
77
+ */
78
+ export async function requestSessionDelegation(
79
+ signer: Signer,
80
+ params: {
81
+ sessionPublicKey: DerEncodedPublicKey;
82
+ maxTimeToLive?: bigint;
83
+ maxTimeToIdle?: bigint;
84
+ derivationOrigin?: string;
85
+ },
86
+ ): Promise<DelegationChain> {
87
+ const response = await signer.sendRequest({
88
+ jsonrpc: '2.0',
89
+ id: globalThis.crypto.randomUUID(),
90
+ method: SESSION_DELEGATION_METHOD,
91
+ params: {
92
+ sessionPublicKey: toBase64(params.sessionPublicKey),
93
+ ...(params.maxTimeToLive === undefined
94
+ ? {}
95
+ : { maxTimeToLive: params.maxTimeToLive.toString() }),
96
+ ...(params.maxTimeToIdle === undefined
97
+ ? {}
98
+ : { maxTimeToIdle: params.maxTimeToIdle.toString() }),
99
+ ...(params.derivationOrigin === undefined
100
+ ? {}
101
+ : { icrc95DerivationOrigin: params.derivationOrigin }),
102
+ },
103
+ });
104
+
105
+ if ('error' in response) {
106
+ // The code carries the only thing a caller can act on, and dropping it left
107
+ // `interaction_required` indistinguishable from a transport failure except by
108
+ // matching a message this library does not define.
109
+ if (response.error.code === INTERACTION_REQUIRED_CODE) {
110
+ const data = response.error.data;
111
+ const reason =
112
+ typeof data === 'object' && data !== null && 'reason' in data
113
+ ? String((data as { reason: unknown }).reason)
114
+ : undefined;
115
+ throw new InteractionRequiredError(response.error.message, reason);
116
+ }
117
+ throw new Error(response.error.message);
118
+ }
119
+
120
+ const result = response.result;
121
+ if (!looksLikeResult(result)) {
122
+ throw new Error(
123
+ 'Invalid session response: missing publicKey, or missing or empty signerDelegation',
124
+ );
125
+ }
126
+
127
+ return DelegationChain.fromDelegations(
128
+ result.signerDelegation.map(({ delegation, signature }) => ({
129
+ delegation: new Delegation(
130
+ fromBase64(delegation.pubkey),
131
+ BigInt(delegation.expiration),
132
+ delegation.targets?.map((target) => Principal.fromText(target)),
133
+ ),
134
+ signature: fromBase64(signature) as DelegationChain['delegations'][number]['signature'],
135
+ })),
136
+ fromBase64(result.publicKey) as DerEncodedPublicKey,
137
+ );
138
+ }