@atproto/space 0.0.0-spaces-alpha-20260818022935
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 +11 -0
- package/LICENSE.txt +7 -0
- package/dist/commit.d.ts +25 -0
- package/dist/commit.d.ts.map +1 -0
- package/dist/commit.js +80 -0
- package/dist/commit.js.map +1 -0
- package/dist/credential.d.ts +77 -0
- package/dist/credential.d.ts.map +1 -0
- package/dist/credential.js +160 -0
- package/dist/credential.js.map +1 -0
- package/dist/dpop.d.ts +40 -0
- package/dist/dpop.d.ts.map +1 -0
- package/dist/dpop.js +96 -0
- package/dist/dpop.js.map +1 -0
- package/dist/error.d.ts +12 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +21 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lthash.d.ts +18 -0
- package/dist/lthash.d.ts.map +1 -0
- package/dist/lthash.js +68 -0
- package/dist/lthash.js.map +1 -0
- package/dist/repo-commit.d.ts +51 -0
- package/dist/repo-commit.d.ts.map +1 -0
- package/dist/repo-commit.js +137 -0
- package/dist/repo-commit.js.map +1 -0
- package/dist/space-members.d.ts +27 -0
- package/dist/space-members.d.ts.map +1 -0
- package/dist/space-members.js +111 -0
- package/dist/space-members.js.map +1 -0
- package/dist/space-repo.d.ts +33 -0
- package/dist/space-repo.d.ts.map +1 -0
- package/dist/space-repo.js +136 -0
- package/dist/space-repo.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +19 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory-members-storage.d.ts +12 -0
- package/dist/storage/memory-members-storage.d.ts.map +1 -0
- package/dist/storage/memory-members-storage.js +36 -0
- package/dist/storage/memory-members-storage.js.map +1 -0
- package/dist/storage/memory-repo-storage.d.ts +17 -0
- package/dist/storage/memory-repo-storage.d.ts.map +1 -0
- package/dist/storage/memory-repo-storage.js +56 -0
- package/dist/storage/memory-repo-storage.js.map +1 -0
- package/dist/storage/types.d.ts +14 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +3 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/sync/car.d.ts +13 -0
- package/dist/sync/car.d.ts.map +1 -0
- package/dist/sync/car.js +185 -0
- package/dist/sync/car.js.map +1 -0
- package/dist/sync/consumer.d.ts +39 -0
- package/dist/sync/consumer.d.ts.map +1 -0
- package/dist/sync/consumer.js +89 -0
- package/dist/sync/consumer.js.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/provider.d.ts +25 -0
- package/dist/sync/provider.d.ts.map +1 -0
- package/dist/sync/provider.js +60 -0
- package/dist/sync/provider.js.map +1 -0
- package/dist/sync/repo-index.d.ts +19 -0
- package/dist/sync/repo-index.d.ts.map +1 -0
- package/dist/sync/repo-index.js +58 -0
- package/dist/sync/repo-index.js.map +1 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +14 -0
- package/dist/util.js.map +1 -0
- package/package.json +44 -0
- package/src/credential.ts +262 -0
- package/src/dpop.ts +166 -0
- package/src/error.ts +38 -0
- package/src/index.ts +8 -0
- package/src/lthash.ts +80 -0
- package/src/repo-commit.ts +171 -0
- package/src/sync/consumer.ts +149 -0
- package/src/sync/index.ts +2 -0
- package/src/sync/provider.ts +82 -0
- package/src/types.ts +62 -0
- package/src/util.ts +21 -0
- package/tests/credential.test.ts +323 -0
- package/tests/dpop.test.ts +234 -0
- package/tests/lthash.test.ts +97 -0
- package/tests/repo-commit.test.ts +281 -0
- package/tests/sync.test.ts +385 -0
- package/tsconfig.build.json +16 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +7 -0
- package/tsconfig.test.json +11 -0
- package/tsconfig.test.tsbuildinfo +1 -0
- package/tsconfig.tests.tsbuildinfo +1 -0
- package/vitest.config.ts +5 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @atproto/space
|
|
2
|
+
|
|
3
|
+
## 0.0.0-spaces-alpha-20260818022935
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5423](https://github.com/bluesky-social/atproto/pull/5423) [`2f77206`](https://github.com/bluesky-social/atproto/commit/2f77206ff8010710db5733c454abff9eb5ebb96b) Thanks [@dholms](https://github.com/dholms)! - atproto spaces
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`2f77206`](https://github.com/bluesky-social/atproto/commit/2f77206ff8010710db5733c454abff9eb5ebb96b), [`5db8f3d`](https://github.com/bluesky-social/atproto/commit/5db8f3db807b1a37793b86521e833a843ada0b15)]:
|
|
10
|
+
- @atproto/common@0.0.0-spaces-alpha-20260818022935
|
|
11
|
+
- @atproto/crypto@0.0.0-spaces-alpha-20260818022935
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Dual MIT/Apache-2.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2026 Bluesky Social PBC, and Contributors
|
|
4
|
+
|
|
5
|
+
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
|
+
|
|
7
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
package/dist/commit.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Keypair } from '@atproto/crypto';
|
|
2
|
+
import { RepoSetHash } from './lthash.js';
|
|
3
|
+
import { CommitCtx, SignedCommit } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The signature covers only the ctx, never the hash, so a leaked commit proves
|
|
6
|
+
* nothing about what the author wrote. The hash is bound to the ctx by a symmetric
|
|
7
|
+
* MAC instead: readers get integrity, third parties get nothing.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createCommit: (setHash: RepoSetHash, ctx: CommitCtx, keypair: Keypair) => Promise<SignedCommit>;
|
|
10
|
+
export declare const verifyCommitMac: (ctx: CommitCtx, commit: SignedCommit) => boolean;
|
|
11
|
+
export declare const verifyCommitSig: (ctx: CommitCtx, commit: SignedCommit, didKey: string) => Promise<boolean>;
|
|
12
|
+
export declare const verifyCommit: (ctx: CommitCtx, commit: SignedCommit, didKey: string) => Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* ```
|
|
15
|
+
* ctx = "atproto-space-v1"
|
|
16
|
+
* || uint16be(len(space)) || space
|
|
17
|
+
* || uint16be(len(author)) || author
|
|
18
|
+
* || uint16be(len(rev)) || rev
|
|
19
|
+
* || uint16be(len(ikm)) || ikm
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Length prefixes are big-endian per the TLS variable-length vector convention
|
|
23
|
+
*/
|
|
24
|
+
export declare const encodeCommitCtx: (ctx: CommitCtx, ikm: Uint8Array) => Uint8Array;
|
|
25
|
+
//# sourceMappingURL=commit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.d.ts","sourceRoot":"","sources":["../src/commit.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EAKR,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAkB,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGpE;;;;GAIG;AACH,eAAO,MAAM,YAAY,YACd,WAAW,OACf,SAAS,WACL,OAAO,KACf,OAAO,CAAC,YAAY,CAYtB,CAAA;AAGD,eAAO,MAAM,eAAe,QACrB,SAAS,UACN,YAAY,KACnB,OAGF,CAAA;AAGD,eAAO,MAAM,eAAe,QACrB,SAAS,UACN,YAAY,UACZ,MAAM,KACb,OAAO,CAAC,OAAO,CAEjB,CAAA;AAGD,eAAO,MAAM,YAAY,QAClB,SAAS,UACN,YAAY,UACZ,MAAM,KACb,OAAO,CAAC,OAAO,CAKjB,CAAA;AAUD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,QACrB,SAAS,OACT,UAAU,KACd,UA2BF,CAAA"}
|
package/dist/commit.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { hkdfSha256, hmacSha256, randomBytes, verifySignature, } from '@atproto/crypto';
|
|
2
|
+
import { COMMIT_VERSION } from './types.js';
|
|
3
|
+
import { bytesEqual } from './util.js';
|
|
4
|
+
/**
|
|
5
|
+
* The signature covers only the ctx, never the hash, so a leaked commit proves
|
|
6
|
+
* nothing about what the author wrote. The hash is bound to the ctx by a symmetric
|
|
7
|
+
* MAC instead: readers get integrity, third parties get nothing.
|
|
8
|
+
*/
|
|
9
|
+
export const createCommit = async (setHash, ctx, keypair) => {
|
|
10
|
+
const hash = setHash.digest();
|
|
11
|
+
const ikm = randomBytes(32);
|
|
12
|
+
const ctxBytes = encodeCommitCtx(ctx, ikm);
|
|
13
|
+
return {
|
|
14
|
+
ver: COMMIT_VERSION,
|
|
15
|
+
hash,
|
|
16
|
+
ikm,
|
|
17
|
+
mac: computeMac(ikm, ctxBytes, hash),
|
|
18
|
+
sig: await keypair.sign(ctxBytes),
|
|
19
|
+
rev: ctx.rev,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
// Integrity only. Use verifyCommit for integrity and authenticity together.
|
|
23
|
+
export const verifyCommitMac = (ctx, commit) => {
|
|
24
|
+
const ctxBytes = encodeCommitCtx(ctx, commit.ikm);
|
|
25
|
+
return bytesEqual(computeMac(commit.ikm, ctxBytes, commit.hash), commit.mac);
|
|
26
|
+
};
|
|
27
|
+
// Authenticity only.
|
|
28
|
+
export const verifyCommitSig = async (ctx, commit, didKey) => {
|
|
29
|
+
return verifySignature(didKey, encodeCommitCtx(ctx, commit.ikm), commit.sig);
|
|
30
|
+
};
|
|
31
|
+
// Once this passes, `hash` is trusted as the author's claim about their repo.
|
|
32
|
+
export const verifyCommit = async (ctx, commit, didKey) => {
|
|
33
|
+
if (commit.ver !== COMMIT_VERSION)
|
|
34
|
+
return false;
|
|
35
|
+
if (commit.rev !== ctx.rev)
|
|
36
|
+
return false;
|
|
37
|
+
if (!verifyCommitMac(ctx, commit))
|
|
38
|
+
return false;
|
|
39
|
+
return verifyCommitSig(ctx, commit, didKey);
|
|
40
|
+
};
|
|
41
|
+
const computeMac = (ikm, ctxBytes, hash) => hmacSha256(hkdfSha256(ikm, ctxBytes), hash);
|
|
42
|
+
const DOMAIN_PREFIX = new TextEncoder().encode('atproto-space-v1');
|
|
43
|
+
/**
|
|
44
|
+
* ```
|
|
45
|
+
* ctx = "atproto-space-v1"
|
|
46
|
+
* || uint16be(len(space)) || space
|
|
47
|
+
* || uint16be(len(author)) || author
|
|
48
|
+
* || uint16be(len(rev)) || rev
|
|
49
|
+
* || uint16be(len(ikm)) || ikm
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* Length prefixes are big-endian per the TLS variable-length vector convention
|
|
53
|
+
*/
|
|
54
|
+
export const encodeCommitCtx = (ctx, ikm) => {
|
|
55
|
+
const encoder = new TextEncoder();
|
|
56
|
+
const fields = [
|
|
57
|
+
encoder.encode(ctx.space),
|
|
58
|
+
encoder.encode(ctx.author),
|
|
59
|
+
encoder.encode(ctx.rev),
|
|
60
|
+
ikm,
|
|
61
|
+
];
|
|
62
|
+
let size = DOMAIN_PREFIX.length;
|
|
63
|
+
for (const field of fields) {
|
|
64
|
+
if (field.length > 0xffff) {
|
|
65
|
+
throw new Error('commit ctx field exceeds uint16 length prefix');
|
|
66
|
+
}
|
|
67
|
+
size += 2 + field.length;
|
|
68
|
+
}
|
|
69
|
+
const out = new Uint8Array(size);
|
|
70
|
+
out.set(DOMAIN_PREFIX);
|
|
71
|
+
let offset = DOMAIN_PREFIX.length;
|
|
72
|
+
for (const field of fields) {
|
|
73
|
+
out[offset++] = (field.length >>> 8) & 0xff;
|
|
74
|
+
out[offset++] = field.length & 0xff;
|
|
75
|
+
out.set(field, offset);
|
|
76
|
+
offset += field.length;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../src/commit.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Keypair,\n hkdfSha256,\n hmacSha256,\n randomBytes,\n verifySignature,\n} from '@atproto/crypto'\nimport { RepoSetHash } from './lthash.js'\nimport { COMMIT_VERSION, CommitCtx, SignedCommit } from './types.js'\nimport { bytesEqual } from './util.js'\n\n/**\n * The signature covers only the ctx, never the hash, so a leaked commit proves\n * nothing about what the author wrote. The hash is bound to the ctx by a symmetric\n * MAC instead: readers get integrity, third parties get nothing.\n */\nexport const createCommit = async (\n setHash: RepoSetHash,\n ctx: CommitCtx,\n keypair: Keypair,\n): Promise<SignedCommit> => {\n const hash = setHash.digest()\n const ikm = randomBytes(32)\n const ctxBytes = encodeCommitCtx(ctx, ikm)\n return {\n ver: COMMIT_VERSION,\n hash,\n ikm,\n mac: computeMac(ikm, ctxBytes, hash),\n sig: await keypair.sign(ctxBytes),\n rev: ctx.rev,\n }\n}\n\n// Integrity only. Use verifyCommit for integrity and authenticity together.\nexport const verifyCommitMac = (\n ctx: CommitCtx,\n commit: SignedCommit,\n): boolean => {\n const ctxBytes = encodeCommitCtx(ctx, commit.ikm)\n return bytesEqual(computeMac(commit.ikm, ctxBytes, commit.hash), commit.mac)\n}\n\n// Authenticity only.\nexport const verifyCommitSig = async (\n ctx: CommitCtx,\n commit: SignedCommit,\n didKey: string,\n): Promise<boolean> => {\n return verifySignature(didKey, encodeCommitCtx(ctx, commit.ikm), commit.sig)\n}\n\n// Once this passes, `hash` is trusted as the author's claim about their repo.\nexport const verifyCommit = async (\n ctx: CommitCtx,\n commit: SignedCommit,\n didKey: string,\n): Promise<boolean> => {\n if (commit.ver !== COMMIT_VERSION) return false\n if (commit.rev !== ctx.rev) return false\n if (!verifyCommitMac(ctx, commit)) return false\n return verifyCommitSig(ctx, commit, didKey)\n}\n\nconst computeMac = (\n ikm: Uint8Array,\n ctxBytes: Uint8Array,\n hash: Uint8Array,\n): Uint8Array => hmacSha256(hkdfSha256(ikm, ctxBytes), hash)\n\nconst DOMAIN_PREFIX = new TextEncoder().encode('atproto-space-v1')\n\n/**\n * ```\n * ctx = \"atproto-space-v1\"\n * || uint16be(len(space)) || space\n * || uint16be(len(author)) || author\n * || uint16be(len(rev)) || rev\n * || uint16be(len(ikm)) || ikm\n * ```\n *\n * Length prefixes are big-endian per the TLS variable-length vector convention\n */\nexport const encodeCommitCtx = (\n ctx: CommitCtx,\n ikm: Uint8Array,\n): Uint8Array => {\n const encoder = new TextEncoder()\n const fields = [\n encoder.encode(ctx.space),\n encoder.encode(ctx.author),\n encoder.encode(ctx.rev),\n ikm,\n ]\n\n let size = DOMAIN_PREFIX.length\n for (const field of fields) {\n if (field.length > 0xffff) {\n throw new Error('commit ctx field exceeds uint16 length prefix')\n }\n size += 2 + field.length\n }\n\n const out = new Uint8Array(size)\n out.set(DOMAIN_PREFIX)\n let offset = DOMAIN_PREFIX.length\n for (const field of fields) {\n out[offset++] = (field.length >>> 8) & 0xff\n out[offset++] = field.length & 0xff\n out.set(field, offset)\n offset += field.length\n }\n return out\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type Keypair } from '@atproto/crypto';
|
|
2
|
+
/**
|
|
3
|
+
* How a space authority is addressed when acting as the space host: the service
|
|
4
|
+
* entry a delegation token and a client attestation name as their audience. Note
|
|
5
|
+
* this is the *audience*, not necessarily where requests are sent — an authority
|
|
6
|
+
* that publishes no such entry is still reached at its `#atproto_pds` endpoint.
|
|
7
|
+
*/
|
|
8
|
+
export declare const spaceHostAud: (spaceDid: string) => string;
|
|
9
|
+
export declare const SPACE_TOKEN_TYPES: {
|
|
10
|
+
readonly delegation: {
|
|
11
|
+
readonly typ: 'atproto-space-delegation+jwt';
|
|
12
|
+
readonly kid: '#atproto';
|
|
13
|
+
readonly expiresInSec: 60;
|
|
14
|
+
readonly requireAud: true;
|
|
15
|
+
readonly requireCnf: false;
|
|
16
|
+
readonly singleUse: true;
|
|
17
|
+
};
|
|
18
|
+
readonly credential: {
|
|
19
|
+
readonly typ: 'atproto-space-credential+jwt';
|
|
20
|
+
readonly kid: '#atproto';
|
|
21
|
+
readonly expiresInSec: 7200;
|
|
22
|
+
readonly requireAud: false;
|
|
23
|
+
readonly requireCnf: true;
|
|
24
|
+
readonly singleUse: false;
|
|
25
|
+
};
|
|
26
|
+
readonly clientAttestation: {
|
|
27
|
+
readonly typ: 'atproto-client-attestation+jwt';
|
|
28
|
+
readonly kid: undefined;
|
|
29
|
+
readonly expiresInSec: 60;
|
|
30
|
+
readonly requireAud: true;
|
|
31
|
+
readonly requireCnf: false;
|
|
32
|
+
readonly singleUse: true;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export type SpaceTokenType = keyof typeof SPACE_TOKEN_TYPES;
|
|
36
|
+
export type SpaceTokenPayload = {
|
|
37
|
+
iss: string;
|
|
38
|
+
sub: string;
|
|
39
|
+
aud?: string;
|
|
40
|
+
iat: number;
|
|
41
|
+
exp: number;
|
|
42
|
+
jti: string;
|
|
43
|
+
cnf?: {
|
|
44
|
+
jkt: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export type SpaceTokenHeader = {
|
|
48
|
+
alg: string;
|
|
49
|
+
typ: string;
|
|
50
|
+
kid?: string;
|
|
51
|
+
};
|
|
52
|
+
export type SpaceToken = {
|
|
53
|
+
header: SpaceTokenHeader;
|
|
54
|
+
payload: SpaceTokenPayload;
|
|
55
|
+
};
|
|
56
|
+
export declare const CLOCK_SKEW_SEC = 5;
|
|
57
|
+
export type CreateSpaceTokenOpts = {
|
|
58
|
+
iss: string;
|
|
59
|
+
sub: string;
|
|
60
|
+
aud?: string;
|
|
61
|
+
dpopJkt?: string;
|
|
62
|
+
expiresInSec?: number;
|
|
63
|
+
kid?: string;
|
|
64
|
+
};
|
|
65
|
+
export declare const createSpaceToken: (type: SpaceTokenType, opts: CreateSpaceTokenOpts, keypair: Keypair) => Promise<string>;
|
|
66
|
+
export declare const parseSpaceToken: (type: SpaceTokenType, jwt: string) => SpaceToken & {
|
|
67
|
+
signingInput: Uint8Array;
|
|
68
|
+
sig: Uint8Array;
|
|
69
|
+
};
|
|
70
|
+
export type VerifySpaceTokenOpts = {
|
|
71
|
+
/** Passed `kid` so a verifier can honour the key id. */
|
|
72
|
+
getSigningKey: (iss: string, kid: string | undefined, forceRefresh: boolean) => string | Promise<string>;
|
|
73
|
+
aud?: string;
|
|
74
|
+
sub?: string;
|
|
75
|
+
};
|
|
76
|
+
export declare const verifySpaceToken: (type: SpaceTokenType, jwt: string, opts: VerifySpaceTokenOpts) => Promise<SpaceToken>;
|
|
77
|
+
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAA8B,MAAM,iBAAiB,CAAA;AAI1E;;;;;GAKG;AACH,eAAO,MAAM,YAAY,aAAc,MAAM,KAAG,MACd,CAAA;AAMlC,eAAO,MAAM,iBAAiB;aAC5B,UAAU;iBACR,GAAG,EAAE,8BAA8B;iBACnC,GAAG,EAAE,UAAU;iBACf,YAAY,EAAE,EAAE;iBAChB,UAAU;iBACV,UAAU;iBACV,SAAS;;aAKX,UAAU;iBACR,GAAG,EAAE,8BAA8B;iBACnC,GAAG,EAAE,UAAU;iBACf,YAAY,EAAE,IAAI;iBAClB,UAAU;iBACV,UAAU;iBACV,SAAS;;aAEX,iBAAiB;iBACf,GAAG,EAAE,gCAAgC;iBACrC,GAAG;iBACH,YAAY,EAAE,EAAE;iBAChB,UAAU;iBACV,UAAU;iBACV,SAAS;;CAEH,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,iBAAiB,CAAA;AAG3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,iBAAiB,CAAA;CAC3B,CAAA;AAED,eAAO,MAAM,cAAc,IAAI,CAAA;AAE/B,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,eAAO,MAAM,gBAAgB,SACrB,cAAc,QACd,oBAAoB,WACjB,OAAO,KACf,OAAO,CAAC,MAAM,CA2BhB,CAAA;AAID,eAAO,MAAM,eAAe,SACpB,cAAc,OACf,MAAM,KACV,UAAU,GAAG;IAAE,YAAY,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAuD1D,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,wDAAwD;IACxD,aAAa,EAAE,CACb,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,YAAY,EAAE,OAAO,KAClB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,eAAO,MAAM,gBAAgB,SACrB,cAAc,OACf,MAAM,QACL,oBAAoB,KACzB,OAAO,CAAC,UAAU,CAkCpB,CAAA"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { randomStr, verifySignature } from '@atproto/crypto';
|
|
2
|
+
import { fromBase64, toBase64 } from '@atproto/lex-data';
|
|
3
|
+
import { SpaceTokenError } from './error.js';
|
|
4
|
+
/**
|
|
5
|
+
* How a space authority is addressed when acting as the space host: the service
|
|
6
|
+
* entry a delegation token and a client attestation name as their audience. Note
|
|
7
|
+
* this is the *audience*, not necessarily where requests are sent — an authority
|
|
8
|
+
* that publishes no such entry is still reached at its `#atproto_pds` endpoint.
|
|
9
|
+
*/
|
|
10
|
+
export const spaceHostAud = (spaceDid) => `${spaceDid}#atproto_space_host`;
|
|
11
|
+
// The three token classes share a wire shape and differ only in who signs them,
|
|
12
|
+
// who they're addressed to, and how long they live — hence data, not three impls.
|
|
13
|
+
// A credential is multi-use across repo hosts, so it carries no aud; it is bound
|
|
14
|
+
// to the holder's DPoP key instead (see dpop.ts).
|
|
15
|
+
export const SPACE_TOKEN_TYPES = {
|
|
16
|
+
delegation: {
|
|
17
|
+
typ: 'atproto-space-delegation+jwt',
|
|
18
|
+
kid: '#atproto',
|
|
19
|
+
expiresInSec: 60,
|
|
20
|
+
requireAud: true,
|
|
21
|
+
requireCnf: false,
|
|
22
|
+
singleUse: true,
|
|
23
|
+
},
|
|
24
|
+
// An authority that publishes a dedicated `#atproto_space` key signs with it
|
|
25
|
+
// and passes that `kid`. Absent one, the space signing key is the account's
|
|
26
|
+
// `#atproto` key, which is the case for any authority hosted on a PDS.
|
|
27
|
+
credential: {
|
|
28
|
+
typ: 'atproto-space-credential+jwt',
|
|
29
|
+
kid: '#atproto',
|
|
30
|
+
expiresInSec: 7200,
|
|
31
|
+
requireAud: false,
|
|
32
|
+
requireCnf: true,
|
|
33
|
+
singleUse: false,
|
|
34
|
+
},
|
|
35
|
+
clientAttestation: {
|
|
36
|
+
typ: 'atproto-client-attestation+jwt',
|
|
37
|
+
kid: undefined,
|
|
38
|
+
expiresInSec: 60,
|
|
39
|
+
requireAud: true,
|
|
40
|
+
requireCnf: false,
|
|
41
|
+
singleUse: true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export const CLOCK_SKEW_SEC = 5;
|
|
45
|
+
export const createSpaceToken = async (type, opts, keypair) => {
|
|
46
|
+
const spec = SPACE_TOKEN_TYPES[type];
|
|
47
|
+
if (spec.requireAud && !opts.aud) {
|
|
48
|
+
throw new SpaceTokenError(`a ${type} token requires an "aud"`);
|
|
49
|
+
}
|
|
50
|
+
if (spec.requireCnf && !opts.dpopJkt) {
|
|
51
|
+
throw new SpaceTokenError(`a ${type} token requires a "dpopJkt"`);
|
|
52
|
+
}
|
|
53
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
54
|
+
const header = { alg: keypair.jwtAlg, typ: spec.typ };
|
|
55
|
+
const kid = opts.kid ?? spec.kid;
|
|
56
|
+
if (kid)
|
|
57
|
+
header.kid = kid;
|
|
58
|
+
const payload = {
|
|
59
|
+
iss: opts.iss,
|
|
60
|
+
sub: opts.sub,
|
|
61
|
+
...(opts.aud ? { aud: opts.aud } : undefined),
|
|
62
|
+
...(opts.dpopJkt ? { cnf: { jkt: opts.dpopJkt } } : undefined),
|
|
63
|
+
iat,
|
|
64
|
+
exp: iat + (opts.expiresInSec ?? spec.expiresInSec),
|
|
65
|
+
jti: randomStr(16, 'hex'),
|
|
66
|
+
};
|
|
67
|
+
const signingInput = `${jsonToB64Url(header)}.${jsonToB64Url(payload)}`;
|
|
68
|
+
const sig = await keypair.sign(new TextEncoder().encode(signingInput));
|
|
69
|
+
return `${signingInput}.${toBase64(sig, 'base64url')}`;
|
|
70
|
+
};
|
|
71
|
+
// Structural validation only, no signature check. This is as far as we go for
|
|
72
|
+
// client attestations, whose key comes from the client's JWKS, not a DID doc.
|
|
73
|
+
export const parseSpaceToken = (type, jwt) => {
|
|
74
|
+
const spec = SPACE_TOKEN_TYPES[type];
|
|
75
|
+
const parts = jwt.split('.');
|
|
76
|
+
if (parts.length !== 3) {
|
|
77
|
+
throw new SpaceTokenError('malformed token: expected 3 parts', 'BadJwt');
|
|
78
|
+
}
|
|
79
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
80
|
+
const header = decodeJsonPart(headerB64, 'header');
|
|
81
|
+
const payload = decodeJsonPart(payloadB64, 'payload');
|
|
82
|
+
if (header.typ !== spec.typ) {
|
|
83
|
+
throw new SpaceTokenError(`wrong token type: expected "${spec.typ}", got "${header.typ}"`, 'BadJwtType');
|
|
84
|
+
}
|
|
85
|
+
if (typeof header.alg !== 'string' || !header.alg) {
|
|
86
|
+
throw new SpaceTokenError('missing token "alg"', 'BadJwt');
|
|
87
|
+
}
|
|
88
|
+
if (!payload.iss) {
|
|
89
|
+
throw new SpaceTokenError('missing token "iss"', 'BadJwtIss');
|
|
90
|
+
}
|
|
91
|
+
if (!payload.sub) {
|
|
92
|
+
throw new SpaceTokenError('missing token "sub"', 'BadJwtSub');
|
|
93
|
+
}
|
|
94
|
+
if (typeof payload.exp !== 'number') {
|
|
95
|
+
throw new SpaceTokenError('missing token "exp"', 'BadJwt');
|
|
96
|
+
}
|
|
97
|
+
if (spec.requireAud && !payload.aud) {
|
|
98
|
+
throw new SpaceTokenError('missing token "aud"', 'BadJwtAudience');
|
|
99
|
+
}
|
|
100
|
+
if (spec.requireCnf && !payload.cnf?.jkt) {
|
|
101
|
+
throw new SpaceTokenError('missing token "cnf.jkt"', 'BadJwtCnf');
|
|
102
|
+
}
|
|
103
|
+
if (spec.singleUse && (typeof payload.jti !== 'string' || !payload.jti)) {
|
|
104
|
+
throw new SpaceTokenError(`a ${type} token requires a "jti" to be consumed by`, 'BadJwt');
|
|
105
|
+
}
|
|
106
|
+
if (type === 'clientAttestation' && payload.iss !== payload.sub) {
|
|
107
|
+
throw new SpaceTokenError('client attestation "iss" and "sub" must both be the client_id', 'BadJwtIss');
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
header,
|
|
111
|
+
payload,
|
|
112
|
+
signingInput: new TextEncoder().encode(`${headerB64}.${payloadB64}`),
|
|
113
|
+
sig: fromBase64(sigB64, 'base64url'),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
export const verifySpaceToken = async (type, jwt, opts) => {
|
|
117
|
+
const { header, payload, signingInput, sig } = parseSpaceToken(type, jwt);
|
|
118
|
+
const now = Math.floor(Date.now() / 1000);
|
|
119
|
+
if (now - CLOCK_SKEW_SEC >= payload.exp) {
|
|
120
|
+
throw new SpaceTokenError('token expired', 'JwtExpired');
|
|
121
|
+
}
|
|
122
|
+
if (opts.aud !== undefined && payload.aud !== opts.aud) {
|
|
123
|
+
throw new SpaceTokenError('token audience does not match this service', 'BadJwtAudience');
|
|
124
|
+
}
|
|
125
|
+
if (opts.sub !== undefined && payload.sub !== opts.sub) {
|
|
126
|
+
throw new SpaceTokenError('token subject does not match the requested space', 'BadJwtSub');
|
|
127
|
+
}
|
|
128
|
+
const didKey = await opts.getSigningKey(payload.iss, header.kid, false);
|
|
129
|
+
if (await matchesSignature(didKey, header, signingInput, sig)) {
|
|
130
|
+
return { header, payload };
|
|
131
|
+
}
|
|
132
|
+
// The signing key may have rotated since the one we hold was cached.
|
|
133
|
+
const freshDidKey = await opts.getSigningKey(payload.iss, header.kid, true);
|
|
134
|
+
if (freshDidKey !== didKey &&
|
|
135
|
+
(await matchesSignature(freshDidKey, header, signingInput, sig))) {
|
|
136
|
+
return { header, payload };
|
|
137
|
+
}
|
|
138
|
+
throw new SpaceTokenError('invalid token signature', 'BadJwtSignature');
|
|
139
|
+
};
|
|
140
|
+
const matchesSignature = async (didKey, header, signingInput, sig) => {
|
|
141
|
+
try {
|
|
142
|
+
return await verifySignature(didKey, signingInput, sig, {
|
|
143
|
+
jwtAlg: header.alg,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw new SpaceTokenError(`could not verify token signature: ${errMsg(err)}`, 'BadJwtSignature');
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const jsonToB64Url = (json) => toBase64(new TextEncoder().encode(JSON.stringify(json)), 'base64url');
|
|
151
|
+
const decodeJsonPart = (b64, part) => {
|
|
152
|
+
try {
|
|
153
|
+
return JSON.parse(new TextDecoder().decode(fromBase64(b64, 'base64url')));
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw new SpaceTokenError(`could not parse token ${part}: ${errMsg(err)}`, 'BadJwt');
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
160
|
+
//# sourceMappingURL=credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC1E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAU,EAAE,CACvD,GAAG,QAAQ,qBAAqB,CAAA;AAElC,gFAAgF;AAChF,kFAAkF;AAClF,iFAAiF;AACjF,kDAAkD;AAClD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,UAAU,EAAE;QACV,GAAG,EAAE,8BAA8B;QACnC,GAAG,EAAE,UAAU;QACf,YAAY,EAAE,EAAE;QAChB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,IAAI;KAChB;IACD,6EAA6E;IAC7E,4EAA4E;IAC5E,uEAAuE;IACvE,UAAU,EAAE;QACV,GAAG,EAAE,8BAA8B;QACnC,GAAG,EAAE,UAAU;QACf,YAAY,EAAE,IAAI;QAClB,UAAU,EAAE,KAAK;QACjB,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,KAAK;KACjB;IACD,iBAAiB,EAAE;QACjB,GAAG,EAAE,gCAAgC;QACrC,GAAG,EAAE,SAAS;QACd,YAAY,EAAE,EAAE;QAChB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,IAAI;KAChB;CACO,CAAA;AA0BV,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAA;AAW/B,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EACnC,IAAoB,EACpB,IAA0B,EAC1B,OAAgB,EACC,EAAE;IACnB,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IACpC,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,MAAM,IAAI,eAAe,CAAC,KAAK,IAAI,0BAA0B,CAAC,CAAA;IAChE,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,eAAe,CAAC,KAAK,IAAI,6BAA6B,CAAC,CAAA;IACnE,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IACzC,MAAM,MAAM,GAAqB,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAA;IACvE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAA;IAChC,IAAI,GAAG;QAAE,MAAM,CAAC,GAAG,GAAG,GAAG,CAAA;IAEzB,MAAM,OAAO,GAAsB;QACjC,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9D,GAAG;QACH,GAAG,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC;QACnD,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC;KAC1B,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,EAAE,CAAA;IACvE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;IACtE,OAAO,GAAG,YAAY,IAAI,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,CAAA;AACxD,CAAC,CAAA;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,IAAoB,EACpB,GAAW,EACiD,EAAE;IAC9D,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IAEpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,eAAe,CAAC,mCAAmC,EAAE,QAAQ,CAAC,CAAA;IAC1E,CAAC;IACD,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,KAAK,CAAA;IAE7C,MAAM,MAAM,GAAG,cAAc,CAAmB,SAAS,EAAE,QAAQ,CAAC,CAAA;IACpE,MAAM,OAAO,GAAG,cAAc,CAAoB,UAAU,EAAE,SAAS,CAAC,CAAA;IAExE,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,eAAe,CACvB,+BAA+B,IAAI,CAAC,GAAG,WAAW,MAAM,CAAC,GAAG,GAAG,EAC/D,YAAY,CACb,CAAA;IACH,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAClD,MAAM,IAAI,eAAe,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,MAAM,IAAI,eAAe,CAAC,qBAAqB,EAAE,WAAW,CAAC,CAAA;IAC/D,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,MAAM,IAAI,eAAe,CAAC,qBAAqB,EAAE,WAAW,CAAC,CAAA;IAC/D,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,eAAe,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,IAAI,eAAe,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAA;IACpE,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;QACzC,MAAM,IAAI,eAAe,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAA;IACnE,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,eAAe,CACvB,KAAK,IAAI,2CAA2C,EACpD,QAAQ,CACT,CAAA;IACH,CAAC;IACD,IAAI,IAAI,KAAK,mBAAmB,IAAI,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;QAChE,MAAM,IAAI,eAAe,CACvB,+DAA+D,EAC/D,WAAW,CACZ,CAAA;IACH,CAAC;IAED,OAAO;QACL,MAAM;QACN,OAAO;QACP,YAAY,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC;QACpE,GAAG,EAAE,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC;KACrC,CAAA;AACH,CAAC,CAAA;AAaD,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EACnC,IAAoB,EACpB,GAAW,EACX,IAA0B,EACL,EAAE;IACvB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAEzE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IACzC,IAAI,GAAG,GAAG,cAAc,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,IAAI,eAAe,CAAC,eAAe,EAAE,YAAY,CAAC,CAAA;IAC1D,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,eAAe,CACvB,4CAA4C,EAC5C,gBAAgB,CACjB,CAAA;IACH,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,eAAe,CACvB,kDAAkD,EAClD,WAAW,CACZ,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACvE,IAAI,MAAM,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;IAC5B,CAAC;IAED,qEAAqE;IACrE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAC3E,IACE,WAAW,KAAK,MAAM;QACtB,CAAC,MAAM,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC,EAChE,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;IAC5B,CAAC;IAED,MAAM,IAAI,eAAe,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CAAA;AACzE,CAAC,CAAA;AAED,MAAM,gBAAgB,GAAG,KAAK,EAC5B,MAAc,EACd,MAAwB,EACxB,YAAwB,EACxB,GAAe,EACG,EAAE;IACpB,IAAI,CAAC;QACH,OAAO,MAAM,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE;YACtD,MAAM,EAAE,MAAM,CAAC,GAAG;SACnB,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,qCAAqC,MAAM,CAAC,GAAG,CAAC,EAAE,EAClD,iBAAiB,CAClB,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,IAA6B,EAAU,EAAE,CAC7D,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAA;AAEvE,MAAM,cAAc,GAAG,CAAI,GAAW,EAAE,IAAY,EAAK,EAAE;IACzD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;IAC3E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,yBAAyB,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,EAC/C,QAAQ,CACT,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,MAAM,GAAG,CAAC,GAAY,EAAU,EAAE,CACtC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA","sourcesContent":["import { type Keypair, randomStr, verifySignature } from '@atproto/crypto'\nimport { fromBase64, toBase64 } from '@atproto/lex-data'\nimport { SpaceTokenError } from './error.js'\n\n/**\n * How a space authority is addressed when acting as the space host: the service\n * entry a delegation token and a client attestation name as their audience. Note\n * this is the *audience*, not necessarily where requests are sent — an authority\n * that publishes no such entry is still reached at its `#atproto_pds` endpoint.\n */\nexport const spaceHostAud = (spaceDid: string): string =>\n `${spaceDid}#atproto_space_host`\n\n// The three token classes share a wire shape and differ only in who signs them,\n// who they're addressed to, and how long they live — hence data, not three impls.\n// A credential is multi-use across repo hosts, so it carries no aud; it is bound\n// to the holder's DPoP key instead (see dpop.ts).\nexport const SPACE_TOKEN_TYPES = {\n delegation: {\n typ: 'atproto-space-delegation+jwt',\n kid: '#atproto',\n expiresInSec: 60,\n requireAud: true,\n requireCnf: false,\n singleUse: true,\n },\n // An authority that publishes a dedicated `#atproto_space` key signs with it\n // and passes that `kid`. Absent one, the space signing key is the account's\n // `#atproto` key, which is the case for any authority hosted on a PDS.\n credential: {\n typ: 'atproto-space-credential+jwt',\n kid: '#atproto',\n expiresInSec: 7200,\n requireAud: false,\n requireCnf: true,\n singleUse: false,\n },\n clientAttestation: {\n typ: 'atproto-client-attestation+jwt',\n kid: undefined,\n expiresInSec: 60,\n requireAud: true,\n requireCnf: false,\n singleUse: true,\n },\n} as const\n\nexport type SpaceTokenType = keyof typeof SPACE_TOKEN_TYPES\n\n// `sub` is the space URI, or the client_id for a client attestation.\nexport type SpaceTokenPayload = {\n iss: string\n sub: string\n aud?: string\n iat: number\n exp: number\n jti: string\n cnf?: { jkt: string }\n}\n\nexport type SpaceTokenHeader = {\n alg: string\n typ: string\n kid?: string\n}\n\nexport type SpaceToken = {\n header: SpaceTokenHeader\n payload: SpaceTokenPayload\n}\n\nexport const CLOCK_SKEW_SEC = 5\n\nexport type CreateSpaceTokenOpts = {\n iss: string\n sub: string\n aud?: string\n dpopJkt?: string\n expiresInSec?: number\n kid?: string\n}\n\nexport const createSpaceToken = async (\n type: SpaceTokenType,\n opts: CreateSpaceTokenOpts,\n keypair: Keypair,\n): Promise<string> => {\n const spec = SPACE_TOKEN_TYPES[type]\n if (spec.requireAud && !opts.aud) {\n throw new SpaceTokenError(`a ${type} token requires an \"aud\"`)\n }\n if (spec.requireCnf && !opts.dpopJkt) {\n throw new SpaceTokenError(`a ${type} token requires a \"dpopJkt\"`)\n }\n\n const iat = Math.floor(Date.now() / 1000)\n const header: SpaceTokenHeader = { alg: keypair.jwtAlg, typ: spec.typ }\n const kid = opts.kid ?? spec.kid\n if (kid) header.kid = kid\n\n const payload: SpaceTokenPayload = {\n iss: opts.iss,\n sub: opts.sub,\n ...(opts.aud ? { aud: opts.aud } : undefined),\n ...(opts.dpopJkt ? { cnf: { jkt: opts.dpopJkt } } : undefined),\n iat,\n exp: iat + (opts.expiresInSec ?? spec.expiresInSec),\n jti: randomStr(16, 'hex'),\n }\n\n const signingInput = `${jsonToB64Url(header)}.${jsonToB64Url(payload)}`\n const sig = await keypair.sign(new TextEncoder().encode(signingInput))\n return `${signingInput}.${toBase64(sig, 'base64url')}`\n}\n\n// Structural validation only, no signature check. This is as far as we go for\n// client attestations, whose key comes from the client's JWKS, not a DID doc.\nexport const parseSpaceToken = (\n type: SpaceTokenType,\n jwt: string,\n): SpaceToken & { signingInput: Uint8Array; sig: Uint8Array } => {\n const spec = SPACE_TOKEN_TYPES[type]\n\n const parts = jwt.split('.')\n if (parts.length !== 3) {\n throw new SpaceTokenError('malformed token: expected 3 parts', 'BadJwt')\n }\n const [headerB64, payloadB64, sigB64] = parts\n\n const header = decodeJsonPart<SpaceTokenHeader>(headerB64, 'header')\n const payload = decodeJsonPart<SpaceTokenPayload>(payloadB64, 'payload')\n\n if (header.typ !== spec.typ) {\n throw new SpaceTokenError(\n `wrong token type: expected \"${spec.typ}\", got \"${header.typ}\"`,\n 'BadJwtType',\n )\n }\n if (typeof header.alg !== 'string' || !header.alg) {\n throw new SpaceTokenError('missing token \"alg\"', 'BadJwt')\n }\n if (!payload.iss) {\n throw new SpaceTokenError('missing token \"iss\"', 'BadJwtIss')\n }\n if (!payload.sub) {\n throw new SpaceTokenError('missing token \"sub\"', 'BadJwtSub')\n }\n if (typeof payload.exp !== 'number') {\n throw new SpaceTokenError('missing token \"exp\"', 'BadJwt')\n }\n if (spec.requireAud && !payload.aud) {\n throw new SpaceTokenError('missing token \"aud\"', 'BadJwtAudience')\n }\n if (spec.requireCnf && !payload.cnf?.jkt) {\n throw new SpaceTokenError('missing token \"cnf.jkt\"', 'BadJwtCnf')\n }\n if (spec.singleUse && (typeof payload.jti !== 'string' || !payload.jti)) {\n throw new SpaceTokenError(\n `a ${type} token requires a \"jti\" to be consumed by`,\n 'BadJwt',\n )\n }\n if (type === 'clientAttestation' && payload.iss !== payload.sub) {\n throw new SpaceTokenError(\n 'client attestation \"iss\" and \"sub\" must both be the client_id',\n 'BadJwtIss',\n )\n }\n\n return {\n header,\n payload,\n signingInput: new TextEncoder().encode(`${headerB64}.${payloadB64}`),\n sig: fromBase64(sigB64, 'base64url'),\n }\n}\n\nexport type VerifySpaceTokenOpts = {\n /** Passed `kid` so a verifier can honour the key id. */\n getSigningKey: (\n iss: string,\n kid: string | undefined,\n forceRefresh: boolean,\n ) => string | Promise<string>\n aud?: string\n sub?: string\n}\n\nexport const verifySpaceToken = async (\n type: SpaceTokenType,\n jwt: string,\n opts: VerifySpaceTokenOpts,\n): Promise<SpaceToken> => {\n const { header, payload, signingInput, sig } = parseSpaceToken(type, jwt)\n\n const now = Math.floor(Date.now() / 1000)\n if (now - CLOCK_SKEW_SEC >= payload.exp) {\n throw new SpaceTokenError('token expired', 'JwtExpired')\n }\n if (opts.aud !== undefined && payload.aud !== opts.aud) {\n throw new SpaceTokenError(\n 'token audience does not match this service',\n 'BadJwtAudience',\n )\n }\n if (opts.sub !== undefined && payload.sub !== opts.sub) {\n throw new SpaceTokenError(\n 'token subject does not match the requested space',\n 'BadJwtSub',\n )\n }\n const didKey = await opts.getSigningKey(payload.iss, header.kid, false)\n if (await matchesSignature(didKey, header, signingInput, sig)) {\n return { header, payload }\n }\n\n // The signing key may have rotated since the one we hold was cached.\n const freshDidKey = await opts.getSigningKey(payload.iss, header.kid, true)\n if (\n freshDidKey !== didKey &&\n (await matchesSignature(freshDidKey, header, signingInput, sig))\n ) {\n return { header, payload }\n }\n\n throw new SpaceTokenError('invalid token signature', 'BadJwtSignature')\n}\n\nconst matchesSignature = async (\n didKey: string,\n header: SpaceTokenHeader,\n signingInput: Uint8Array,\n sig: Uint8Array,\n): Promise<boolean> => {\n try {\n return await verifySignature(didKey, signingInput, sig, {\n jwtAlg: header.alg,\n })\n } catch (err) {\n throw new SpaceTokenError(\n `could not verify token signature: ${errMsg(err)}`,\n 'BadJwtSignature',\n )\n }\n}\n\nconst jsonToB64Url = (json: Record<string, unknown>): string =>\n toBase64(new TextEncoder().encode(JSON.stringify(json)), 'base64url')\n\nconst decodeJsonPart = <T>(b64: string, part: string): T => {\n try {\n return JSON.parse(new TextDecoder().decode(fromBase64(b64, 'base64url')))\n } catch (err) {\n throw new SpaceTokenError(\n `could not parse token ${part}: ${errMsg(err)}`,\n 'BadJwt',\n )\n }\n}\n\nconst errMsg = (err: unknown): string =>\n err instanceof Error ? err.message : String(err)\n"]}
|
package/dist/dpop.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Key } from '@atproto/jwk';
|
|
2
|
+
/**
|
|
3
|
+
* DPoP binding for space credentials (RFC 9449).
|
|
4
|
+
*
|
|
5
|
+
* A credential reads a whole space and is presented to every repo host in it. As a
|
|
6
|
+
* bearer token it would be a shared secret: a host given one to serve its own repo
|
|
7
|
+
* could replay it against every other host in the space.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DPOP_PROOF_TYP = "dpop+jwt";
|
|
10
|
+
export declare const MAX_PROOF_AGE_SEC = 60;
|
|
11
|
+
export type DpopProof = {
|
|
12
|
+
jti: string;
|
|
13
|
+
jkt: string;
|
|
14
|
+
htm: string;
|
|
15
|
+
htu: string;
|
|
16
|
+
};
|
|
17
|
+
export type CreateDpopProofOpts = {
|
|
18
|
+
htm: string;
|
|
19
|
+
htu: string;
|
|
20
|
+
/** Present when proving possession of a key bound to an access credential. */
|
|
21
|
+
credential?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const createDpopProof: (key: Key, opts: CreateDpopProofOpts) => Promise<string>;
|
|
24
|
+
type DpopRequest = {
|
|
25
|
+
htm: string;
|
|
26
|
+
htu: string;
|
|
27
|
+
};
|
|
28
|
+
export type VerifyDpopProofOpts = DpopRequest & ({
|
|
29
|
+
credential: string;
|
|
30
|
+
jkt: string;
|
|
31
|
+
} | {
|
|
32
|
+
credential?: undefined;
|
|
33
|
+
jkt?: undefined;
|
|
34
|
+
});
|
|
35
|
+
/** Checks everything but replay; the caller records the returned `jti` itself. */
|
|
36
|
+
export declare const verifyDpopProof: (proof: string, opts: VerifyDpopProofOpts) => Promise<DpopProof>;
|
|
37
|
+
/** The RFC 7638 thumbprint of a DPoP key. */
|
|
38
|
+
export declare const dpopJktForKey: (key: Key) => Promise<string>;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=dpop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAKvC;;;;;;GAMG;AAEH,eAAO,MAAM,cAAc,aAAa,CAAA;AACxC,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAGnC,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,eAAO,MAAM,eAAe,QACrB,GAAG,QACF,mBAAmB,KACxB,OAAO,CAAC,MAAM,CAuBhB,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAC3C,CACI;IACE,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;CACZ,GACD;IACE,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,GAAG,CAAC,EAAE,SAAS,CAAA;CAChB,CACJ,CAAA;AAEH,kFAAkF;AAClF,eAAO,MAAM,eAAe,UACnB,MAAM,QACP,mBAAmB,KACxB,OAAO,CAAC,SAAS,CA6DnB,CAAA;AAED,6CAA6C;AAC7C,eAAO,MAAM,aAAa,QAAe,GAAG,KAAG,OAAO,CAAC,MAAM,CAM5D,CAAA"}
|
package/dist/dpop.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { EmbeddedJWK, calculateJwkThumbprint, errors, jwtVerify } from 'jose';
|
|
2
|
+
import { randomStr, sha256 } from '@atproto/crypto';
|
|
3
|
+
import { toBase64 } from '@atproto/lex-data';
|
|
4
|
+
import { CLOCK_SKEW_SEC } from './credential.js';
|
|
5
|
+
import { DpopProofError } from './error.js';
|
|
6
|
+
/**
|
|
7
|
+
* DPoP binding for space credentials (RFC 9449).
|
|
8
|
+
*
|
|
9
|
+
* A credential reads a whole space and is presented to every repo host in it. As a
|
|
10
|
+
* bearer token it would be a shared secret: a host given one to serve its own repo
|
|
11
|
+
* could replay it against every other host in the space.
|
|
12
|
+
*/
|
|
13
|
+
export const DPOP_PROOF_TYP = 'dpop+jwt';
|
|
14
|
+
export const MAX_PROOF_AGE_SEC = 60;
|
|
15
|
+
const SIGNING_ALG = 'ES256';
|
|
16
|
+
export const createDpopProof = async (key, opts) => {
|
|
17
|
+
const jwk = key.bareJwk;
|
|
18
|
+
if (!jwk) {
|
|
19
|
+
throw new DpopProofError('a DPoP proof requires an asymmetric key');
|
|
20
|
+
}
|
|
21
|
+
if (!key.algorithms.includes(SIGNING_ALG)) {
|
|
22
|
+
throw new DpopProofError(`a DPoP key must support ${SIGNING_ALG}, got: ${key.algorithms.join(', ')}`);
|
|
23
|
+
}
|
|
24
|
+
return key.createJwt({ alg: SIGNING_ALG, typ: DPOP_PROOF_TYP, jwk }, {
|
|
25
|
+
jti: randomStr(16, 'hex'),
|
|
26
|
+
htm: opts.htm,
|
|
27
|
+
htu: normalizeHtu(opts.htu),
|
|
28
|
+
...(opts.credential !== undefined
|
|
29
|
+
? { ath: await hashCredential(opts.credential) }
|
|
30
|
+
: undefined),
|
|
31
|
+
iat: Math.floor(Date.now() / 1000),
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
/** Checks everything but replay; the caller records the returned `jti` itself. */
|
|
35
|
+
export const verifyDpopProof = async (proof, opts) => {
|
|
36
|
+
// EmbeddedJWK verifies against the proof's own `jwk` header; the thumbprint
|
|
37
|
+
// comparison below is what ties that key to the credential.
|
|
38
|
+
//
|
|
39
|
+
// Without the skew allowance a holder whose clock runs even slightly fast is
|
|
40
|
+
// rejected outright, since jose refuses an `iat` in the verifier's future.
|
|
41
|
+
const { protectedHeader, payload } = await jwtVerify(proof, EmbeddedJWK, {
|
|
42
|
+
typ: DPOP_PROOF_TYP,
|
|
43
|
+
algorithms: [SIGNING_ALG],
|
|
44
|
+
maxTokenAge: MAX_PROOF_AGE_SEC,
|
|
45
|
+
clockTolerance: CLOCK_SKEW_SEC,
|
|
46
|
+
}).catch((err) => {
|
|
47
|
+
if (err instanceof errors.JWTExpired) {
|
|
48
|
+
throw new DpopProofError(`DPoP proof is expired: ${errMsg(err)}`, 'DpopProofExpired');
|
|
49
|
+
}
|
|
50
|
+
throw new DpopProofError(`could not verify DPoP proof: ${errMsg(err)}`, 'BadDpopProofSignature');
|
|
51
|
+
});
|
|
52
|
+
const { jti, htm, htu, ath } = payload;
|
|
53
|
+
if (typeof jti !== 'string' || !jti) {
|
|
54
|
+
throw new DpopProofError('missing DPoP proof "jti"');
|
|
55
|
+
}
|
|
56
|
+
if (htm !== opts.htm) {
|
|
57
|
+
throw new DpopProofError('DPoP proof "htm" does not match the request');
|
|
58
|
+
}
|
|
59
|
+
if (typeof htu !== 'string' || htu !== normalizeHtu(opts.htu)) {
|
|
60
|
+
throw new DpopProofError('DPoP proof "htu" does not match the request');
|
|
61
|
+
}
|
|
62
|
+
if (opts.credential !== undefined) {
|
|
63
|
+
if (ath !== (await hashCredential(opts.credential))) {
|
|
64
|
+
throw new DpopProofError('DPoP proof "ath" does not match the credential');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (ath !== undefined) {
|
|
68
|
+
throw new DpopProofError('DPoP proof "ath" must be omitted when obtaining a credential');
|
|
69
|
+
}
|
|
70
|
+
// Present because jwtVerify used EmbeddedJWK.
|
|
71
|
+
const jkt = await calculateJwkThumbprint(protectedHeader.jwk, 'sha256').catch((err) => {
|
|
72
|
+
throw new DpopProofError(`could not compute DPoP key thumbprint: ${errMsg(err)}`);
|
|
73
|
+
});
|
|
74
|
+
if (opts.jkt !== undefined && jkt !== opts.jkt) {
|
|
75
|
+
throw new DpopProofError('DPoP proof is not signed by the key the credential is bound to', 'DpopKeyMismatch');
|
|
76
|
+
}
|
|
77
|
+
return { jti, jkt, htm, htu };
|
|
78
|
+
};
|
|
79
|
+
/** The RFC 7638 thumbprint of a DPoP key. */
|
|
80
|
+
export const dpopJktForKey = async (key) => {
|
|
81
|
+
const jwk = key.bareJwk;
|
|
82
|
+
if (!jwk) {
|
|
83
|
+
throw new DpopProofError('a DPoP key must be asymmetric');
|
|
84
|
+
}
|
|
85
|
+
return calculateJwkThumbprint(jwk, 'sha256');
|
|
86
|
+
};
|
|
87
|
+
// Strips query and fragment (RFC 9449 §4.2), so one proof covers any query on a path.
|
|
88
|
+
const normalizeHtu = (url) => {
|
|
89
|
+
const parsed = new URL(url);
|
|
90
|
+
return parsed.origin + parsed.pathname;
|
|
91
|
+
};
|
|
92
|
+
const hashCredential = async (credential) => {
|
|
93
|
+
return toBase64(await sha256(credential), 'base64url');
|
|
94
|
+
};
|
|
95
|
+
const errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
96
|
+
//# sourceMappingURL=dpop.js.map
|
package/dist/dpop.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dpop.js","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"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","sourcesContent":["import { EmbeddedJWK, calculateJwkThumbprint, errors, jwtVerify } from 'jose'\nimport { randomStr, sha256 } from '@atproto/crypto'\nimport type { Key } from '@atproto/jwk'\nimport { toBase64 } from '@atproto/lex-data'\nimport { CLOCK_SKEW_SEC } from './credential.js'\nimport { DpopProofError } from './error.js'\n\n/**\n * DPoP binding for space credentials (RFC 9449).\n *\n * A credential reads a whole space and is presented to every repo host in it. As a\n * bearer token it would be a shared secret: a host given one to serve its own repo\n * could replay it against every other host in the space.\n */\n\nexport const DPOP_PROOF_TYP = 'dpop+jwt'\nexport const MAX_PROOF_AGE_SEC = 60\nconst SIGNING_ALG = 'ES256'\n\nexport type DpopProof = {\n jti: string\n jkt: string\n htm: string\n htu: string\n}\n\nexport type CreateDpopProofOpts = {\n htm: string\n htu: string\n /** Present when proving possession of a key bound to an access credential. */\n credential?: string\n}\n\nexport const createDpopProof = async (\n key: Key,\n opts: CreateDpopProofOpts,\n): Promise<string> => {\n const jwk = key.bareJwk\n if (!jwk) {\n throw new DpopProofError('a DPoP proof requires an asymmetric key')\n }\n if (!key.algorithms.includes(SIGNING_ALG)) {\n throw new DpopProofError(\n `a DPoP key must support ${SIGNING_ALG}, got: ${key.algorithms.join(', ')}`,\n )\n }\n\n return key.createJwt(\n { alg: SIGNING_ALG, typ: DPOP_PROOF_TYP, jwk },\n {\n jti: randomStr(16, 'hex'),\n htm: opts.htm,\n htu: normalizeHtu(opts.htu),\n ...(opts.credential !== undefined\n ? { ath: await hashCredential(opts.credential) }\n : undefined),\n iat: Math.floor(Date.now() / 1000),\n },\n )\n}\n\ntype DpopRequest = {\n htm: string\n htu: string\n}\n\nexport type VerifyDpopProofOpts = DpopRequest &\n (\n | {\n credential: string\n jkt: string\n }\n | {\n credential?: undefined\n jkt?: undefined\n }\n )\n\n/** Checks everything but replay; the caller records the returned `jti` itself. */\nexport const verifyDpopProof = async (\n proof: string,\n opts: VerifyDpopProofOpts,\n): Promise<DpopProof> => {\n // EmbeddedJWK verifies against the proof's own `jwk` header; the thumbprint\n // comparison below is what ties that key to the credential.\n //\n // Without the skew allowance a holder whose clock runs even slightly fast is\n // rejected outright, since jose refuses an `iat` in the verifier's future.\n const { protectedHeader, payload } = await jwtVerify(proof, EmbeddedJWK, {\n typ: DPOP_PROOF_TYP,\n algorithms: [SIGNING_ALG],\n maxTokenAge: MAX_PROOF_AGE_SEC,\n clockTolerance: CLOCK_SKEW_SEC,\n }).catch((err) => {\n if (err instanceof errors.JWTExpired) {\n throw new DpopProofError(\n `DPoP proof is expired: ${errMsg(err)}`,\n 'DpopProofExpired',\n )\n }\n throw new DpopProofError(\n `could not verify DPoP proof: ${errMsg(err)}`,\n 'BadDpopProofSignature',\n )\n })\n\n const { jti, htm, htu, ath } = payload\n if (typeof jti !== 'string' || !jti) {\n throw new DpopProofError('missing DPoP proof \"jti\"')\n }\n if (htm !== opts.htm) {\n throw new DpopProofError('DPoP proof \"htm\" does not match the request')\n }\n if (typeof htu !== 'string' || htu !== normalizeHtu(opts.htu)) {\n throw new DpopProofError('DPoP proof \"htu\" does not match the request')\n }\n if (opts.credential !== undefined) {\n if (ath !== (await hashCredential(opts.credential))) {\n throw new DpopProofError('DPoP proof \"ath\" does not match the credential')\n }\n } else if (ath !== undefined) {\n throw new DpopProofError(\n 'DPoP proof \"ath\" must be omitted when obtaining a credential',\n )\n }\n\n // Present because jwtVerify used EmbeddedJWK.\n const jkt = await calculateJwkThumbprint(\n protectedHeader.jwk!,\n 'sha256',\n ).catch((err) => {\n throw new DpopProofError(\n `could not compute DPoP key thumbprint: ${errMsg(err)}`,\n )\n })\n if (opts.jkt !== undefined && jkt !== opts.jkt) {\n throw new DpopProofError(\n 'DPoP proof is not signed by the key the credential is bound to',\n 'DpopKeyMismatch',\n )\n }\n\n return { jti, jkt, htm, htu }\n}\n\n/** The RFC 7638 thumbprint of a DPoP key. */\nexport const dpopJktForKey = async (key: Key): Promise<string> => {\n const jwk = key.bareJwk\n if (!jwk) {\n throw new DpopProofError('a DPoP key must be asymmetric')\n }\n return calculateJwkThumbprint(jwk, 'sha256')\n}\n\n// Strips query and fragment (RFC 9449 §4.2), so one proof covers any query on a path.\nconst normalizeHtu = (url: string): string => {\n const parsed = new URL(url)\n return parsed.origin + parsed.pathname\n}\n\nconst hashCredential = async (credential: string): Promise<string> => {\n return toBase64(await sha256(credential), 'base64url')\n}\n\nconst errMsg = (err: unknown): string =>\n err instanceof Error ? err.message : String(err)\n"]}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class SpaceTokenError extends Error {
|
|
2
|
+
readonly code: 'BadJwt' | 'BadJwtType' | 'BadJwtIss' | 'BadJwtSub' | 'BadJwtAudience' | 'BadJwtCnf' | 'BadJwtSignature' | 'JwtExpired';
|
|
3
|
+
constructor(message: string, code?: 'BadJwt' | 'BadJwtType' | 'BadJwtIss' | 'BadJwtSub' | 'BadJwtAudience' | 'BadJwtCnf' | 'BadJwtSignature' | 'JwtExpired');
|
|
4
|
+
}
|
|
5
|
+
export declare class DpopProofError extends Error {
|
|
6
|
+
readonly code: 'BadDpopProof' | 'BadDpopProofSignature' | 'DpopProofExpired' | 'DpopKeyMismatch';
|
|
7
|
+
constructor(message: string, code?: 'BadDpopProof' | 'BadDpopProofSignature' | 'DpopProofExpired' | 'DpopKeyMismatch');
|
|
8
|
+
}
|
|
9
|
+
export declare class RepoVerificationError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;aAGtB,IAAI,EAChB,QAAQ,GACR,YAAY,GACZ,WAAW,GACX,WAAW,GACX,gBAAgB,GAChB,WAAW,GACX,iBAAiB,GACjB,YAAY;IAVlB,YACE,OAAO,EAAE,MAAM,EACC,IAAI,GAChB,QAAQ,GACR,YAAY,GACZ,WAAW,GACX,WAAW,GACX,gBAAgB,GAChB,WAAW,GACX,iBAAiB,GACjB,YAAuB,EAI5B;CACF;AAED,qBAAa,cAAe,SAAQ,KAAK;aAGrB,IAAI,EAChB,cAAc,GACd,uBAAuB,GACvB,kBAAkB,GAClB,iBAAiB;IANvB,YACE,OAAO,EAAE,MAAM,EACC,IAAI,GAChB,cAAc,GACd,uBAAuB,GACvB,kBAAkB,GAClB,iBAAkC,EAIvC;CACF;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class SpaceTokenError extends Error {
|
|
2
|
+
constructor(message, code = 'BadJwt') {
|
|
3
|
+
super(message);
|
|
4
|
+
this.code = code;
|
|
5
|
+
this.name = 'SpaceTokenError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class DpopProofError extends Error {
|
|
9
|
+
constructor(message, code = 'BadDpopProof') {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.name = 'DpopProofError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class RepoVerificationError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'RepoVerificationError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YACE,OAAe,EACC,IAAI,GAQD,QAAQ;QAE3B,KAAK,CAAC,OAAO,CAAC,CAAA;oBAVE,IAAI;QAWpB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YACE,OAAe,EACC,IAAI,GAII,cAAc;QAEtC,KAAK,CAAC,OAAO,CAAC,CAAA;oBANE,IAAI;QAOpB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,CAAC;CACF","sourcesContent":["export class SpaceTokenError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'BadJwt'\n | 'BadJwtType'\n | 'BadJwtIss'\n | 'BadJwtSub'\n | 'BadJwtAudience'\n | 'BadJwtCnf'\n | 'BadJwtSignature'\n | 'JwtExpired' = 'BadJwt',\n ) {\n super(message)\n this.name = 'SpaceTokenError'\n }\n}\n\nexport class DpopProofError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'BadDpopProof'\n | 'BadDpopProofSignature'\n | 'DpopProofExpired'\n | 'DpopKeyMismatch' = 'BadDpopProof',\n ) {\n super(message)\n this.name = 'DpopProofError'\n }\n}\n\nexport class RepoVerificationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'RepoVerificationError'\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './credential.js';
|
|
2
|
+
export * from './dpop.js';
|
|
3
|
+
export * from './error.js';
|
|
4
|
+
export * from './lthash.js';
|
|
5
|
+
export * from './repo-commit.js';
|
|
6
|
+
export * from './sync/index.js';
|
|
7
|
+
export * from './types.js';
|
|
8
|
+
export * from './util.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|