canvas-lms-mcp 1.29.4 → 1.30.0

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.
Files changed (103) hide show
  1. package/README.md +40 -5
  2. package/bin/canvas-lms-mcp.js +2 -0
  3. package/dist/auth/doctor.d.ts +29 -0
  4. package/dist/auth/doctor.d.ts.map +1 -0
  5. package/dist/auth/oauth/authorization-server.d.ts +75 -0
  6. package/dist/auth/oauth/authorization-server.d.ts.map +1 -0
  7. package/dist/auth/oauth/canvas-oauth.d.ts +49 -0
  8. package/dist/auth/oauth/canvas-oauth.d.ts.map +1 -0
  9. package/dist/auth/oauth/clients.d.ts +59 -0
  10. package/dist/auth/oauth/clients.d.ts.map +1 -0
  11. package/dist/auth/oauth/config.d.ts +92 -0
  12. package/dist/auth/oauth/config.d.ts.map +1 -0
  13. package/dist/auth/oauth/crypto.d.ts +28 -0
  14. package/dist/auth/oauth/crypto.d.ts.map +1 -0
  15. package/dist/auth/oauth/file-store.d.ts +49 -0
  16. package/dist/auth/oauth/file-store.d.ts.map +1 -0
  17. package/dist/auth/oauth/html.d.ts +27 -0
  18. package/dist/auth/oauth/html.d.ts.map +1 -0
  19. package/dist/auth/oauth/http-util.d.ts +75 -0
  20. package/dist/auth/oauth/http-util.d.ts.map +1 -0
  21. package/dist/auth/oauth/redirect-uri.d.ts +19 -0
  22. package/dist/auth/oauth/redirect-uri.d.ts.map +1 -0
  23. package/dist/auth/oauth/resource-server.d.ts +54 -0
  24. package/dist/auth/oauth/resource-server.d.ts.map +1 -0
  25. package/dist/auth/oauth/store.d.ts +153 -0
  26. package/dist/auth/oauth/store.d.ts.map +1 -0
  27. package/dist/auth/profile.d.ts +38 -0
  28. package/dist/auth/profile.d.ts.map +1 -0
  29. package/dist/canvas/index.cjs +3 -3
  30. package/dist/canvas/index.js +2 -2
  31. package/dist/canvas/quizzes.d.ts +9 -0
  32. package/dist/canvas/quizzes.d.ts.map +1 -1
  33. package/dist/{chunk-Q3VCYFOV.cjs → chunk-2MQFCIVT.cjs} +2 -2
  34. package/dist/{chunk-Q3VCYFOV.cjs.map → chunk-2MQFCIVT.cjs.map} +1 -1
  35. package/dist/{chunk-NIFJL3F7.js → chunk-3ONOWZUS.js} +1 -9
  36. package/dist/chunk-3ONOWZUS.js.map +1 -0
  37. package/dist/chunk-5RCGMMRP.js +179 -0
  38. package/dist/chunk-5RCGMMRP.js.map +1 -0
  39. package/dist/chunk-CYVGBLJZ.cjs +179 -0
  40. package/dist/chunk-CYVGBLJZ.cjs.map +1 -0
  41. package/dist/{chunk-56JXEKIO.cjs → chunk-D4GKVNGN.cjs} +18 -4
  42. package/dist/chunk-D4GKVNGN.cjs.map +1 -0
  43. package/dist/{chunk-SDEHATXU.js → chunk-IIO76ZAT.js} +100 -26
  44. package/dist/chunk-IIO76ZAT.js.map +1 -0
  45. package/dist/chunk-P5SCK76M.cjs +365 -0
  46. package/dist/chunk-P5SCK76M.cjs.map +1 -0
  47. package/dist/{chunk-HVFM7RUX.js → chunk-PMBGCMGK.js} +16 -2
  48. package/dist/chunk-PMBGCMGK.js.map +1 -0
  49. package/dist/chunk-QCEGMTXW.cjs +11 -0
  50. package/dist/chunk-QCEGMTXW.cjs.map +1 -0
  51. package/dist/{chunk-67YTMYAF.cjs → chunk-QMR35KCE.cjs} +3 -11
  52. package/dist/chunk-QMR35KCE.cjs.map +1 -0
  53. package/dist/{chunk-HQLRSSVQ.js → chunk-U5PW3OWK.js} +2 -2
  54. package/dist/{chunk-HQLRSSVQ.js.map → chunk-U5PW3OWK.js.map} +1 -1
  55. package/dist/{chunk-KPCH67HL.cjs → chunk-UICJU2JM.cjs} +144 -70
  56. package/dist/chunk-UICJU2JM.cjs.map +1 -0
  57. package/dist/chunk-VMVKS3DW.js +365 -0
  58. package/dist/chunk-VMVKS3DW.js.map +1 -0
  59. package/dist/chunk-XVZ3T4Z4.js +11 -0
  60. package/dist/chunk-XVZ3T4Z4.js.map +1 -0
  61. package/dist/cli.cjs +5 -3
  62. package/dist/cli.cjs.map +1 -1
  63. package/dist/cli.d.ts +16 -0
  64. package/dist/cli.d.ts.map +1 -1
  65. package/dist/cli.js +4 -2
  66. package/dist/doctor.cjs +272 -0
  67. package/dist/doctor.cjs.map +1 -0
  68. package/dist/doctor.d.ts +5 -0
  69. package/dist/doctor.d.ts.map +1 -0
  70. package/dist/doctor.js +272 -0
  71. package/dist/doctor.js.map +1 -0
  72. package/dist/http.cjs +2127 -42
  73. package/dist/http.cjs.map +1 -1
  74. package/dist/http.d.ts +29 -2
  75. package/dist/http.d.ts.map +1 -1
  76. package/dist/http.js +2124 -39
  77. package/dist/http.js.map +1 -1
  78. package/dist/init.cjs +3 -3
  79. package/dist/init.js +1 -1
  80. package/dist/server.cjs +6 -5
  81. package/dist/server.cjs.map +1 -1
  82. package/dist/server.d.ts +8 -1
  83. package/dist/server.d.ts.map +1 -1
  84. package/dist/server.js +5 -4
  85. package/dist/stdio.cjs +9 -7
  86. package/dist/stdio.cjs.map +1 -1
  87. package/dist/stdio.js +7 -5
  88. package/dist/stdio.js.map +1 -1
  89. package/dist/tools/index.d.ts.map +1 -1
  90. package/dist/tools/quiz-question-responses.d.ts.map +1 -1
  91. package/dist/tools/types.d.ts +10 -0
  92. package/dist/tools/types.d.ts.map +1 -1
  93. package/package.json +1 -1
  94. package/dist/chunk-56JXEKIO.cjs.map +0 -1
  95. package/dist/chunk-67YTMYAF.cjs.map +0 -1
  96. package/dist/chunk-FFK34HYS.cjs +0 -99
  97. package/dist/chunk-FFK34HYS.cjs.map +0 -1
  98. package/dist/chunk-HIX2GVT6.js +0 -99
  99. package/dist/chunk-HIX2GVT6.js.map +0 -1
  100. package/dist/chunk-HVFM7RUX.js.map +0 -1
  101. package/dist/chunk-KPCH67HL.cjs.map +0 -1
  102. package/dist/chunk-NIFJL3F7.js.map +0 -1
  103. package/dist/chunk-SDEHATXU.js.map +0 -1
package/README.md CHANGED
@@ -212,7 +212,9 @@ Host verification (Claude Desktop, ChatGPT, Codex fallback) is performed manuall
212
212
 
213
213
  ## Deployment Modes
214
214
 
215
- ### stdio (Default)
215
+ Every process runs exactly one **auth profile**: `local_static_token` (stdio, the default), `remote_static_token` (`serve`, the default), or `oauth_brokered` (`serve --auth-profile oauth_brokered`). Run `npx canvas-lms-mcp doctor` to see which profile your configuration resolves to and what it is missing, without printing any secret.
216
+
217
+ ### stdio (Default) — `local_static_token`
216
218
 
217
219
  For local AI clients like Claude Desktop, Cursor, and VS Code. The server communicates over stdin/stdout.
218
220
 
@@ -220,9 +222,11 @@ For local AI clients like Claude Desktop, Cursor, and VS Code. The server commun
220
222
  npx canvas-lms-mcp --token $CANVAS_API_TOKEN --base-url $CANVAS_BASE_URL
221
223
  ```
222
224
 
223
- ### HTTP
225
+ stdio has no network edge, so hosts cannot show a login state for it: Codex lists a stdio server as `Auth Unsupported` and `codex mcp login` refuses it. That is by design (the MCP authorization spec applies to HTTP transports only). Use the OAuth profile below when you need a native **Authenticate** experience.
226
+
227
+ ### HTTP (static token, self-managed) — `remote_static_token`
224
228
 
225
- For web-based clients or hosted services. Starts an HTTP server with Streamable HTTP transport.
229
+ For a developer's own HTTP experiments, or an application that already holds Canvas tokens. Starts an HTTP server with Streamable HTTP transport; each request carries a Canvas token in `X-Canvas-Token`, or the server's configured token is used.
226
230
 
227
231
  ```bash
228
232
  npx canvas-lms-mcp serve \
@@ -236,6 +240,22 @@ Endpoints:
236
240
  - `POST /mcp` -- MCP protocol endpoint
237
241
  - `GET /health` -- Health check (returns `{"status":"ok"}`)
238
242
 
243
+ This profile is **self-managed only**: anyone who can reach the port can present any token, and Canvas's API policy forbids asking other users for personal tokens. Do not expose it to other people; use the OAuth profile instead.
244
+
245
+ ### HTTP (OAuth, host-visible login) — `oauth_brokered`
246
+
247
+ For Codex, ChatGPT, Claude, and any other host that implements the MCP authorization specification. The server is an OAuth 2.1 authorization + resource server for MCP clients and connects each user to your Canvas institution through a Canvas Developer Key. Hosts show **Not logged in** → **Authenticate**; `codex mcp login canvas-lms` completes the login in the browser. No `CANVAS_API_TOKEN` is needed, and `X-Canvas-Token` is refused.
248
+
249
+ ```bash
250
+ export CANVAS_BASE_URL=https://school.instructure.com
251
+ export CANVAS_MCP_ISSUER=http://127.0.0.1:3001 # public URL of this server; https when hosted
252
+ export CANVAS_OAUTH_CLIENT_ID=… # Canvas Developer Key
253
+ export CANVAS_OAUTH_CLIENT_SECRET=…
254
+ npx canvas-lms-mcp serve --auth-profile oauth_brokered
255
+ ```
256
+
257
+ Full setup (Canvas admin prerequisites, Codex `config.toml`, hosted deployment, verification matrix): [docs/oauth-profile.md](docs/oauth-profile.md).
258
+
239
259
  ### Docker
240
260
 
241
261
  ```bash
@@ -289,7 +309,11 @@ const courses = await canvas.courses.list()
289
309
  | `--base-url` | `CANVAS_BASE_URL` | (required) | Canvas instance URL |
290
310
  | `serve` | -- | stdio mode | Switch to HTTP mode |
291
311
  | `--port` | -- | `3001` | HTTP server port |
292
- | `--allowed-origin` | `CANVAS_ALLOWED_ORIGIN` | `http://localhost:3000` | CORS allowed origin |
312
+ | `--allowed-origin` | `CANVAS_ALLOWED_ORIGIN` | `http://localhost:3000` | CORS allowed origin; requests carrying any other `Origin` header are refused |
313
+ | `--auth-profile` | `CANVAS_AUTH_PROFILE` | `local_static_token` (stdio) / `remote_static_token` (`serve`) | Auth profile: `local_static_token`, `remote_static_token`, or `oauth_brokered` (see [docs/oauth-profile.md](docs/oauth-profile.md)) |
314
+ | `--host` | `CANVAS_HTTP_HOST` | all interfaces; `127.0.0.1` in `oauth_brokered` | Bind address for HTTP mode |
315
+ | `--issuer` | `CANVAS_MCP_ISSUER` | (required in `oauth_brokered`) | Public URL of this server; OAuth issuer and resource prefix |
316
+ | `doctor` | -- | -- | Print an identity-safe setup report (also `auth status`); exit 1 when something is missing |
293
317
  | `--role` | `CANVAS_ROLE` | (all tools) | Filter tools by Canvas role: `student`, `teacher`, or `admin` (see [Role-based tool filtering](#role-based-tool-filtering)) |
294
318
  | `--destructive-tools=<mode>` | `CANVAS_DESTRUCTIVE_TOOLS` | `allow` | `allow` or `block`. `block` unregisters the seven irreversible delete tools (see [Destructive tool policy](#destructive-tool-policy)) |
295
319
 
@@ -297,9 +321,20 @@ const courses = await canvas.courses.list()
297
321
 
298
322
  | Variable | Required | Description |
299
323
  |----------|----------|-------------|
300
- | `CANVAS_API_TOKEN` | Yes | Canvas personal access token |
324
+ | `CANVAS_API_TOKEN` | Yes, except in `oauth_brokered` | Canvas personal access token |
301
325
  | `CANVAS_BASE_URL` | Yes | Canvas instance URL (e.g., `https://school.instructure.com`) |
302
326
  | `CANVAS_ALLOWED_ORIGIN` | No | CORS origin for HTTP mode (default: `http://localhost:3000`) |
327
+ | `CANVAS_AUTH_PROFILE` | No | `local_static_token`, `remote_static_token`, or `oauth_brokered` (see [docs/oauth-profile.md](docs/oauth-profile.md)) |
328
+ | `CANVAS_HTTP_HOST` | No | Bind address for HTTP mode (default: all interfaces; `127.0.0.1` in `oauth_brokered`) |
329
+ | `CANVAS_MCP_ISSUER` | `oauth_brokered` | Public URL of this server, `https` unless loopback |
330
+ | `CANVAS_OAUTH_CLIENT_ID` | `oauth_brokered` | Canvas Developer Key ID |
331
+ | `CANVAS_OAUTH_CLIENT_SECRET` | `oauth_brokered` | Canvas Developer Key secret |
332
+ | `CANVAS_OAUTH_SCOPES` | No | Space-separated Canvas API scopes for a Developer Key with *Enforce Scopes* |
333
+ | `CANVAS_MCP_OAUTH_CLIENTS` | No | JSON array of pre-registered MCP clients |
334
+ | `CANVAS_MCP_OAUTH_DCR` | No | `true` (default) / `false`: dynamic client registration |
335
+ | `CANVAS_MCP_OAUTH_CIMD_ALLOWED_HOSTS` | No | Trusted Client ID Metadata Document hosts (default `chatgpt.com`; `*` any; `none` off) |
336
+ | `CANVAS_MCP_OAUTH_STORE` | No | Path of the encrypted OAuth grant store (default: in-memory) |
337
+ | `CANVAS_MCP_OAUTH_STORE_KEY` | With store | Secret that encrypts the grant store |
303
338
  | `CANVAS_ROLE` | No | Filter the tool list by role: `student`, `teacher`, or `admin` (see [Role-based tool filtering](#role-based-tool-filtering)) |
304
339
  | `CANVAS_ENABLE_ASSIGNMENT_SUBMISSION` | No | Set to `true` to register the opt-in [assignment submission tools](#student-assignment-submission-opt-in) |
305
340
  | `CANVAS_PSEUDONYMIZE_STUDENTS` | No | Set to `true` to enable [FERPA mode](#ferpa-mode-student-pseudonymization) |
@@ -6,6 +6,8 @@ if (sub === 'init') {
6
6
  await import('../dist/init.js')
7
7
  } else if (sub === 'serve') {
8
8
  await import('../dist/http.js')
9
+ } else if (sub === 'doctor' || (sub === 'auth' && process.argv[3] === 'status')) {
10
+ await import('../dist/doctor.js')
9
11
  } else {
10
12
  await import('../dist/stdio.js')
11
13
  }
@@ -0,0 +1,29 @@
1
+ import { type AuthProfile, type TransportMode } from './profile';
2
+ export type DoctorStatus = 'ok' | 'warn' | 'missing' | 'info';
3
+ export interface DoctorCheck {
4
+ label: string;
5
+ status: DoctorStatus;
6
+ detail: string;
7
+ }
8
+ export interface DoctorReport {
9
+ mode: TransportMode;
10
+ profile?: AuthProfile;
11
+ checks: DoctorCheck[];
12
+ /** True when the server would start with this configuration. */
13
+ ready: boolean;
14
+ }
15
+ export type DoctorEnv = Record<string, string | undefined>;
16
+ interface DoctorArgs {
17
+ mode: TransportMode;
18
+ authProfile?: string;
19
+ token?: string;
20
+ baseUrl?: string;
21
+ issuer?: string;
22
+ host?: string;
23
+ }
24
+ /** A lenient scan of the same flags `parseArgs` reads; never exits. */
25
+ export declare function scanDoctorArgs(argv: string[]): DoctorArgs;
26
+ export declare function diagnose(argv: string[], env: DoctorEnv): DoctorReport;
27
+ export declare function formatReport(report: DoctorReport): string;
28
+ export {};
29
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/auth/doctor.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,WAAW,CAAA;AAElB,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;AAE7D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,gEAAgE;IAChE,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAE1D,UAAU,UAAU;IAClB,IAAI,EAAE,aAAa,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyCzD;AAwDD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,GAAG,YAAY,CAuKrE;AASD,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAazD"}
@@ -0,0 +1,75 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { CanvasOAuthClient } from './canvas-oauth';
3
+ import { ClientResolver } from './clients';
4
+ import { type McpScope, type OAuthProfileConfig } from './config';
5
+ import type { OAuthStore } from './store';
6
+ export declare const FLOW_COOKIE = "canvas_mcp_authz";
7
+ export declare const SCOPE_DESCRIPTIONS: Record<McpScope, string>;
8
+ export interface AuthorizationServerLogger {
9
+ warn(message: string): void;
10
+ error(message: string): void;
11
+ }
12
+ export interface AuthorizationServerOptions {
13
+ config: OAuthProfileConfig;
14
+ store: OAuthStore;
15
+ clients: ClientResolver;
16
+ canvas: CanvasOAuthClient;
17
+ now?: () => number;
18
+ log?: AuthorizationServerLogger;
19
+ }
20
+ export interface OAuthErrorBody {
21
+ error: string;
22
+ error_description?: string;
23
+ }
24
+ type Query = URLSearchParams;
25
+ /** Canonical comparison for RFC 8707 `resource` values (§7). */
26
+ export declare function normalizeResource(value: string): string | undefined;
27
+ export declare class AuthorizationServer {
28
+ readonly config: OAuthProfileConfig;
29
+ private readonly store;
30
+ private readonly clients;
31
+ private readonly canvas;
32
+ private readonly now;
33
+ private readonly log;
34
+ private lastPurge;
35
+ constructor(options: AuthorizationServerOptions);
36
+ /** Well-known documents are host-rooted, so they match on the raw path. */
37
+ wellKnownPaths(): {
38
+ protectedResource: string[];
39
+ authorizationServer: string[];
40
+ };
41
+ /**
42
+ * Dispatch one request. `rawPath` is the request path as received;
43
+ * `path` has the issuer's own path prefix removed. Returns false when the
44
+ * request is not an OAuth endpoint.
45
+ */
46
+ handle(req: IncomingMessage, res: ServerResponse, rawPath: string, path: string, query: Query): Promise<boolean>;
47
+ private requireMethod;
48
+ get resourceMetadataUrl(): string;
49
+ protectedResourceMetadata(): Record<string, unknown>;
50
+ authorizationServerMetadata(): Record<string, unknown>;
51
+ handleRegister(req: IncomingMessage, res: ServerResponse): Promise<void>;
52
+ handleAuthorize(res: ServerResponse, query: Query): Promise<void>;
53
+ handleContinue(req: IncomingMessage, res: ServerResponse): Promise<void>;
54
+ handleCanvasCallback(req: IncomingMessage, res: ServerResponse, query: Query): Promise<void>;
55
+ handleToken(req: IncomingMessage, res: ServerResponse): Promise<void>;
56
+ private exchangeAuthorizationCode;
57
+ private refreshTokens;
58
+ private issueTokens;
59
+ handleRevoke(req: IncomingMessage, res: ServerResponse): Promise<void>;
60
+ /**
61
+ * Revoke everything behind a grant: every MCP token, the grant record, and
62
+ * the Canvas token it holds. Canvas revocation is best-effort — a Canvas
63
+ * outage must not leave the MCP side of the grant alive.
64
+ */
65
+ revokeGrant(grantId: string): Promise<void>;
66
+ private authenticateClient;
67
+ private invalidGrant;
68
+ private sendBodyError;
69
+ private htmlError;
70
+ private maybePurge;
71
+ }
72
+ /** Build `redirect_uri?error=…&error_description=…&state=…`. */
73
+ export declare function errorRedirect(redirectUri: string, error: string, description: string, state: string | undefined): string;
74
+ export {};
75
+ //# sourceMappingURL=authorization-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization-server.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/authorization-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ export type CanvasOAuthErrorKind =
2
+ /** Canvas refused the grant: code invalid/expired, or the token was revoked. */
3
+ 'invalid_grant'
4
+ /** Canvas answered, but not with a usable token response. */
5
+ | 'malformed'
6
+ /** Canvas is unreachable or returned a 5xx. */
7
+ | 'unavailable';
8
+ export declare class CanvasOAuthError extends Error {
9
+ readonly kind: CanvasOAuthErrorKind;
10
+ readonly status?: number;
11
+ constructor(kind: CanvasOAuthErrorKind, message: string, status?: number);
12
+ }
13
+ export interface CanvasOAuthClientConfig {
14
+ baseUrl: string;
15
+ clientId: string;
16
+ clientSecret: string;
17
+ redirectUri: string;
18
+ scopes?: string;
19
+ fetch?: typeof fetch;
20
+ now?: () => number;
21
+ }
22
+ export interface CanvasTokenSet {
23
+ accessToken: string;
24
+ refreshToken: string;
25
+ /** Epoch milliseconds. */
26
+ expiresAt: number;
27
+ canvasUserId: string;
28
+ }
29
+ export interface CanvasRefreshedToken {
30
+ accessToken: string;
31
+ expiresAt: number;
32
+ /** Present only if Canvas ever starts rotating refresh tokens. */
33
+ refreshToken?: string;
34
+ }
35
+ export declare class CanvasOAuthClient {
36
+ private readonly config;
37
+ private readonly fetchImpl;
38
+ private readonly now;
39
+ constructor(config: CanvasOAuthClientConfig);
40
+ /** Where to send the user's browser. `state` is the pending authorization id. */
41
+ authorizationUrl(state: string): string;
42
+ exchangeCode(code: string): Promise<CanvasTokenSet>;
43
+ refresh(refreshToken: string): Promise<CanvasRefreshedToken>;
44
+ /** Revoke a Canvas access token (and with it the grant's refresh token). */
45
+ revoke(accessToken: string): Promise<void>;
46
+ private expiresAt;
47
+ private tokenRequest;
48
+ }
49
+ //# sourceMappingURL=canvas-oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-oauth.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/canvas-oauth.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,oBAAoB;AAC9B,gFAAgF;AAC9E,eAAe;AACjB,6DAA6D;GAC3D,WAAW;AACb,+CAA+C;GAC7C,aAAa,CAAA;AAEjB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;gBAEZ,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAMzE;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AASD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,MAAM,EAAE,uBAAuB;IAM3C,iFAAiF;IACjF,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAUjC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgBnD,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAgBlE,4EAA4E;IACtE,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhD,OAAO,CAAC,SAAS;YAQH,YAAY;CAmF3B"}
@@ -0,0 +1,59 @@
1
+ import { type McpScope, type OAuthProfileConfig } from './config';
2
+ import type { ClientAuthMethod, OAuthStore, RegisteredClient } from './store';
3
+ export declare const ALLOWED_GRANT_TYPES: readonly ["authorization_code", "refresh_token"];
4
+ export declare const ALLOWED_AUTH_METHODS: readonly ClientAuthMethod[];
5
+ export declare class ClientRegistrationError extends Error {
6
+ readonly error: 'invalid_client_metadata' | 'invalid_redirect_uri';
7
+ constructor(error: ClientRegistrationError['error'], message: string);
8
+ }
9
+ export interface RegistrationResult {
10
+ client: RegisteredClient;
11
+ /** Returned once, never stored in the clear. */
12
+ clientSecret?: string;
13
+ }
14
+ export interface ClientResolverOptions {
15
+ store: OAuthStore;
16
+ config: Pick<OAuthProfileConfig, 'clients' | 'cimdAllowedHosts' | 'dynamicRegistration'>;
17
+ fetch?: typeof fetch;
18
+ now?: () => number;
19
+ }
20
+ export declare function parseScopeParam(raw: string | null | undefined): McpScope[] | undefined;
21
+ /** Whether a client_id is shaped like a Client ID Metadata Document URL. */
22
+ export declare function isCimdClientId(clientId: string): boolean;
23
+ export declare class ClientResolver {
24
+ private readonly store;
25
+ private readonly config;
26
+ private readonly fetchImpl;
27
+ private readonly now;
28
+ constructor(options: ClientResolverOptions);
29
+ get cimdEnabled(): boolean;
30
+ get dynamicRegistrationEnabled(): boolean;
31
+ /** Write the operator's pre-registered clients into the store. Idempotent. */
32
+ seedPreregistered(): Promise<void>;
33
+ /**
34
+ * Find a client by id: the store first (pre-registered, dynamic, or a fresh
35
+ * CIMD cache entry), then a CIMD fetch when the id is an allowlisted URL.
36
+ */
37
+ resolve(clientId: string): Promise<RegisteredClient | undefined>;
38
+ isCimdHostAllowed(hostname: string): boolean;
39
+ private fetchCimd;
40
+ /** RFC 7591 dynamic registration. Throws `ClientRegistrationError`. */
41
+ register(body: unknown): Promise<RegistrationResult>;
42
+ }
43
+ interface ValidatedMetadata {
44
+ redirectUris: string[];
45
+ clientName?: string;
46
+ tokenEndpointAuthMethod: ClientAuthMethod;
47
+ grantTypes: string[];
48
+ }
49
+ /**
50
+ * Validate RFC 7591 client metadata. Shared by DCR and CIMD; the latter is
51
+ * public-only because this server does not support `private_key_jwt`.
52
+ */
53
+ export declare function validateClientMetadata(body: unknown, options: {
54
+ requirePublic: boolean;
55
+ }): ValidatedMetadata;
56
+ /** The RFC 7591 response body for a registered client. */
57
+ export declare function registrationResponse(result: RegistrationResult): Record<string, unknown>;
58
+ export {};
59
+ //# sourceMappingURL=clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/clients.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,kBAAkB,EAExB,MAAM,UAAU,CAAA;AAGjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE7E,eAAO,MAAM,mBAAmB,kDAAmD,CAAA;AACnF,eAAO,MAAM,oBAAoB,EAAE,SAAS,gBAAgB,EAI3D,CAAA;AASD,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,KAAK,EAAE,yBAAyB,GAAG,sBAAsB,CAAA;gBACtD,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM;CAKrE;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,gBAAgB,CAAA;IACxB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,kBAAkB,GAAG,qBAAqB,CAAC,CAAA;IACxF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,EAAE,GAAG,SAAS,CAUtF;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQxD;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,OAAO,EAAE,qBAAqB;IAO1C,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,0BAA0B,IAAI,OAAO,CAExC;IAED,8EAA8E;IACxE,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAMxC;;;OAGG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAoBtE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;YAM9B,SAAS;IA0DvB,uEAAuE;IACjE,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAqB3D;AAgBD,UAAU,iBAAiB;IACzB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uBAAuB,EAAE,gBAAgB,CAAA;IACzC,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;IAAE,aAAa,EAAE,OAAO,CAAA;CAAE,GAClC,iBAAiB,CAiHnB;AAaD,0DAA0D;AAC1D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcxF"}
@@ -0,0 +1,92 @@
1
+ /** Scopes this server issues. MCP scopes, not Canvas scopes (§7.4). */
2
+ export declare const MCP_SCOPES: readonly ["canvas:read", "canvas:write"];
3
+ export type McpScope = (typeof MCP_SCOPES)[number];
4
+ export declare function isMcpScope(value: string): value is McpScope;
5
+ /** Default CIMD trust: where Codex hosts its client metadata documents. */
6
+ export declare const DEFAULT_CIMD_ALLOWED_HOSTS: string[];
7
+ /** Lifetimes (seconds). */
8
+ export declare const ACCESS_TOKEN_TTL_SECONDS: number;
9
+ export declare const REFRESH_TOKEN_TTL_SECONDS: number;
10
+ export declare const AUTHORIZATION_CODE_TTL_SECONDS: number;
11
+ export declare const PENDING_AUTHORIZATION_TTL_SECONDS: number;
12
+ export interface PreregisteredClient {
13
+ client_id: string;
14
+ client_name?: string;
15
+ redirect_uris: string[];
16
+ /** Present for confidential clients. Stored hashed in the client store. */
17
+ client_secret?: string;
18
+ }
19
+ export interface OAuthProfileConfig {
20
+ /** Canonical issuer: absolute URL, no trailing slash, no query/fragment. */
21
+ issuer: string;
22
+ /** `new URL(issuer).pathname` with a trailing slash stripped; '' at the root. */
23
+ issuerPath: string;
24
+ /** RFC 8707 resource identifier: `${issuer}/mcp`. */
25
+ resource: string;
26
+ canvas: {
27
+ baseUrl: string;
28
+ clientId: string;
29
+ clientSecret: string;
30
+ /** Canvas API scopes, or undefined for the Developer Key default. */
31
+ scopes?: string;
32
+ };
33
+ clients: PreregisteredClient[];
34
+ dynamicRegistration: boolean;
35
+ /** Lower-cased hostnames; `['*']` = any https host; `[]` = CIMD disabled. */
36
+ cimdAllowedHosts: string[];
37
+ store?: {
38
+ path: string;
39
+ keySecret: string;
40
+ };
41
+ }
42
+ /**
43
+ * Every environment variable the OAuth profile reads. The docs drift guard
44
+ * (tests/docs/oauth-profile-doc-consistency.test.ts) checks that each one is
45
+ * documented, so a new variable cannot ship undocumented.
46
+ */
47
+ export declare const OAUTH_ENV_VARS: readonly ["CANVAS_AUTH_PROFILE", "CANVAS_HTTP_HOST", "CANVAS_MCP_ISSUER", "CANVAS_OAUTH_CLIENT_ID", "CANVAS_OAUTH_CLIENT_SECRET", "CANVAS_OAUTH_SCOPES", "CANVAS_MCP_OAUTH_CLIENTS", "CANVAS_MCP_OAUTH_DCR", "CANVAS_MCP_OAUTH_CIMD_ALLOWED_HOSTS", "CANVAS_MCP_OAUTH_STORE", "CANVAS_MCP_OAUTH_STORE_KEY"];
48
+ export interface OAuthEnv {
49
+ CANVAS_BASE_URL?: string;
50
+ CANVAS_MCP_ISSUER?: string;
51
+ CANVAS_OAUTH_CLIENT_ID?: string;
52
+ CANVAS_OAUTH_CLIENT_SECRET?: string;
53
+ CANVAS_OAUTH_SCOPES?: string;
54
+ CANVAS_MCP_OAUTH_CLIENTS?: string;
55
+ CANVAS_MCP_OAUTH_DCR?: string;
56
+ CANVAS_MCP_OAUTH_CIMD_ALLOWED_HOSTS?: string;
57
+ CANVAS_MCP_OAUTH_STORE?: string;
58
+ CANVAS_MCP_OAUTH_STORE_KEY?: string;
59
+ }
60
+ export interface OAuthConfigOverrides {
61
+ /** `--issuer` */
62
+ issuer?: string;
63
+ /** `--base-url` / resolved Canvas base URL. */
64
+ baseUrl?: string;
65
+ }
66
+ export declare class OAuthConfigError extends Error {
67
+ constructor(message: string);
68
+ }
69
+ /**
70
+ * Normalise and validate the issuer (§5): absolute, `https` unless loopback,
71
+ * no query or fragment, trailing slash stripped.
72
+ */
73
+ export declare function parseIssuer(raw: string): {
74
+ issuer: string;
75
+ issuerPath: string;
76
+ url: URL;
77
+ };
78
+ export declare function isLoopbackIssuer(issuer: string): boolean;
79
+ /**
80
+ * Build the OAuth profile configuration, or throw `OAuthConfigError` naming
81
+ * the first thing that is wrong. `overrides` carry CLI flags, which win over
82
+ * the environment.
83
+ */
84
+ export declare function loadOAuthProfileConfig(env: OAuthEnv, overrides?: OAuthConfigOverrides): OAuthProfileConfig;
85
+ /**
86
+ * Bind-host rule (§5): a loopback issuer must not be reachable from the
87
+ * network. Returns the host to bind, or throws.
88
+ */
89
+ export declare function resolveBindHost(config: OAuthProfileConfig, requestedHost: string | undefined): string;
90
+ /** Hash a pre-registered client secret for storage; exported for the client store. */
91
+ export declare function hashClientSecret(secret: string): string;
92
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export declare const TOKEN_PREFIX: {
2
+ readonly access: "mcpat_";
3
+ readonly refresh: "mcprt_";
4
+ readonly code: "mcpac_";
5
+ readonly client: "mcpcl_";
6
+ readonly secret: "mcpcs_";
7
+ };
8
+ export type TokenKind = keyof typeof TOKEN_PREFIX;
9
+ /** 256 bits of entropy behind a recognisable prefix. */
10
+ export declare function mintToken(kind: TokenKind): string;
11
+ /** Random opaque identifier (pending authorizations, grants, cookies). */
12
+ export declare function randomId(): string;
13
+ /** Lookup key for a token, code, or secret. Hex so it is safe in JSON and file names. */
14
+ export declare function hashToken(value: string): string;
15
+ export declare function sha256Base64Url(value: string): string;
16
+ /** Constant-time string equality; unequal lengths compare false without leaking where. */
17
+ export declare function timingSafeEqualStrings(a: string, b: string): boolean;
18
+ /** True when `secret` hashes to `hash` (client secrets are stored hashed). */
19
+ export declare function verifyHashedSecret(secret: string, hash: string): boolean;
20
+ export declare function isValidCodeChallenge(challenge: string): boolean;
21
+ export declare function isValidCodeVerifier(verifier: string): boolean;
22
+ /**
23
+ * PKCE S256 check (RFC 7636 §4.6). `plain` is not a method this server
24
+ * accepts, so there is nothing to select on: the challenge is always compared
25
+ * to `BASE64URL(SHA256(verifier))`.
26
+ */
27
+ export declare function verifyPkceS256(codeVerifier: string, codeChallenge: string): boolean;
28
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/crypto.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,YAAY;;;;;;CAMf,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,YAAY,CAAA;AAEjD,wDAAwD;AACxD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEjD;AAED,0EAA0E;AAC1E,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,0FAA0F;AAC1F,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKpE;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAExE;AAOD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAGnF"}
@@ -0,0 +1,49 @@
1
+ import { type AuthorizationCode, type ConsumeCodeResult, type Grant, type MemoryOAuthStoreOptions, type OAuthStore, type PendingAuthorization, type RegisteredClient, type StoreSnapshot, type TokenRecord } from './store';
2
+ export declare class OAuthStoreFileError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function deriveStoreKey(secret: string): Buffer;
6
+ export declare function encryptSnapshot(snapshot: StoreSnapshot, key: Buffer): string;
7
+ export declare function decryptSnapshot(contents: string, key: Buffer): StoreSnapshot;
8
+ export type FileOAuthStoreOptions = MemoryOAuthStoreOptions;
9
+ export declare class FileOAuthStore implements OAuthStore {
10
+ private readonly inner;
11
+ private readonly path;
12
+ private readonly key;
13
+ private chain;
14
+ private dirty;
15
+ private writing;
16
+ private constructor();
17
+ /**
18
+ * Open (or create) the store. Throws `OAuthStoreFileError` when the file
19
+ * exists but cannot be read with this key — silently starting empty would
20
+ * strand every existing grant and hide a misconfiguration.
21
+ */
22
+ static open(path: string, keySecret: string, options?: FileOAuthStoreOptions): Promise<FileOAuthStore>;
23
+ /** Resolves once every mutation so far has reached disk. */
24
+ flush(): Promise<void>;
25
+ private persist;
26
+ private writeLoop;
27
+ private writeOnce;
28
+ private mutate;
29
+ getClient(clientId: string): Promise<RegisteredClient | undefined>;
30
+ putClient(client: RegisteredClient): Promise<void>;
31
+ deleteClient(clientId: string): Promise<void>;
32
+ putPendingAuthorization(pending: PendingAuthorization): Promise<void>;
33
+ getPendingAuthorization(id: string): Promise<PendingAuthorization | undefined>;
34
+ updatePendingAuthorization(pending: PendingAuthorization): Promise<void>;
35
+ takePendingAuthorization(id: string): Promise<PendingAuthorization | undefined>;
36
+ putAuthorizationCode(code: AuthorizationCode): Promise<void>;
37
+ consumeAuthorizationCode(hash: string, now: number): Promise<ConsumeCodeResult | undefined>;
38
+ putGrant(grant: Grant): Promise<void>;
39
+ getGrant(id: string): Promise<Grant | undefined>;
40
+ updateGrant(grant: Grant): Promise<void>;
41
+ deleteGrant(id: string): Promise<void>;
42
+ putToken(token: TokenRecord): Promise<void>;
43
+ getToken(hash: string): Promise<TokenRecord | undefined>;
44
+ takeToken(hash: string): Promise<TokenRecord | undefined>;
45
+ deleteToken(hash: string): Promise<void>;
46
+ deleteTokensForGrant(grantId: string): Promise<void>;
47
+ purgeExpired(now: number): Promise<void>;
48
+ }
49
+ //# sourceMappingURL=file-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/file-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export declare function escapeHtml(value: string): string;
2
+ export interface ConsentScope {
3
+ name: string;
4
+ description: string;
5
+ }
6
+ export interface ConsentPageInput {
7
+ clientName: string;
8
+ clientId: string;
9
+ /** True when `clientId` is a Client ID Metadata Document URL. */
10
+ clientIdIsUrl: boolean;
11
+ redirectUri: string;
12
+ redirectHost: string;
13
+ isLoopbackRedirect: boolean;
14
+ scopes: ConsentScope[];
15
+ canvasHost: string;
16
+ /** Form action, relative to the issuer. */
17
+ actionPath: string;
18
+ pendingId: string;
19
+ csrf: string;
20
+ }
21
+ export declare function renderConsentPage(input: ConsentPageInput): string;
22
+ export interface ErrorPageInput {
23
+ title: string;
24
+ message: string;
25
+ }
26
+ export declare function renderErrorPage(input: ErrorPageInput): string;
27
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/html.ts"],"names":[],"mappings":"AAcA,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AA2CD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CA8BjE;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAO7D"}
@@ -0,0 +1,75 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ /** Hard cap on any request body this server reads for OAuth purposes. */
3
+ export declare const MAX_BODY_BYTES: number;
4
+ export declare class BodyError extends Error {
5
+ readonly status: number;
6
+ constructor(message: string, status: number);
7
+ }
8
+ export declare function firstHeader(req: IncomingMessage, name: string): string | undefined;
9
+ /** Media type only, lower-cased, without parameters. */
10
+ export declare function contentType(req: IncomingMessage): string;
11
+ export declare function readBody(req: IncomingMessage, limit?: number): Promise<string>;
12
+ /** `application/x-www-form-urlencoded` body, as OAuth token/revoke/consent require. */
13
+ export declare function readFormBody(req: IncomingMessage): Promise<URLSearchParams>;
14
+ /** `application/json` body (dynamic client registration). */
15
+ export declare function readJsonBody(req: IncomingMessage): Promise<unknown>;
16
+ export declare function sendJson(res: ServerResponse, status: number, body: unknown, headers?: Record<string, string>): void;
17
+ /**
18
+ * CSP for the two HTML pages this server serves (consent and error).
19
+ *
20
+ * Deliberately no `form-action`: both outcomes of the consent POST are a 302
21
+ * to an origin chosen at runtime — Canvas on allow, the client's own redirect
22
+ * URI on deny — and Chrome enforces `form-action` against every hop of that
23
+ * navigation, so `'self'` aborted the login. `form-action` has no
24
+ * `default-src` fallback in CSP 3, so leaving it out is what permits the
25
+ * redirect; nothing else in the policy is relaxed.
26
+ */
27
+ export declare const HTML_CONTENT_SECURITY_POLICY = "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'";
28
+ /**
29
+ * Referrer policy for those same pages.
30
+ *
31
+ * Not `no-referrer`: per Fetch, a non-CORS POST under that policy carries
32
+ * `Origin: null`, which the transport's own origin allowlist then refuses —
33
+ * so the consent form could not be submitted from any browser. `same-origin`
34
+ * keeps the privacy intent (no `Referer` on the hop to Canvas or to the
35
+ * client's redirect URI) while still sending a real `Origin` on the form
36
+ * post back to the issuer.
37
+ */
38
+ export declare const HTML_REFERRER_POLICY = "same-origin";
39
+ export declare function sendHtml(res: ServerResponse, status: number, html: string, headers?: Record<string, string>): void;
40
+ export declare function sendRedirect(res: ServerResponse, location: string, headers?: Record<string, string>): void;
41
+ export declare function parseCookies(header: string | undefined): Record<string, string>;
42
+ export interface CookieOptions {
43
+ /** Seconds; omit for a session cookie, 0 to delete. */
44
+ maxAge?: number;
45
+ secure: boolean;
46
+ path?: string;
47
+ }
48
+ export declare function serializeCookie(name: string, value: string, options: CookieOptions): string;
49
+ /** The token from `Authorization: Bearer <token>`, or undefined. */
50
+ export declare function parseBearer(header: string | undefined): string | undefined;
51
+ /** Credentials from `Authorization: Basic <base64>`, or undefined. */
52
+ export declare function parseBasicAuth(header: string | undefined): {
53
+ username: string;
54
+ password: string;
55
+ } | undefined;
56
+ /** A request target this server was able to parse. */
57
+ export interface RoutedPath {
58
+ /** Path with the issuer path prefix removed, for routing. */
59
+ path: string;
60
+ /** Path exactly as received, for the well-known endpoints. */
61
+ rawPath: string;
62
+ query: URLSearchParams;
63
+ }
64
+ /**
65
+ * Parse `req.url` against the issuer. The path is returned with the issuer's
66
+ * own path prefix removed when the request carries it, so routing is the same
67
+ * whether a reverse proxy strips the prefix or passes it through.
68
+ *
69
+ * Returns `undefined` when the request target is not a URL at all. The node
70
+ * HTTP parser is happy with targets `new URL` rejects (`//a:b`, `//`), and
71
+ * this runs before any authentication, so throwing here ended the process for
72
+ * anyone who could open a socket. The caller answers 400.
73
+ */
74
+ export declare function routePath(rawUrl: string | undefined, issuerPath: string): RoutedPath | undefined;
75
+ //# sourceMappingURL=http-util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-util.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/http-util.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,yEAAyE;AACzE,eAAO,MAAM,cAAc,QAAY,CAAA;AAEvC,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;gBACX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIlF;AAED,wDAAwD;AACxD,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAGxD;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,SAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmB5F;AAED,uFAAuF;AACvF,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAMjF;AAED,6DAA6D;AAC7D,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAWzE;AAED,wBAAgB,QAAQ,CACtB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAQN;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,2FACiD,CAAA;AAE1F;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,gBAAgB,CAAA;AAEjD,wBAAgB,QAAQ,CACtB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAWN;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAGN;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiB/E;AAED,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAQ3F;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAqBpD;AAED,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,eAAe,CAAA;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAahG"}