@tokono.ma/firkin 0.3.0 → 0.4.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/README.md +84 -14
- package/dist/bao.d.ts +25 -0
- package/dist/bao.js +225 -0
- package/dist/client.d.ts +210 -10
- package/dist/client.js +309 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/plc.d.ts +87 -0
- package/dist/plc.js +97 -0
- package/dist/ucan.d.ts +7 -0
- package/dist/ucan.js +4 -1
- package/dist/webauthn.d.ts +56 -9
- package/dist/webauthn.js +83 -22
- package/package.json +5 -4
package/dist/webauthn.d.ts
CHANGED
|
@@ -6,15 +6,62 @@ export interface WebAuthnAssertion {
|
|
|
6
6
|
/** The authenticator's signature over authData || SHA-256(clientDataJSON). */
|
|
7
7
|
signature: Uint8Array;
|
|
8
8
|
}
|
|
9
|
+
/** Default session lease: 30 days (the server caps it). */
|
|
10
|
+
export declare const ATTESTATION_DEFAULT_TTL: number;
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* The challenge for a passkey session attestation: DAG-CBOR naming the
|
|
13
|
+
* account, the session key and the granted commands. Every field is inside
|
|
14
|
+
* `clientDataJSON.challenge`, hence covered by the authenticator's signature.
|
|
15
|
+
*
|
|
16
|
+
* Pass it to `navigator.credentials.get` as `publicKey.challenge`, then send
|
|
17
|
+
* the resulting assertion to `Firkin.attest()`.
|
|
16
18
|
*/
|
|
17
|
-
export declare function
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
export declare function attestationChallenge(opts: {
|
|
20
|
+
/** The API origin (`https://firkin.example`) — must match server config. */
|
|
21
|
+
firkin: string;
|
|
22
|
+
/** The passkey account DID. */
|
|
23
|
+
account: string;
|
|
24
|
+
/** The `did:key` session that will sign API calls. */
|
|
25
|
+
session: string;
|
|
26
|
+
/** Capability prefixes the session may exercise. */
|
|
27
|
+
commands: string[];
|
|
28
|
+
/** Lease in seconds (default 30 days, server-capped). */
|
|
29
|
+
expireIn?: number;
|
|
30
|
+
/** Override the start time (unix seconds) — tests only. */
|
|
31
|
+
now?: number;
|
|
32
|
+
}): Uint8Array;
|
|
33
|
+
/**
|
|
34
|
+
* The challenge for a step-up action (revocation): bound to one account and
|
|
35
|
+
* one command, short-lived and single-use. Sent in the `X-Firkin-Assertion`
|
|
36
|
+
* header alongside an ordinary session invocation.
|
|
37
|
+
*/
|
|
38
|
+
export declare function stepUpChallenge(opts: {
|
|
39
|
+
firkin: string;
|
|
40
|
+
account: string;
|
|
41
|
+
/** The capability the action requires, e.g. "/firkin/account/revoke". */
|
|
42
|
+
action: string;
|
|
43
|
+
/** Seconds until expiry (default 120; server caps at 300). */
|
|
44
|
+
expireIn?: number;
|
|
45
|
+
/** Override the start time (unix seconds) — tests only. */
|
|
46
|
+
now?: number;
|
|
47
|
+
}): Uint8Array;
|
|
48
|
+
/**
|
|
49
|
+
* The challenge for a recovery grant: authorizes `recovery` (a did:key) to
|
|
50
|
+
* call `POST /v1/account/recover` for this account. Omit `expireIn` for an
|
|
51
|
+
* indefinite grant (revoke it by its attestation CID).
|
|
52
|
+
*/
|
|
53
|
+
export declare function recoveryChallenge(opts: {
|
|
54
|
+
firkin: string;
|
|
55
|
+
account: string;
|
|
56
|
+
recovery: string;
|
|
57
|
+
expireIn?: number;
|
|
58
|
+
now?: number;
|
|
59
|
+
}): Uint8Array;
|
|
60
|
+
/** The JSON body expected by `POST /v1/tokens/attestation`. */
|
|
61
|
+
export declare function packageAssertion(assertion: WebAuthnAssertion): {
|
|
62
|
+
ad: string;
|
|
63
|
+
cd: string;
|
|
64
|
+
sg: string;
|
|
20
65
|
};
|
|
66
|
+
/** The `X-Firkin-Assertion` header value (base64url DAG-CBOR). */
|
|
67
|
+
export declare function assertionHeader(assertion: WebAuthnAssertion): string;
|
package/dist/webauthn.js
CHANGED
|
@@ -1,29 +1,90 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Passkey attestations: a WebAuthn assertion whose challenge is a Firkin
|
|
2
|
+
// attestation (`firkin/attestation/passkey`) or step-up (`firkin/step-up`)
|
|
3
|
+
// payload.
|
|
4
|
+
//
|
|
5
|
+
// An assertion is NOT a UCAN proof. The account key lives in the
|
|
6
|
+
// authenticator and only signs `authData || SHA-256(clientDataJSON)`, so the
|
|
7
|
+
// assertion enrols a `did:key` session (or authorizes one account-level
|
|
8
|
+
// action) and is verified once by the server. Tokens are then ordinary
|
|
9
|
+
// self-issued invocations by the session key.
|
|
5
10
|
import * as dagcbor from "@ipld/dag-cbor";
|
|
6
|
-
import {
|
|
7
|
-
|
|
11
|
+
import { randomBytes } from "@noble/hashes/utils";
|
|
12
|
+
/** Default session lease: 30 days (the server caps it). */
|
|
13
|
+
export const ATTESTATION_DEFAULT_TTL = 30 * 24 * 60 * 60;
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* The challenge for a passkey session attestation: DAG-CBOR naming the
|
|
16
|
+
* account, the session key and the granted commands. Every field is inside
|
|
17
|
+
* `clientDataJSON.challenge`, hence covered by the authenticator's signature.
|
|
18
|
+
*
|
|
19
|
+
* Pass it to `navigator.credentials.get` as `publicKey.challenge`, then send
|
|
20
|
+
* the resulting assertion to `Firkin.attest()`.
|
|
15
21
|
*/
|
|
16
|
-
export function
|
|
17
|
-
const
|
|
22
|
+
export function attestationChallenge(opts) {
|
|
23
|
+
const nbf = opts.now ?? Math.floor(Date.now() / 1000);
|
|
24
|
+
const exp = nbf + (opts.expireIn ?? ATTESTATION_DEFAULT_TTL);
|
|
25
|
+
return dagcbor.encode({
|
|
26
|
+
$type: "firkin/attestation/passkey",
|
|
27
|
+
firkin: opts.firkin,
|
|
28
|
+
account: opts.account,
|
|
29
|
+
session: opts.session,
|
|
30
|
+
commands: opts.commands,
|
|
31
|
+
nbf,
|
|
32
|
+
exp,
|
|
33
|
+
nonce: randomBytes(16),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The challenge for a step-up action (revocation): bound to one account and
|
|
38
|
+
* one command, short-lived and single-use. Sent in the `X-Firkin-Assertion`
|
|
39
|
+
* header alongside an ordinary session invocation.
|
|
40
|
+
*/
|
|
41
|
+
export function stepUpChallenge(opts) {
|
|
42
|
+
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
43
|
+
return dagcbor.encode({
|
|
44
|
+
$type: "firkin/step-up",
|
|
45
|
+
firkin: opts.firkin,
|
|
46
|
+
account: opts.account,
|
|
47
|
+
action: opts.action,
|
|
48
|
+
exp: now + (opts.expireIn ?? 120),
|
|
49
|
+
nonce: randomBytes(16),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The challenge for a recovery grant: authorizes `recovery` (a did:key) to
|
|
54
|
+
* call `POST /v1/account/recover` for this account. Omit `expireIn` for an
|
|
55
|
+
* indefinite grant (revoke it by its attestation CID).
|
|
56
|
+
*/
|
|
57
|
+
export function recoveryChallenge(opts) {
|
|
58
|
+
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
59
|
+
return dagcbor.encode({
|
|
60
|
+
$type: "firkin/recovery/passkey",
|
|
61
|
+
firkin: opts.firkin,
|
|
62
|
+
account: opts.account,
|
|
63
|
+
recovery: opts.recovery,
|
|
64
|
+
exp: opts.expireIn === undefined ? null : now + opts.expireIn,
|
|
65
|
+
nonce: randomBytes(16),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** The JSON body expected by `POST /v1/tokens/attestation`. */
|
|
69
|
+
export function packageAssertion(assertion) {
|
|
70
|
+
return {
|
|
71
|
+
ad: b64url(assertion.authData),
|
|
72
|
+
// The EXACT clientDataJSON text — the server hashes those bytes.
|
|
73
|
+
cd: b64url(new TextEncoder().encode(assertion.clientDataJSON)),
|
|
74
|
+
sg: b64url(assertion.signature),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** The `X-Firkin-Assertion` header value (base64url DAG-CBOR). */
|
|
78
|
+
export function assertionHeader(assertion) {
|
|
79
|
+
return b64url(dagcbor.encode({
|
|
18
80
|
ad: assertion.authData,
|
|
19
81
|
cd: assertion.clientDataJSON,
|
|
20
82
|
sg: assertion.signature,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return { proof, cid: cidForBytes(envelope, { codec: 0x71 }) };
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
function b64url(bytes) {
|
|
86
|
+
let binary = "";
|
|
87
|
+
for (const byte of bytes)
|
|
88
|
+
binary += String.fromCharCode(byte);
|
|
89
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
29
90
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokono.ma/firkin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Browser-first TypeScript client SDK for the Firkin API (UCAN 1.0, DASL CIDs, CAR Mirror sync)",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,13 +42,14 @@
|
|
|
42
42
|
"@ipld/dag-cbor": "^9.2.0",
|
|
43
43
|
"@noble/ed25519": "^1.7.3",
|
|
44
44
|
"@noble/hashes": "^1.5.0",
|
|
45
|
+
"@scure/bip39": "^1.5.0",
|
|
45
46
|
"bs58": "^6.0.0",
|
|
46
|
-
"multiformats": "^13.0.0",
|
|
47
47
|
"hash-wasm": "^4.12.0",
|
|
48
|
-
"
|
|
48
|
+
"multiformats": "^13.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"typescript": "^5.7.0"
|
|
51
|
+
"typescript": "^5.7.0",
|
|
52
|
+
"blake3-bao": "1.3.4"
|
|
52
53
|
},
|
|
53
54
|
"engines": {
|
|
54
55
|
"node": ">=22"
|