@atproto/pds 0.4.220 → 0.4.221
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 +9 -0
- package/dist/auth-verifier.d.ts +1 -0
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js +7 -13
- package/dist/auth-verifier.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +1 -0
- package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/actor/defs.defs.js +1 -0
- package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/actor/defs.defs.d.ts +11 -3
- package/dist/lexicons/chat/bsky/actor/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/actor/defs.defs.js +6 -2
- package/dist/lexicons/chat/bsky/actor/defs.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts +98 -23
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.js +34 -24
- package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.d.ts +3 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.defs.d.ts +26 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.defs.js +55 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.js +45 -0
- package/dist/lexicons/chat/bsky/convo/getConvoMembers.js.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getMessages.defs.d.ts +3 -0
- package/dist/lexicons/chat/bsky/convo/getMessages.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getMessages.defs.js +2 -0
- package/dist/lexicons/chat/bsky/convo/getMessages.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo.d.ts +1 -0
- package/dist/lexicons/chat/bsky/convo.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo.js +2 -1
- package/dist/lexicons/chat/bsky/convo.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts +3 -0
- package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group/addMembers.defs.js +2 -0
- package/dist/lexicons/chat/bsky/group/addMembers.defs.js.map +1 -1
- package/package.json +11 -12
- package/src/auth-verifier.ts +3 -11
- package/src/index.ts +5 -1
- package/tests/auth.test.ts +3 -1
- package/tests/entryway-mock.ts +317 -0
- package/tests/entryway.test.ts +18 -100
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { l } from '@atproto/lex';
|
|
2
2
|
import * as ConvoDefs from '../convo/defs.defs.js';
|
|
3
|
+
import * as ActorDefs from '../actor/defs.defs.js';
|
|
3
4
|
declare const $nsid = "chat.bsky.group.addMembers";
|
|
4
5
|
export { $nsid };
|
|
5
6
|
/** [NOTE: This is under active development and should be considered unstable while this note is here]. Adds members to a group. The members are added in 'request' status, so they have to accept it. This creates convo memberships. */
|
|
@@ -10,6 +11,7 @@ declare const main: l.Procedure<"chat.bsky.group.addMembers", l.ParamsSchema<{}>
|
|
|
10
11
|
}>>;
|
|
11
12
|
}>>, l.Payload<"application/json", l.ObjectSchema<{
|
|
12
13
|
convo: l.RefSchema<l.Validator<ConvoDefs.ConvoView, ConvoDefs.ConvoView>>;
|
|
14
|
+
addedMembers: l.OptionalSchema<l.ArraySchema<l.RefSchema<l.Validator<ActorDefs.ProfileViewBasic, ActorDefs.ProfileViewBasic>>>>;
|
|
13
15
|
}>>, readonly ["AccountSuspended", "BlockedActor", "GroupInvitesDisabled", "ConvoLocked", "InsufficientRole", "InvalidConvo", "MemberLimitReached", "NotFollowedBySender", "RecipientNotFound"]>;
|
|
14
16
|
export { main };
|
|
15
17
|
export type $Params = l.InferMethodParams<typeof main>;
|
|
@@ -24,5 +26,6 @@ export declare const $lxm: "chat.bsky.group.addMembers", $params: l.ParamsSchema
|
|
|
24
26
|
}>>;
|
|
25
27
|
}>>, $output: l.Payload<"application/json", l.ObjectSchema<{
|
|
26
28
|
convo: l.RefSchema<l.Validator<ConvoDefs.ConvoView, ConvoDefs.ConvoView>>;
|
|
29
|
+
addedMembers: l.OptionalSchema<l.ArraySchema<l.RefSchema<l.Validator<ActorDefs.ProfileViewBasic, ActorDefs.ProfileViewBasic>>>>;
|
|
27
30
|
}>>;
|
|
28
31
|
//# sourceMappingURL=addMembers.defs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addMembers.defs.d.ts","sourceRoot":"","sources":["../../../../../src/lexicons/chat/bsky/group/addMembers.defs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAElD,QAAA,MAAM,KAAK,+BAA+B,CAAA;AAE1C,OAAO,EAAE,KAAK,EAAE,CAAA;AAEhB,yOAAyO;AACzO,QAAA,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"addMembers.defs.d.ts","sourceRoot":"","sources":["../../../../../src/lexicons/chat/bsky/group/addMembers.defs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAElD,QAAA,MAAM,KAAK,+BAA+B,CAAA;AAE1C,OAAO,EAAE,KAAK,EAAE,CAAA;AAEhB,yOAAyO;AACzO,QAAA,MAAM,IAAI;;;;;;;;gMA4BT,CAAA;AACD,OAAO,EAAE,IAAI,EAAE,CAAA;AAEf,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,iBAAiB,CAAC,OAAO,IAAI,CAAC,CAAA;AACtD,MAAM,MAAM,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;AACzE,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,oBAAoB,CAC/D,OAAO,IAAI,EACX,CAAC,CACF,CAAA;AACD,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,iBAAiB,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;AAC3E,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,qBAAqB,CACjE,OAAO,IAAI,EACX,CAAC,CACF,CAAA;AAED,eAAO,MAAM,IAAI,8BAAY,EAC3B,OAAO,oBAAkB,EACzB,MAAM;;;;;GAAa,EACnB,OAAO;;;GAAc,CAAA"}
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.$output = exports.$input = exports.$params = exports.$lxm = exports.main = exports.$nsid = void 0;
|
|
40
40
|
const lex_1 = require("@atproto/lex");
|
|
41
41
|
const ConvoDefs = __importStar(require("../convo/defs.defs.js"));
|
|
42
|
+
const ActorDefs = __importStar(require("../actor/defs.defs.js"));
|
|
42
43
|
const $nsid = 'chat.bsky.group.addMembers';
|
|
43
44
|
exports.$nsid = $nsid;
|
|
44
45
|
/** [NOTE: This is under active development and should be considered unstable while this note is here]. Adds members to a group. The members are added in 'request' status, so they have to accept it. This creates convo memberships. */
|
|
@@ -47,6 +48,7 @@ const main = lex_1.l.procedure($nsid, lex_1.l.params(), lex_1.l.jsonPayload({
|
|
|
47
48
|
members: lex_1.l.array(lex_1.l.string({ format: 'did' }), { minLength: 1 }),
|
|
48
49
|
}), lex_1.l.jsonPayload({
|
|
49
50
|
convo: lex_1.l.ref((() => ConvoDefs.convoView)),
|
|
51
|
+
addedMembers: lex_1.l.optional(lex_1.l.array(lex_1.l.ref((() => ActorDefs.profileViewBasic)))),
|
|
50
52
|
}), [
|
|
51
53
|
'AccountSuspended',
|
|
52
54
|
'BlockedActor',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addMembers.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/chat/bsky/group/addMembers.defs.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,sCAAgC;AAChC,iEAAkD;AAElD,MAAM,KAAK,GAAG,4BAA4B,CAAA;AAEjC,sBAAK;AAEd,yOAAyO;AACzO,MAAM,IAAI,GAAG,OAAC,CAAC,SAAS,CACtB,KAAK,EACL,OAAC,CAAC,MAAM,EAAE,EACV,OAAC,CAAC,WAAW,CAAC;IACZ,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;CAChE,CAAC,EACF,OAAC,CAAC,WAAW,CAAC;IACZ,KAAK,EAAE,OAAC,CAAC,GAAG,CAAsB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"addMembers.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/chat/bsky/group/addMembers.defs.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,sCAAgC;AAChC,iEAAkD;AAClD,iEAAkD;AAElD,MAAM,KAAK,GAAG,4BAA4B,CAAA;AAEjC,sBAAK;AAEd,yOAAyO;AACzO,MAAM,IAAI,GAAG,OAAC,CAAC,SAAS,CACtB,KAAK,EACL,OAAC,CAAC,MAAM,EAAE,EACV,OAAC,CAAC,WAAW,CAAC;IACZ,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;CAChE,CAAC,EACF,OAAC,CAAC,WAAW,CAAC;IACZ,KAAK,EAAE,OAAC,CAAC,GAAG,CAAsB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAQ,CAAC;IACrE,YAAY,EAAE,OAAC,CAAC,QAAQ,CACtB,OAAC,CAAC,KAAK,CACL,OAAC,CAAC,GAAG,CACH,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAQ,CAC1C,CACF,CACF;CACF,CAAC,EACF;IACE,kBAAkB;IAClB,cAAc;IACd,sBAAsB;IACtB,aAAa;IACb,kBAAkB;IAClB,cAAc;IACd,oBAAoB;IACpB,qBAAqB;IACrB,mBAAmB;CACpB,CACF,CAAA;AACQ,oBAAI;AAcA,QAAA,IAAI,GAAG,IAAI,CAAC,IAAI,EAC3B,QAAA,OAAO,GAAG,IAAI,CAAC,UAAU,EACzB,QAAA,MAAM,GAAG,IAAI,CAAC,KAAK,EACnB,QAAA,OAAO,GAAG,IAAI,CAAC,MAAM,CAAA","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from '@atproto/lex'\nimport * as ConvoDefs from '../convo/defs.defs.js'\nimport * as ActorDefs from '../actor/defs.defs.js'\n\nconst $nsid = 'chat.bsky.group.addMembers'\n\nexport { $nsid }\n\n/** [NOTE: This is under active development and should be considered unstable while this note is here]. Adds members to a group. The members are added in 'request' status, so they have to accept it. This creates convo memberships. */\nconst main = l.procedure(\n $nsid,\n l.params(),\n l.jsonPayload({\n convoId: l.string(),\n members: l.array(l.string({ format: 'did' }), { minLength: 1 }),\n }),\n l.jsonPayload({\n convo: l.ref<ConvoDefs.ConvoView>((() => ConvoDefs.convoView) as any),\n addedMembers: l.optional(\n l.array(\n l.ref<ActorDefs.ProfileViewBasic>(\n (() => ActorDefs.profileViewBasic) as any,\n ),\n ),\n ),\n }),\n [\n 'AccountSuspended',\n 'BlockedActor',\n 'GroupInvitesDisabled',\n 'ConvoLocked',\n 'InsufficientRole',\n 'InvalidConvo',\n 'MemberLimitReached',\n 'NotFollowedBySender',\n 'RecipientNotFound',\n ],\n)\nexport { main }\n\nexport type $Params = l.InferMethodParams<typeof main>\nexport type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>\nexport type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<\n typeof main,\n B\n>\nexport type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>\nexport type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<\n typeof main,\n B\n>\n\nexport const $lxm = main.nsid,\n $params = main.parameters,\n $input = main.input,\n $output = main.output\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/pds",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.221",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of atproto Personal Data Server (PDS)",
|
|
6
6
|
"keywords": [
|
|
@@ -45,27 +45,26 @@
|
|
|
45
45
|
"undici": "^6.19.8",
|
|
46
46
|
"zod": "^3.23.8",
|
|
47
47
|
"@atproto-labs/fetch-node": "^0.2.0",
|
|
48
|
-
"@atproto-labs/simple-store-redis": "^0.0.1",
|
|
49
|
-
"@atproto-labs/simple-store-memory": "^0.1.4",
|
|
50
48
|
"@atproto-labs/simple-store": "^0.3.0",
|
|
49
|
+
"@atproto-labs/simple-store-memory": "^0.1.4",
|
|
50
|
+
"@atproto-labs/simple-store-redis": "^0.0.1",
|
|
51
51
|
"@atproto-labs/xrpc-utils": "^0.0.24",
|
|
52
|
+
"@atproto/aws": "^0.2.32",
|
|
52
53
|
"@atproto/common": "^0.5.16",
|
|
53
54
|
"@atproto/crypto": "^0.4.5",
|
|
54
55
|
"@atproto/identity": "^0.4.12",
|
|
55
56
|
"@atproto/lex": "^0.0.25",
|
|
56
|
-
"@atproto/aws": "^0.2.32",
|
|
57
57
|
"@atproto/lex-cbor": "^0.0.16",
|
|
58
|
-
"@atproto/lex-json": "^0.0.16",
|
|
59
58
|
"@atproto/lex-data": "^0.0.15",
|
|
59
|
+
"@atproto/lex-json": "^0.0.16",
|
|
60
|
+
"@atproto/oauth-provider": "^0.16.3",
|
|
60
61
|
"@atproto/oauth-scopes": "^0.3.2",
|
|
61
|
-
"@atproto/
|
|
62
|
-
"@atproto/xrpc": "^0.7.7",
|
|
62
|
+
"@atproto/repo": "^0.9.1",
|
|
63
63
|
"@atproto/syntax": "^0.5.4",
|
|
64
|
-
"@atproto/xrpc
|
|
65
|
-
"@atproto/
|
|
64
|
+
"@atproto/xrpc": "^0.7.7",
|
|
65
|
+
"@atproto/xrpc-server": "^0.10.20"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@atproto/pds-entryway": "npm:@atproto/pds@0.3.0-entryway.3",
|
|
69
68
|
"@did-plc/server": "^0.0.1",
|
|
70
69
|
"@types/cors": "^2.8.12",
|
|
71
70
|
"@types/express": "^4.17.13",
|
|
@@ -80,8 +79,8 @@
|
|
|
80
79
|
"ts-node": "^10.8.2",
|
|
81
80
|
"typescript": "^5.6.3",
|
|
82
81
|
"ws": "^8.12.0",
|
|
83
|
-
"@atproto/api": "^0.19.
|
|
84
|
-
"@atproto/bsky": "^0.0.
|
|
82
|
+
"@atproto/api": "^0.19.11",
|
|
83
|
+
"@atproto/bsky": "^0.0.227",
|
|
85
84
|
"@atproto/lex-document": "^0.0.20",
|
|
86
85
|
"@atproto/oauth-client-browser-example": "^0.0.10"
|
|
87
86
|
},
|
package/src/auth-verifier.ts
CHANGED
|
@@ -465,8 +465,8 @@ export class AuthVerifier {
|
|
|
465
465
|
throw new AuthRequiredError(undefined, 'AuthMissing')
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
const { payload
|
|
469
|
-
.jwtVerify(token, this._jwtKey,
|
|
468
|
+
const { payload } = await jose
|
|
469
|
+
.jwtVerify(token, this._jwtKey, options)
|
|
470
470
|
.catch((cause) => {
|
|
471
471
|
if (cause instanceof jose.errors.JWTExpired) {
|
|
472
472
|
throw new InvalidRequestError('Token has expired', 'ExpiredToken', {
|
|
@@ -481,14 +481,6 @@ export class AuthVerifier {
|
|
|
481
481
|
}
|
|
482
482
|
})
|
|
483
483
|
|
|
484
|
-
// @NOTE: the "typ" is now set in production environments, so we should be
|
|
485
|
-
// able to safely check it through jose.jwtVerify(). However, tests depend
|
|
486
|
-
// on @atproto/pds-entryway which does not set "typ" in the access tokens.
|
|
487
|
-
// For that reason, we still allow it to be missing.
|
|
488
|
-
if (protectedHeader.typ && options.typ !== protectedHeader.typ) {
|
|
489
|
-
throw new InvalidRequestError('Invalid token type', 'InvalidToken')
|
|
490
|
-
}
|
|
491
|
-
|
|
492
484
|
const { sub, aud, scope, lxm, cnf, jti } = payload
|
|
493
485
|
|
|
494
486
|
if (typeof lxm !== 'undefined') {
|
|
@@ -634,7 +626,7 @@ const extractAuthType = (req: IncomingMessage): AuthType | null => {
|
|
|
634
626
|
return type
|
|
635
627
|
}
|
|
636
628
|
|
|
637
|
-
const bearerTokenFromReq = (req: IncomingMessage) => {
|
|
629
|
+
export const bearerTokenFromReq = (req: IncomingMessage) => {
|
|
638
630
|
const [type, token] = parseAuthorizationHeader(req)
|
|
639
631
|
return type === AuthType.BEARER ? token : null
|
|
640
632
|
}
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,11 @@ import compression from './util/compression'
|
|
|
32
32
|
import * as wellKnown from './well-known'
|
|
33
33
|
|
|
34
34
|
export * from './lexicons.js'
|
|
35
|
-
export {
|
|
35
|
+
export {
|
|
36
|
+
bearerTokenFromReq,
|
|
37
|
+
createPublicKeyObject,
|
|
38
|
+
createSecretKeyObject,
|
|
39
|
+
} from './auth-verifier'
|
|
36
40
|
export * from './config'
|
|
37
41
|
export { AppContext } from './context'
|
|
38
42
|
export { Database } from './db'
|
package/tests/auth.test.ts
CHANGED
|
@@ -331,7 +331,9 @@ describe('auth', () => {
|
|
|
331
331
|
password: 'password',
|
|
332
332
|
})
|
|
333
333
|
const refreshWithAccess = refreshSession(account.accessJwt)
|
|
334
|
-
await expect(refreshWithAccess).rejects.toThrow(
|
|
334
|
+
await expect(refreshWithAccess).rejects.toThrow(
|
|
335
|
+
'Token could not be verified',
|
|
336
|
+
)
|
|
335
337
|
})
|
|
336
338
|
|
|
337
339
|
it('expired refresh token cannot be used to refresh a session.', async () => {
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { createPrivateKey } from 'node:crypto'
|
|
2
|
+
import * as http from 'node:http'
|
|
3
|
+
import * as plcLib from '@did-plc/lib'
|
|
4
|
+
import { HttpTerminator, createHttpTerminator } from 'http-terminator'
|
|
5
|
+
import * as jose from 'jose'
|
|
6
|
+
import KeyEncoder from 'key-encoder'
|
|
7
|
+
import * as ui8 from 'uint8arrays'
|
|
8
|
+
import { AtpAgent } from '@atproto/api'
|
|
9
|
+
import { getVerificationMaterial } from '@atproto/common'
|
|
10
|
+
import { Secp256k1Keypair, randomStr } from '@atproto/crypto'
|
|
11
|
+
import { IdResolver, getDidKeyFromMultibase } from '@atproto/identity'
|
|
12
|
+
import { DidString, HandleString } from '@atproto/syntax'
|
|
13
|
+
import {
|
|
14
|
+
AuthRequiredError,
|
|
15
|
+
createServer,
|
|
16
|
+
parseReqNsid,
|
|
17
|
+
verifyJwt as verifyServiceJwt,
|
|
18
|
+
} from '@atproto/xrpc-server'
|
|
19
|
+
import { bearerTokenFromReq, createPublicKeyObject } from '../src/auth-verifier'
|
|
20
|
+
import { com } from '../src/lexicons/index.js'
|
|
21
|
+
|
|
22
|
+
interface Account {
|
|
23
|
+
did: DidString
|
|
24
|
+
handle: HandleString
|
|
25
|
+
email?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface MockEntrywayOpts {
|
|
29
|
+
port: number
|
|
30
|
+
serviceDid: string
|
|
31
|
+
plcUrl: string
|
|
32
|
+
pdsUrl: string
|
|
33
|
+
pdsDid: string
|
|
34
|
+
adminPassword: string
|
|
35
|
+
jwtSigningKey: Secp256k1Keypair
|
|
36
|
+
plcRotationKey: Secp256k1Keypair
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type AccessAuthResult = { credentials: { did: string; type: 'access' } }
|
|
40
|
+
type ServiceAuthResult = { credentials: { did: string; type: 'service' } }
|
|
41
|
+
|
|
42
|
+
export class MockEntryway {
|
|
43
|
+
public url: string
|
|
44
|
+
public serviceDid: string
|
|
45
|
+
public plcRotationKey: Secp256k1Keypair
|
|
46
|
+
public idResolver: IdResolver
|
|
47
|
+
|
|
48
|
+
private server: http.Server
|
|
49
|
+
private terminator: HttpTerminator
|
|
50
|
+
private accounts = new Map<string, Account>()
|
|
51
|
+
|
|
52
|
+
private constructor(
|
|
53
|
+
server: http.Server,
|
|
54
|
+
terminator: HttpTerminator,
|
|
55
|
+
idResolver: IdResolver,
|
|
56
|
+
opts: MockEntrywayOpts,
|
|
57
|
+
) {
|
|
58
|
+
this.server = server
|
|
59
|
+
this.terminator = terminator
|
|
60
|
+
this.url = `http://localhost:${opts.port}`
|
|
61
|
+
this.serviceDid = opts.serviceDid
|
|
62
|
+
this.plcRotationKey = opts.plcRotationKey
|
|
63
|
+
this.idResolver = idResolver
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static async create(opts: MockEntrywayOpts): Promise<MockEntryway> {
|
|
67
|
+
const keyEncoder = new KeyEncoder('secp256k1')
|
|
68
|
+
const privateKeyHex = ui8.toString(await opts.jwtSigningKey.export(), 'hex')
|
|
69
|
+
const privatePem = keyEncoder.encodePrivate(privateKeyHex, 'raw', 'pem')
|
|
70
|
+
const jwtPrivateKey = createPrivateKey({ format: 'pem', key: privatePem })
|
|
71
|
+
const jwtPublicKey = createPublicKeyObject(
|
|
72
|
+
opts.jwtSigningKey.publicKeyStr('hex'),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const plcClient = new plcLib.Client(opts.plcUrl)
|
|
76
|
+
const pdsAgent = new AtpAgent({ service: opts.pdsUrl })
|
|
77
|
+
const idResolver = new IdResolver({ plcUrl: opts.plcUrl })
|
|
78
|
+
|
|
79
|
+
const accounts = new Map<string, Account>()
|
|
80
|
+
|
|
81
|
+
const getSigningKey = async (
|
|
82
|
+
iss: string,
|
|
83
|
+
forceRefresh: boolean,
|
|
84
|
+
): Promise<string> => {
|
|
85
|
+
const [did, serviceId] = iss.split('#')
|
|
86
|
+
if (serviceId) {
|
|
87
|
+
throw new AuthRequiredError('no service id expected in iss claim')
|
|
88
|
+
}
|
|
89
|
+
const didDoc = await idResolver.did.resolve(did, forceRefresh)
|
|
90
|
+
if (!didDoc) {
|
|
91
|
+
throw new AuthRequiredError(`could not resolve did: ${did}`)
|
|
92
|
+
}
|
|
93
|
+
const parsedKey = getVerificationMaterial(didDoc, 'atproto')
|
|
94
|
+
if (!parsedKey) {
|
|
95
|
+
throw new AuthRequiredError('missing or bad key in did doc')
|
|
96
|
+
}
|
|
97
|
+
const didKey = getDidKeyFromMultibase(parsedKey)
|
|
98
|
+
if (!didKey) {
|
|
99
|
+
throw new AuthRequiredError('missing or bad key in did doc')
|
|
100
|
+
}
|
|
101
|
+
return didKey
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const bearerToken = (req: http.IncomingMessage): string => {
|
|
105
|
+
const token = bearerTokenFromReq(req)
|
|
106
|
+
if (!token) {
|
|
107
|
+
throw new AuthRequiredError('missing bearer token')
|
|
108
|
+
}
|
|
109
|
+
return token
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Auth: verify user access token (typ: 'at+jwt') signed by entryway
|
|
113
|
+
const accessAuth = async ({
|
|
114
|
+
req,
|
|
115
|
+
}: {
|
|
116
|
+
req: http.IncomingMessage
|
|
117
|
+
}): Promise<AccessAuthResult> => {
|
|
118
|
+
try {
|
|
119
|
+
const token = bearerToken(req)
|
|
120
|
+
const { protectedHeader, payload } = await jose.jwtVerify(
|
|
121
|
+
token,
|
|
122
|
+
jwtPublicKey,
|
|
123
|
+
)
|
|
124
|
+
if (protectedHeader.typ !== 'at+jwt') {
|
|
125
|
+
throw new AuthRequiredError('expected typ: at+jwt')
|
|
126
|
+
}
|
|
127
|
+
if (!payload.sub) {
|
|
128
|
+
throw new AuthRequiredError('missing sub in token')
|
|
129
|
+
}
|
|
130
|
+
return { credentials: { did: payload.sub, type: 'access' } }
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.log(err)
|
|
133
|
+
throw err
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Auth: verify service auth token from PDS (no typ / typ !== 'at+jwt')
|
|
138
|
+
const serviceAuth = async ({
|
|
139
|
+
req,
|
|
140
|
+
}: {
|
|
141
|
+
req: http.IncomingMessage
|
|
142
|
+
}): Promise<ServiceAuthResult> => {
|
|
143
|
+
try {
|
|
144
|
+
const token = bearerToken(req)
|
|
145
|
+
const { typ } = jose.decodeProtectedHeader(token)
|
|
146
|
+
if (typ === 'at+jwt') {
|
|
147
|
+
throw new AuthRequiredError(
|
|
148
|
+
'expected service auth: typ must not be at+jwt',
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
const nsid = parseReqNsid(req)
|
|
152
|
+
const payload = await verifyServiceJwt(
|
|
153
|
+
token,
|
|
154
|
+
opts.serviceDid,
|
|
155
|
+
nsid,
|
|
156
|
+
getSigningKey,
|
|
157
|
+
)
|
|
158
|
+
return { credentials: { did: payload.iss, type: 'service' } }
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.log(err)
|
|
161
|
+
throw err
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Auth: accept either access token or service auth
|
|
166
|
+
const accessOrServiceAuth = async ({
|
|
167
|
+
req,
|
|
168
|
+
}: {
|
|
169
|
+
req: http.IncomingMessage
|
|
170
|
+
}): Promise<AccessAuthResult | ServiceAuthResult> => {
|
|
171
|
+
const token = bearerToken(req)
|
|
172
|
+
const { typ } = jose.decodeProtectedHeader(token)
|
|
173
|
+
if (typ === 'at+jwt') {
|
|
174
|
+
return accessAuth({ req })
|
|
175
|
+
}
|
|
176
|
+
return serviceAuth({ req })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const server = createServer()
|
|
180
|
+
|
|
181
|
+
server.add(com.atproto.server.createAccount, {
|
|
182
|
+
handler: async ({ input }) => {
|
|
183
|
+
const { email, handle } = input.body
|
|
184
|
+
|
|
185
|
+
// Reserve a signing key on the PDS
|
|
186
|
+
const {
|
|
187
|
+
data: { signingKey },
|
|
188
|
+
} = await pdsAgent.com.atproto.server.reserveSigningKey({})
|
|
189
|
+
|
|
190
|
+
// Create PLC operation
|
|
191
|
+
const plcCreate = await plcLib.createOp({
|
|
192
|
+
signingKey,
|
|
193
|
+
rotationKeys: [opts.plcRotationKey.did()],
|
|
194
|
+
handle,
|
|
195
|
+
pds: opts.pdsUrl,
|
|
196
|
+
signer: opts.plcRotationKey,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Create account on PDS (no auth needed — userServiceAuthOptional)
|
|
200
|
+
await pdsAgent.com.atproto.server.createAccount({
|
|
201
|
+
did: plcCreate.did,
|
|
202
|
+
handle,
|
|
203
|
+
plcOp: plcCreate.op,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Store account in memory
|
|
207
|
+
accounts.set(plcCreate.did, {
|
|
208
|
+
did: plcCreate.did as DidString,
|
|
209
|
+
handle,
|
|
210
|
+
email,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Sign access + refresh JWTs
|
|
214
|
+
const now = Math.floor(Date.now() / 1000)
|
|
215
|
+
const accessJwt = await new jose.SignJWT({
|
|
216
|
+
scope: 'com.atproto.access',
|
|
217
|
+
})
|
|
218
|
+
.setProtectedHeader({ alg: 'ES256K', typ: 'at+jwt' })
|
|
219
|
+
.setSubject(plcCreate.did)
|
|
220
|
+
.setAudience(opts.pdsDid)
|
|
221
|
+
.setIssuedAt(now)
|
|
222
|
+
.setExpirationTime(now + 60 * 60)
|
|
223
|
+
.setJti(randomStr(16, 'base32'))
|
|
224
|
+
.sign(jwtPrivateKey)
|
|
225
|
+
|
|
226
|
+
const refreshJwt = await new jose.SignJWT({
|
|
227
|
+
scope: 'com.atproto.refresh',
|
|
228
|
+
})
|
|
229
|
+
.setProtectedHeader({ alg: 'ES256K', typ: 'at+jwt' })
|
|
230
|
+
.setSubject(plcCreate.did)
|
|
231
|
+
.setAudience(opts.pdsDid)
|
|
232
|
+
.setIssuedAt(now)
|
|
233
|
+
.setExpirationTime(now + 90 * 24 * 60 * 60)
|
|
234
|
+
.setJti(randomStr(16, 'base32'))
|
|
235
|
+
.sign(jwtPrivateKey)
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
encoding: 'application/json' as const,
|
|
239
|
+
body: {
|
|
240
|
+
did: plcCreate.did as DidString,
|
|
241
|
+
handle,
|
|
242
|
+
accessJwt,
|
|
243
|
+
refreshJwt,
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
server.add(com.atproto.server.getSession, {
|
|
250
|
+
auth: accessOrServiceAuth,
|
|
251
|
+
handler: async ({ auth }) => {
|
|
252
|
+
const account = accounts.get(auth.credentials.did)
|
|
253
|
+
if (!account) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Could not find account for DID: ${auth.credentials.did}`,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
encoding: 'application/json' as const,
|
|
260
|
+
body: {
|
|
261
|
+
did: account.did,
|
|
262
|
+
handle: account.handle,
|
|
263
|
+
email: account.email,
|
|
264
|
+
emailConfirmed: false,
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
server.add(com.atproto.identity.updateHandle, {
|
|
271
|
+
auth: serviceAuth,
|
|
272
|
+
handler: async ({ auth, input }) => {
|
|
273
|
+
// The PDS sends { did, handle } where did is the target user
|
|
274
|
+
const body = input.body as typeof input.body & { did?: string }
|
|
275
|
+
const targetDid = body.did || auth.credentials.did
|
|
276
|
+
const newHandle = body.handle
|
|
277
|
+
|
|
278
|
+
// Update handle in PLC
|
|
279
|
+
await plcClient.updateHandle(targetDid, opts.plcRotationKey, newHandle)
|
|
280
|
+
|
|
281
|
+
// Update in-memory account
|
|
282
|
+
const account = accounts.get(targetDid)
|
|
283
|
+
if (account) {
|
|
284
|
+
account.handle = newHandle
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Notify PDS via admin endpoint
|
|
288
|
+
const adminAuth = Buffer.from(`admin:${opts.adminPassword}`).toString(
|
|
289
|
+
'base64',
|
|
290
|
+
)
|
|
291
|
+
await pdsAgent.com.atproto.admin.updateAccountHandle(
|
|
292
|
+
{ did: targetDid, handle: newHandle },
|
|
293
|
+
{
|
|
294
|
+
headers: { authorization: `Basic ${adminAuth}` },
|
|
295
|
+
encoding: 'application/json',
|
|
296
|
+
},
|
|
297
|
+
)
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const httpServer = server.listen(opts.port)
|
|
302
|
+
const terminator = createHttpTerminator({ server: httpServer })
|
|
303
|
+
|
|
304
|
+
const instance = new MockEntryway(httpServer, terminator, idResolver, opts)
|
|
305
|
+
instance.accounts = accounts
|
|
306
|
+
|
|
307
|
+
return instance
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getAccount(did: string): Account | undefined {
|
|
311
|
+
return this.accounts.get(did)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async destroy(): Promise<void> {
|
|
315
|
+
await this.terminator.terminate()
|
|
316
|
+
}
|
|
317
|
+
}
|
package/tests/entryway.test.ts
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import * as os from 'node:os'
|
|
3
|
-
import * as path from 'node:path'
|
|
4
2
|
import * as plcLib from '@did-plc/lib'
|
|
5
3
|
import getPort from 'get-port'
|
|
6
|
-
import { decodeJwt } from 'jose'
|
|
7
|
-
import * as ui8 from 'uint8arrays'
|
|
8
4
|
import { AtpAgent } from '@atproto/api'
|
|
9
|
-
import { Secp256k1Keypair
|
|
5
|
+
import { Secp256k1Keypair } from '@atproto/crypto'
|
|
10
6
|
import { SeedClient, TestPds, TestPlc, mockResolvers } from '@atproto/dev-env'
|
|
11
7
|
import { isDidString } from '@atproto/lex'
|
|
12
|
-
import * as pdsEntryway from '@atproto/pds-entryway'
|
|
13
8
|
import { DidString } from '@atproto/syntax'
|
|
14
|
-
import {
|
|
9
|
+
import { MockEntryway } from './entryway-mock'
|
|
15
10
|
|
|
16
11
|
describe('entryway', () => {
|
|
17
12
|
let plc: TestPlc
|
|
18
13
|
let pds: TestPds
|
|
19
|
-
let entryway:
|
|
14
|
+
let entryway: MockEntryway
|
|
20
15
|
let pdsAgent: AtpAgent
|
|
21
16
|
let entrywayAgent: AtpAgent
|
|
22
17
|
let alice: DidString
|
|
@@ -30,7 +25,7 @@ describe('entryway', () => {
|
|
|
30
25
|
pds = await TestPds.create({
|
|
31
26
|
entrywayUrl: `http://localhost:${entrywayPort}`,
|
|
32
27
|
entrywayDid: 'did:example:entryway',
|
|
33
|
-
entrywayJwtVerifyKeyK256PublicKeyHex:
|
|
28
|
+
entrywayJwtVerifyKeyK256PublicKeyHex: jwtSigningKey.publicKeyStr('hex'),
|
|
34
29
|
entrywayPlcRotationKey: plcRotationKey.did(),
|
|
35
30
|
adminPassword: 'admin-pass',
|
|
36
31
|
serviceHandleDomains: [],
|
|
@@ -38,29 +33,21 @@ describe('entryway', () => {
|
|
|
38
33
|
serviceDid: 'did:example:pds',
|
|
39
34
|
inviteRequired: false,
|
|
40
35
|
})
|
|
41
|
-
entryway = await
|
|
42
|
-
dbPostgresSchema: 'entryway',
|
|
36
|
+
entryway = await MockEntryway.create({
|
|
43
37
|
port: entrywayPort,
|
|
44
|
-
adminPassword: 'admin-pass',
|
|
45
|
-
jwtSigningKeyK256PrivateKeyHex: await getPrivateHex(jwtSigningKey),
|
|
46
|
-
plcRotationKeyK256PrivateKeyHex: await getPrivateHex(plcRotationKey),
|
|
47
|
-
inviteRequired: false,
|
|
48
38
|
serviceDid: 'did:example:entryway',
|
|
49
|
-
|
|
39
|
+
plcUrl: plc.url,
|
|
40
|
+
pdsUrl: pds.url,
|
|
41
|
+
pdsDid: 'did:example:pds',
|
|
42
|
+
adminPassword: 'admin-pass',
|
|
43
|
+
jwtSigningKey,
|
|
44
|
+
plcRotationKey,
|
|
50
45
|
})
|
|
51
46
|
mockResolvers(pds.ctx.idResolver, pds)
|
|
52
|
-
mockResolvers(entryway.
|
|
53
|
-
await entryway.ctx.db.db
|
|
54
|
-
.insertInto('pds')
|
|
55
|
-
.values({
|
|
56
|
-
did: pds.ctx.cfg.service.did,
|
|
57
|
-
host: new URL(pds.ctx.cfg.service.publicUrl).host,
|
|
58
|
-
weight: 1,
|
|
59
|
-
})
|
|
60
|
-
.execute()
|
|
47
|
+
mockResolvers(entryway.idResolver, pds)
|
|
61
48
|
pdsAgent = pds.getAgent()
|
|
62
49
|
entrywayAgent = new AtpAgent({
|
|
63
|
-
service: entryway.
|
|
50
|
+
service: entryway.url,
|
|
64
51
|
})
|
|
65
52
|
})
|
|
66
53
|
|
|
@@ -110,9 +97,7 @@ describe('entryway', () => {
|
|
|
110
97
|
const doc = await pds.ctx.idResolver.did.resolve(alice)
|
|
111
98
|
const handleToDid = await pds.ctx.idResolver.handle.resolve('alice2.test')
|
|
112
99
|
const accountFromPds = await pds.ctx.accountManager.getAccount(alice)
|
|
113
|
-
const accountFromEntryway =
|
|
114
|
-
.account(entryway.ctx.db)
|
|
115
|
-
.getAccount(alice)
|
|
100
|
+
const accountFromEntryway = entryway.getAccount(alice)
|
|
116
101
|
expect(doc?.alsoKnownAs).toEqual(['at://alice2.test'])
|
|
117
102
|
expect(handleToDid).toEqual(alice)
|
|
118
103
|
expect(accountFromPds?.handle).toEqual('alice2.test')
|
|
@@ -128,13 +113,10 @@ describe('entryway', () => {
|
|
|
128
113
|
'com.atproto.identity.updateHandle',
|
|
129
114
|
),
|
|
130
115
|
)
|
|
131
|
-
const doc = await entryway.
|
|
132
|
-
const handleToDid =
|
|
133
|
-
await entryway.ctx.idResolver.handle.resolve('alice3.test')
|
|
116
|
+
const doc = await entryway.idResolver.did.resolve(alice)
|
|
117
|
+
const handleToDid = await entryway.idResolver.handle.resolve('alice3.test')
|
|
134
118
|
const accountFromPds = await pds.ctx.accountManager.getAccount(alice)
|
|
135
|
-
const accountFromEntryway =
|
|
136
|
-
.account(entryway.ctx.db)
|
|
137
|
-
.getAccount(alice)
|
|
119
|
+
const accountFromEntryway = entryway.getAccount(alice)
|
|
138
120
|
expect(doc?.alsoKnownAs).toEqual(['at://alice3.test'])
|
|
139
121
|
expect(handleToDid).toEqual(alice)
|
|
140
122
|
expect(accountFromPds?.handle).toEqual('alice3.test')
|
|
@@ -148,7 +130,7 @@ describe('entryway', () => {
|
|
|
148
130
|
const rotationKey = await Secp256k1Keypair.create()
|
|
149
131
|
const plcCreate = await plcLib.createOp({
|
|
150
132
|
signingKey,
|
|
151
|
-
rotationKeys: [rotationKey.did(), entryway.
|
|
133
|
+
rotationKeys: [rotationKey.did(), entryway.plcRotationKey.did()],
|
|
152
134
|
handle: 'weirdalice.test',
|
|
153
135
|
pds: pds.ctx.cfg.service.publicUrl,
|
|
154
136
|
signer: rotationKey,
|
|
@@ -161,67 +143,3 @@ describe('entryway', () => {
|
|
|
161
143
|
await expect(tryCreateAccount).rejects.toThrow('invalid plc operation')
|
|
162
144
|
})
|
|
163
145
|
})
|
|
164
|
-
|
|
165
|
-
const createEntryway = async (
|
|
166
|
-
config: pdsEntryway.ServerEnvironment & {
|
|
167
|
-
adminPassword: string
|
|
168
|
-
jwtSigningKeyK256PrivateKeyHex: string
|
|
169
|
-
plcRotationKeyK256PrivateKeyHex: string
|
|
170
|
-
},
|
|
171
|
-
) => {
|
|
172
|
-
const signingKey = await Secp256k1Keypair.create({ exportable: true })
|
|
173
|
-
const recoveryKey = await Secp256k1Keypair.create({ exportable: true })
|
|
174
|
-
const env: pdsEntryway.ServerEnvironment = {
|
|
175
|
-
isEntryway: true,
|
|
176
|
-
recoveryDidKey: recoveryKey.did(),
|
|
177
|
-
serviceHandleDomains: ['.test'],
|
|
178
|
-
dbPostgresUrl: process.env.DB_POSTGRES_URL,
|
|
179
|
-
blobstoreDiskLocation: path.join(os.tmpdir(), randomStr(8, 'base32')),
|
|
180
|
-
bskyAppViewUrl: 'https://appview.invalid',
|
|
181
|
-
bskyAppViewDid: 'did:example:invalid',
|
|
182
|
-
bskyAppViewCdnUrlPattern: 'http://cdn.appview.com/%s/%s/%s',
|
|
183
|
-
jwtSecret: randomStr(8, 'base32'),
|
|
184
|
-
repoSigningKeyK256PrivateKeyHex: await getPrivateHex(signingKey),
|
|
185
|
-
modServiceUrl: 'https://mod.invalid',
|
|
186
|
-
modServiceDid: 'did:example:invalid',
|
|
187
|
-
...config,
|
|
188
|
-
}
|
|
189
|
-
const cfg = pdsEntryway.envToCfg(env)
|
|
190
|
-
const secrets = pdsEntryway.envToSecrets(env)
|
|
191
|
-
const server = await pdsEntryway.PDS.create(cfg, secrets)
|
|
192
|
-
await server.ctx.db.migrateToLatestOrThrow()
|
|
193
|
-
await server.start()
|
|
194
|
-
// patch entryway access token verification to handle internal service auth pds -> entryway
|
|
195
|
-
const origValidateAccessToken =
|
|
196
|
-
server.ctx.authVerifier.validateAccessToken.bind(server.ctx.authVerifier)
|
|
197
|
-
server.ctx.authVerifier.validateAccessToken = async (req, scopes) => {
|
|
198
|
-
const jwt = req.headers.authorization?.replace('Bearer ', '') ?? ''
|
|
199
|
-
const claims = decodeJwt(jwt)
|
|
200
|
-
if (claims.aud === 'did:example:entryway') {
|
|
201
|
-
assert(claims.lxm === parseReqNsid(req), 'bad lxm claim in service auth')
|
|
202
|
-
assert(claims.aud, 'missing aud claim in service auth')
|
|
203
|
-
assert(claims.iss, 'missing iss claim in service auth')
|
|
204
|
-
return {
|
|
205
|
-
artifacts: jwt,
|
|
206
|
-
credentials: {
|
|
207
|
-
type: 'access',
|
|
208
|
-
scope: 'com.atproto.access' as any,
|
|
209
|
-
audience: claims.aud,
|
|
210
|
-
did: claims.iss,
|
|
211
|
-
},
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return origValidateAccessToken(req, scopes)
|
|
215
|
-
}
|
|
216
|
-
// @TODO temp hack because entryway teardown calls signupActivator.run() by mistake
|
|
217
|
-
server.ctx.signupActivator.run = server.ctx.signupActivator.destroy
|
|
218
|
-
return server
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const getPublicHex = (key: Secp256k1Keypair) => {
|
|
222
|
-
return key.publicKeyStr('hex')
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const getPrivateHex = async (key: Secp256k1Keypair) => {
|
|
226
|
-
return ui8.toString(await key.export(), 'hex')
|
|
227
|
-
}
|