@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 +12 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +26 -2
- package/dist/auth.js.map +1 -1
- package/package.json +4 -4
- package/src/auth.ts +39 -2
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
package/dist/auth.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wDAAyC;AACzC,4CAAwC;AACxC,wDAAyC;AACzC,iDAAkC;AAClC,mCAA2C;
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
|
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 (
|