@37signals/basecamp 0.2.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.
- package/README.md +600 -0
- package/dist/auth-strategy.d.ts +36 -0
- package/dist/auth-strategy.d.ts.map +1 -0
- package/dist/auth-strategy.js +33 -0
- package/dist/auth-strategy.js.map +1 -0
- package/dist/client.d.ts +224 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +715 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +137 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +269 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated/metadata.json +2281 -0
- package/dist/generated/openapi-stripped.json +21268 -0
- package/dist/generated/path-mapping.d.ts +8 -0
- package/dist/generated/path-mapping.d.ts.map +1 -0
- package/dist/generated/path-mapping.js +192 -0
- package/dist/generated/path-mapping.js.map +1 -0
- package/dist/generated/path-mapping.ts +199 -0
- package/dist/generated/schema.d.ts +14883 -0
- package/dist/generated/services/attachments.d.ts +27 -0
- package/dist/generated/services/attachments.d.ts.map +1 -0
- package/dist/generated/services/attachments.js +49 -0
- package/dist/generated/services/attachments.js.map +1 -0
- package/dist/generated/services/attachments.ts +59 -0
- package/dist/generated/services/boosts.d.ts +114 -0
- package/dist/generated/services/boosts.d.ts.map +1 -0
- package/dist/generated/services/boosts.js +179 -0
- package/dist/generated/services/boosts.js.map +1 -0
- package/dist/generated/services/boosts.ts +250 -0
- package/dist/generated/services/campfires.d.ts +202 -0
- package/dist/generated/services/campfires.d.ts.map +1 -0
- package/dist/generated/services/campfires.js +309 -0
- package/dist/generated/services/campfires.js.map +1 -0
- package/dist/generated/services/campfires.ts +433 -0
- package/dist/generated/services/card-columns.d.ts +163 -0
- package/dist/generated/services/card-columns.d.ts.map +1 -0
- package/dist/generated/services/card-columns.js +264 -0
- package/dist/generated/services/card-columns.js.map +1 -0
- package/dist/generated/services/card-columns.ts +360 -0
- package/dist/generated/services/card-steps.d.ts +105 -0
- package/dist/generated/services/card-steps.d.ts.map +1 -0
- package/dist/generated/services/card-steps.js +148 -0
- package/dist/generated/services/card-steps.js.map +1 -0
- package/dist/generated/services/card-steps.ts +221 -0
- package/dist/generated/services/card-tables.d.ts +27 -0
- package/dist/generated/services/card-tables.d.ts.map +1 -0
- package/dist/generated/services/card-tables.js +40 -0
- package/dist/generated/services/card-tables.js.map +1 -0
- package/dist/generated/services/card-tables.ts +55 -0
- package/dist/generated/services/cards.d.ts +118 -0
- package/dist/generated/services/cards.d.ts.map +1 -0
- package/dist/generated/services/cards.js +166 -0
- package/dist/generated/services/cards.js.map +1 -0
- package/dist/generated/services/cards.ts +247 -0
- package/dist/generated/services/checkins.d.ts +278 -0
- package/dist/generated/services/checkins.d.ts.map +1 -0
- package/dist/generated/services/checkins.js +420 -0
- package/dist/generated/services/checkins.js.map +1 -0
- package/dist/generated/services/checkins.ts +600 -0
- package/dist/generated/services/client-approvals.d.ts +45 -0
- package/dist/generated/services/client-approvals.d.ts.map +1 -0
- package/dist/generated/services/client-approvals.js +58 -0
- package/dist/generated/services/client-approvals.js.map +1 -0
- package/dist/generated/services/client-approvals.ts +89 -0
- package/dist/generated/services/client-correspondences.d.ts +45 -0
- package/dist/generated/services/client-correspondences.d.ts.map +1 -0
- package/dist/generated/services/client-correspondences.js +58 -0
- package/dist/generated/services/client-correspondences.js.map +1 -0
- package/dist/generated/services/client-correspondences.ts +89 -0
- package/dist/generated/services/client-replies.d.ts +47 -0
- package/dist/generated/services/client-replies.d.ts.map +1 -0
- package/dist/generated/services/client-replies.js +65 -0
- package/dist/generated/services/client-replies.js.map +1 -0
- package/dist/generated/services/client-replies.ts +95 -0
- package/dist/generated/services/client-visibility.d.ts +35 -0
- package/dist/generated/services/client-visibility.d.ts.map +1 -0
- package/dist/generated/services/client-visibility.js +44 -0
- package/dist/generated/services/client-visibility.js.map +1 -0
- package/dist/generated/services/client-visibility.ts +68 -0
- package/dist/generated/services/comments.d.ts +86 -0
- package/dist/generated/services/comments.d.ts.map +1 -0
- package/dist/generated/services/comments.js +129 -0
- package/dist/generated/services/comments.js.map +1 -0
- package/dist/generated/services/comments.ts +185 -0
- package/dist/generated/services/documents.d.ts +92 -0
- package/dist/generated/services/documents.d.ts.map +1 -0
- package/dist/generated/services/documents.js +129 -0
- package/dist/generated/services/documents.js.map +1 -0
- package/dist/generated/services/documents.ts +191 -0
- package/dist/generated/services/events.d.ts +34 -0
- package/dist/generated/services/events.d.ts.map +1 -0
- package/dist/generated/services/events.js +39 -0
- package/dist/generated/services/events.js.map +1 -0
- package/dist/generated/services/events.ts +64 -0
- package/dist/generated/services/forwards.d.ts +112 -0
- package/dist/generated/services/forwards.d.ts.map +1 -0
- package/dist/generated/services/forwards.js +172 -0
- package/dist/generated/services/forwards.js.map +1 -0
- package/dist/generated/services/forwards.ts +241 -0
- package/dist/generated/services/index.d.ts +39 -0
- package/dist/generated/services/index.d.ts.map +1 -0
- package/dist/generated/services/index.js +39 -0
- package/dist/generated/services/index.js.map +1 -0
- package/dist/generated/services/index.ts +38 -0
- package/dist/generated/services/lineup.d.ts +67 -0
- package/dist/generated/services/lineup.d.ts.map +1 -0
- package/dist/generated/services/lineup.js +99 -0
- package/dist/generated/services/lineup.js.map +1 -0
- package/dist/generated/services/lineup.ts +143 -0
- package/dist/generated/services/message-boards.d.ts +27 -0
- package/dist/generated/services/message-boards.d.ts.map +1 -0
- package/dist/generated/services/message-boards.js +40 -0
- package/dist/generated/services/message-boards.js.map +1 -0
- package/dist/generated/services/message-boards.ts +55 -0
- package/dist/generated/services/message-types.d.ts +100 -0
- package/dist/generated/services/message-types.d.ts.map +1 -0
- package/dist/generated/services/message-types.js +144 -0
- package/dist/generated/services/message-types.js.map +1 -0
- package/dist/generated/services/message-types.ts +210 -0
- package/dist/generated/services/messages.d.ts +122 -0
- package/dist/generated/services/messages.d.ts.map +1 -0
- package/dist/generated/services/messages.js +180 -0
- package/dist/generated/services/messages.js.map +1 -0
- package/dist/generated/services/messages.ts +258 -0
- package/dist/generated/services/people.d.ts +122 -0
- package/dist/generated/services/people.d.ts.map +1 -0
- package/dist/generated/services/people.js +167 -0
- package/dist/generated/services/people.js.map +1 -0
- package/dist/generated/services/people.ts +252 -0
- package/dist/generated/services/projects.d.ts +109 -0
- package/dist/generated/services/projects.d.ts.map +1 -0
- package/dist/generated/services/projects.js +153 -0
- package/dist/generated/services/projects.js.map +1 -0
- package/dist/generated/services/projects.ts +224 -0
- package/dist/generated/services/recordings.d.ts +93 -0
- package/dist/generated/services/recordings.d.ts.map +1 -0
- package/dist/generated/services/recordings.js +138 -0
- package/dist/generated/services/recordings.js.map +1 -0
- package/dist/generated/services/recordings.ts +191 -0
- package/dist/generated/services/reports.d.ts +90 -0
- package/dist/generated/services/reports.d.ts.map +1 -0
- package/dist/generated/services/reports.js +124 -0
- package/dist/generated/services/reports.js.map +1 -0
- package/dist/generated/services/reports.ts +187 -0
- package/dist/generated/services/schedules.d.ts +162 -0
- package/dist/generated/services/schedules.d.ts.map +1 -0
- package/dist/generated/services/schedules.js +228 -0
- package/dist/generated/services/schedules.js.map +1 -0
- package/dist/generated/services/schedules.ts +335 -0
- package/dist/generated/services/search.d.ts +45 -0
- package/dist/generated/services/search.d.ts.map +1 -0
- package/dist/generated/services/search.js +56 -0
- package/dist/generated/services/search.js.map +1 -0
- package/dist/generated/services/search.ts +89 -0
- package/dist/generated/services/subscriptions.d.ts +73 -0
- package/dist/generated/services/subscriptions.d.ts.map +1 -0
- package/dist/generated/services/subscriptions.js +119 -0
- package/dist/generated/services/subscriptions.js.map +1 -0
- package/dist/generated/services/subscriptions.ts +160 -0
- package/dist/generated/services/templates.d.ts +140 -0
- package/dist/generated/services/templates.d.ts.map +1 -0
- package/dist/generated/services/templates.js +207 -0
- package/dist/generated/services/templates.js.map +1 -0
- package/dist/generated/services/templates.ts +294 -0
- package/dist/generated/services/timeline.d.ts +31 -0
- package/dist/generated/services/timeline.d.ts.map +1 -0
- package/dist/generated/services/timeline.js +39 -0
- package/dist/generated/services/timeline.js.map +1 -0
- package/dist/generated/services/timeline.ts +62 -0
- package/dist/generated/services/timesheets.d.ts +145 -0
- package/dist/generated/services/timesheets.d.ts.map +1 -0
- package/dist/generated/services/timesheets.js +186 -0
- package/dist/generated/services/timesheets.js.map +1 -0
- package/dist/generated/services/timesheets.ts +289 -0
- package/dist/generated/services/todolist-groups.d.ts +74 -0
- package/dist/generated/services/todolist-groups.d.ts.map +1 -0
- package/dist/generated/services/todolist-groups.js +100 -0
- package/dist/generated/services/todolist-groups.js.map +1 -0
- package/dist/generated/services/todolist-groups.ts +151 -0
- package/dist/generated/services/todolists.d.ts +95 -0
- package/dist/generated/services/todolists.d.ts.map +1 -0
- package/dist/generated/services/todolists.js +130 -0
- package/dist/generated/services/todolists.js.map +1 -0
- package/dist/generated/services/todolists.ts +192 -0
- package/dist/generated/services/todos.d.ts +175 -0
- package/dist/generated/services/todos.d.ts.map +1 -0
- package/dist/generated/services/todos.js +255 -0
- package/dist/generated/services/todos.js.map +1 -0
- package/dist/generated/services/todos.ts +369 -0
- package/dist/generated/services/todosets.d.ts +27 -0
- package/dist/generated/services/todosets.d.ts.map +1 -0
- package/dist/generated/services/todosets.js +40 -0
- package/dist/generated/services/todosets.js.map +1 -0
- package/dist/generated/services/todosets.ts +55 -0
- package/dist/generated/services/tools.d.ts +122 -0
- package/dist/generated/services/tools.d.ts.map +1 -0
- package/dist/generated/services/tools.js +197 -0
- package/dist/generated/services/tools.js.map +1 -0
- package/dist/generated/services/tools.ts +267 -0
- package/dist/generated/services/uploads.d.ts +109 -0
- package/dist/generated/services/uploads.d.ts.map +1 -0
- package/dist/generated/services/uploads.js +153 -0
- package/dist/generated/services/uploads.js.map +1 -0
- package/dist/generated/services/uploads.ts +227 -0
- package/dist/generated/services/vaults.d.ts +86 -0
- package/dist/generated/services/vaults.d.ts.map +1 -0
- package/dist/generated/services/vaults.js +126 -0
- package/dist/generated/services/vaults.js.map +1 -0
- package/dist/generated/services/vaults.ts +182 -0
- package/dist/generated/services/webhooks.d.ts +106 -0
- package/dist/generated/services/webhooks.d.ts.map +1 -0
- package/dist/generated/services/webhooks.js +157 -0
- package/dist/generated/services/webhooks.js.map +1 -0
- package/dist/generated/services/webhooks.ts +226 -0
- package/dist/hooks/otel.d.ts +136 -0
- package/dist/hooks/otel.d.ts.map +1 -0
- package/dist/hooks/otel.js +240 -0
- package/dist/hooks/otel.js.map +1 -0
- package/dist/hooks.d.ts +171 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +196 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +122 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/authorize.d.ts +48 -0
- package/dist/oauth/authorize.d.ts.map +1 -0
- package/dist/oauth/authorize.js +56 -0
- package/dist/oauth/authorize.js.map +1 -0
- package/dist/oauth/callback-server.d.ts +56 -0
- package/dist/oauth/callback-server.d.ts.map +1 -0
- package/dist/oauth/callback-server.js +150 -0
- package/dist/oauth/callback-server.js.map +1 -0
- package/dist/oauth/discovery.d.ts +54 -0
- package/dist/oauth/discovery.d.ts.map +1 -0
- package/dist/oauth/discovery.js +116 -0
- package/dist/oauth/discovery.js.map +1 -0
- package/dist/oauth/exchange.d.ts +96 -0
- package/dist/oauth/exchange.d.ts.map +1 -0
- package/dist/oauth/exchange.js +324 -0
- package/dist/oauth/exchange.js.map +1 -0
- package/dist/oauth/identity.d.ts +30 -0
- package/dist/oauth/identity.d.ts.map +1 -0
- package/dist/oauth/identity.js +82 -0
- package/dist/oauth/identity.js.map +1 -0
- package/dist/oauth/index.d.ts +50 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +58 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/interactive-login.d.ts +62 -0
- package/dist/oauth/interactive-login.d.ts.map +1 -0
- package/dist/oauth/interactive-login.js +106 -0
- package/dist/oauth/interactive-login.js.map +1 -0
- package/dist/oauth/pkce.d.ts +68 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +80 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/token-manager.d.ts +92 -0
- package/dist/oauth/token-manager.d.ts.map +1 -0
- package/dist/oauth/token-manager.js +126 -0
- package/dist/oauth/token-manager.js.map +1 -0
- package/dist/oauth/token-store.d.ts +45 -0
- package/dist/oauth/token-store.d.ts.map +1 -0
- package/dist/oauth/token-store.js +105 -0
- package/dist/oauth/token-store.js.map +1 -0
- package/dist/oauth/types.d.ts +98 -0
- package/dist/oauth/types.d.ts.map +1 -0
- package/dist/oauth/types.js +8 -0
- package/dist/oauth/types.js.map +1 -0
- package/dist/pagination-utils.d.ts +27 -0
- package/dist/pagination-utils.d.ts.map +1 -0
- package/dist/pagination-utils.js +54 -0
- package/dist/pagination-utils.js.map +1 -0
- package/dist/pagination.d.ts +56 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +57 -0
- package/dist/pagination.js.map +1 -0
- package/dist/security.d.ts +68 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +107 -0
- package/dist/security.js.map +1 -0
- package/dist/services/authorization.d.ts +124 -0
- package/dist/services/authorization.d.ts.map +1 -0
- package/dist/services/authorization.js +117 -0
- package/dist/services/authorization.js.map +1 -0
- package/dist/services/base.d.ts +102 -0
- package/dist/services/base.d.ts.map +1 -0
- package/dist/services/base.js +230 -0
- package/dist/services/base.js.map +1 -0
- package/dist/webhooks/adapters/node-http.d.ts +14 -0
- package/dist/webhooks/adapters/node-http.d.ts.map +1 -0
- package/dist/webhooks/adapters/node-http.js +60 -0
- package/dist/webhooks/adapters/node-http.js.map +1 -0
- package/dist/webhooks/events.d.ts +52 -0
- package/dist/webhooks/events.d.ts.map +1 -0
- package/dist/webhooks/events.js +52 -0
- package/dist/webhooks/events.js.map +1 -0
- package/dist/webhooks/handler.d.ts +62 -0
- package/dist/webhooks/handler.d.ts.map +1 -0
- package/dist/webhooks/handler.js +226 -0
- package/dist/webhooks/handler.js.map +1 -0
- package/dist/webhooks/index.d.ts +5 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +5 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/verify.d.ts +11 -0
- package/dist/webhooks/verify.d.ts.map +1 -0
- package/dist/webhooks/verify.js +25 -0
- package/dist/webhooks/verify.js.map +1 -0
- package/package.json +70 -0
- package/src/generated/metadata.json +2281 -0
- package/src/generated/openapi-stripped.json +21268 -0
- package/src/generated/path-mapping.ts +199 -0
- package/src/generated/schema.d.ts +14883 -0
- package/src/generated/services/attachments.ts +59 -0
- package/src/generated/services/boosts.ts +250 -0
- package/src/generated/services/campfires.ts +433 -0
- package/src/generated/services/card-columns.ts +360 -0
- package/src/generated/services/card-steps.ts +221 -0
- package/src/generated/services/card-tables.ts +55 -0
- package/src/generated/services/cards.ts +247 -0
- package/src/generated/services/checkins.ts +600 -0
- package/src/generated/services/client-approvals.ts +89 -0
- package/src/generated/services/client-correspondences.ts +89 -0
- package/src/generated/services/client-replies.ts +95 -0
- package/src/generated/services/client-visibility.ts +68 -0
- package/src/generated/services/comments.ts +185 -0
- package/src/generated/services/documents.ts +191 -0
- package/src/generated/services/events.ts +64 -0
- package/src/generated/services/forwards.ts +241 -0
- package/src/generated/services/index.ts +38 -0
- package/src/generated/services/lineup.ts +143 -0
- package/src/generated/services/message-boards.ts +55 -0
- package/src/generated/services/message-types.ts +210 -0
- package/src/generated/services/messages.ts +258 -0
- package/src/generated/services/people.ts +252 -0
- package/src/generated/services/projects.ts +224 -0
- package/src/generated/services/recordings.ts +191 -0
- package/src/generated/services/reports.ts +187 -0
- package/src/generated/services/schedules.ts +335 -0
- package/src/generated/services/search.ts +89 -0
- package/src/generated/services/subscriptions.ts +160 -0
- package/src/generated/services/templates.ts +294 -0
- package/src/generated/services/timeline.ts +62 -0
- package/src/generated/services/timesheets.ts +289 -0
- package/src/generated/services/todolist-groups.ts +151 -0
- package/src/generated/services/todolists.ts +192 -0
- package/src/generated/services/todos.ts +369 -0
- package/src/generated/services/todosets.ts +55 -0
- package/src/generated/services/tools.ts +267 -0
- package/src/generated/services/uploads.ts +227 -0
- package/src/generated/services/vaults.ts +182 -0
- package/src/generated/services/webhooks.ts +226 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local HTTP callback server for OAuth 2.0 authorization code flow.
|
|
3
|
+
*
|
|
4
|
+
* Starts a temporary server to receive the authorization callback,
|
|
5
|
+
* validates the state parameter, and extracts the authorization code.
|
|
6
|
+
*/
|
|
7
|
+
import { createServer } from "node:http";
|
|
8
|
+
import { BasecampError } from "../errors.js";
|
|
9
|
+
const SUCCESS_HTML = `<!DOCTYPE html>
|
|
10
|
+
<html><head><title>Authorization Complete</title></head>
|
|
11
|
+
<body style="font-family:system-ui;text-align:center;padding:40px">
|
|
12
|
+
<h1>Authorization complete</h1>
|
|
13
|
+
<p>You can close this window.</p>
|
|
14
|
+
</body></html>`;
|
|
15
|
+
const ERROR_HTML = `<!DOCTYPE html>
|
|
16
|
+
<html><head><title>Authorization Failed</title></head>
|
|
17
|
+
<body style="font-family:system-ui;text-align:center;padding:40px">
|
|
18
|
+
<h1>Authorization failed</h1>
|
|
19
|
+
<p>Please try again.</p>
|
|
20
|
+
</body></html>`;
|
|
21
|
+
/**
|
|
22
|
+
* Starts a local HTTP server to receive the OAuth callback.
|
|
23
|
+
*
|
|
24
|
+
* The server listens for a single GET /callback request, validates
|
|
25
|
+
* the state parameter, and extracts the authorization code.
|
|
26
|
+
* It auto-closes after receiving the callback or on timeout.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Server configuration
|
|
29
|
+
* @returns The callback URL, a promise for the result, and a close function
|
|
30
|
+
* @throws BasecampError if the port is already in use
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const { url, waitForCallback, close } = await startCallbackServer({
|
|
35
|
+
* expectedState: myState,
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // Use `url` as the redirect_uri in the authorization request
|
|
39
|
+
* // Then wait for the user to complete the flow:
|
|
40
|
+
* const { code } = await waitForCallback();
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export async function startCallbackServer(options) {
|
|
44
|
+
const { port = 14923, host = "localhost", timeoutMs = 120_000, expectedState, } = options;
|
|
45
|
+
let resolve;
|
|
46
|
+
let reject;
|
|
47
|
+
const promise = new Promise((res, rej) => {
|
|
48
|
+
resolve = res;
|
|
49
|
+
reject = rej;
|
|
50
|
+
});
|
|
51
|
+
// Prevent unhandled rejection warnings when reject() fires before
|
|
52
|
+
// the consumer has called waitForCallback(). The consumer's call
|
|
53
|
+
// still receives the rejection.
|
|
54
|
+
promise.catch(() => { });
|
|
55
|
+
let settled = false;
|
|
56
|
+
let timeoutId;
|
|
57
|
+
const httpServer = createServer((req, res) => {
|
|
58
|
+
if (settled) {
|
|
59
|
+
res.writeHead(404);
|
|
60
|
+
res.end();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let reqUrl;
|
|
64
|
+
try {
|
|
65
|
+
const reqHost = req.headers.host ?? `${host}:${port}`;
|
|
66
|
+
reqUrl = new URL(req.url ?? "/", `http://${reqHost}`);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
70
|
+
res.end(ERROR_HTML);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (reqUrl.pathname !== "/callback") {
|
|
74
|
+
res.writeHead(404);
|
|
75
|
+
res.end();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const code = reqUrl.searchParams.get("code");
|
|
79
|
+
const state = reqUrl.searchParams.get("state");
|
|
80
|
+
const error = reqUrl.searchParams.get("error");
|
|
81
|
+
if (error) {
|
|
82
|
+
settled = true;
|
|
83
|
+
const description = reqUrl.searchParams.get("error_description") ?? error;
|
|
84
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
85
|
+
res.end(ERROR_HTML);
|
|
86
|
+
reject(new BasecampError("auth", `OAuth callback error: ${description}`));
|
|
87
|
+
scheduleClose();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!code || !state) {
|
|
91
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
92
|
+
res.end(ERROR_HTML);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (state !== expectedState) {
|
|
96
|
+
settled = true;
|
|
97
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
98
|
+
res.end(ERROR_HTML);
|
|
99
|
+
reject(new BasecampError("auth", "OAuth state mismatch — possible CSRF attack"));
|
|
100
|
+
scheduleClose();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
settled = true;
|
|
104
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
105
|
+
res.end(SUCCESS_HTML);
|
|
106
|
+
resolve({ code, state });
|
|
107
|
+
scheduleClose();
|
|
108
|
+
});
|
|
109
|
+
function scheduleClose() {
|
|
110
|
+
if (timeoutId)
|
|
111
|
+
clearTimeout(timeoutId);
|
|
112
|
+
// Close on next tick to allow the response to flush
|
|
113
|
+
setImmediate(() => httpServer.close());
|
|
114
|
+
}
|
|
115
|
+
function close() {
|
|
116
|
+
if (timeoutId)
|
|
117
|
+
clearTimeout(timeoutId);
|
|
118
|
+
if (!settled) {
|
|
119
|
+
settled = true;
|
|
120
|
+
reject(new BasecampError("auth", "Callback server closed before receiving callback"));
|
|
121
|
+
}
|
|
122
|
+
httpServer.close();
|
|
123
|
+
}
|
|
124
|
+
// Start listening
|
|
125
|
+
await new Promise((listenResolve, listenReject) => {
|
|
126
|
+
httpServer.on("error", (err) => {
|
|
127
|
+
if (err.code === "EADDRINUSE") {
|
|
128
|
+
listenReject(new BasecampError("network", `Port ${port} is already in use — cannot start OAuth callback server`));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
listenReject(err);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
httpServer.listen(port, host, listenResolve);
|
|
135
|
+
});
|
|
136
|
+
// Resolve actual bound port (important when port=0 for OS-assigned ports)
|
|
137
|
+
const addr = httpServer.address();
|
|
138
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
139
|
+
// Start timeout
|
|
140
|
+
timeoutId = setTimeout(() => {
|
|
141
|
+
if (!settled) {
|
|
142
|
+
settled = true;
|
|
143
|
+
reject(new BasecampError("auth", `OAuth callback timed out after ${timeoutMs}ms`));
|
|
144
|
+
httpServer.close();
|
|
145
|
+
}
|
|
146
|
+
}, timeoutMs);
|
|
147
|
+
const url = `http://${host}:${actualPort}/callback`;
|
|
148
|
+
return { url, waitForCallback: () => promise, close };
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=callback-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-server.js","sourceRoot":"","sources":["../../src/oauth/callback-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 discovery for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Fetches OAuth server configuration from the well-known discovery endpoint.
|
|
5
|
+
*/
|
|
6
|
+
import type { OAuthConfig } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Options for OAuth discovery.
|
|
9
|
+
*/
|
|
10
|
+
export interface DiscoverOptions {
|
|
11
|
+
/** Custom fetch function for testing or custom HTTP handling */
|
|
12
|
+
fetch?: typeof globalThis.fetch;
|
|
13
|
+
/** Request timeout in milliseconds (default: 10000) */
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Discovers OAuth server configuration from the well-known endpoint.
|
|
18
|
+
*
|
|
19
|
+
* Fetches the OAuth 2.0 Authorization Server Metadata from:
|
|
20
|
+
* `{baseUrl}/.well-known/oauth-authorization-server`
|
|
21
|
+
*
|
|
22
|
+
* @param baseUrl - The OAuth server's base URL (e.g., "https://launchpad.37signals.com")
|
|
23
|
+
* @param options - Optional configuration
|
|
24
|
+
* @returns The OAuth server configuration
|
|
25
|
+
* @throws BasecampError on network or parsing errors
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const config = await discover("https://launchpad.37signals.com");
|
|
30
|
+
* console.log(config.tokenEndpoint);
|
|
31
|
+
* // => "https://launchpad.37signals.com/authorization/token"
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function discover(baseUrl: string, options?: DiscoverOptions): Promise<OAuthConfig>;
|
|
35
|
+
/**
|
|
36
|
+
* Default Basecamp/Launchpad OAuth server URL.
|
|
37
|
+
*/
|
|
38
|
+
export declare const LAUNCHPAD_BASE_URL = "https://launchpad.37signals.com";
|
|
39
|
+
/**
|
|
40
|
+
* Discovers OAuth configuration from Basecamp's Launchpad server.
|
|
41
|
+
*
|
|
42
|
+
* Convenience function that calls discover() with the Launchpad base URL.
|
|
43
|
+
*
|
|
44
|
+
* @param options - Optional configuration
|
|
45
|
+
* @returns The OAuth server configuration
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const config = await discoverLaunchpad();
|
|
50
|
+
* // Use config.authorizationEndpoint to start OAuth flow
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function discoverLaunchpad(options?: DiscoverOptions): Promise<OAuthConfig>;
|
|
54
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAa9C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAkFtB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,oCAAoC,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAEtB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 discovery for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Fetches OAuth server configuration from the well-known discovery endpoint.
|
|
5
|
+
*/
|
|
6
|
+
import { BasecampError } from "../errors.js";
|
|
7
|
+
import { isLocalhost } from "../security.js";
|
|
8
|
+
/**
|
|
9
|
+
* Discovers OAuth server configuration from the well-known endpoint.
|
|
10
|
+
*
|
|
11
|
+
* Fetches the OAuth 2.0 Authorization Server Metadata from:
|
|
12
|
+
* `{baseUrl}/.well-known/oauth-authorization-server`
|
|
13
|
+
*
|
|
14
|
+
* @param baseUrl - The OAuth server's base URL (e.g., "https://launchpad.37signals.com")
|
|
15
|
+
* @param options - Optional configuration
|
|
16
|
+
* @returns The OAuth server configuration
|
|
17
|
+
* @throws BasecampError on network or parsing errors
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const config = await discover("https://launchpad.37signals.com");
|
|
22
|
+
* console.log(config.tokenEndpoint);
|
|
23
|
+
* // => "https://launchpad.37signals.com/authorization/token"
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export async function discover(baseUrl, options = {}) {
|
|
27
|
+
const { fetch: customFetch = globalThis.fetch, timeoutMs = 10000 } = options;
|
|
28
|
+
// Validate HTTPS before making any network request (allow localhost for testing)
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(baseUrl);
|
|
31
|
+
if (parsed.protocol !== "https:" && !isLocalhost(parsed.hostname)) {
|
|
32
|
+
throw new BasecampError("validation", `OAuth discovery base URL must use HTTPS: ${baseUrl}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err instanceof BasecampError)
|
|
37
|
+
throw err;
|
|
38
|
+
throw new BasecampError("validation", `Invalid OAuth discovery base URL: ${baseUrl}`);
|
|
39
|
+
}
|
|
40
|
+
// Normalize base URL (remove trailing slash)
|
|
41
|
+
const normalizedBase = baseUrl.replace(/\/$/, "");
|
|
42
|
+
const discoveryUrl = `${normalizedBase}/.well-known/oauth-authorization-server`;
|
|
43
|
+
// Create abort controller for timeout
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
46
|
+
try {
|
|
47
|
+
const response = await customFetch(discoveryUrl, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
headers: {
|
|
50
|
+
Accept: "application/json",
|
|
51
|
+
},
|
|
52
|
+
signal: controller.signal,
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const body = await response.text().catch(() => "");
|
|
56
|
+
throw new BasecampError("network", `OAuth discovery failed with status ${response.status}: ${body}`, { httpStatus: response.status });
|
|
57
|
+
}
|
|
58
|
+
const data = (await response.json());
|
|
59
|
+
// Validate required fields
|
|
60
|
+
if (!data.issuer || !data.authorization_endpoint || !data.token_endpoint) {
|
|
61
|
+
throw new BasecampError("api_error", "Invalid OAuth discovery response: missing required fields");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
issuer: data.issuer,
|
|
65
|
+
authorizationEndpoint: data.authorization_endpoint,
|
|
66
|
+
tokenEndpoint: data.token_endpoint,
|
|
67
|
+
registrationEndpoint: data.registration_endpoint,
|
|
68
|
+
scopesSupported: data.scopes_supported,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err instanceof BasecampError) {
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
if (err instanceof Error) {
|
|
76
|
+
if (err.name === "AbortError") {
|
|
77
|
+
throw new BasecampError("network", "OAuth discovery request timed out", {
|
|
78
|
+
cause: err,
|
|
79
|
+
retryable: true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
throw new BasecampError("network", `OAuth discovery failed: ${err.message}`, {
|
|
83
|
+
cause: err,
|
|
84
|
+
retryable: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
throw new BasecampError("network", "OAuth discovery failed with unknown error", {
|
|
88
|
+
retryable: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
clearTimeout(timeoutId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Default Basecamp/Launchpad OAuth server URL.
|
|
97
|
+
*/
|
|
98
|
+
export const LAUNCHPAD_BASE_URL = "https://launchpad.37signals.com";
|
|
99
|
+
/**
|
|
100
|
+
* Discovers OAuth configuration from Basecamp's Launchpad server.
|
|
101
|
+
*
|
|
102
|
+
* Convenience function that calls discover() with the Launchpad base URL.
|
|
103
|
+
*
|
|
104
|
+
* @param options - Optional configuration
|
|
105
|
+
* @returns The OAuth server configuration
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const config = await discoverLaunchpad();
|
|
110
|
+
* // Use config.authorizationEndpoint to start OAuth flow
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export async function discoverLaunchpad(options = {}) {
|
|
114
|
+
return discover(LAUNCHPAD_BASE_URL, options);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 token exchange and refresh for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Handles authorization code exchange and token refresh operations.
|
|
5
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExchangeRequest, RefreshRequest, OAuthToken } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Options for token exchange/refresh operations.
|
|
10
|
+
*/
|
|
11
|
+
export interface TokenOptions {
|
|
12
|
+
/** Custom fetch function for testing or custom HTTP handling */
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Exchanges an authorization code for access and refresh tokens.
|
|
19
|
+
*
|
|
20
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
21
|
+
* Use `useLegacyFormat: true` for Launchpad compatibility.
|
|
22
|
+
*
|
|
23
|
+
* @param request - Exchange request parameters
|
|
24
|
+
* @param options - Optional configuration
|
|
25
|
+
* @returns The token response
|
|
26
|
+
* @throws BasecampError on validation, network, or authentication errors
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Standard OAuth 2.0
|
|
31
|
+
* const token = await exchangeCode({
|
|
32
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
33
|
+
* code: "auth_code_from_callback",
|
|
34
|
+
* redirectUri: "https://myapp.com/callback",
|
|
35
|
+
* clientId: "my_client_id",
|
|
36
|
+
* clientSecret: "my_client_secret",
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Launchpad legacy format
|
|
40
|
+
* const token = await exchangeCode({
|
|
41
|
+
* tokenEndpoint: "https://launchpad.37signals.com/authorization/token",
|
|
42
|
+
* code: "auth_code",
|
|
43
|
+
* redirectUri: "https://myapp.com/callback",
|
|
44
|
+
* clientId: "my_client_id",
|
|
45
|
+
* clientSecret: "my_client_secret",
|
|
46
|
+
* useLegacyFormat: true,
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function exchangeCode(request: ExchangeRequest, options?: TokenOptions): Promise<OAuthToken>;
|
|
51
|
+
/**
|
|
52
|
+
* Refreshes an access token using a refresh token.
|
|
53
|
+
*
|
|
54
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
55
|
+
* Use `useLegacyFormat: true` for Launchpad compatibility.
|
|
56
|
+
*
|
|
57
|
+
* @param request - Refresh request parameters
|
|
58
|
+
* @param options - Optional configuration
|
|
59
|
+
* @returns The new token response
|
|
60
|
+
* @throws BasecampError on validation, network, or authentication errors
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* // Standard OAuth 2.0
|
|
65
|
+
* const newToken = await refreshToken({
|
|
66
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
67
|
+
* refreshToken: oldToken.refreshToken,
|
|
68
|
+
* clientId: "my_client_id",
|
|
69
|
+
* clientSecret: "my_client_secret",
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Launchpad legacy format
|
|
73
|
+
* const newToken = await refreshToken({
|
|
74
|
+
* tokenEndpoint: "https://launchpad.37signals.com/authorization/token",
|
|
75
|
+
* refreshToken: oldToken.refreshToken,
|
|
76
|
+
* useLegacyFormat: true,
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function refreshToken(request: RefreshRequest, options?: TokenOptions): Promise<OAuthToken>;
|
|
81
|
+
/**
|
|
82
|
+
* Checks if a token is expired or about to expire.
|
|
83
|
+
*
|
|
84
|
+
* @param token - The token to check
|
|
85
|
+
* @param bufferSeconds - Buffer time before actual expiration (default: 60)
|
|
86
|
+
* @returns true if the token is expired or will expire within the buffer time
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* if (isTokenExpired(token)) {
|
|
91
|
+
* token = await refreshToken({ ... });
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function isTokenExpired(token: OAuthToken, bufferSeconds?: number): boolean;
|
|
96
|
+
//# sourceMappingURL=exchange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange.d.ts","sourceRoot":"","sources":["../../src/oauth/exchange.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,UAAU,EAGX,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,UAAU,CAAC,CAsCrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,UAAU,CAAC,CA8BrB;AA0ND;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,SAAK,GAAG,OAAO,CAQ7E"}
|