@atproto/oauth-provider 0.22.7 → 0.22.8
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,18 @@
|
|
|
1
1
|
# @atproto/oauth-provider
|
|
2
2
|
|
|
3
|
+
## 0.22.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5502](https://github.com/bluesky-social/atproto/pull/5502) [`ac0989b`](https://github.com/bluesky-social/atproto/commit/ac0989bf9e16affd4aa919df8609b12f4ee8cd65) Thanks [@devinivy](https://github.com/devinivy)! - Bound the request bodies accepted by the OAuth and account-management endpoints
|
|
8
|
+
at 100 KiB. The bound is on the decoded body, so a compressed request is
|
|
9
|
+
measured after decompression, and a `content-length` above it is rejected
|
|
10
|
+
without reading the body. Oversized bodies fail with a 413.
|
|
11
|
+
- Updated dependencies [[`f88aa58`](https://github.com/bluesky-social/atproto/commit/f88aa5842df9aba9f208a6073272b43ef9bd089d)]:
|
|
12
|
+
- @atproto-labs/fetch-node@0.4.0
|
|
13
|
+
- @atproto/oauth-provider-ui@0.10.3
|
|
14
|
+
- @atproto/lex-resolver@0.2.13
|
|
15
|
+
|
|
3
16
|
## 0.22.7
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
|
-
import type
|
|
2
|
+
import { type Readable } from 'node:stream';
|
|
3
3
|
import { type KnownNames, type ParserResult } from './parser.js';
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Bounds the decoded request body, whichever content-encoding it arrived with.
|
|
6
|
+
* The payloads these routes legitimately carry are single-digit kilobytes, so
|
|
7
|
+
* this leaves ample headroom.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_MAX_BODY_SIZE: number;
|
|
10
|
+
export declare function decodeHttpRequest(req: IncomingMessage, maxSize: number): Readable;
|
|
5
11
|
/**
|
|
6
12
|
* Generic method that parses a stream of unknown nature (HTTP request/response,
|
|
7
13
|
* socket, file, etc.), but of known mime type, into a parsed object.
|
|
8
14
|
*
|
|
9
15
|
* @throws {TypeError} If the content-type is not valid or supported.
|
|
10
16
|
*/
|
|
11
|
-
export declare function parseHttpRequest<A extends readonly KnownNames[]>(req: IncomingMessage, allow: A): Promise<ParserResult<Extract<{
|
|
17
|
+
export declare function parseHttpRequest<A extends readonly KnownNames[]>(req: IncomingMessage, allow: A, maxSize?: number): Promise<ParserResult<Extract<{
|
|
12
18
|
readonly name: 'json';
|
|
13
19
|
readonly test: (mime: string) => mime is `application/json` | `application/${string}+json`;
|
|
14
20
|
readonly parse: (buffer: Buffer<ArrayBufferLike>, { charset }: import("./parser.js").ContentType) => import("./parser.js").Json;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/lib/http/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/lib/http/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAe,KAAK,QAAQ,EAAY,MAAM,aAAa,CAAA;AAOlE,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,YAAY,EAGlB,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAa,CAAA;AAE/C,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,MAAM,GACd,QAAQ,CAoCV;AAED;;;;;GAKG;AAEH,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,SAAS,UAAU,EAAE,EACpE,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,CAAC,EACR,OAAO,GAAE,MAA8B;;;;;UAiBR,CAAC,CAAC,MAAM,CAAC;;;;;;;;;;;;;KAEzC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAI3E"}
|
package/dist/lib/http/stream.js
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
|
+
import { pipeline } from 'node:stream';
|
|
1
2
|
import createHttpError from 'http-errors';
|
|
2
|
-
import {
|
|
3
|
+
import { MaxSizeChecker, createDecoders, streamToNodeBuffer, } from '@atproto/common';
|
|
3
4
|
import { parseContentType, parsers, } from './parser.js';
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Bounds the decoded request body, whichever content-encoding it arrived with.
|
|
7
|
+
* The payloads these routes legitimately carry are single-digit kilobytes, so
|
|
8
|
+
* this leaves ample headroom.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MAX_BODY_SIZE = 100 * 1024; // 100 KiB
|
|
11
|
+
export function decodeHttpRequest(req, maxSize) {
|
|
12
|
+
// @NOTE Content-Length counts the octets actually transferred, i.e. the
|
|
13
|
+
// body *after* content-encoding. It therefore bounds the wire size, which
|
|
14
|
+
// the MaxSizeChecker below does not (that one bounds the decoded size);
|
|
15
|
+
// these are two distinct bounds that happen to share a constant. It is not
|
|
16
|
+
// a substitute for the checker: the header is absent under chunked transfer
|
|
17
|
+
// encoding, and a client may understate it.
|
|
18
|
+
const contentLength = req.headers['content-length'];
|
|
19
|
+
if (contentLength != null) {
|
|
20
|
+
const size = Number(contentLength);
|
|
21
|
+
if (!Number.isInteger(size) || size < 0) {
|
|
22
|
+
throw createHttpError(400, 'Invalid content-length');
|
|
23
|
+
}
|
|
24
|
+
if (size > maxSize) {
|
|
25
|
+
throw createHttpError(413, 'Payload too large');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
5
28
|
try {
|
|
6
|
-
|
|
29
|
+
// @NOTE The checker is applied even when the body is not encoded: nothing
|
|
30
|
+
// else bounds the size of these requests.
|
|
31
|
+
return pipeline([
|
|
32
|
+
req,
|
|
33
|
+
...createDecoders(req.headers['content-encoding']),
|
|
34
|
+
new MaxSizeChecker(maxSize, () => createHttpError(413, 'Payload too large')),
|
|
35
|
+
], () => { });
|
|
7
36
|
}
|
|
8
37
|
catch (cause) {
|
|
9
38
|
const message = cause instanceof TypeError ? cause.message : `Invalid content-encoding`;
|
|
@@ -16,13 +45,13 @@ export function decodeHttpRequest(req) {
|
|
|
16
45
|
*
|
|
17
46
|
* @throws {TypeError} If the content-type is not valid or supported.
|
|
18
47
|
*/
|
|
19
|
-
export async function parseHttpRequest(req, allow) {
|
|
48
|
+
export async function parseHttpRequest(req, allow, maxSize = DEFAULT_MAX_BODY_SIZE) {
|
|
20
49
|
const type = parseContentType(req.headers['content-type'] ?? 'application/octet-stream');
|
|
21
50
|
const parser = parsers.find((parser) => allow.includes(parser.name) && parser.test(type.mime));
|
|
22
51
|
if (!parser) {
|
|
23
52
|
throw createHttpError(415, `Unsupported content-type: ${type.mime}`);
|
|
24
53
|
}
|
|
25
|
-
const stream = decodeHttpRequest(req);
|
|
54
|
+
const stream = decodeHttpRequest(req, maxSize);
|
|
26
55
|
const buffer = await streamToNodeBuffer(stream);
|
|
27
56
|
return parser.parse(buffer, type);
|
|
28
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../../src/lib/http/stream.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../../src/lib/http/stream.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,QAAQ,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,eAAe,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAIL,gBAAgB,EAChB,OAAO,GACR,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,GAAG,IAAI,CAAA,CAAC,UAAU;AAE1D,MAAM,UAAU,iBAAiB,CAC/B,GAAoB,EACpB,OAAe;IAEf,wEAAwE;IACxE,0EAA0E;IAC1E,wEAAwE;IACxE,2EAA2E;IAC3E,4EAA4E;IAC5E,4CAA4C;IAC5C,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACnD,IAAI,aAAa,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;YACxC,MAAM,eAAe,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAA;QACtD,CAAC;QACD,IAAI,IAAI,GAAG,OAAO,EAAE,CAAC;YACnB,MAAM,eAAe,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,0EAA0E;QAC1E,0CAA0C;QAC1C,OAAO,QAAQ,CACb;YACE,GAAG;YACH,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;YAClD,IAAI,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,CAC/B,eAAe,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAC1C;SACF,EACD,GAAG,EAAE,GAAE,CAAC,CACC,CAAA;IACb,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GACX,KAAK,YAAY,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAA;QACzE,MAAM,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAChD,CAAC;AACH,CAAC;AAED;;;;;GAKG;AAEH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAoB,EACpB,KAAQ,EACR,OAAO,GAAW,qBAAqB;IAEvC,MAAM,IAAI,GAAG,gBAAgB,CAC3B,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,0BAA0B,CAC1D,CAAA;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CACzB,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAClE,CAAA;IAED,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,eAAe,CAAC,GAAG,EAAE,6BAA6B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IACtE,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAC/C,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAE/B,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAA0B;IAC1D,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QAC7B,mCAAmC;IACrC,CAAC;AACH,CAAC","sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport { type Duplex, type Readable, pipeline } from 'node:stream'\nimport createHttpError from 'http-errors'\nimport {\n MaxSizeChecker,\n createDecoders,\n streamToNodeBuffer,\n} from '@atproto/common'\nimport {\n type KnownNames,\n type KnownParser,\n type ParserResult,\n parseContentType,\n parsers,\n} from './parser.js'\n\n/**\n * Bounds the decoded request body, whichever content-encoding it arrived with.\n * The payloads these routes legitimately carry are single-digit kilobytes, so\n * this leaves ample headroom.\n */\nexport const DEFAULT_MAX_BODY_SIZE = 100 * 1024 // 100 KiB\n\nexport function decodeHttpRequest(\n req: IncomingMessage,\n maxSize: number,\n): Readable {\n // @NOTE Content-Length counts the octets actually transferred, i.e. the\n // body *after* content-encoding. It therefore bounds the wire size, which\n // the MaxSizeChecker below does not (that one bounds the decoded size);\n // these are two distinct bounds that happen to share a constant. It is not\n // a substitute for the checker: the header is absent under chunked transfer\n // encoding, and a client may understate it.\n const contentLength = req.headers['content-length']\n if (contentLength != null) {\n const size = Number(contentLength)\n if (!Number.isInteger(size) || size < 0) {\n throw createHttpError(400, 'Invalid content-length')\n }\n if (size > maxSize) {\n throw createHttpError(413, 'Payload too large')\n }\n }\n\n try {\n // @NOTE The checker is applied even when the body is not encoded: nothing\n // else bounds the size of these requests.\n return pipeline(\n [\n req,\n ...createDecoders(req.headers['content-encoding']),\n new MaxSizeChecker(maxSize, () =>\n createHttpError(413, 'Payload too large'),\n ),\n ],\n () => {},\n ) as Duplex\n } catch (cause) {\n const message =\n cause instanceof TypeError ? cause.message : `Invalid content-encoding`\n throw createHttpError(415, message, { cause })\n }\n}\n\n/**\n * Generic method that parses a stream of unknown nature (HTTP request/response,\n * socket, file, etc.), but of known mime type, into a parsed object.\n *\n * @throws {TypeError} If the content-type is not valid or supported.\n */\n\nexport async function parseHttpRequest<A extends readonly KnownNames[]>(\n req: IncomingMessage,\n allow: A,\n maxSize: number = DEFAULT_MAX_BODY_SIZE,\n) {\n const type = parseContentType(\n req.headers['content-type'] ?? 'application/octet-stream',\n )\n\n const parser = parsers.find(\n (parser) => allow.includes(parser.name) && parser.test(type.mime),\n )\n\n if (!parser) {\n throw createHttpError(415, `Unsupported content-type: ${type.mime}`)\n }\n\n const stream = decodeHttpRequest(req, maxSize)\n const buffer = await streamToNodeBuffer(stream)\n return parser.parse(buffer, type) as ParserResult<\n Extract<KnownParser, { name: A[number] }>\n >\n}\n\nexport async function flushStream(stream: AsyncIterable<any>): Promise<void> {\n for await (const _ of stream) {\n // Consume the stream to completion\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/oauth-provider",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Generic OAuth2 and OpenID Connect provider for Node.js. Currently only supports features needed for Atproto.",
|
|
6
6
|
"keywords": [
|
|
@@ -72,18 +72,18 @@
|
|
|
72
72
|
"ioredis": "^5.3.2",
|
|
73
73
|
"jose": "^5.2.0",
|
|
74
74
|
"zod": "^3.23.8",
|
|
75
|
-
"@atproto-labs/fetch-node": "^0.3.7",
|
|
76
75
|
"@atproto-labs/pipe": "^0.2.4",
|
|
77
|
-
"@atproto-labs/
|
|
78
|
-
"@atproto-labs/simple-store-memory": "^0.2.6",
|
|
79
|
-
"@atproto/did": "^0.5.5",
|
|
76
|
+
"@atproto-labs/fetch-node": "^0.4.0",
|
|
80
77
|
"@atproto/common": "^0.8.3",
|
|
78
|
+
"@atproto-labs/simple-store-memory": "^0.2.6",
|
|
79
|
+
"@atproto-labs/simple-store": "^0.5.1",
|
|
81
80
|
"@atproto/jwk": "^0.7.4",
|
|
82
81
|
"@atproto/jwk-jose": "^0.2.4",
|
|
82
|
+
"@atproto/did": "^0.5.5",
|
|
83
83
|
"@atproto/lex-document": "^0.1.11",
|
|
84
|
+
"@atproto/lex-resolver": "^0.2.13",
|
|
84
85
|
"@atproto/oauth-types": "^0.7.6",
|
|
85
86
|
"@atproto/oauth-provider-api": "0.8.3",
|
|
86
|
-
"@atproto/lex-resolver": "^0.2.12",
|
|
87
87
|
"@atproto/oauth-provider-ui": "0.10.3",
|
|
88
88
|
"@atproto/oauth-scopes": "^0.5.12",
|
|
89
89
|
"@atproto/syntax": "^0.7.6"
|