@abraca/dabra 2.21.0 → 2.22.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/abracadabra-provider.cjs +35 -6
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +35 -6
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/package.json +2 -2
- package/src/AbracadabraClient.ts +35 -3
- package/src/DocKeyManager.ts +7 -2
- package/src/TokenManager.ts +9 -1
package/dist/index.d.ts
CHANGED
|
@@ -357,7 +357,14 @@ declare class AbracadabraClient {
|
|
|
357
357
|
listKeys(): Promise<PublicKeyInfo[]>;
|
|
358
358
|
/** Rename a registered device key. */
|
|
359
359
|
renameKey(keyId: string, deviceName: string): Promise<void>;
|
|
360
|
-
/**
|
|
360
|
+
/**
|
|
361
|
+
* Revoke a public key by its ID. The server treats this as a panic
|
|
362
|
+
* action: every outstanding JWT for the account is invalidated (a stolen
|
|
363
|
+
* device's token must die immediately, and tokens carry no per-key
|
|
364
|
+
* claim), and a fresh token for THIS session is returned and adopted
|
|
365
|
+
* automatically — so the device performing the revocation stays
|
|
366
|
+
* authenticated while every other device silently re-auths.
|
|
367
|
+
*/
|
|
361
368
|
revokeKey(keyId: string): Promise<void>;
|
|
362
369
|
/** Create a single-use device invite code for pairing a new device to this account. */
|
|
363
370
|
createDeviceInvite(opts?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abraca/dabra",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.22.0",
|
|
4
4
|
"description": "abracadabra provider",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abracadabra",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"yjs": "^13.6.8"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@abraca/schema": "2.
|
|
44
|
+
"@abraca/schema": "2.22.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"test": "node --no-warnings --conditions=source --experimental-transform-types --test 'tests/*.test.ts'"
|
package/src/AbracadabraClient.ts
CHANGED
|
@@ -31,6 +31,21 @@ function fromBase64(b64: string): Uint8Array {
|
|
|
31
31
|
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Decode a base64url-encoded string (e.g. a JWT header/payload segment) to
|
|
36
|
+
* UTF-8 text. `atob` only accepts standard base64, so we translate the
|
|
37
|
+
* url-safe alphabet (`-`/`_`) and re-pad before decoding — otherwise a
|
|
38
|
+
* perfectly valid token whose payload happens to contain `-` or `_` throws
|
|
39
|
+
* and gets misclassified as invalid/expired.
|
|
40
|
+
*/
|
|
41
|
+
function decodeBase64UrlToString(b64url: string): string {
|
|
42
|
+
let b64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
43
|
+
const pad = b64.length % 4;
|
|
44
|
+
if (pad) b64 += "=".repeat(4 - pad);
|
|
45
|
+
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
46
|
+
return new TextDecoder().decode(bytes);
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
/**
|
|
35
50
|
* Reason classifications surfaced to `onAuthFailed`. Consumers can decide
|
|
36
51
|
* whether to silently re-register the keypair (`user_not_found`) or
|
|
@@ -142,7 +157,10 @@ export class AbracadabraClient {
|
|
|
142
157
|
if (!this._token) return false;
|
|
143
158
|
try {
|
|
144
159
|
const [, payload] = this._token.split(".");
|
|
145
|
-
|
|
160
|
+
// JWT payloads are base64url — `atob` only handles standard base64,
|
|
161
|
+
// so a payload containing `-` or `_` would throw and a valid token
|
|
162
|
+
// would be misread as expired, forcing a spurious re-auth.
|
|
163
|
+
const { exp } = JSON.parse(decodeBase64UrlToString(payload));
|
|
146
164
|
return typeof exp === "number" && exp * 1000 > Date.now();
|
|
147
165
|
} catch {
|
|
148
166
|
return false;
|
|
@@ -262,9 +280,23 @@ export class AbracadabraClient {
|
|
|
262
280
|
await this.request("PATCH", `/auth/keys/${encodeURIComponent(keyId)}`, { body: { deviceName } });
|
|
263
281
|
}
|
|
264
282
|
|
|
265
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* Revoke a public key by its ID. The server treats this as a panic
|
|
285
|
+
* action: every outstanding JWT for the account is invalidated (a stolen
|
|
286
|
+
* device's token must die immediately, and tokens carry no per-key
|
|
287
|
+
* claim), and a fresh token for THIS session is returned and adopted
|
|
288
|
+
* automatically — so the device performing the revocation stays
|
|
289
|
+
* authenticated while every other device silently re-auths.
|
|
290
|
+
*/
|
|
266
291
|
async revokeKey(keyId: string): Promise<void> {
|
|
267
|
-
await this.request
|
|
292
|
+
const res = await this.request<{ token?: string } | null>(
|
|
293
|
+
"DELETE",
|
|
294
|
+
`/auth/keys/${encodeURIComponent(keyId)}`,
|
|
295
|
+
);
|
|
296
|
+
// Older servers respond 204 with no body — keep working against them.
|
|
297
|
+
if (res && typeof res === "object" && typeof res.token === "string") {
|
|
298
|
+
this.token = res.token;
|
|
299
|
+
}
|
|
268
300
|
}
|
|
269
301
|
|
|
270
302
|
// ── Device Invites ───────────────────────────────────────────────────────
|
package/src/DocKeyManager.ts
CHANGED
|
@@ -14,7 +14,12 @@ import type { CryptoIdentityKeystore } from "./CryptoIdentityKeystore.ts";
|
|
|
14
14
|
const HKDF_INFO = new TextEncoder().encode("abracadabra-dockey-v1");
|
|
15
15
|
|
|
16
16
|
function fromBase64(b64: string): Uint8Array {
|
|
17
|
-
|
|
17
|
+
// Accept both standard base64 and base64url, padded or not — server
|
|
18
|
+
// payloads mix the two alphabets depending on the field.
|
|
19
|
+
let normalized = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
20
|
+
const pad = normalized.length % 4;
|
|
21
|
+
if (pad) normalized += "=".repeat(4 - pad);
|
|
22
|
+
return Uint8Array.from(atob(normalized), (c) => c.charCodeAt(0));
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export class DocKeyManager {
|
|
@@ -84,7 +89,7 @@ export class DocKeyManager {
|
|
|
84
89
|
const myKeys = await client.listUserKeys(me.id);
|
|
85
90
|
const primaryKey = myKeys[0];
|
|
86
91
|
if (primaryKey?.x25519Key) {
|
|
87
|
-
const x25519Pub = fromBase64(primaryKey.x25519Key
|
|
92
|
+
const x25519Pub = fromBase64(primaryKey.x25519Key);
|
|
88
93
|
const rewrapped = await this.wrapKeyForRecipient(docKey, x25519Pub, docId);
|
|
89
94
|
const b64 = (() => {
|
|
90
95
|
let s = "";
|
package/src/TokenManager.ts
CHANGED
|
@@ -270,7 +270,15 @@ export class TokenManager extends EventEmitter {
|
|
|
270
270
|
try {
|
|
271
271
|
const [, payload] = jwt.split(".");
|
|
272
272
|
if (!payload) return null;
|
|
273
|
-
|
|
273
|
+
// base64url-safe: `atob` alone throws on `-`/`_`, which would make
|
|
274
|
+
// the proactive-refresh timer treat a valid token as unparseable.
|
|
275
|
+
let b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
276
|
+
const pad = b64.length % 4;
|
|
277
|
+
if (pad) b64 += "=".repeat(4 - pad);
|
|
278
|
+
const json = new TextDecoder().decode(
|
|
279
|
+
Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)),
|
|
280
|
+
);
|
|
281
|
+
const { exp } = JSON.parse(json);
|
|
274
282
|
return typeof exp === "number" ? exp : null;
|
|
275
283
|
} catch {
|
|
276
284
|
return null;
|