@caido/server-auth 0.1.0 → 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/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Client, fetchExchange } from "@urql/core";
2
+ import { print } from "graphql";
2
3
  import { createClient } from "graphql-ws";
3
4
  import { gql } from "graphql-tag";
4
5
 
@@ -13,51 +14,42 @@ var AuthenticationError = class extends Error {
13
14
  }
14
15
  };
15
16
  /**
16
- * Error thrown when the authentication flow fails to start.
17
+ * Error thrown for errors coming from the Caido cloud API.
18
+ * Used for device approval and device information operations.
17
19
  */
18
- var AuthenticationFlowError = class extends AuthenticationError {
19
- /** Error code from the API */
20
- code;
21
- constructor(code, message) {
22
- super(`${code}: ${message}`);
23
- this.name = "AuthenticationFlowError";
24
- this.code = code;
25
- }
26
- };
27
- /**
28
- * Error thrown when token refresh fails.
29
- */
30
- var TokenRefreshError = class extends AuthenticationError {
31
- /** Error code from the API */
32
- code;
33
- constructor(code, message) {
34
- super(`${code}: ${message}`);
35
- this.name = "TokenRefreshError";
36
- this.code = code;
37
- }
38
- };
39
- /**
40
- * Error thrown when device approval fails.
41
- */
42
- var DeviceApprovalError = class extends AuthenticationError {
20
+ var CloudError = class extends AuthenticationError {
43
21
  /** HTTP status code if available */
44
22
  statusCode;
45
- constructor(message, statusCode) {
23
+ /** Error code from the API if available */
24
+ code;
25
+ /** Reason for the error if available */
26
+ reason;
27
+ constructor(message, options) {
46
28
  super(message);
47
- this.name = "DeviceApprovalError";
48
- this.statusCode = statusCode;
29
+ this.name = "CloudError";
30
+ this.statusCode = options?.statusCode;
31
+ this.code = options?.code;
32
+ this.reason = options?.reason;
49
33
  }
50
34
  };
51
35
  /**
52
- * Error thrown when fetching device information fails.
36
+ * Error thrown for errors coming from the Caido instance.
37
+ * Used for authentication flow and token refresh operations.
53
38
  */
54
- var DeviceInformationError = class extends AuthenticationError {
55
- /** HTTP status code if available */
56
- statusCode;
57
- constructor(message, statusCode) {
39
+ var InstanceError = class extends AuthenticationError {
40
+ /** Error code from the API */
41
+ code;
42
+ /** Reason for the error if available */
43
+ reason;
44
+ /** Error message if available */
45
+ errorMessage;
46
+ constructor(code, options) {
47
+ const message = options?.reason ?? options?.message ?? code;
58
48
  super(message);
59
- this.name = "DeviceInformationError";
60
- this.statusCode = statusCode;
49
+ this.name = "InstanceError";
50
+ this.code = code;
51
+ this.reason = options?.reason;
52
+ this.errorMessage = options?.message;
61
53
  }
62
54
  };
63
55
 
@@ -73,8 +65,21 @@ const START_AUTHENTICATION_FLOW = gql`
73
65
  expiresAt
74
66
  }
75
67
  error {
76
- code
77
- message
68
+ ... on AuthenticationUserError {
69
+ code
70
+ reason
71
+ }
72
+ ... on CloudUserError {
73
+ code
74
+ reason
75
+ }
76
+ ... on InternalUserError {
77
+ code
78
+ message
79
+ }
80
+ ... on OtherUserError {
81
+ code
82
+ }
78
83
  }
79
84
  }
80
85
  }
@@ -84,27 +89,51 @@ const CREATED_AUTHENTICATION_TOKEN = gql`
84
89
  createdAuthenticationToken(requestId: $requestId) {
85
90
  token {
86
91
  accessToken
87
- refreshToken
88
92
  expiresAt
93
+ refreshToken
94
+ scopes
89
95
  }
90
96
  error {
91
- code
92
- message
97
+ ... on AuthenticationUserError {
98
+ code
99
+ reason
100
+ }
101
+ ... on InternalUserError {
102
+ code
103
+ message
104
+ }
105
+ ... on OtherUserError {
106
+ code
107
+ }
93
108
  }
94
109
  }
95
110
  }
96
111
  `;
97
112
  const REFRESH_AUTHENTICATION_TOKEN = gql`
98
- mutation RefreshAuthenticationToken($refreshToken: String!) {
113
+ mutation RefreshAuthenticationToken($refreshToken: Token!) {
99
114
  refreshAuthenticationToken(refreshToken: $refreshToken) {
100
115
  token {
101
116
  accessToken
102
- refreshToken
103
117
  expiresAt
118
+ refreshToken
119
+ scopes
104
120
  }
105
121
  error {
106
- code
107
- message
122
+ ... on AuthenticationUserError {
123
+ code
124
+ reason
125
+ }
126
+ ... on CloudUserError {
127
+ code
128
+ reason
129
+ }
130
+ ... on InternalUserError {
131
+ code
132
+ message
133
+ }
134
+ ... on OtherUserError {
135
+ code
136
+ }
108
137
  }
109
138
  }
110
139
  }
@@ -117,48 +146,54 @@ const REFRESH_AUTHENTICATION_TOKEN = gql`
117
146
  *
118
147
  * @example
119
148
  * ```typescript
120
- * import { CaidoAuth, BrowserApprover } from "@caido/auth";
149
+ * import { AuthClient, BrowserApprover } from "@caido/auth";
121
150
  *
122
- * const auth = new CaidoAuth(
123
- * "http://localhost:8080",
124
- * new BrowserApprover((request) => {
151
+ * const auth = new AuthClient({
152
+ * instanceUrl: "http://localhost:8080",
153
+ * approver: new BrowserApprover((request) => {
125
154
  * console.log(`Visit ${request.verificationUrl}`);
126
155
  * })
127
- * );
156
+ * });
128
157
  *
129
158
  * const token = await auth.startAuthenticationFlow();
130
159
  * console.log("Access token:", token.accessToken);
131
160
  * ```
132
161
  */
133
- var CaidoAuth = class {
162
+ var AuthClient = class {
134
163
  instanceUrl;
135
164
  graphqlUrl;
136
165
  websocketUrl;
137
166
  approver;
138
167
  client;
139
- /**
140
- * Create a new CaidoAuth client.
141
- *
142
- * @param instanceUrl - Base URL of the Caido instance (e.g., "http://localhost:8080")
143
- * @param approver - The approver to use for the authentication flow
144
- */
145
- constructor(instanceUrl, approver) {
146
- this.instanceUrl = instanceUrl.replace(/\/$/, "");
168
+ fetchFn;
169
+ timeout;
170
+ constructor(options) {
171
+ this.instanceUrl = options.instanceUrl.replace(/\/$/, "");
147
172
  this.graphqlUrl = `${this.instanceUrl}/graphql`;
148
173
  this.websocketUrl = this.getWebsocketUrl();
149
- this.approver = approver;
174
+ this.approver = options.approver;
175
+ this.fetchFn = options.fetch;
176
+ this.timeout = options.timeout;
150
177
  this.client = new Client({
151
178
  url: this.graphqlUrl,
152
- exchanges: [fetchExchange]
179
+ exchanges: [fetchExchange],
180
+ fetchOptions: () => {
181
+ const fetchOptions = {};
182
+ if (this.timeout !== void 0) fetchOptions.signal = AbortSignal.timeout(this.timeout);
183
+ return fetchOptions;
184
+ },
185
+ fetch: this.fetchFn
153
186
  });
154
187
  }
155
- /**
156
- * Convert HTTP(S) URL to WS(S) URL for subscriptions.
157
- */
158
188
  getWebsocketUrl() {
159
189
  const url = new URL(this.graphqlUrl);
160
190
  return `${url.protocol === "https:" ? "wss:" : "ws:"}//${url.host}/ws/graphql`;
161
191
  }
192
+ extractErrorDetails(error) {
193
+ if ("reason" in error) return { reason: error.reason };
194
+ if ("message" in error) return { message: error.message };
195
+ return {};
196
+ }
162
197
  /**
163
198
  * Start the device code authentication flow.
164
199
  *
@@ -169,16 +204,19 @@ var CaidoAuth = class {
169
204
  * 4. Returns the authentication token once approved
170
205
  *
171
206
  * @returns The authentication token
172
- * @throws {AuthenticationFlowError} If the flow fails to start
207
+ * @throws {InstanceError} If the flow fails to start
173
208
  * @throws {AuthenticationError} If token retrieval fails
174
209
  */
175
210
  async startAuthenticationFlow() {
176
211
  const result = await this.client.mutation(START_AUTHENTICATION_FLOW, {}).toPromise();
177
- if (result.error) throw new AuthenticationFlowError("GRAPHQL_ERROR", result.error.message);
212
+ if (result.error) throw new InstanceError("GRAPHQL_ERROR", { message: result.error.message });
178
213
  const payload = result.data?.startAuthenticationFlow;
179
- if (!payload) throw new AuthenticationFlowError("NO_RESPONSE", "No response from startAuthenticationFlow");
180
- if (payload.error) throw new AuthenticationFlowError(payload.error.code, payload.error.message);
181
- if (!payload.request) throw new AuthenticationFlowError("NO_REQUEST", "No authentication request returned");
214
+ if (!payload) throw new InstanceError("NO_RESPONSE", { message: "No response from startAuthenticationFlow" });
215
+ if (payload.error) {
216
+ const details = this.extractErrorDetails(payload.error);
217
+ throw new InstanceError(payload.error.code, details);
218
+ }
219
+ if (!payload.request) throw new InstanceError("NO_REQUEST", { message: "No authentication request returned" });
182
220
  const authRequest = {
183
221
  id: payload.request.id,
184
222
  userCode: payload.request.userCode,
@@ -188,23 +226,11 @@ var CaidoAuth = class {
188
226
  await this.approver.approve(authRequest);
189
227
  return await this.waitForToken(authRequest.id);
190
228
  }
191
- /**
192
- * Subscribe and wait for the authentication token.
193
- *
194
- * @param requestId - The authentication request ID
195
- * @returns The authentication token once the user authorizes
196
- * @throws {AuthenticationError} If subscription fails or returns an error
197
- */
198
229
  async waitForToken(requestId) {
199
230
  return new Promise((resolve, reject) => {
200
231
  const wsClient = createClient({ url: this.websocketUrl });
201
232
  const unsubscribe = wsClient.subscribe({
202
- query: CREATED_AUTHENTICATION_TOKEN.loc?.source.body ?? `subscription CreatedAuthenticationToken($requestId: ID!) {
203
- createdAuthenticationToken(requestId: $requestId) {
204
- token { accessToken refreshToken expiresAt }
205
- error { code message }
206
- }
207
- }`,
233
+ query: print(CREATED_AUTHENTICATION_TOKEN),
208
234
  variables: { requestId }
209
235
  }, {
210
236
  next: (result) => {
@@ -212,7 +238,8 @@ var CaidoAuth = class {
212
238
  if (payload?.error) {
213
239
  unsubscribe();
214
240
  wsClient.dispose();
215
- reject(new AuthenticationError(`${payload.error.code}: ${payload.error.message}`));
241
+ const details = this.extractErrorDetails(payload.error);
242
+ reject(new InstanceError(payload.error.code, details));
216
243
  return;
217
244
  }
218
245
  if (payload?.token) {
@@ -221,17 +248,18 @@ var CaidoAuth = class {
221
248
  resolve({
222
249
  accessToken: payload.token.accessToken,
223
250
  refreshToken: payload.token.refreshToken,
224
- expiresAt: new Date(payload.token.expiresAt)
251
+ expiresAt: new Date(payload.token.expiresAt),
252
+ scopes: payload.token.scopes
225
253
  });
226
254
  }
227
255
  },
228
256
  error: (error) => {
229
257
  wsClient.dispose();
230
- reject(new AuthenticationError(error instanceof Error ? error.message : String(error)));
258
+ reject(new InstanceError("SUBSCRIPTION_ERROR", { message: error instanceof Error ? error.message : String(error) }));
231
259
  },
232
260
  complete: () => {
233
261
  wsClient.dispose();
234
- reject(new AuthenticationError("Subscription ended without receiving token"));
262
+ reject(new InstanceError("SUBSCRIPTION_COMPLETE", { message: "Subscription ended without receiving token" }));
235
263
  }
236
264
  });
237
265
  });
@@ -241,19 +269,23 @@ var CaidoAuth = class {
241
269
  *
242
270
  * @param refreshToken - The refresh token from a previous authentication
243
271
  * @returns New authentication token with updated access and refresh tokens
244
- * @throws {TokenRefreshError} If the refresh fails
272
+ * @throws {InstanceError} If the refresh fails
245
273
  */
246
274
  async refreshToken(refreshToken) {
247
275
  const result = await this.client.mutation(REFRESH_AUTHENTICATION_TOKEN, { refreshToken }).toPromise();
248
- if (result.error) throw new TokenRefreshError("GRAPHQL_ERROR", result.error.message);
276
+ if (result.error) throw new InstanceError("GRAPHQL_ERROR", { message: result.error.message });
249
277
  const payload = result.data?.refreshAuthenticationToken;
250
- if (!payload) throw new TokenRefreshError("NO_RESPONSE", "No response from refreshAuthenticationToken");
251
- if (payload.error) throw new TokenRefreshError(payload.error.code, payload.error.message);
252
- if (!payload.token) throw new TokenRefreshError("NO_TOKEN", "No token returned from refresh");
278
+ if (!payload) throw new InstanceError("NO_RESPONSE", { message: "No response from refreshAuthenticationToken" });
279
+ if (payload.error) {
280
+ const details = this.extractErrorDetails(payload.error);
281
+ throw new InstanceError(payload.error.code, details);
282
+ }
283
+ if (!payload.token) throw new InstanceError("NO_TOKEN", { message: "No token returned from refresh" });
253
284
  return {
254
285
  accessToken: payload.token.accessToken,
255
286
  refreshToken: payload.token.refreshToken,
256
- expiresAt: new Date(payload.token.expiresAt)
287
+ expiresAt: new Date(payload.token.expiresAt),
288
+ scopes: payload.token.scopes
257
289
  };
258
290
  }
259
291
  };
@@ -316,15 +348,14 @@ var PATApprover = class {
316
348
  pat;
317
349
  allowedScopes;
318
350
  apiUrl;
319
- /**
320
- * Create a new PATApprover.
321
- *
322
- * @param options - Configuration options for the approver
323
- */
351
+ fetchFn;
352
+ timeout;
324
353
  constructor(options) {
325
354
  this.pat = options.pat;
326
355
  this.allowedScopes = options.allowedScopes;
327
356
  this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
357
+ this.fetchFn = options.fetch ?? globalThis.fetch;
358
+ this.timeout = options.timeout;
328
359
  }
329
360
  /**
330
361
  * Approve the authentication request using the PAT.
@@ -333,50 +364,71 @@ var PATApprover = class {
333
364
  * and finally approves the device.
334
365
  *
335
366
  * @param request - The authentication request
336
- * @throws {DeviceInformationError} If fetching device information fails
337
- * @throws {DeviceApprovalError} If approving the device fails
367
+ * @throws {CloudError} If fetching device information or approving the device fails
338
368
  */
339
369
  async approve(request) {
340
370
  let scopesToApprove = (await this.getDeviceInformation(request.userCode)).scopes.map((s) => s.name);
341
371
  if (this.allowedScopes) scopesToApprove = scopesToApprove.filter((scope) => this.allowedScopes.includes(scope));
342
372
  await this.approveDevice(request.userCode, scopesToApprove);
343
373
  }
344
- /**
345
- * Fetch device information from the API.
346
- *
347
- * @param userCode - The user code from the authentication request
348
- * @returns The device information including available scopes
349
- * @throws {DeviceInformationError} If the request fails
350
- */
374
+ async sendRequest(url, options) {
375
+ const fetchOptions = {
376
+ method: options.method,
377
+ headers: options.headers
378
+ };
379
+ if (this.timeout !== void 0) fetchOptions.signal = AbortSignal.timeout(this.timeout);
380
+ return this.fetchFn(url, fetchOptions);
381
+ }
382
+ async parseOAuth2Error(response) {
383
+ let errorText;
384
+ let errorCode;
385
+ let errorDescription;
386
+ try {
387
+ const errorData = await response.json();
388
+ if (typeof errorData === "string") errorText = errorData;
389
+ else {
390
+ errorCode = errorData.error;
391
+ errorDescription = errorData.error_description;
392
+ errorText = errorDescription ?? errorCode ?? "Unknown error";
393
+ }
394
+ } catch {
395
+ errorText = await response.text().catch(() => "Unknown error");
396
+ }
397
+ return {
398
+ errorText,
399
+ errorCode,
400
+ errorDescription
401
+ };
402
+ }
351
403
  async getDeviceInformation(userCode) {
352
404
  const params = new URLSearchParams();
353
405
  params.append("user_code", userCode);
354
406
  const url = new URL(`${this.apiUrl}/oauth2/device/information`);
355
407
  url.search = params.toString();
356
- const response = await fetch(url, {
408
+ const response = await this.sendRequest(url, {
357
409
  method: "GET",
358
410
  headers: {
359
411
  Authorization: `Bearer ${this.pat}`,
360
412
  Accept: "application/json"
361
413
  }
362
414
  });
363
- if (!response.ok) throw new DeviceInformationError(`Failed to get device information: ${await response.text().catch(() => "Unknown error")}`, response.status);
415
+ if (!response.ok) {
416
+ const { errorText, errorCode, errorDescription } = await this.parseOAuth2Error(response);
417
+ throw new CloudError(`Failed to get device information: ${errorText}`, {
418
+ statusCode: response.status,
419
+ code: errorCode,
420
+ reason: errorDescription
421
+ });
422
+ }
364
423
  return await response.json();
365
424
  }
366
- /**
367
- * Approve the device with the specified scopes.
368
- *
369
- * @param userCode - The user code from the authentication request
370
- * @param scopes - The scopes to approve
371
- * @throws {DeviceApprovalError} If the request fails
372
- */
373
425
  async approveDevice(userCode, scopes) {
374
426
  const params = new URLSearchParams();
375
427
  params.append("user_code", userCode);
376
428
  params.append("scope", scopes.join(","));
377
429
  const url = new URL(`${this.apiUrl}/oauth2/device/approve`);
378
430
  url.search = params.toString();
379
- const response = await fetch(url, {
431
+ const response = await this.sendRequest(url, {
380
432
  method: "POST",
381
433
  headers: {
382
434
  Authorization: `Bearer ${this.pat}`,
@@ -384,10 +436,17 @@ var PATApprover = class {
384
436
  Accept: "application/json"
385
437
  }
386
438
  });
387
- if (!response.ok) throw new DeviceApprovalError(`Failed to approve device: ${await response.text().catch(() => "Unknown error")}`, response.status);
439
+ if (!response.ok) {
440
+ const { errorText, errorCode, errorDescription } = await this.parseOAuth2Error(response);
441
+ throw new CloudError(`Failed to approve device: ${errorText}`, {
442
+ statusCode: response.status,
443
+ code: errorCode,
444
+ reason: errorDescription
445
+ });
446
+ }
388
447
  }
389
448
  };
390
449
 
391
450
  //#endregion
392
- export { AuthenticationError, AuthenticationFlowError, BrowserApprover, CaidoAuth, DeviceApprovalError, DeviceInformationError, PATApprover, TokenRefreshError };
451
+ export { AuthClient, AuthenticationError, BrowserApprover, CloudError, InstanceError, PATApprover };
393
452
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["createWSClient"],"sources":["../src/errors.ts","../src/queries.ts","../src/client.ts","../src/approvers/browser.ts","../src/approvers/pat.ts"],"sourcesContent":["/**\n * Base error class for authentication-related errors.\n */\nexport class AuthenticationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AuthenticationError\";\n }\n}\n\n/**\n * Error thrown when the authentication flow fails to start.\n */\nexport class AuthenticationFlowError extends AuthenticationError {\n /** Error code from the API */\n readonly code: string;\n\n constructor(code: string, message: string) {\n super(`${code}: ${message}`);\n this.name = \"AuthenticationFlowError\";\n this.code = code;\n }\n}\n\n/**\n * Error thrown when token refresh fails.\n */\nexport class TokenRefreshError extends AuthenticationError {\n /** Error code from the API */\n readonly code: string;\n\n constructor(code: string, message: string) {\n super(`${code}: ${message}`);\n this.name = \"TokenRefreshError\";\n this.code = code;\n }\n}\n\n/**\n * Error thrown when device approval fails.\n */\nexport class DeviceApprovalError extends AuthenticationError {\n /** HTTP status code if available */\n readonly statusCode: number | undefined;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = \"DeviceApprovalError\";\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Error thrown when fetching device information fails.\n */\nexport class DeviceInformationError extends AuthenticationError {\n /** HTTP status code if available */\n readonly statusCode: number | undefined;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = \"DeviceInformationError\";\n this.statusCode = statusCode;\n }\n}\n","import type { DocumentNode } from \"graphql\";\nimport { gql } from \"graphql-tag\";\n\nexport const START_AUTHENTICATION_FLOW: DocumentNode = gql`\n mutation StartAuthenticationFlow {\n startAuthenticationFlow {\n request {\n id\n userCode\n verificationUrl\n expiresAt\n }\n error {\n code\n message\n }\n }\n }\n`;\n\nexport const CREATED_AUTHENTICATION_TOKEN: DocumentNode = gql`\n subscription CreatedAuthenticationToken($requestId: ID!) {\n createdAuthenticationToken(requestId: $requestId) {\n token {\n accessToken\n refreshToken\n expiresAt\n }\n error {\n code\n message\n }\n }\n }\n`;\n\nexport const REFRESH_AUTHENTICATION_TOKEN: DocumentNode = gql`\n mutation RefreshAuthenticationToken($refreshToken: String!) {\n refreshAuthenticationToken(refreshToken: $refreshToken) {\n token {\n accessToken\n refreshToken\n expiresAt\n }\n error {\n code\n message\n }\n }\n }\n`;\n","import { Client, fetchExchange } from \"@urql/core\";\nimport { createClient as createWSClient } from \"graphql-ws\";\n\nimport type { AuthApprover } from \"./approvers/types.js\";\nimport {\n AuthenticationError,\n AuthenticationFlowError,\n TokenRefreshError,\n} from \"./errors.js\";\nimport {\n CREATED_AUTHENTICATION_TOKEN,\n REFRESH_AUTHENTICATION_TOKEN,\n START_AUTHENTICATION_FLOW,\n} from \"./queries.js\";\nimport type {\n AuthenticationRequest,\n AuthenticationToken,\n CreatedAuthenticationTokenResponse,\n RefreshAuthenticationTokenResponse,\n StartAuthenticationFlowResponse,\n} from \"./types.js\";\n\n/**\n * Client for authenticating with a Caido instance.\n *\n * @example\n * ```typescript\n * import { CaidoAuth, BrowserApprover } from \"@caido/auth\";\n *\n * const auth = new CaidoAuth(\n * \"http://localhost:8080\",\n * new BrowserApprover((request) => {\n * console.log(`Visit ${request.verificationUrl}`);\n * })\n * );\n *\n * const token = await auth.startAuthenticationFlow();\n * console.log(\"Access token:\", token.accessToken);\n * ```\n */\nexport class CaidoAuth {\n private readonly instanceUrl: string;\n private readonly graphqlUrl: string;\n private readonly websocketUrl: string;\n private readonly approver: AuthApprover;\n private readonly client: Client;\n\n /**\n * Create a new CaidoAuth client.\n *\n * @param instanceUrl - Base URL of the Caido instance (e.g., \"http://localhost:8080\")\n * @param approver - The approver to use for the authentication flow\n */\n constructor(instanceUrl: string, approver: AuthApprover) {\n this.instanceUrl = instanceUrl.replace(/\\/$/, \"\");\n this.graphqlUrl = `${this.instanceUrl}/graphql`;\n this.websocketUrl = this.getWebsocketUrl();\n this.approver = approver;\n\n this.client = new Client({\n url: this.graphqlUrl,\n exchanges: [fetchExchange],\n });\n }\n\n /**\n * Convert HTTP(S) URL to WS(S) URL for subscriptions.\n */\n private getWebsocketUrl(): string {\n const url = new URL(this.graphqlUrl);\n const scheme = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return `${scheme}//${url.host}/ws/graphql`;\n }\n\n /**\n * Start the device code authentication flow.\n *\n * This method:\n * 1. Initiates the authentication flow via GraphQL mutation\n * 2. Calls the approver with the authentication request\n * 3. Waits for the user to authorize via WebSocket subscription\n * 4. Returns the authentication token once approved\n *\n * @returns The authentication token\n * @throws {AuthenticationFlowError} If the flow fails to start\n * @throws {AuthenticationError} If token retrieval fails\n */\n async startAuthenticationFlow(): Promise<AuthenticationToken> {\n // Step 1: Start the authentication flow\n const result = await this.client\n .mutation<StartAuthenticationFlowResponse>(START_AUTHENTICATION_FLOW, {})\n .toPromise();\n\n if (result.error) {\n throw new AuthenticationFlowError(\"GRAPHQL_ERROR\", result.error.message);\n }\n\n const payload = result.data?.startAuthenticationFlow;\n if (!payload) {\n throw new AuthenticationFlowError(\n \"NO_RESPONSE\",\n \"No response from startAuthenticationFlow\",\n );\n }\n\n if (payload.error) {\n throw new AuthenticationFlowError(\n payload.error.code,\n payload.error.message,\n );\n }\n\n if (!payload.request) {\n throw new AuthenticationFlowError(\n \"NO_REQUEST\",\n \"No authentication request returned\",\n );\n }\n\n const authRequest: AuthenticationRequest = {\n id: payload.request.id,\n userCode: payload.request.userCode,\n verificationUrl: payload.request.verificationUrl,\n expiresAt: new Date(payload.request.expiresAt),\n };\n\n // Step 2: Call the approver\n await this.approver.approve(authRequest);\n\n // Step 3: Wait for the token via subscription\n const token = await this.waitForToken(authRequest.id);\n return token;\n }\n\n /**\n * Subscribe and wait for the authentication token.\n *\n * @param requestId - The authentication request ID\n * @returns The authentication token once the user authorizes\n * @throws {AuthenticationError} If subscription fails or returns an error\n */\n private async waitForToken(requestId: string): Promise<AuthenticationToken> {\n return new Promise<AuthenticationToken>((resolve, reject) => {\n const wsClient = createWSClient({\n url: this.websocketUrl,\n });\n\n const unsubscribe =\n wsClient.subscribe<CreatedAuthenticationTokenResponse>(\n {\n query:\n CREATED_AUTHENTICATION_TOKEN.loc?.source.body ??\n `subscription CreatedAuthenticationToken($requestId: ID!) {\n createdAuthenticationToken(requestId: $requestId) {\n token { accessToken refreshToken expiresAt }\n error { code message }\n }\n }`,\n variables: { requestId },\n },\n {\n next: (result) => {\n const payload = result.data?.createdAuthenticationToken;\n\n if (payload?.error) {\n unsubscribe();\n wsClient.dispose();\n reject(\n new AuthenticationError(\n `${payload.error.code}: ${payload.error.message}`,\n ),\n );\n return;\n }\n\n if (payload?.token) {\n unsubscribe();\n wsClient.dispose();\n resolve({\n accessToken: payload.token.accessToken,\n refreshToken: payload.token.refreshToken,\n expiresAt: new Date(payload.token.expiresAt),\n });\n }\n },\n error: (error) => {\n wsClient.dispose();\n reject(\n new AuthenticationError(\n error instanceof Error ? error.message : String(error),\n ),\n );\n },\n complete: () => {\n wsClient.dispose();\n reject(\n new AuthenticationError(\n \"Subscription ended without receiving token\",\n ),\n );\n },\n },\n );\n });\n }\n\n /**\n * Refresh an access token using a refresh token.\n *\n * @param refreshToken - The refresh token from a previous authentication\n * @returns New authentication token with updated access and refresh tokens\n * @throws {TokenRefreshError} If the refresh fails\n */\n async refreshToken(refreshToken: string): Promise<AuthenticationToken> {\n const result = await this.client\n .mutation<RefreshAuthenticationTokenResponse>(\n REFRESH_AUTHENTICATION_TOKEN,\n { refreshToken },\n )\n .toPromise();\n\n if (result.error) {\n throw new TokenRefreshError(\"GRAPHQL_ERROR\", result.error.message);\n }\n\n const payload = result.data?.refreshAuthenticationToken;\n if (!payload) {\n throw new TokenRefreshError(\n \"NO_RESPONSE\",\n \"No response from refreshAuthenticationToken\",\n );\n }\n\n if (payload.error) {\n throw new TokenRefreshError(payload.error.code, payload.error.message);\n }\n\n if (!payload.token) {\n throw new TokenRefreshError(\"NO_TOKEN\", \"No token returned from refresh\");\n }\n\n return {\n accessToken: payload.token.accessToken,\n refreshToken: payload.token.refreshToken,\n expiresAt: new Date(payload.token.expiresAt),\n };\n }\n}\n","import type { AuthenticationRequest } from \"../types.js\";\n\nimport type { AuthApprover } from \"./types.js\";\n\n/**\n * Callback function that receives the authentication request details.\n * Used to display the verification URL and user code to the user.\n */\nexport type OnRequestCallback = (\n request: AuthenticationRequest,\n) => Promise<void> | void;\n\n/**\n * Browser-based approver that delegates to a callback function.\n * The callback should display the verification URL and user code to the user,\n * who then manually approves the request in their browser.\n *\n * @example\n * ```typescript\n * const approver = new BrowserApprover((request) => {\n * console.log(`Visit ${request.verificationUrl}`);\n * });\n * ```\n */\nexport class BrowserApprover implements AuthApprover {\n private readonly onRequest: OnRequestCallback;\n\n /**\n * Create a new BrowserApprover.\n *\n * @param onRequest - Callback function that will be called with the authentication request\n */\n constructor(onRequest: OnRequestCallback) {\n this.onRequest = onRequest;\n }\n\n /**\n * Approve the authentication request by calling the callback.\n * The actual approval happens when the user visits the URL and enters the code.\n *\n * @param request - The authentication request\n */\n async approve(request: AuthenticationRequest): Promise<void> {\n await this.onRequest(request);\n }\n}\n","import { DeviceApprovalError, DeviceInformationError } from \"../errors.js\";\nimport type { AuthenticationRequest, DeviceInformation } from \"../types.js\";\n\nimport type { AuthApprover } from \"./types.js\";\n\nconst DEFAULT_API_URL = \"https://api.caido.io\";\n\n/**\n * Options for the PATApprover.\n */\nexport interface PATApproverOptions {\n /** The Personal Access Token to use for approval */\n pat: string;\n /** If provided, only approve these scopes. Others will be filtered out. */\n allowedScopes?: string[];\n /** The API URL to use. Defaults to \"https://api.caido.io\" */\n apiUrl?: string;\n}\n\n/**\n * PAT-based approver that automatically approves device code requests.\n * Uses a Personal Access Token to call the Caido API directly.\n *\n * @example\n * ```typescript\n * // Approve all scopes\n * const approver = new PATApprover({ pat: \"caido_xxxxx\" });\n *\n * // Approve only specific scopes\n * const limitedApprover = new PATApprover({\n * pat: \"caido_xxxxx\",\n * allowedScopes: [\"read:projects\", \"write:requests\"],\n * });\n * ```\n */\nexport class PATApprover implements AuthApprover {\n private readonly pat: string;\n private readonly allowedScopes: string[] | undefined;\n private readonly apiUrl: string;\n\n /**\n * Create a new PATApprover.\n *\n * @param options - Configuration options for the approver\n */\n constructor(options: PATApproverOptions) {\n this.pat = options.pat;\n this.allowedScopes = options.allowedScopes;\n this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\\/$/, \"\");\n }\n\n /**\n * Approve the authentication request using the PAT.\n * First fetches device information to get available scopes,\n * then filters scopes if allowedScopes is set,\n * and finally approves the device.\n *\n * @param request - The authentication request\n * @throws {DeviceInformationError} If fetching device information fails\n * @throws {DeviceApprovalError} If approving the device fails\n */\n async approve(request: AuthenticationRequest): Promise<void> {\n // Step 1: Get device information to retrieve available scopes\n const deviceInfo = await this.getDeviceInformation(request.userCode);\n\n // Step 2: Filter scopes if allowedScopes is provided\n let scopesToApprove = deviceInfo.scopes.map((s) => s.name);\n if (this.allowedScopes) {\n scopesToApprove = scopesToApprove.filter((scope) =>\n this.allowedScopes!.includes(scope),\n );\n }\n\n // Step 3: Approve the device with the filtered scopes\n await this.approveDevice(request.userCode, scopesToApprove);\n }\n\n /**\n * Fetch device information from the API.\n *\n * @param userCode - The user code from the authentication request\n * @returns The device information including available scopes\n * @throws {DeviceInformationError} If the request fails\n */\n private async getDeviceInformation(\n userCode: string,\n ): Promise<DeviceInformation> {\n const params = new URLSearchParams();\n params.append(\"user_code\", userCode);\n const url = new URL(`${this.apiUrl}/oauth2/device/information`);\n url.search = params.toString();\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.pat}`,\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n throw new DeviceInformationError(\n `Failed to get device information: ${errorText}`,\n response.status,\n );\n }\n\n const data = (await response.json()) as DeviceInformation;\n return data;\n }\n\n /**\n * Approve the device with the specified scopes.\n *\n * @param userCode - The user code from the authentication request\n * @param scopes - The scopes to approve\n * @throws {DeviceApprovalError} If the request fails\n */\n private async approveDevice(\n userCode: string,\n scopes: string[],\n ): Promise<void> {\n const params = new URLSearchParams();\n params.append(\"user_code\", userCode);\n params.append(\"scope\", scopes.join(\",\"));\n const url = new URL(`${this.apiUrl}/oauth2/device/approve`);\n url.search = params.toString();\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.pat}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n throw new DeviceApprovalError(\n `Failed to approve device: ${errorText}`,\n response.status,\n );\n }\n }\n}\n"],"mappings":";;;;;;;;AAGA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;AAOhB,IAAa,0BAAb,cAA6C,oBAAoB;;CAE/D,AAAS;CAET,YAAY,MAAc,SAAiB;AACzC,QAAM,GAAG,KAAK,IAAI,UAAU;AAC5B,OAAK,OAAO;AACZ,OAAK,OAAO;;;;;;AAOhB,IAAa,oBAAb,cAAuC,oBAAoB;;CAEzD,AAAS;CAET,YAAY,MAAc,SAAiB;AACzC,QAAM,GAAG,KAAK,IAAI,UAAU;AAC5B,OAAK,OAAO;AACZ,OAAK,OAAO;;;;;;AAOhB,IAAa,sBAAb,cAAyC,oBAAoB;;CAE3D,AAAS;CAET,YAAY,SAAiB,YAAqB;AAChD,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,aAAa;;;;;;AAOtB,IAAa,yBAAb,cAA4C,oBAAoB;;CAE9D,AAAS;CAET,YAAY,SAAiB,YAAqB;AAChD,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,aAAa;;;;;;AC3DtB,MAAa,4BAA0C,GAAG;;;;;;;;;;;;;;;;AAiB1D,MAAa,+BAA6C,GAAG;;;;;;;;;;;;;;;AAgB7D,MAAa,+BAA6C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACI7D,IAAa,YAAb,MAAuB;CACrB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;;;;;;;CAQjB,YAAY,aAAqB,UAAwB;AACvD,OAAK,cAAc,YAAY,QAAQ,OAAO,GAAG;AACjD,OAAK,aAAa,GAAG,KAAK,YAAY;AACtC,OAAK,eAAe,KAAK,iBAAiB;AAC1C,OAAK,WAAW;AAEhB,OAAK,SAAS,IAAI,OAAO;GACvB,KAAK,KAAK;GACV,WAAW,CAAC,cAAc;GAC3B,CAAC;;;;;CAMJ,AAAQ,kBAA0B;EAChC,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;AAEpC,SAAO,GADQ,IAAI,aAAa,WAAW,SAAS,MACnC,IAAI,IAAI,KAAK;;;;;;;;;;;;;;;CAgBhC,MAAM,0BAAwD;EAE5D,MAAM,SAAS,MAAM,KAAK,OACvB,SAA0C,2BAA2B,EAAE,CAAC,CACxE,WAAW;AAEd,MAAI,OAAO,MACT,OAAM,IAAI,wBAAwB,iBAAiB,OAAO,MAAM,QAAQ;EAG1E,MAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,CAAC,QACH,OAAM,IAAI,wBACR,eACA,2CACD;AAGH,MAAI,QAAQ,MACV,OAAM,IAAI,wBACR,QAAQ,MAAM,MACd,QAAQ,MAAM,QACf;AAGH,MAAI,CAAC,QAAQ,QACX,OAAM,IAAI,wBACR,cACA,qCACD;EAGH,MAAM,cAAqC;GACzC,IAAI,QAAQ,QAAQ;GACpB,UAAU,QAAQ,QAAQ;GAC1B,iBAAiB,QAAQ,QAAQ;GACjC,WAAW,IAAI,KAAK,QAAQ,QAAQ,UAAU;GAC/C;AAGD,QAAM,KAAK,SAAS,QAAQ,YAAY;AAIxC,SADc,MAAM,KAAK,aAAa,YAAY,GAAG;;;;;;;;;CAWvD,MAAc,aAAa,WAAiD;AAC1E,SAAO,IAAI,SAA8B,SAAS,WAAW;GAC3D,MAAM,WAAWA,aAAe,EAC9B,KAAK,KAAK,cACX,CAAC;GAEF,MAAM,cACJ,SAAS,UACP;IACE,OACE,6BAA6B,KAAK,OAAO,QACzC;;;;;;IAMF,WAAW,EAAE,WAAW;IACzB,EACD;IACE,OAAO,WAAW;KAChB,MAAM,UAAU,OAAO,MAAM;AAE7B,SAAI,SAAS,OAAO;AAClB,mBAAa;AACb,eAAS,SAAS;AAClB,aACE,IAAI,oBACF,GAAG,QAAQ,MAAM,KAAK,IAAI,QAAQ,MAAM,UACzC,CACF;AACD;;AAGF,SAAI,SAAS,OAAO;AAClB,mBAAa;AACb,eAAS,SAAS;AAClB,cAAQ;OACN,aAAa,QAAQ,MAAM;OAC3B,cAAc,QAAQ,MAAM;OAC5B,WAAW,IAAI,KAAK,QAAQ,MAAM,UAAU;OAC7C,CAAC;;;IAGN,QAAQ,UAAU;AAChB,cAAS,SAAS;AAClB,YACE,IAAI,oBACF,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD,CACF;;IAEH,gBAAgB;AACd,cAAS,SAAS;AAClB,YACE,IAAI,oBACF,6CACD,CACF;;IAEJ,CACF;IACH;;;;;;;;;CAUJ,MAAM,aAAa,cAAoD;EACrE,MAAM,SAAS,MAAM,KAAK,OACvB,SACC,8BACA,EAAE,cAAc,CACjB,CACA,WAAW;AAEd,MAAI,OAAO,MACT,OAAM,IAAI,kBAAkB,iBAAiB,OAAO,MAAM,QAAQ;EAGpE,MAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,CAAC,QACH,OAAM,IAAI,kBACR,eACA,8CACD;AAGH,MAAI,QAAQ,MACV,OAAM,IAAI,kBAAkB,QAAQ,MAAM,MAAM,QAAQ,MAAM,QAAQ;AAGxE,MAAI,CAAC,QAAQ,MACX,OAAM,IAAI,kBAAkB,YAAY,iCAAiC;AAG3E,SAAO;GACL,aAAa,QAAQ,MAAM;GAC3B,cAAc,QAAQ,MAAM;GAC5B,WAAW,IAAI,KAAK,QAAQ,MAAM,UAAU;GAC7C;;;;;;;;;;;;;;;;;;AC7NL,IAAa,kBAAb,MAAqD;CACnD,AAAiB;;;;;;CAOjB,YAAY,WAA8B;AACxC,OAAK,YAAY;;;;;;;;CASnB,MAAM,QAAQ,SAA+C;AAC3D,QAAM,KAAK,UAAU,QAAQ;;;;;;ACtCjC,MAAM,kBAAkB;;;;;;;;;;;;;;;;;AA8BxB,IAAa,cAAb,MAAiD;CAC/C,AAAiB;CACjB,AAAiB;CACjB,AAAiB;;;;;;CAOjB,YAAY,SAA6B;AACvC,OAAK,MAAM,QAAQ;AACnB,OAAK,gBAAgB,QAAQ;AAC7B,OAAK,UAAU,QAAQ,UAAU,iBAAiB,QAAQ,OAAO,GAAG;;;;;;;;;;;;CAatE,MAAM,QAAQ,SAA+C;EAK3D,IAAI,mBAHe,MAAM,KAAK,qBAAqB,QAAQ,SAAS,EAGnC,OAAO,KAAK,MAAM,EAAE,KAAK;AAC1D,MAAI,KAAK,cACP,mBAAkB,gBAAgB,QAAQ,UACxC,KAAK,cAAe,SAAS,MAAM,CACpC;AAIH,QAAM,KAAK,cAAc,QAAQ,UAAU,gBAAgB;;;;;;;;;CAU7D,MAAc,qBACZ,UAC4B;EAC5B,MAAM,SAAS,IAAI,iBAAiB;AACpC,SAAO,OAAO,aAAa,SAAS;EACpC,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,4BAA4B;AAC/D,MAAI,SAAS,OAAO,UAAU;EAE9B,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,eAAe,UAAU,KAAK;IAC9B,QAAQ;IACT;GACF,CAAC;AAEF,MAAI,CAAC,SAAS,GAEZ,OAAM,IAAI,uBACR,qCAFgB,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB,IAGlE,SAAS,OACV;AAIH,SADc,MAAM,SAAS,MAAM;;;;;;;;;CAWrC,MAAc,cACZ,UACA,QACe;EACf,MAAM,SAAS,IAAI,iBAAiB;AACpC,SAAO,OAAO,aAAa,SAAS;AACpC,SAAO,OAAO,SAAS,OAAO,KAAK,IAAI,CAAC;EACxC,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,wBAAwB;AAC3D,MAAI,SAAS,OAAO,UAAU;EAE9B,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,eAAe,UAAU,KAAK;IAC9B,gBAAgB;IAChB,QAAQ;IACT;GACF,CAAC;AAEF,MAAI,CAAC,SAAS,GAEZ,OAAM,IAAI,oBACR,6BAFgB,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB,IAGlE,SAAS,OACV"}
1
+ {"version":3,"file":"index.mjs","names":["createWSClient"],"sources":["../src/errors.ts","../src/queries.ts","../src/client.ts","../src/approvers/browser.ts","../src/approvers/pat.ts"],"sourcesContent":["/**\n * Base error class for authentication-related errors.\n */\nexport class AuthenticationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AuthenticationError\";\n }\n}\n\n/**\n * Error thrown for errors coming from the Caido cloud API.\n * Used for device approval and device information operations.\n */\nexport class CloudError extends AuthenticationError {\n /** HTTP status code if available */\n readonly statusCode: number | undefined;\n /** Error code from the API if available */\n readonly code: string | undefined;\n /** Reason for the error if available */\n readonly reason: string | undefined;\n\n constructor(\n message: string,\n options?: {\n statusCode?: number;\n code?: string;\n reason?: string;\n },\n ) {\n super(message);\n this.name = \"CloudError\";\n this.statusCode = options?.statusCode;\n this.code = options?.code;\n this.reason = options?.reason;\n }\n}\n\n/**\n * Error thrown for errors coming from the Caido instance.\n * Used for authentication flow and token refresh operations.\n */\nexport class InstanceError extends AuthenticationError {\n /** Error code from the API */\n readonly code: string;\n /** Reason for the error if available */\n readonly reason: string | undefined;\n /** Error message if available */\n readonly errorMessage: string | undefined;\n\n constructor(\n code: string,\n options?: {\n reason?: string;\n message?: string;\n },\n ) {\n const message = options?.reason ?? options?.message ?? code;\n super(message);\n this.name = \"InstanceError\";\n this.code = code;\n this.reason = options?.reason;\n this.errorMessage = options?.message;\n }\n}\n","import type { DocumentNode } from \"graphql\";\nimport { gql } from \"graphql-tag\";\n\nexport const START_AUTHENTICATION_FLOW: DocumentNode = gql`\n mutation StartAuthenticationFlow {\n startAuthenticationFlow {\n request {\n id\n userCode\n verificationUrl\n expiresAt\n }\n error {\n ... on AuthenticationUserError {\n code\n reason\n }\n ... on CloudUserError {\n code\n reason\n }\n ... on InternalUserError {\n code\n message\n }\n ... on OtherUserError {\n code\n }\n }\n }\n }\n`;\n\nexport const CREATED_AUTHENTICATION_TOKEN: DocumentNode = gql`\n subscription CreatedAuthenticationToken($requestId: ID!) {\n createdAuthenticationToken(requestId: $requestId) {\n token {\n accessToken\n expiresAt\n refreshToken\n scopes\n }\n error {\n ... on AuthenticationUserError {\n code\n reason\n }\n ... on InternalUserError {\n code\n message\n }\n ... on OtherUserError {\n code\n }\n }\n }\n }\n`;\n\nexport const REFRESH_AUTHENTICATION_TOKEN: DocumentNode = gql`\n mutation RefreshAuthenticationToken($refreshToken: Token!) {\n refreshAuthenticationToken(refreshToken: $refreshToken) {\n token {\n accessToken\n expiresAt\n refreshToken\n scopes\n }\n error {\n ... on AuthenticationUserError {\n code\n reason\n }\n ... on CloudUserError {\n code\n reason\n }\n ... on InternalUserError {\n code\n message\n }\n ... on OtherUserError {\n code\n }\n }\n }\n }\n`;\n","import { Client, fetchExchange } from \"@urql/core\";\nimport { print } from \"graphql\";\nimport { createClient as createWSClient } from \"graphql-ws\";\n\nimport type { AuthApprover } from \"./approvers/types.js\";\nimport { AuthenticationError, InstanceError } from \"./errors.js\";\nimport {\n CREATED_AUTHENTICATION_TOKEN,\n REFRESH_AUTHENTICATION_TOKEN,\n START_AUTHENTICATION_FLOW,\n} from \"./queries.js\";\nimport type {\n AuthenticationRequest,\n AuthenticationToken,\n CreatedAuthenticationTokenError,\n CreatedAuthenticationTokenResponse,\n RefreshAuthenticationTokenError,\n RefreshAuthenticationTokenResponse,\n StartAuthenticationFlowError,\n StartAuthenticationFlowResponse,\n} from \"./types.js\";\n\n/**\n * Options for configuring the AuthClient.\n */\nexport interface AuthClientOptions {\n /** Base URL of the Caido instance (e.g., \"http://localhost:8080\") */\n instanceUrl: string;\n /** The approver to use for the authentication flow */\n approver: AuthApprover;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Custom fetch implementation */\n fetch?: typeof globalThis.fetch;\n}\n\ninterface ErrorDetails {\n reason?: string;\n message?: string;\n}\n\n/**\n * Client for authenticating with a Caido instance.\n *\n * @example\n * ```typescript\n * import { AuthClient, BrowserApprover } from \"@caido/auth\";\n *\n * const auth = new AuthClient({\n * instanceUrl: \"http://localhost:8080\",\n * approver: new BrowserApprover((request) => {\n * console.log(`Visit ${request.verificationUrl}`);\n * })\n * });\n *\n * const token = await auth.startAuthenticationFlow();\n * console.log(\"Access token:\", token.accessToken);\n * ```\n */\nexport class AuthClient {\n private readonly instanceUrl: string;\n private readonly graphqlUrl: string;\n private readonly websocketUrl: string;\n private readonly approver: AuthApprover;\n private readonly client: Client;\n private readonly fetchFn: typeof globalThis.fetch | undefined;\n private readonly timeout: number | undefined;\n\n constructor(options: AuthClientOptions) {\n this.instanceUrl = options.instanceUrl.replace(/\\/$/, \"\");\n this.graphqlUrl = `${this.instanceUrl}/graphql`;\n this.websocketUrl = this.getWebsocketUrl();\n this.approver = options.approver;\n this.fetchFn = options.fetch;\n this.timeout = options.timeout;\n\n this.client = new Client({\n url: this.graphqlUrl,\n exchanges: [fetchExchange],\n fetchOptions: () => {\n const fetchOptions: RequestInit = {};\n if (this.timeout !== undefined) {\n fetchOptions.signal = AbortSignal.timeout(this.timeout);\n }\n return fetchOptions;\n },\n fetch: this.fetchFn,\n });\n }\n\n private getWebsocketUrl(): string {\n const url = new URL(this.graphqlUrl);\n const scheme = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return `${scheme}//${url.host}/ws/graphql`;\n }\n\n private extractErrorDetails(\n error:\n | StartAuthenticationFlowError\n | CreatedAuthenticationTokenError\n | RefreshAuthenticationTokenError,\n ): ErrorDetails {\n if (\"reason\" in error) {\n return { reason: error.reason };\n }\n if (\"message\" in error) {\n return { message: error.message };\n }\n return {};\n }\n\n /**\n * Start the device code authentication flow.\n *\n * This method:\n * 1. Initiates the authentication flow via GraphQL mutation\n * 2. Calls the approver with the authentication request\n * 3. Waits for the user to authorize via WebSocket subscription\n * 4. Returns the authentication token once approved\n *\n * @returns The authentication token\n * @throws {InstanceError} If the flow fails to start\n * @throws {AuthenticationError} If token retrieval fails\n */\n async startAuthenticationFlow(): Promise<AuthenticationToken> {\n // Step 1: Start the authentication flow\n const result = await this.client\n .mutation<StartAuthenticationFlowResponse>(START_AUTHENTICATION_FLOW, {})\n .toPromise();\n\n if (result.error) {\n throw new InstanceError(\"GRAPHQL_ERROR\", {\n message: result.error.message,\n });\n }\n\n const payload = result.data?.startAuthenticationFlow;\n if (!payload) {\n throw new InstanceError(\"NO_RESPONSE\", {\n message: \"No response from startAuthenticationFlow\",\n });\n }\n\n if (payload.error) {\n const details = this.extractErrorDetails(payload.error);\n throw new InstanceError(payload.error.code, details);\n }\n\n if (!payload.request) {\n throw new InstanceError(\"NO_REQUEST\", {\n message: \"No authentication request returned\",\n });\n }\n\n const authRequest: AuthenticationRequest = {\n id: payload.request.id,\n userCode: payload.request.userCode,\n verificationUrl: payload.request.verificationUrl,\n expiresAt: new Date(payload.request.expiresAt),\n };\n\n // Step 2: Call the approver\n await this.approver.approve(authRequest);\n\n // Step 3: Wait for the token via subscription\n const token = await this.waitForToken(authRequest.id);\n return token;\n }\n\n private async waitForToken(requestId: string): Promise<AuthenticationToken> {\n return new Promise<AuthenticationToken>((resolve, reject) => {\n const wsClient = createWSClient({\n url: this.websocketUrl,\n });\n\n const unsubscribe =\n wsClient.subscribe<CreatedAuthenticationTokenResponse>(\n {\n query: print(CREATED_AUTHENTICATION_TOKEN),\n variables: { requestId },\n },\n {\n next: (result) => {\n const payload = result.data?.createdAuthenticationToken;\n\n if (payload?.error) {\n unsubscribe();\n wsClient.dispose();\n const details = this.extractErrorDetails(payload.error);\n reject(new InstanceError(payload.error.code, details));\n return;\n }\n\n if (payload?.token) {\n unsubscribe();\n wsClient.dispose();\n resolve({\n accessToken: payload.token.accessToken,\n refreshToken: payload.token.refreshToken,\n expiresAt: new Date(payload.token.expiresAt),\n scopes: payload.token.scopes,\n });\n }\n },\n error: (error) => {\n wsClient.dispose();\n reject(\n new InstanceError(\"SUBSCRIPTION_ERROR\", {\n message:\n error instanceof Error ? error.message : String(error),\n }),\n );\n },\n complete: () => {\n wsClient.dispose();\n reject(\n new InstanceError(\"SUBSCRIPTION_COMPLETE\", {\n message: \"Subscription ended without receiving token\",\n }),\n );\n },\n },\n );\n });\n }\n\n /**\n * Refresh an access token using a refresh token.\n *\n * @param refreshToken - The refresh token from a previous authentication\n * @returns New authentication token with updated access and refresh tokens\n * @throws {InstanceError} If the refresh fails\n */\n async refreshToken(refreshToken: string): Promise<AuthenticationToken> {\n const result = await this.client\n .mutation<RefreshAuthenticationTokenResponse>(\n REFRESH_AUTHENTICATION_TOKEN,\n { refreshToken },\n )\n .toPromise();\n\n if (result.error) {\n throw new InstanceError(\"GRAPHQL_ERROR\", {\n message: result.error.message,\n });\n }\n\n const payload = result.data?.refreshAuthenticationToken;\n if (!payload) {\n throw new InstanceError(\"NO_RESPONSE\", {\n message: \"No response from refreshAuthenticationToken\",\n });\n }\n\n if (payload.error) {\n const details = this.extractErrorDetails(payload.error);\n throw new InstanceError(payload.error.code, details);\n }\n\n if (!payload.token) {\n throw new InstanceError(\"NO_TOKEN\", {\n message: \"No token returned from refresh\",\n });\n }\n\n return {\n accessToken: payload.token.accessToken,\n refreshToken: payload.token.refreshToken,\n expiresAt: new Date(payload.token.expiresAt),\n scopes: payload.token.scopes,\n };\n }\n}\n","import type { AuthenticationRequest } from \"../types.js\";\n\nimport type { AuthApprover } from \"./types.js\";\n\n/**\n * Callback function that receives the authentication request details.\n * Used to display the verification URL and user code to the user.\n */\nexport type OnRequestCallback = (\n request: AuthenticationRequest,\n) => Promise<void> | void;\n\n/**\n * Browser-based approver that delegates to a callback function.\n * The callback should display the verification URL and user code to the user,\n * who then manually approves the request in their browser.\n *\n * @example\n * ```typescript\n * const approver = new BrowserApprover((request) => {\n * console.log(`Visit ${request.verificationUrl}`);\n * });\n * ```\n */\nexport class BrowserApprover implements AuthApprover {\n private readonly onRequest: OnRequestCallback;\n\n /**\n * Create a new BrowserApprover.\n *\n * @param onRequest - Callback function that will be called with the authentication request\n */\n constructor(onRequest: OnRequestCallback) {\n this.onRequest = onRequest;\n }\n\n /**\n * Approve the authentication request by calling the callback.\n * The actual approval happens when the user visits the URL and enters the code.\n *\n * @param request - The authentication request\n */\n async approve(request: AuthenticationRequest): Promise<void> {\n await this.onRequest(request);\n }\n}\n","import { CloudError } from \"../errors.js\";\nimport type { AuthenticationRequest, DeviceInformation } from \"../types.js\";\n\nimport type { AuthApprover } from \"./types.js\";\n\nconst DEFAULT_API_URL = \"https://api.caido.io\";\n\ninterface OAuth2ErrorResponse {\n error?: string;\n error_description?: string;\n}\n\ntype OAuth2ErrorData = OAuth2ErrorResponse | string;\n\ninterface ParsedOAuth2Error {\n errorText: string;\n errorCode: string | undefined;\n errorDescription: string | undefined;\n}\n\ninterface RequestOptions {\n method: string;\n headers: Record<string, string>;\n}\n\n/**\n * Options for the PATApprover.\n */\nexport interface PATApproverOptions {\n /** The Personal Access Token to use for approval */\n pat: string;\n /** If provided, only approve these scopes. Others will be filtered out. */\n allowedScopes?: string[];\n /** The API URL to use. Defaults to \"https://api.caido.io\" */\n apiUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Custom fetch implementation */\n fetch?: typeof globalThis.fetch;\n}\n\n/**\n * PAT-based approver that automatically approves device code requests.\n * Uses a Personal Access Token to call the Caido API directly.\n *\n * @example\n * ```typescript\n * // Approve all scopes\n * const approver = new PATApprover({ pat: \"caido_xxxxx\" });\n *\n * // Approve only specific scopes\n * const limitedApprover = new PATApprover({\n * pat: \"caido_xxxxx\",\n * allowedScopes: [\"read:projects\", \"write:requests\"],\n * });\n * ```\n */\nexport class PATApprover implements AuthApprover {\n private readonly pat: string;\n private readonly allowedScopes: string[] | undefined;\n private readonly apiUrl: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly timeout: number | undefined;\n\n constructor(options: PATApproverOptions) {\n this.pat = options.pat;\n this.allowedScopes = options.allowedScopes;\n this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\\/$/, \"\");\n this.fetchFn = options.fetch ?? globalThis.fetch;\n this.timeout = options.timeout;\n }\n\n /**\n * Approve the authentication request using the PAT.\n * First fetches device information to get available scopes,\n * then filters scopes if allowedScopes is set,\n * and finally approves the device.\n *\n * @param request - The authentication request\n * @throws {CloudError} If fetching device information or approving the device fails\n */\n async approve(request: AuthenticationRequest): Promise<void> {\n // Step 1: Get device information to retrieve available scopes\n const deviceInfo = await this.getDeviceInformation(request.userCode);\n\n // Step 2: Filter scopes if allowedScopes is provided\n let scopesToApprove = deviceInfo.scopes.map((s) => s.name);\n if (this.allowedScopes) {\n scopesToApprove = scopesToApprove.filter((scope) =>\n this.allowedScopes!.includes(scope),\n );\n }\n\n // Step 3: Approve the device with the filtered scopes\n await this.approveDevice(request.userCode, scopesToApprove);\n }\n\n private async sendRequest(\n url: URL,\n options: RequestOptions,\n ): Promise<Response> {\n const fetchOptions: RequestInit = {\n method: options.method,\n headers: options.headers,\n };\n\n if (this.timeout !== undefined) {\n fetchOptions.signal = AbortSignal.timeout(this.timeout);\n }\n\n return this.fetchFn(url, fetchOptions);\n }\n\n private async parseOAuth2Error(\n response: Response,\n ): Promise<ParsedOAuth2Error> {\n let errorText: string;\n let errorCode: string | undefined;\n let errorDescription: string | undefined;\n\n try {\n const errorData = (await response.json()) as OAuth2ErrorData;\n if (typeof errorData === \"string\") {\n errorText = errorData;\n } else {\n errorCode = errorData.error;\n errorDescription = errorData.error_description;\n errorText = errorDescription ?? errorCode ?? \"Unknown error\";\n }\n } catch {\n errorText = await response.text().catch(() => \"Unknown error\");\n }\n\n return { errorText, errorCode, errorDescription };\n }\n\n private async getDeviceInformation(\n userCode: string,\n ): Promise<DeviceInformation> {\n const params = new URLSearchParams();\n params.append(\"user_code\", userCode);\n const url = new URL(`${this.apiUrl}/oauth2/device/information`);\n url.search = params.toString();\n\n const response = await this.sendRequest(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.pat}`,\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n const { errorText, errorCode, errorDescription } =\n await this.parseOAuth2Error(response);\n\n throw new CloudError(`Failed to get device information: ${errorText}`, {\n statusCode: response.status,\n code: errorCode,\n reason: errorDescription,\n });\n }\n\n const data = (await response.json()) as DeviceInformation;\n return data;\n }\n\n private async approveDevice(\n userCode: string,\n scopes: string[],\n ): Promise<void> {\n const params = new URLSearchParams();\n params.append(\"user_code\", userCode);\n params.append(\"scope\", scopes.join(\",\"));\n const url = new URL(`${this.apiUrl}/oauth2/device/approve`);\n url.search = params.toString();\n\n const response = await this.sendRequest(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.pat}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n const { errorText, errorCode, errorDescription } =\n await this.parseOAuth2Error(response);\n\n throw new CloudError(`Failed to approve device: ${errorText}`, {\n statusCode: response.status,\n code: errorCode,\n reason: errorDescription,\n });\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caido/server-auth",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Authenticate with a Caido instance",
5
5
  "author": "Caido Labs Inc. <dev@caido.io>",
6
6
  "repository": "https://github.com/caido/sdk-js/",
@@ -24,13 +24,14 @@
24
24
  "files": [
25
25
  "dist/*"
26
26
  ],
27
- "scripts": {
28
- "build": "tsdown"
29
- },
30
27
  "dependencies": {
31
28
  "@urql/core": "^6.0.0",
32
29
  "graphql": "^16.12.0",
33
30
  "graphql-tag": "^2.12.0",
34
31
  "graphql-ws": "^6.0.0"
32
+ },
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "typecheck": "tsc --noEmit"
35
36
  }
36
- }
37
+ }