@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,324 @@
|
|
|
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 { BasecampError } from "../errors.js";
|
|
8
|
+
import { isLocalhost } from "../security.js";
|
|
9
|
+
/**
|
|
10
|
+
* Exchanges an authorization code for access and refresh tokens.
|
|
11
|
+
*
|
|
12
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
13
|
+
* Use `useLegacyFormat: true` for Launchpad compatibility.
|
|
14
|
+
*
|
|
15
|
+
* @param request - Exchange request parameters
|
|
16
|
+
* @param options - Optional configuration
|
|
17
|
+
* @returns The token response
|
|
18
|
+
* @throws BasecampError on validation, network, or authentication errors
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // Standard OAuth 2.0
|
|
23
|
+
* const token = await exchangeCode({
|
|
24
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
25
|
+
* code: "auth_code_from_callback",
|
|
26
|
+
* redirectUri: "https://myapp.com/callback",
|
|
27
|
+
* clientId: "my_client_id",
|
|
28
|
+
* clientSecret: "my_client_secret",
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Launchpad legacy format
|
|
32
|
+
* const token = await exchangeCode({
|
|
33
|
+
* tokenEndpoint: "https://launchpad.37signals.com/authorization/token",
|
|
34
|
+
* code: "auth_code",
|
|
35
|
+
* redirectUri: "https://myapp.com/callback",
|
|
36
|
+
* clientId: "my_client_id",
|
|
37
|
+
* clientSecret: "my_client_secret",
|
|
38
|
+
* useLegacyFormat: true,
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export async function exchangeCode(request, options = {}) {
|
|
43
|
+
// Validate required fields
|
|
44
|
+
if (!request.tokenEndpoint) {
|
|
45
|
+
throw new BasecampError("validation", "Token endpoint is required");
|
|
46
|
+
}
|
|
47
|
+
if (!request.code) {
|
|
48
|
+
throw new BasecampError("validation", "Authorization code is required");
|
|
49
|
+
}
|
|
50
|
+
if (!request.redirectUri) {
|
|
51
|
+
throw new BasecampError("validation", "Redirect URI is required");
|
|
52
|
+
}
|
|
53
|
+
if (!request.clientId) {
|
|
54
|
+
throw new BasecampError("validation", "Client ID is required");
|
|
55
|
+
}
|
|
56
|
+
// Build request body
|
|
57
|
+
const body = new URLSearchParams();
|
|
58
|
+
if (request.useLegacyFormat) {
|
|
59
|
+
// Launchpad uses non-standard "type" parameter
|
|
60
|
+
body.set("type", "web_server");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Standard OAuth 2.0
|
|
64
|
+
body.set("grant_type", "authorization_code");
|
|
65
|
+
}
|
|
66
|
+
body.set("code", request.code);
|
|
67
|
+
body.set("redirect_uri", request.redirectUri);
|
|
68
|
+
body.set("client_id", request.clientId);
|
|
69
|
+
if (request.clientSecret) {
|
|
70
|
+
body.set("client_secret", request.clientSecret);
|
|
71
|
+
}
|
|
72
|
+
if (request.codeVerifier) {
|
|
73
|
+
body.set("code_verifier", request.codeVerifier);
|
|
74
|
+
}
|
|
75
|
+
return doTokenRequest(request.tokenEndpoint, body, options);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Refreshes an access token using a refresh token.
|
|
79
|
+
*
|
|
80
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
81
|
+
* Use `useLegacyFormat: true` for Launchpad compatibility.
|
|
82
|
+
*
|
|
83
|
+
* @param request - Refresh request parameters
|
|
84
|
+
* @param options - Optional configuration
|
|
85
|
+
* @returns The new token response
|
|
86
|
+
* @throws BasecampError on validation, network, or authentication errors
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // Standard OAuth 2.0
|
|
91
|
+
* const newToken = await refreshToken({
|
|
92
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
93
|
+
* refreshToken: oldToken.refreshToken,
|
|
94
|
+
* clientId: "my_client_id",
|
|
95
|
+
* clientSecret: "my_client_secret",
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* // Launchpad legacy format
|
|
99
|
+
* const newToken = await refreshToken({
|
|
100
|
+
* tokenEndpoint: "https://launchpad.37signals.com/authorization/token",
|
|
101
|
+
* refreshToken: oldToken.refreshToken,
|
|
102
|
+
* useLegacyFormat: true,
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export async function refreshToken(request, options = {}) {
|
|
107
|
+
// Validate required fields
|
|
108
|
+
if (!request.tokenEndpoint) {
|
|
109
|
+
throw new BasecampError("validation", "Token endpoint is required");
|
|
110
|
+
}
|
|
111
|
+
if (!request.refreshToken) {
|
|
112
|
+
throw new BasecampError("validation", "Refresh token is required");
|
|
113
|
+
}
|
|
114
|
+
// Build request body
|
|
115
|
+
const body = new URLSearchParams();
|
|
116
|
+
if (request.useLegacyFormat) {
|
|
117
|
+
// Launchpad uses non-standard "type" parameter
|
|
118
|
+
body.set("type", "refresh");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Standard OAuth 2.0
|
|
122
|
+
body.set("grant_type", "refresh_token");
|
|
123
|
+
}
|
|
124
|
+
body.set("refresh_token", request.refreshToken);
|
|
125
|
+
if (request.clientId) {
|
|
126
|
+
body.set("client_id", request.clientId);
|
|
127
|
+
}
|
|
128
|
+
if (request.clientSecret) {
|
|
129
|
+
body.set("client_secret", request.clientSecret);
|
|
130
|
+
}
|
|
131
|
+
return doTokenRequest(request.tokenEndpoint, body, options);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Validates that a URL uses the HTTPS scheme (allows localhost for testing).
|
|
135
|
+
*/
|
|
136
|
+
function requireHTTPS(url, label) {
|
|
137
|
+
try {
|
|
138
|
+
const parsed = new URL(url);
|
|
139
|
+
if (parsed.protocol !== "https:" && !isLocalhost(parsed.hostname)) {
|
|
140
|
+
throw new BasecampError("validation", `${label} must use HTTPS: ${url}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
if (err instanceof BasecampError)
|
|
145
|
+
throw err;
|
|
146
|
+
throw new BasecampError("validation", `Invalid ${label}: ${url}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parses Content-Length header, returning null for missing/invalid values.
|
|
151
|
+
* Treats non-numeric or negative values as invalid (fail closed).
|
|
152
|
+
*/
|
|
153
|
+
function parseContentLength(header) {
|
|
154
|
+
if (!header)
|
|
155
|
+
return null;
|
|
156
|
+
const parsed = parseInt(header, 10);
|
|
157
|
+
// NaN, negative, or non-integer values are invalid
|
|
158
|
+
if (!Number.isInteger(parsed) || parsed < 0)
|
|
159
|
+
return null;
|
|
160
|
+
// Reject if the header contains non-digit characters (e.g., "123abc")
|
|
161
|
+
if (!/^\d+$/.test(header.trim()))
|
|
162
|
+
return null;
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reads a response body with streaming byte limit.
|
|
167
|
+
* Uses true byte counting even when Content-Length is absent or inaccurate.
|
|
168
|
+
*/
|
|
169
|
+
async function readResponseWithByteLimit(response, maxBytes, httpStatus) {
|
|
170
|
+
// Parse Content-Length, treating invalid values as missing (fail closed)
|
|
171
|
+
const contentLengthBytes = parseContentLength(response.headers.get("Content-Length"));
|
|
172
|
+
// Fast path: reject if Content-Length exceeds limit
|
|
173
|
+
if (contentLengthBytes !== null && contentLengthBytes > maxBytes) {
|
|
174
|
+
throw new BasecampError("api_error", `Token response too large (Content-Length: ${contentLengthBytes} bytes, max ${maxBytes})`, { httpStatus });
|
|
175
|
+
}
|
|
176
|
+
// If no body or body isn't streamable, require valid Content-Length
|
|
177
|
+
if (!response.body) {
|
|
178
|
+
// If Content-Length is missing or invalid, we cannot protect against DoS - fail closed.
|
|
179
|
+
// If it was valid and within limits, we can safely read.
|
|
180
|
+
if (contentLengthBytes === null) {
|
|
181
|
+
throw new BasecampError("api_error", "Cannot safely read token response: no valid Content-Length header and streaming unavailable", { httpStatus });
|
|
182
|
+
}
|
|
183
|
+
// Content-Length was valid and within limits, safe to read
|
|
184
|
+
return response.text();
|
|
185
|
+
}
|
|
186
|
+
// Stream with byte counting
|
|
187
|
+
const reader = response.body.getReader();
|
|
188
|
+
const chunks = [];
|
|
189
|
+
let totalBytes = 0;
|
|
190
|
+
try {
|
|
191
|
+
while (true) {
|
|
192
|
+
const { done, value } = await reader.read();
|
|
193
|
+
if (done)
|
|
194
|
+
break;
|
|
195
|
+
totalBytes += value.length;
|
|
196
|
+
if (totalBytes > maxBytes) {
|
|
197
|
+
reader.cancel();
|
|
198
|
+
throw new BasecampError("api_error", `Token response too large (${totalBytes}+ bytes, max ${maxBytes})`, { httpStatus });
|
|
199
|
+
}
|
|
200
|
+
chunks.push(value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
reader.releaseLock();
|
|
205
|
+
}
|
|
206
|
+
// Concatenate and decode
|
|
207
|
+
const combined = new Uint8Array(totalBytes);
|
|
208
|
+
let offset = 0;
|
|
209
|
+
for (const chunk of chunks) {
|
|
210
|
+
combined.set(chunk, offset);
|
|
211
|
+
offset += chunk.length;
|
|
212
|
+
}
|
|
213
|
+
return new TextDecoder().decode(combined);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Performs the actual HTTP token request.
|
|
217
|
+
*/
|
|
218
|
+
async function doTokenRequest(tokenEndpoint, body, options) {
|
|
219
|
+
requireHTTPS(tokenEndpoint, "token endpoint");
|
|
220
|
+
const { fetch: customFetch = globalThis.fetch, timeoutMs = 30000 } = options;
|
|
221
|
+
// Create abort controller for timeout
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
224
|
+
try {
|
|
225
|
+
const response = await customFetch(tokenEndpoint, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: {
|
|
228
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
229
|
+
Accept: "application/json",
|
|
230
|
+
},
|
|
231
|
+
body: body.toString(),
|
|
232
|
+
signal: controller.signal,
|
|
233
|
+
});
|
|
234
|
+
const MAX_TOKEN_RESPONSE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
235
|
+
// Use streaming reader with true byte-level limit enforcement.
|
|
236
|
+
// This handles cases where Content-Length is absent or inaccurate.
|
|
237
|
+
const responseText = await readResponseWithByteLimit(response, MAX_TOKEN_RESPONSE_BYTES, response.status);
|
|
238
|
+
let data;
|
|
239
|
+
try {
|
|
240
|
+
data = JSON.parse(responseText);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Truncate response text to avoid leaking sensitive data in error messages
|
|
244
|
+
const truncated = responseText.length > 500 ? responseText.slice(0, 497) + "..." : responseText;
|
|
245
|
+
throw new BasecampError("api_error", `Failed to parse token response: ${truncated}`, { httpStatus: response.status });
|
|
246
|
+
}
|
|
247
|
+
// Check for error response
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
const errorData = data;
|
|
250
|
+
const rawMessage = errorData.error_description || errorData.error || "Token request failed";
|
|
251
|
+
const message = rawMessage.length > 500 ? rawMessage.slice(0, 497) + "..." : rawMessage;
|
|
252
|
+
if (response.status === 401 || errorData.error === "invalid_grant") {
|
|
253
|
+
throw new BasecampError("auth", message, {
|
|
254
|
+
httpStatus: response.status,
|
|
255
|
+
hint: "The authorization code or refresh token may be invalid or expired",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
throw new BasecampError("api_error", message, {
|
|
259
|
+
httpStatus: response.status,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Parse successful response
|
|
263
|
+
const tokenData = data;
|
|
264
|
+
if (!tokenData.access_token) {
|
|
265
|
+
throw new BasecampError("api_error", "Token response missing access_token");
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
accessToken: tokenData.access_token,
|
|
269
|
+
refreshToken: tokenData.refresh_token,
|
|
270
|
+
tokenType: tokenData.token_type || "Bearer",
|
|
271
|
+
expiresIn: tokenData.expires_in,
|
|
272
|
+
expiresAt: tokenData.expires_in
|
|
273
|
+
? new Date(Date.now() + tokenData.expires_in * 1000)
|
|
274
|
+
: undefined,
|
|
275
|
+
scope: tokenData.scope,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (err instanceof BasecampError) {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
if (err instanceof Error) {
|
|
283
|
+
if (err.name === "AbortError") {
|
|
284
|
+
throw new BasecampError("network", "Token request timed out", {
|
|
285
|
+
cause: err,
|
|
286
|
+
retryable: true,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
throw new BasecampError("network", `Token request failed: ${err.message}`, {
|
|
290
|
+
cause: err,
|
|
291
|
+
retryable: true,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
throw new BasecampError("network", "Token request failed with unknown error", {
|
|
295
|
+
retryable: true,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
clearTimeout(timeoutId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Checks if a token is expired or about to expire.
|
|
304
|
+
*
|
|
305
|
+
* @param token - The token to check
|
|
306
|
+
* @param bufferSeconds - Buffer time before actual expiration (default: 60)
|
|
307
|
+
* @returns true if the token is expired or will expire within the buffer time
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* if (isTokenExpired(token)) {
|
|
312
|
+
* token = await refreshToken({ ... });
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export function isTokenExpired(token, bufferSeconds = 60) {
|
|
317
|
+
if (!token.expiresAt) {
|
|
318
|
+
// No expiration info - assume not expired
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
const bufferMs = bufferSeconds * 1000;
|
|
322
|
+
return Date.now() + bufferMs >= token.expiresAt.getTime();
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../src/oauth/exchange.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth identity discovery for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Fetches authorization information (identity and accounts) from
|
|
5
|
+
* the Launchpad endpoint without requiring a full client instance.
|
|
6
|
+
*/
|
|
7
|
+
import type { TokenProvider } from "../client.js";
|
|
8
|
+
import type { AuthorizationInfo } from "../services/authorization.js";
|
|
9
|
+
/**
|
|
10
|
+
* Fetches authorization information using an access token.
|
|
11
|
+
*
|
|
12
|
+
* Calls the Launchpad authorization endpoint to retrieve the
|
|
13
|
+
* authenticated user's identity and list of accessible accounts.
|
|
14
|
+
* Does not require a full Basecamp client instance.
|
|
15
|
+
*
|
|
16
|
+
* @param accessToken - A token string or async function returning one
|
|
17
|
+
* @returns Authorization information including identity and accounts
|
|
18
|
+
* @throws BasecampError on network or auth errors
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const info = await discoverIdentity("my_access_token");
|
|
23
|
+
* console.log(info.identity.firstName, info.identity.lastName);
|
|
24
|
+
* for (const account of info.accounts) {
|
|
25
|
+
* console.log(`${account.name} (${account.product})`);
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function discoverIdentity(accessToken: TokenProvider): Promise<AuthorizationInfo>;
|
|
30
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/oauth/identity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AA2BtE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsD7F"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth identity discovery for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Fetches authorization information (identity and accounts) from
|
|
5
|
+
* the Launchpad endpoint without requiring a full client instance.
|
|
6
|
+
*/
|
|
7
|
+
import { BasecampError } from "../errors.js";
|
|
8
|
+
const AUTHORIZATION_ENDPOINT = "https://launchpad.37signals.com/authorization.json";
|
|
9
|
+
/**
|
|
10
|
+
* Fetches authorization information using an access token.
|
|
11
|
+
*
|
|
12
|
+
* Calls the Launchpad authorization endpoint to retrieve the
|
|
13
|
+
* authenticated user's identity and list of accessible accounts.
|
|
14
|
+
* Does not require a full Basecamp client instance.
|
|
15
|
+
*
|
|
16
|
+
* @param accessToken - A token string or async function returning one
|
|
17
|
+
* @returns Authorization information including identity and accounts
|
|
18
|
+
* @throws BasecampError on network or auth errors
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const info = await discoverIdentity("my_access_token");
|
|
23
|
+
* console.log(info.identity.firstName, info.identity.lastName);
|
|
24
|
+
* for (const account of info.accounts) {
|
|
25
|
+
* console.log(`${account.name} (${account.product})`);
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export async function discoverIdentity(accessToken) {
|
|
30
|
+
const token = typeof accessToken === "function" ? await accessToken() : accessToken;
|
|
31
|
+
let response;
|
|
32
|
+
try {
|
|
33
|
+
response = await fetch(AUTHORIZATION_ENDPOINT, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${token}`,
|
|
37
|
+
Accept: "application/json",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new BasecampError("network", `Identity discovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
43
|
+
}
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
if (response.status === 401) {
|
|
46
|
+
throw new BasecampError("auth", "Invalid or expired access token", {
|
|
47
|
+
httpStatus: 401,
|
|
48
|
+
hint: "The access token may need to be refreshed",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
throw new BasecampError("api_error", `Authorization endpoint returned ${response.status}`, {
|
|
52
|
+
httpStatus: response.status,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let raw;
|
|
56
|
+
try {
|
|
57
|
+
raw = (await response.json());
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new BasecampError("api_error", "Identity discovery returned invalid JSON");
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
expiresAt: new Date(raw.expires_at),
|
|
64
|
+
identity: {
|
|
65
|
+
id: raw.identity.id,
|
|
66
|
+
firstName: raw.identity.first_name,
|
|
67
|
+
lastName: raw.identity.last_name,
|
|
68
|
+
emailAddress: raw.identity.email_address,
|
|
69
|
+
},
|
|
70
|
+
accounts: raw.accounts.map((a) => ({
|
|
71
|
+
id: a.id,
|
|
72
|
+
name: a.name,
|
|
73
|
+
product: a.product,
|
|
74
|
+
href: a.href,
|
|
75
|
+
appHref: a.app_href ?? "",
|
|
76
|
+
hidden: a.hidden,
|
|
77
|
+
expired: a.expired,
|
|
78
|
+
featured: a.featured,
|
|
79
|
+
})),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/oauth/identity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA2B7C,MAAM,sBAAsB,GAAG,oDAAoD,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAA0B;IAC/D,MAAM,KAAK,GAAG,OAAO,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IAEpF,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,sBAAsB,EAAE;YAC7C,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,MAAM,EAAE,kBAAkB;aAC3B;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,8BAA8B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACvH,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,MAAM,EAAE,iCAAiC,EAAE;gBACjE,UAAU,EAAE,GAAG;gBACf,IAAI,EAAE,2CAA2C;aAClD,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,mCAAmC,QAAQ,CAAC,MAAM,EAAE,EAAE;YACzF,UAAU,EAAE,QAAQ,CAAC,MAAM;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,GAA6B,CAAC;IAClC,IAAI,CAAC;QACH,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA6B,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,0CAA0C,CAAC,CAAC;IACnF,CAAC;IAED,OAAO;QACL,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;QACnC,QAAQ,EAAE;YACR,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE;YACnB,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU;YAClC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS;YAChC,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,aAAa;SACzC;QACD,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjC,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;YACzB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;SACrB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 module for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Provides OAuth discovery, token exchange, and token refresh functionality.
|
|
5
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { discover, exchangeCode, refreshToken, isTokenExpired } from "@basecamp/sdk/oauth";
|
|
10
|
+
*
|
|
11
|
+
* // 1. Discover OAuth configuration
|
|
12
|
+
* const config = await discover("https://launchpad.37signals.com");
|
|
13
|
+
*
|
|
14
|
+
* // 2. Exchange authorization code for tokens
|
|
15
|
+
* const token = await exchangeCode({
|
|
16
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
17
|
+
* code: "auth_code_from_callback",
|
|
18
|
+
* redirectUri: "https://myapp.com/callback",
|
|
19
|
+
* clientId: process.env.BASECAMP_CLIENT_ID!,
|
|
20
|
+
* clientSecret: process.env.BASECAMP_CLIENT_SECRET!,
|
|
21
|
+
* useLegacyFormat: true, // Required for Launchpad
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // 3. Use the token
|
|
25
|
+
* const client = createBasecampClient({
|
|
26
|
+
* accountId: "12345",
|
|
27
|
+
* accessToken: token.accessToken,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // 4. Refresh when needed
|
|
31
|
+
* if (isTokenExpired(token)) {
|
|
32
|
+
* const newToken = await refreshToken({
|
|
33
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
34
|
+
* refreshToken: token.refreshToken!,
|
|
35
|
+
* useLegacyFormat: true,
|
|
36
|
+
* });
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export type { OAuthConfig, OAuthToken, ExchangeRequest, RefreshRequest, RawTokenResponse, OAuthErrorResponse, } from "./types.js";
|
|
41
|
+
export { discover, discoverLaunchpad, LAUNCHPAD_BASE_URL, type DiscoverOptions, } from "./discovery.js";
|
|
42
|
+
export { exchangeCode, refreshToken, isTokenExpired, type TokenOptions, } from "./exchange.js";
|
|
43
|
+
export { generatePKCE, generateState, type PKCE, } from "./pkce.js";
|
|
44
|
+
export { buildAuthorizationUrl, type AuthorizeParams, } from "./authorize.js";
|
|
45
|
+
export { FileTokenStore, type TokenStore, } from "./token-store.js";
|
|
46
|
+
export { TokenManager, type TokenManagerOptions, } from "./token-manager.js";
|
|
47
|
+
export { startCallbackServer, type CallbackResult, type CallbackServerOptions, } from "./callback-server.js";
|
|
48
|
+
export { performInteractiveLogin, type InteractiveLoginOptions, } from "./interactive-login.js";
|
|
49
|
+
export { discoverIdentity, } from "./identity.js";
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,YAAY,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,KAAK,IAAI,GACV,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 module for Basecamp SDK.
|
|
3
|
+
*
|
|
4
|
+
* Provides OAuth discovery, token exchange, and token refresh functionality.
|
|
5
|
+
* Supports both standard OAuth 2.0 and Basecamp's Launchpad legacy format.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { discover, exchangeCode, refreshToken, isTokenExpired } from "@basecamp/sdk/oauth";
|
|
10
|
+
*
|
|
11
|
+
* // 1. Discover OAuth configuration
|
|
12
|
+
* const config = await discover("https://launchpad.37signals.com");
|
|
13
|
+
*
|
|
14
|
+
* // 2. Exchange authorization code for tokens
|
|
15
|
+
* const token = await exchangeCode({
|
|
16
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
17
|
+
* code: "auth_code_from_callback",
|
|
18
|
+
* redirectUri: "https://myapp.com/callback",
|
|
19
|
+
* clientId: process.env.BASECAMP_CLIENT_ID!,
|
|
20
|
+
* clientSecret: process.env.BASECAMP_CLIENT_SECRET!,
|
|
21
|
+
* useLegacyFormat: true, // Required for Launchpad
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // 3. Use the token
|
|
25
|
+
* const client = createBasecampClient({
|
|
26
|
+
* accountId: "12345",
|
|
27
|
+
* accessToken: token.accessToken,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // 4. Refresh when needed
|
|
31
|
+
* if (isTokenExpired(token)) {
|
|
32
|
+
* const newToken = await refreshToken({
|
|
33
|
+
* tokenEndpoint: config.tokenEndpoint,
|
|
34
|
+
* refreshToken: token.refreshToken!,
|
|
35
|
+
* useLegacyFormat: true,
|
|
36
|
+
* });
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
// Discovery
|
|
41
|
+
export { discover, discoverLaunchpad, LAUNCHPAD_BASE_URL, } from "./discovery.js";
|
|
42
|
+
// Token exchange
|
|
43
|
+
export { exchangeCode, refreshToken, isTokenExpired, } from "./exchange.js";
|
|
44
|
+
// PKCE utilities
|
|
45
|
+
export { generatePKCE, generateState, } from "./pkce.js";
|
|
46
|
+
// Authorization URL
|
|
47
|
+
export { buildAuthorizationUrl, } from "./authorize.js";
|
|
48
|
+
// Token store
|
|
49
|
+
export { FileTokenStore, } from "./token-store.js";
|
|
50
|
+
// Token manager
|
|
51
|
+
export { TokenManager, } from "./token-manager.js";
|
|
52
|
+
// Callback server
|
|
53
|
+
export { startCallbackServer, } from "./callback-server.js";
|
|
54
|
+
// Interactive login
|
|
55
|
+
export { performInteractiveLogin, } from "./interactive-login.js";
|
|
56
|
+
// Identity
|
|
57
|
+
export { discoverIdentity, } from "./identity.js";
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAYH,YAAY;AACZ,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,GAEnB,MAAM,gBAAgB,CAAC;AAExB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,GAEf,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,GAEd,MAAM,WAAW,CAAC;AAEnB,oBAAoB;AACpB,OAAO,EACL,qBAAqB,GAEtB,MAAM,gBAAgB,CAAC;AAExB,cAAc;AACd,OAAO,EACL,cAAc,GAEf,MAAM,kBAAkB,CAAC;AAE1B,gBAAgB;AAChB,OAAO,EACL,YAAY,GAEb,MAAM,oBAAoB,CAAC;AAE5B,kBAAkB;AAClB,OAAO,EACL,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAE9B,oBAAoB;AACpB,OAAO,EACL,uBAAuB,GAExB,MAAM,wBAAwB,CAAC;AAEhC,WAAW;AACX,OAAO,EACL,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive OAuth login flow for CLI and desktop applications.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the full OAuth 2.0 authorization code flow:
|
|
5
|
+
* discovery, PKCE, local callback server, browser launch, code exchange.
|
|
6
|
+
*/
|
|
7
|
+
import type { TokenStore } from "./token-store.js";
|
|
8
|
+
import type { OAuthToken } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Options for the interactive login flow.
|
|
11
|
+
*/
|
|
12
|
+
export interface InteractiveLoginOptions {
|
|
13
|
+
/** The client identifier */
|
|
14
|
+
clientId: string;
|
|
15
|
+
/** The client secret (optional for public clients) */
|
|
16
|
+
clientSecret?: string;
|
|
17
|
+
/** Token store for persisting the resulting token */
|
|
18
|
+
store: TokenStore;
|
|
19
|
+
/** OAuth server base URL (defaults to Launchpad) */
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
/** Use Launchpad's non-standard token format (default: true) */
|
|
22
|
+
useLegacyFormat?: boolean;
|
|
23
|
+
/** Port for the local callback server (optional) */
|
|
24
|
+
callbackPort?: number;
|
|
25
|
+
/** Function to open the authorization URL in a browser */
|
|
26
|
+
openBrowser: (url: string) => Promise<void>;
|
|
27
|
+
/** Fallback for when browser launch fails — prompt user to visit URL manually */
|
|
28
|
+
promptForManualVisit?: (authUrl: string) => Promise<void>;
|
|
29
|
+
/** Status callback for progress messages */
|
|
30
|
+
onStatus?: (message: string) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Performs the full interactive OAuth login flow.
|
|
34
|
+
*
|
|
35
|
+
* Steps:
|
|
36
|
+
* 1. Discover OAuth endpoints
|
|
37
|
+
* 2. Generate PKCE and state parameters
|
|
38
|
+
* 3. Start local callback server
|
|
39
|
+
* 4. Build authorization URL and open browser
|
|
40
|
+
* 5. Wait for callback with authorization code
|
|
41
|
+
* 6. Exchange code for tokens
|
|
42
|
+
* 7. Save token to store
|
|
43
|
+
*
|
|
44
|
+
* @param options - Login flow configuration
|
|
45
|
+
* @returns The resulting OAuth token
|
|
46
|
+
* @throws BasecampError on any flow failure
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import open from "open"; // or use child_process
|
|
51
|
+
*
|
|
52
|
+
* const token = await performInteractiveLogin({
|
|
53
|
+
* clientId: process.env.BASECAMP_CLIENT_ID!,
|
|
54
|
+
* clientSecret: process.env.BASECAMP_CLIENT_SECRET!,
|
|
55
|
+
* store: new FileTokenStore("~/.config/basecamp/tokens.json"),
|
|
56
|
+
* openBrowser: (url) => open(url),
|
|
57
|
+
* onStatus: (msg) => console.log(msg),
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function performInteractiveLogin(options: InteractiveLoginOptions): Promise<OAuthToken>;
|
|
62
|
+
//# sourceMappingURL=interactive-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive-login.d.ts","sourceRoot":"","sources":["../../src/oauth/interactive-login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,KAAK,EAAE,UAAU,CAAC;IAClB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAgFrB"}
|