@atproto/xrpc-server 0.6.2 → 0.6.3

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atproto/xrpc-server
2
2
 
3
+ ## 0.6.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2743](https://github.com/bluesky-social/atproto/pull/2743) [`ebb318325`](https://github.com/bluesky-social/atproto/commit/ebb318325b6e80c4ea1a93a617569da2698afe31) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `iat` claim to service JWTs
8
+
9
+ - [#2743](https://github.com/bluesky-social/atproto/pull/2743) [`ebb318325`](https://github.com/bluesky-social/atproto/commit/ebb318325b6e80c4ea1a93a617569da2698afe31) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Ensure that service auth JWT headers contain an `alg` claim, and ensure that `typ`, if present, is not an unexpected type (e.g. not an access or DPoP token).
10
+
11
+ - Updated dependencies [[`d9ffa3c46`](https://github.com/bluesky-social/atproto/commit/d9ffa3c460924010d7002b616cb7a0c66111cc6c), [`ebb318325`](https://github.com/bluesky-social/atproto/commit/ebb318325b6e80c4ea1a93a617569da2698afe31), [`d9ffa3c46`](https://github.com/bluesky-social/atproto/commit/d9ffa3c460924010d7002b616cb7a0c66111cc6c), [`d9ffa3c46`](https://github.com/bluesky-social/atproto/commit/d9ffa3c460924010d7002b616cb7a0c66111cc6c)]:
12
+ - @atproto/xrpc@0.6.1
13
+ - @atproto/crypto@0.4.1
14
+
3
15
  ## 0.6.2
4
16
 
5
17
  ### Patch Changes
package/dist/auth.d.ts CHANGED
@@ -2,6 +2,7 @@ import * as crypto from '@atproto/crypto';
2
2
  type ServiceJwtParams = {
3
3
  iss: string;
4
4
  aud: string;
5
+ iat?: number;
5
6
  exp?: number;
6
7
  lxm: string | null;
7
8
  keypair: crypto.Keypair;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AAIzC,KAAK,gBAAgB,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAA;CACxB,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,eAAO,MAAM,gBAAgB,WACnB,gBAAgB,KACvB,QAAQ,MAAM,CAoBhB,CAAA;AAED,eAAO,MAAM,wBAAwB,WAAkB,gBAAgB;;;;EAKtE,CAAA;AAMD,eAAO,MAAM,SAAS,WACZ,MAAM,UACN,MAAM,GAAG,IAAI,OAChB,MAAM,GAAG,IAAI,iBACH,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,QAAQ,MAAM,CAAC,KACrE,QAAQ,iBAAiB,CAsE3B,CAAA"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AAIzC,KAAK,gBAAgB,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAA;CACxB,CAAA;AAMD,KAAK,iBAAiB,GAAG;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,eAAO,MAAM,gBAAgB,WACnB,gBAAgB,KACvB,QAAQ,MAAM,CAsBhB,CAAA;AAED,eAAO,MAAM,wBAAwB,WAAkB,gBAAgB;;;;EAKtE,CAAA;AAMD,eAAO,MAAM,SAAS,WACZ,MAAM,UACN,MAAM,GAAG,IAAI,OAChB,MAAM,GAAG,IAAI,iBACH,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,QAAQ,MAAM,CAAC,KACrE,QAAQ,iBAAiB,CA4F3B,CAAA"}
package/dist/auth.js CHANGED
@@ -31,7 +31,8 @@ const ui8 = __importStar(require("uint8arrays"));
31
31
  const types_1 = require("./types");
32
32
  const createServiceJwt = async (params) => {
33
33
  const { iss, aud, keypair } = params;
34
- const exp = params.exp ?? Math.floor((Date.now() + common_1.MINUTE) / 1000);
34
+ const iat = params.iat ?? Math.floor(Date.now() / 1e3);
35
+ const exp = params.exp ?? iat + common_1.MINUTE / 1e3;
35
36
  const lxm = params.lxm ?? undefined;
36
37
  const jti = await crypto.randomStr(16, 'hex');
37
38
  const header = {
@@ -39,6 +40,7 @@ const createServiceJwt = async (params) => {
39
40
  alg: keypair.jwtAlg,
40
41
  };
41
42
  const payload = common.noUndefinedVals({
43
+ iat,
42
44
  iss,
43
45
  aud,
44
46
  exp,
@@ -68,6 +70,20 @@ getSigningKey) => {
68
70
  if (parts.length !== 3) {
69
71
  throw new types_1.AuthRequiredError('poorly formatted jwt', 'BadJwt');
70
72
  }
73
+ const header = parseHeader(parts[0]);
74
+ // The spec does not describe what to do with the "typ" claim. We can,
75
+ // however, forbid some values that are not compatible with our use case.
76
+ if (
77
+ // service tokens are not OAuth 2.0 access tokens
78
+ // https://datatracker.ietf.org/doc/html/rfc9068
79
+ header['typ'] === 'at+jwt' ||
80
+ // "refresh+jwt" is a non-standard type used by the @atproto packages
81
+ header['typ'] === 'refresh+jwt' ||
82
+ // "DPoP" proofs are not meant to be used as service tokens
83
+ // https://datatracker.ietf.org/doc/html/rfc9449
84
+ header['typ'] === 'dpop+jwt') {
85
+ throw new types_1.AuthRequiredError(`Invalid jwt type "${header['typ']}"`, 'BadJwtType');
86
+ }
71
87
  const payload = parsePayload(parts[1]);
72
88
  const sig = parts[2];
73
89
  if (Date.now() / 1000 > payload.exp) {
@@ -83,8 +99,9 @@ getSigningKey) => {
83
99
  }
84
100
  const msgBytes = ui8.fromString(parts.slice(0, 2).join('.'), 'utf8');
85
101
  const sigBytes = ui8.fromString(sig, 'base64url');
86
- const verifySignatureWithKey = (key) => {
102
+ const verifySignatureWithKey = async (key) => {
87
103
  return crypto.verifySignature(key, msgBytes, sigBytes, {
104
+ jwtAlg: header.alg,
88
105
  allowMalleableSig: true,
89
106
  });
90
107
  };
@@ -118,6 +135,13 @@ exports.verifyJwt = verifyJwt;
118
135
  const parseB64UrlToJson = (b64) => {
119
136
  return JSON.parse(common.b64UrlToUtf8(b64));
120
137
  };
138
+ const parseHeader = (b64) => {
139
+ const header = parseB64UrlToJson(b64);
140
+ if (!header || typeof header !== 'object' || typeof header.alg !== 'string') {
141
+ throw new types_1.AuthRequiredError('poorly formatted jwt', 'BadJwt');
142
+ }
143
+ return header;
144
+ };
121
145
  const parsePayload = (b64) => {
122
146
  const payload = parseB64UrlToJson(b64);
123
147
  if (!payload ||
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/xrpc-server",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "license": "MIT",
5
5
  "description": "atproto HTTP API (XRPC) server library",
6
6
  "keywords": [
@@ -25,9 +25,9 @@
25
25
  "ws": "^8.12.0",
26
26
  "zod": "^3.23.8",
27
27
  "@atproto/common": "^0.4.1",
28
+ "@atproto/crypto": "^0.4.1",
28
29
  "@atproto/lexicon": "^0.4.1",
29
- "@atproto/crypto": "^0.4.0",
30
- "@atproto/xrpc": "^0.6.0"
30
+ "@atproto/xrpc": "^0.6.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/express": "^4.17.13",
@@ -39,7 +39,7 @@
39
39
  "jest": "^28.1.2",
40
40
  "key-encoder": "^2.0.3",
41
41
  "multiformats": "^9.9.0",
42
- "@atproto/crypto": "^0.4.0"
42
+ "@atproto/crypto": "^0.4.1"
43
43
  },
44
44
  "scripts": {
45
45
  "test": "jest",
package/src/auth.ts CHANGED
@@ -7,11 +7,16 @@ import { AuthRequiredError } from './types'
7
7
  type ServiceJwtParams = {
8
8
  iss: string
9
9
  aud: string
10
+ iat?: number
10
11
  exp?: number
11
12
  lxm: string | null
12
13
  keypair: crypto.Keypair
13
14
  }
14
15
 
16
+ type ServiceJwtHeaders = {
17
+ alg: string
18
+ } & Record<string, unknown>
19
+
15
20
  type ServiceJwtPayload = {
16
21
  iss: string
17
22
  aud: string
@@ -24,7 +29,8 @@ export const createServiceJwt = async (
24
29
  params: ServiceJwtParams,
25
30
  ): Promise<string> => {
26
31
  const { iss, aud, keypair } = params
27
- const exp = params.exp ?? Math.floor((Date.now() + MINUTE) / 1000)
32
+ const iat = params.iat ?? Math.floor(Date.now() / 1e3)
33
+ const exp = params.exp ?? iat + MINUTE / 1e3
28
34
  const lxm = params.lxm ?? undefined
29
35
  const jti = await crypto.randomStr(16, 'hex')
30
36
  const header = {
@@ -32,6 +38,7 @@ export const createServiceJwt = async (
32
38
  alg: keypair.jwtAlg,
33
39
  }
34
40
  const payload = common.noUndefinedVals({
41
+ iat,
35
42
  iss,
36
43
  aud,
37
44
  exp,
@@ -65,6 +72,27 @@ export const verifyJwt = async (
65
72
  if (parts.length !== 3) {
66
73
  throw new AuthRequiredError('poorly formatted jwt', 'BadJwt')
67
74
  }
75
+
76
+ const header = parseHeader(parts[0])
77
+
78
+ // The spec does not describe what to do with the "typ" claim. We can,
79
+ // however, forbid some values that are not compatible with our use case.
80
+ if (
81
+ // service tokens are not OAuth 2.0 access tokens
82
+ // https://datatracker.ietf.org/doc/html/rfc9068
83
+ header['typ'] === 'at+jwt' ||
84
+ // "refresh+jwt" is a non-standard type used by the @atproto packages
85
+ header['typ'] === 'refresh+jwt' ||
86
+ // "DPoP" proofs are not meant to be used as service tokens
87
+ // https://datatracker.ietf.org/doc/html/rfc9449
88
+ header['typ'] === 'dpop+jwt'
89
+ ) {
90
+ throw new AuthRequiredError(
91
+ `Invalid jwt type "${header['typ']}"`,
92
+ 'BadJwtType',
93
+ )
94
+ }
95
+
68
96
  const payload = parsePayload(parts[1])
69
97
  const sig = parts[2]
70
98
 
@@ -88,8 +116,9 @@ export const verifyJwt = async (
88
116
 
89
117
  const msgBytes = ui8.fromString(parts.slice(0, 2).join('.'), 'utf8')
90
118
  const sigBytes = ui8.fromString(sig, 'base64url')
91
- const verifySignatureWithKey = (key: string) => {
119
+ const verifySignatureWithKey = async (key: string) => {
92
120
  return crypto.verifySignature(key, msgBytes, sigBytes, {
121
+ jwtAlg: header.alg,
93
122
  allowMalleableSig: true,
94
123
  })
95
124
  }
@@ -136,6 +165,14 @@ const parseB64UrlToJson = (b64: string) => {
136
165
  return JSON.parse(common.b64UrlToUtf8(b64))
137
166
  }
138
167
 
168
+ const parseHeader = (b64: string): ServiceJwtHeaders => {
169
+ const header = parseB64UrlToJson(b64)
170
+ if (!header || typeof header !== 'object' || typeof header.alg !== 'string') {
171
+ throw new AuthRequiredError('poorly formatted jwt', 'BadJwt')
172
+ }
173
+ return header
174
+ }
175
+
139
176
  const parsePayload = (b64: string): ServiceJwtPayload => {
140
177
  const payload = parseB64UrlToJson(b64)
141
178
  if (