@moxxy/plugin-oauth 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/openai-device-flow.d.ts +34 -0
  3. package/dist/adapters/openai-device-flow.d.ts.map +1 -0
  4. package/dist/adapters/openai-device-flow.js +136 -0
  5. package/dist/adapters/openai-device-flow.js.map +1 -0
  6. package/dist/adapters/rfc8628-device-flow.d.ts +20 -0
  7. package/dist/adapters/rfc8628-device-flow.d.ts.map +1 -0
  8. package/dist/adapters/rfc8628-device-flow.js +54 -0
  9. package/dist/adapters/rfc8628-device-flow.js.map +1 -0
  10. package/dist/credential-lock.d.ts +60 -0
  11. package/dist/credential-lock.d.ts.map +1 -0
  12. package/dist/credential-lock.js +149 -0
  13. package/dist/credential-lock.js.map +1 -0
  14. package/dist/ensure-fresh.d.ts +63 -0
  15. package/dist/ensure-fresh.d.ts.map +1 -0
  16. package/dist/ensure-fresh.js +122 -0
  17. package/dist/ensure-fresh.js.map +1 -0
  18. package/dist/flow.d.ts +6 -0
  19. package/dist/flow.d.ts.map +1 -0
  20. package/dist/flow.js +5 -0
  21. package/dist/flow.js.map +1 -0
  22. package/dist/index.d.ts +43 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +82 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/oauth/browser-flow.d.ts +23 -0
  27. package/dist/oauth/browser-flow.d.ts.map +1 -0
  28. package/dist/oauth/browser-flow.js +82 -0
  29. package/dist/oauth/browser-flow.js.map +1 -0
  30. package/dist/oauth/callback-server.d.ts +10 -0
  31. package/dist/oauth/callback-server.d.ts.map +1 -0
  32. package/dist/oauth/callback-server.js +158 -0
  33. package/dist/oauth/callback-server.js.map +1 -0
  34. package/dist/oauth/device-flow-shared.d.ts +39 -0
  35. package/dist/oauth/device-flow-shared.d.ts.map +1 -0
  36. package/dist/oauth/device-flow-shared.js +101 -0
  37. package/dist/oauth/device-flow-shared.js.map +1 -0
  38. package/dist/oauth/device-flow.d.ts +29 -0
  39. package/dist/oauth/device-flow.d.ts.map +1 -0
  40. package/dist/oauth/device-flow.js +74 -0
  41. package/dist/oauth/device-flow.js.map +1 -0
  42. package/dist/oauth/poll-until.d.ts +41 -0
  43. package/dist/oauth/poll-until.d.ts.map +1 -0
  44. package/dist/oauth/poll-until.js +70 -0
  45. package/dist/oauth/poll-until.js.map +1 -0
  46. package/dist/oauth/token-exchange.d.ts +28 -0
  47. package/dist/oauth/token-exchange.d.ts.map +1 -0
  48. package/dist/oauth/token-exchange.js +142 -0
  49. package/dist/oauth/token-exchange.js.map +1 -0
  50. package/dist/oauth/types.d.ts +90 -0
  51. package/dist/oauth/types.d.ts.map +1 -0
  52. package/dist/oauth/types.js +2 -0
  53. package/dist/oauth/types.js.map +1 -0
  54. package/dist/open-browser.d.ts +32 -0
  55. package/dist/open-browser.d.ts.map +1 -0
  56. package/dist/open-browser.js +83 -0
  57. package/dist/open-browser.js.map +1 -0
  58. package/dist/pkce.d.ts +20 -0
  59. package/dist/pkce.d.ts.map +1 -0
  60. package/dist/pkce.js +34 -0
  61. package/dist/pkce.js.map +1 -0
  62. package/dist/profile.d.ts +129 -0
  63. package/dist/profile.d.ts.map +1 -0
  64. package/dist/profile.js +11 -0
  65. package/dist/profile.js.map +1 -0
  66. package/dist/run-login.d.ts +9 -0
  67. package/dist/run-login.d.ts.map +1 -0
  68. package/dist/run-login.js +93 -0
  69. package/dist/run-login.js.map +1 -0
  70. package/dist/storage.d.ts +46 -0
  71. package/dist/storage.d.ts.map +1 -0
  72. package/dist/storage.js +167 -0
  73. package/dist/storage.js.map +1 -0
  74. package/dist/tools.d.ts +8 -0
  75. package/dist/tools.d.ts.map +1 -0
  76. package/dist/tools.js +283 -0
  77. package/dist/tools.js.map +1 -0
  78. package/package.json +73 -0
  79. package/skills/google-oauth.md +313 -0
  80. package/skills/oauth-flow.md +132 -0
  81. package/src/adapters/openai-device-flow.test.ts +191 -0
  82. package/src/adapters/openai-device-flow.ts +179 -0
  83. package/src/adapters/rfc8628-device-flow.ts +75 -0
  84. package/src/credential-lock.ts +183 -0
  85. package/src/discovery.test.ts +35 -0
  86. package/src/ensure-fresh.test.ts +279 -0
  87. package/src/ensure-fresh.ts +178 -0
  88. package/src/flow.ts +9 -0
  89. package/src/index.ts +153 -0
  90. package/src/oauth/browser-flow.ts +96 -0
  91. package/src/oauth/callback-server.test.ts +90 -0
  92. package/src/oauth/callback-server.ts +208 -0
  93. package/src/oauth/device-flow-shared.test.ts +142 -0
  94. package/src/oauth/device-flow-shared.ts +129 -0
  95. package/src/oauth/device-flow.test.ts +102 -0
  96. package/src/oauth/device-flow.ts +82 -0
  97. package/src/oauth/poll-until.test.ts +63 -0
  98. package/src/oauth/poll-until.ts +100 -0
  99. package/src/oauth/token-exchange.test.ts +224 -0
  100. package/src/oauth/token-exchange.ts +169 -0
  101. package/src/oauth/types.ts +92 -0
  102. package/src/oauth.test.ts +185 -0
  103. package/src/open-browser-spawn.test.ts +69 -0
  104. package/src/open-browser.test.ts +30 -0
  105. package/src/open-browser.ts +84 -0
  106. package/src/pkce.ts +37 -0
  107. package/src/profile.test.ts +331 -0
  108. package/src/profile.ts +135 -0
  109. package/src/run-login.ts +123 -0
  110. package/src/storage.test.ts +191 -0
  111. package/src/storage.ts +208 -0
  112. package/src/tools.test.ts +239 -0
  113. package/src/tools.ts +332 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow-shared.d.ts","sourceRoot":"","sources":["../../src/oauth/device-flow-shared.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyD/B;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACtD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC5B,WAAW,CAAC,QAAQ,CAAC,CA8BvB"}
@@ -0,0 +1,101 @@
1
+ import { classifyHttpStatus, MoxxyError } from '@moxxy/sdk';
2
+ import { parseTokenResponse } from './token-exchange.js';
3
+ /**
4
+ * POST the RFC 8628 device-authorization request and parse the response. The
5
+ * single home for the request + field parsing + error handling shared by the
6
+ * legacy `runDeviceCodeFlow` and the `rfc8628DeviceFlow` adapter.
7
+ */
8
+ export async function requestDeviceAuthorization(args) {
9
+ const body = new URLSearchParams();
10
+ body.set('client_id', args.clientId);
11
+ body.set('scope', args.scopes.join(' '));
12
+ const res = await fetch(args.deviceUrl, {
13
+ method: 'POST',
14
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
15
+ body: body.toString(),
16
+ ...(args.signal ? { signal: args.signal } : {}),
17
+ });
18
+ if (!res.ok) {
19
+ const text = await res.text().catch(() => '');
20
+ throw (classifyHttpStatus(res.status, { url: args.deviceUrl, body: text }) ??
21
+ new MoxxyError({
22
+ code: 'AUTH_INVALID',
23
+ message: `device-code request failed (HTTP ${res.status}): ${text.slice(0, 300)}`,
24
+ context: { status: res.status, url: args.deviceUrl },
25
+ }));
26
+ }
27
+ // A 200 with a non-JSON/truncated body (proxy/captive-portal error page)
28
+ // would make res.json() reject with a raw SyntaxError that escapes the
29
+ // MoxxyError boundary; convert it to a typed PROVIDER_UNKNOWN_RESPONSE.
30
+ const json = (await res.json().catch(() => {
31
+ throw new MoxxyError({
32
+ code: 'PROVIDER_UNKNOWN_RESPONSE',
33
+ message: 'device-authorization endpoint returned a non-JSON success body',
34
+ context: { url: args.deviceUrl },
35
+ });
36
+ }));
37
+ const deviceCode = typeof json.device_code === 'string' ? json.device_code : null;
38
+ const userCode = typeof json.user_code === 'string' ? json.user_code : null;
39
+ const verificationUri = typeof json.verification_uri === 'string'
40
+ ? json.verification_uri
41
+ : typeof json.verification_url === 'string'
42
+ ? json.verification_url
43
+ : null;
44
+ const verificationUriComplete = typeof json.verification_uri_complete === 'string' ? json.verification_uri_complete : undefined;
45
+ const expiresInSec = typeof json.expires_in === 'number' ? json.expires_in : 600;
46
+ const intervalSec = typeof json.interval === 'number' ? json.interval : 5;
47
+ if (!deviceCode || !userCode || !verificationUri) {
48
+ throw new MoxxyError({
49
+ code: 'PROVIDER_UNKNOWN_RESPONSE',
50
+ message: `device-code response missing required fields: ${JSON.stringify(json).slice(0, 200)}`,
51
+ });
52
+ }
53
+ return {
54
+ deviceCode,
55
+ userCode,
56
+ verificationUri,
57
+ ...(verificationUriComplete ? { verificationUriComplete } : {}),
58
+ expiresInSec,
59
+ intervalSec,
60
+ };
61
+ }
62
+ /**
63
+ * Classify a device-flow token-poll response per RFC 8628 §3.5: success →
64
+ * `done`; `authorization_pending` → keep waiting; `slow_down` → +5s and wait;
65
+ * `access_denied` / `expired_token` / other → throw a MoxxyError. Shared by both
66
+ * poll loops; each caller builds its own request body (the legacy flow appends
67
+ * client_id/client_secret) and hands the response + parsed JSON here.
68
+ */
69
+ export function classifyDeviceTokenResponse(res, json, state) {
70
+ if (res.ok && typeof json.access_token === 'string') {
71
+ return { done: parseTokenResponse(json) };
72
+ }
73
+ const err = typeof json.error === 'string' ? json.error : `HTTP ${res.status}`;
74
+ if (err === 'authorization_pending')
75
+ return { pending: true };
76
+ if (err === 'slow_down') {
77
+ state.intervalMs += 5000;
78
+ return { pending: true };
79
+ }
80
+ if (err === 'access_denied') {
81
+ throw new MoxxyError({
82
+ code: 'OAUTH_FLOW_DENIED',
83
+ message: 'You declined the device authorization.',
84
+ hint: 'Re-run the login command and approve the consent screen on your browser device.',
85
+ });
86
+ }
87
+ if (err === 'expired_token') {
88
+ throw new MoxxyError({
89
+ code: 'OAUTH_FLOW_TIMEOUT',
90
+ message: 'The device code expired before you finished signing in.',
91
+ hint: 'Re-run the login command — a new code will be generated.',
92
+ });
93
+ }
94
+ const desc = typeof json.error_description === 'string' ? json.error_description : '';
95
+ throw new MoxxyError({
96
+ code: 'AUTH_INVALID',
97
+ message: `OAuth device flow failed: ${err}${desc ? ` — ${desc}` : ''}.`,
98
+ context: { provider_error: String(err), ...(desc ? { description: desc } : {}) },
99
+ });
100
+ }
101
+ //# sourceMappingURL=device-flow-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow-shared.js","sourceRoot":"","sources":["../../src/oauth/device-flow-shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { DeviceFlowOptions, TokenSet } from './types.js';
2
+ /**
3
+ * Run the RFC 8628 device-authorization flow. Suitable for headless
4
+ * environments (SSH session, CI, kiosk, no display): the user opens
5
+ * the verification URL on any device, types the short user_code,
6
+ * approves the scopes, and the local process discovers the grant by
7
+ * polling the token endpoint.
8
+ *
9
+ * Phases:
10
+ * 1. POST `client_id` + `scope` to deviceUrl → returns user_code,
11
+ * verification_uri, device_code, expires_in, interval.
12
+ * 2. `onPrompt` fires once with the user-facing pieces — the caller
13
+ * surfaces them in whatever UI it has.
14
+ * 3. Poll tokenUrl every `interval` seconds with
15
+ * grant_type=urn:ietf:params:oauth:grant-type:device_code +
16
+ * device_code. The provider replies:
17
+ * - authorization_pending → keep polling.
18
+ * - slow_down → bump interval by 5s and keep polling.
19
+ * - access_denied → user clicked deny; throw.
20
+ * - expired_token → device_code expired; throw.
21
+ * - access_token, ... → success; return TokenSet.
22
+ *
23
+ * The device-authorization request + the poll-response classification are
24
+ * shared with the {@link rfc8628DeviceFlow} adapter via `device-flow-shared`.
25
+ * This flow additionally sends `client_id` (+ optional `client_secret`) on the
26
+ * poll, which is why it builds its own poll body.
27
+ */
28
+ export declare function runDeviceCodeFlow(opts: DeviceFlowOptions): Promise<TokenSet>;
29
+ //# sourceMappingURL=device-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../../src/oauth/device-flow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAU9D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuBlF"}
@@ -0,0 +1,74 @@
1
+ import { pollUntil } from './poll-until.js';
2
+ import { classifyDeviceTokenResponse, requestDeviceAuthorization } from './device-flow-shared.js';
3
+ /**
4
+ * Small margin added to the provider's interval before the first/each poll, so
5
+ * a server that enforces the interval strictly doesn't answer edge polls with
6
+ * `slow_down`. Mirrors `run-login.ts`'s margin so both device-flow drivers
7
+ * behave identically.
8
+ */
9
+ const DEVICE_POLL_SAFETY_MARGIN_MS = 3000;
10
+ /**
11
+ * Run the RFC 8628 device-authorization flow. Suitable for headless
12
+ * environments (SSH session, CI, kiosk, no display): the user opens
13
+ * the verification URL on any device, types the short user_code,
14
+ * approves the scopes, and the local process discovers the grant by
15
+ * polling the token endpoint.
16
+ *
17
+ * Phases:
18
+ * 1. POST `client_id` + `scope` to deviceUrl → returns user_code,
19
+ * verification_uri, device_code, expires_in, interval.
20
+ * 2. `onPrompt` fires once with the user-facing pieces — the caller
21
+ * surfaces them in whatever UI it has.
22
+ * 3. Poll tokenUrl every `interval` seconds with
23
+ * grant_type=urn:ietf:params:oauth:grant-type:device_code +
24
+ * device_code. The provider replies:
25
+ * - authorization_pending → keep polling.
26
+ * - slow_down → bump interval by 5s and keep polling.
27
+ * - access_denied → user clicked deny; throw.
28
+ * - expired_token → device_code expired; throw.
29
+ * - access_token, ... → success; return TokenSet.
30
+ *
31
+ * The device-authorization request + the poll-response classification are
32
+ * shared with the {@link rfc8628DeviceFlow} adapter via `device-flow-shared`.
33
+ * This flow additionally sends `client_id` (+ optional `client_secret`) on the
34
+ * poll, which is why it builds its own poll body.
35
+ */
36
+ export async function runDeviceCodeFlow(opts) {
37
+ const auth = await requestDeviceAuthorization({
38
+ deviceUrl: opts.deviceUrl,
39
+ clientId: opts.clientId,
40
+ scopes: opts.scopes,
41
+ ...(opts.signal ? { signal: opts.signal } : {}),
42
+ });
43
+ opts.onPrompt({
44
+ userCode: auth.userCode,
45
+ verificationUri: auth.verificationUri,
46
+ ...(auth.verificationUriComplete ? { verificationUriComplete: auth.verificationUriComplete } : {}),
47
+ expiresIn: auth.expiresInSec,
48
+ interval: auth.intervalSec,
49
+ });
50
+ return pollUntil((state) => pollOnce(opts, auth.deviceCode, state), {
51
+ intervalMs: auth.intervalSec * 1000 + DEVICE_POLL_SAFETY_MARGIN_MS,
52
+ timeoutMs: Math.min(opts.timeoutMs ?? auth.expiresInSec * 1000, auth.expiresInSec * 1000),
53
+ label: 'OAuth device flow',
54
+ leadingWait: true,
55
+ ...(opts.signal ? { signal: opts.signal } : {}),
56
+ });
57
+ }
58
+ async function pollOnce(opts, deviceCode, state) {
59
+ const body = new URLSearchParams();
60
+ body.set('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
61
+ body.set('device_code', deviceCode);
62
+ body.set('client_id', opts.clientId);
63
+ if (opts.clientSecret)
64
+ body.set('client_secret', opts.clientSecret);
65
+ const pollRes = await fetch(opts.tokenUrl, {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
68
+ body: body.toString(),
69
+ ...(state.signal ? { signal: state.signal } : {}),
70
+ });
71
+ const pollJson = (await pollRes.json().catch(() => ({})));
72
+ return classifyDeviceTokenResponse(pollRes, pollJson, state);
73
+ }
74
+ //# sourceMappingURL=device-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.js","sourceRoot":"","sources":["../../src/oauth/device-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared polling primitive for OAuth-style "ask again later" modes. Handles
3
+ * the gnarly bits — deadline math, abort-responsive sleep, interval bumps
4
+ * for `slow_down`-style backpressure — so each device-flow dialect only has
5
+ * to encode its HTTP shape, not the timing harness.
6
+ *
7
+ * Consumed by both `runDeviceCodeFlow` (RFC 8628) and the Codex device flow
8
+ * (non-standard OpenAI endpoints). The polling fn returns `{done}` to finish
9
+ * or `{pending}` to keep going, and may mutate `state.intervalMs` mid-flight
10
+ * to apply a `slow_down` bump.
11
+ */
12
+ export interface PollState {
13
+ /** Mutable so the polling fn can bump on `slow_down`. */
14
+ intervalMs: number;
15
+ /**
16
+ * The flow's abort signal, threaded through so a polling fn can pass it
17
+ * into its in-flight fetch and cancel a hung request — not just the
18
+ * inter-poll sleep. Undefined when the caller supplied no signal.
19
+ */
20
+ readonly signal?: AbortSignal;
21
+ }
22
+ export type PollOutcome<T> = {
23
+ done: T;
24
+ } | {
25
+ pending: true;
26
+ };
27
+ export interface PollUntilOpts {
28
+ readonly intervalMs: number;
29
+ readonly timeoutMs: number;
30
+ readonly signal?: AbortSignal;
31
+ /**
32
+ * Wait BEFORE the first call. RFC 8628 says clients SHOULD wait `interval`
33
+ * before the first poll; some flows (e.g. Codex) poll immediately. Default
34
+ * true to match the more conservative RFC behavior.
35
+ */
36
+ readonly leadingWait?: boolean;
37
+ /** Used in timeout / abort error messages. */
38
+ readonly label?: string;
39
+ }
40
+ export declare function pollUntil<T>(fn: (state: PollState) => Promise<PollOutcome<T>>, opts: PollUntilOpts): Promise<T>;
41
+ //# sourceMappingURL=poll-until.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll-until.d.ts","sourceRoot":"","sources":["../../src/oauth/poll-until.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EACjD,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,CAAC,CAAC,CA0BZ"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared polling primitive for OAuth-style "ask again later" modes. Handles
3
+ * the gnarly bits — deadline math, abort-responsive sleep, interval bumps
4
+ * for `slow_down`-style backpressure — so each device-flow dialect only has
5
+ * to encode its HTTP shape, not the timing harness.
6
+ *
7
+ * Consumed by both `runDeviceCodeFlow` (RFC 8628) and the Codex device flow
8
+ * (non-standard OpenAI endpoints). The polling fn returns `{done}` to finish
9
+ * or `{pending}` to keep going, and may mutate `state.intervalMs` mid-flight
10
+ * to apply a `slow_down` bump.
11
+ */
12
+ import { MoxxyError } from '@moxxy/sdk';
13
+ export async function pollUntil(fn, opts) {
14
+ const state = {
15
+ intervalMs: opts.intervalMs,
16
+ ...(opts.signal ? { signal: opts.signal } : {}),
17
+ };
18
+ const leadingWait = opts.leadingWait ?? true;
19
+ const deadline = Date.now() + opts.timeoutMs;
20
+ const label = opts.label ?? 'poll';
21
+ let first = true;
22
+ while (Date.now() < deadline) {
23
+ if (opts.signal?.aborted)
24
+ throw abortedError(label);
25
+ if (!first || leadingWait) {
26
+ const remaining = deadline - Date.now();
27
+ if (remaining <= 0)
28
+ break;
29
+ await sleep(Math.min(state.intervalMs, remaining), opts.signal, label);
30
+ }
31
+ first = false;
32
+ const result = await fn(state);
33
+ if ('done' in result)
34
+ return result.done;
35
+ }
36
+ throw new MoxxyError({
37
+ code: 'OAUTH_FLOW_TIMEOUT',
38
+ message: `${label} timed out waiting for completion`,
39
+ context: { label, timeout_ms: opts.timeoutMs },
40
+ });
41
+ }
42
+ function abortedError(label) {
43
+ return new MoxxyError({
44
+ code: 'NETWORK_ABORTED',
45
+ message: `${label} aborted`,
46
+ context: { label },
47
+ });
48
+ }
49
+ function sleep(ms, signal, label) {
50
+ return new Promise((resolve, reject) => {
51
+ if (signal?.aborted)
52
+ return reject(abortedError(label));
53
+ // `{ once: true }` only auto-removes a listener that actually FIRES; on the
54
+ // timer-resolved path it would linger. pollUntil calls sleep() once per
55
+ // poll cycle against the SAME long-lived signal, so an unremoved listener
56
+ // accumulates one-per-iteration (~120 over a 10-min device flow), tripping
57
+ // MaxListenersExceededWarning and retaining each closure. Remove on BOTH
58
+ // exits so it's exactly one add / one remove per sleep.
59
+ const onAbort = () => {
60
+ clearTimeout(t);
61
+ reject(abortedError(label));
62
+ };
63
+ const t = setTimeout(() => {
64
+ signal?.removeEventListener('abort', onAbort);
65
+ resolve();
66
+ }, ms);
67
+ signal?.addEventListener('abort', onAbort, { once: true });
68
+ });
69
+ }
70
+ //# sourceMappingURL=poll-until.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll-until.js","sourceRoot":"","sources":["../../src/oauth/poll-until.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA6BxC,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAiD,EACjD,IAAmB;IAEnB,MAAM,KAAK,GAAc;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;IACF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IAEnC,IAAI,KAAK,GAAG,IAAI,CAAC;IACjB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO;YAAE,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK,IAAI,WAAW,EAAE,CAAC;YAC1B,MAAM,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACxC,IAAI,SAAS,IAAI,CAAC;gBAAE,MAAM;YAC1B,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzE,CAAC;QACD,KAAK,GAAG,KAAK,CAAC;QACd,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IAC3C,CAAC;IACD,MAAM,IAAI,UAAU,CAAC;QACnB,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,GAAG,KAAK,mCAAmC;QACpD,OAAO,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,SAAS,EAAE;KAC/C,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,IAAI,UAAU,CAAC;QACpB,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,GAAG,KAAK,UAAU;QAC3B,OAAO,EAAE,EAAE,KAAK,EAAE;KACnB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,KAAK,CAAC,EAAU,EAAE,MAA+B,EAAE,KAAa;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,EAAE,OAAO;YAAE,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QACxD,4EAA4E;QAC5E,wEAAwE;QACxE,0EAA0E;QAC1E,2EAA2E;QAC3E,yEAAyE;QACzE,wDAAwD;QACxD,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC;QACF,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE;YACxB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { TokenSet } from './types.js';
2
+ interface ExchangeCodeInput {
3
+ readonly tokenUrl: string;
4
+ readonly code: string;
5
+ readonly redirectUri: string;
6
+ readonly clientId: string;
7
+ readonly clientSecret?: string;
8
+ readonly codeVerifier: string;
9
+ /** Abort the exchange fetch when the caller's flow is cancelled. */
10
+ readonly signal?: AbortSignal;
11
+ }
12
+ export declare function exchangeCodeForToken(input: ExchangeCodeInput, fetchImpl?: typeof fetch): Promise<TokenSet>;
13
+ /**
14
+ * Refresh an access token using a stored refresh_token. Same token
15
+ * endpoint, different grant_type. Returns a new TokenSet — note that
16
+ * providers MAY or MAY NOT rotate the refresh_token (Google does
17
+ * not; Auth0 with rotation does). Caller should preserve the prior
18
+ * refresh_token if the response omits one.
19
+ */
20
+ export declare function refreshAccessToken(input: {
21
+ readonly tokenUrl: string;
22
+ readonly clientId: string;
23
+ readonly clientSecret?: string;
24
+ readonly refreshToken: string;
25
+ }, fetchImpl?: typeof fetch): Promise<TokenSet>;
26
+ export declare function parseTokenResponse(json: Record<string, unknown>): TokenSet;
27
+ export {};
28
+ //# sourceMappingURL=token-exchange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-exchange.d.ts","sourceRoot":"","sources":["../../src/oauth/token-exchange.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,UAAU,iBAAiB;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,iBAAiB,EACxB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA2BnB;AA2DD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE;IACL,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,EACD,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA2BnB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAqB1E"}
@@ -0,0 +1,142 @@
1
+ import { classifyHttpStatus, MoxxyError } from '@moxxy/sdk';
2
+ export async function exchangeCodeForToken(input, fetchImpl = fetch) {
3
+ const body = new URLSearchParams();
4
+ body.set('grant_type', 'authorization_code');
5
+ body.set('code', input.code);
6
+ body.set('redirect_uri', input.redirectUri);
7
+ body.set('client_id', input.clientId);
8
+ body.set('code_verifier', input.codeVerifier);
9
+ if (input.clientSecret)
10
+ body.set('client_secret', input.clientSecret);
11
+ const res = await fetchImpl(input.tokenUrl, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
14
+ body: body.toString(),
15
+ ...(input.signal ? { signal: input.signal } : {}),
16
+ });
17
+ if (!res.ok) {
18
+ const text = await res.text().catch(() => '');
19
+ throw (classifyHttpStatus(res.status, { url: input.tokenUrl, body: text }) ??
20
+ new MoxxyError({
21
+ code: 'AUTH_INVALID',
22
+ message: `Token exchange failed (HTTP ${res.status})${errorSummary(text)}`,
23
+ context: { status: res.status, url: input.tokenUrl, ...bodyContext(text) },
24
+ }));
25
+ }
26
+ const json = await parseJsonBody(res, input.tokenUrl);
27
+ return parseTokenResponse(json);
28
+ }
29
+ /**
30
+ * Build a human-message suffix from a token-endpoint error body. Token
31
+ * endpoints return `{error, error_description}` (RFC 6749 §5.2); prefer those
32
+ * structured fields verbatim. For an opaque/HTML body (proxy/captive-portal
33
+ * error page) — provider/attacker-influenced text that flows to logs and can
34
+ * reach the model — emit nothing in the human message; the bounded raw body
35
+ * still lands in `context` via {@link bodyContext}.
36
+ */
37
+ function errorSummary(body) {
38
+ const fields = parseOauthError(body);
39
+ if (!fields)
40
+ return '';
41
+ const { error, error_description } = fields;
42
+ const detail = error_description ?? error;
43
+ return detail ? `: ${detail.slice(0, 300)}` : '';
44
+ }
45
+ /** Truncated raw body for the error context only (never the human message). */
46
+ function bodyContext(body) {
47
+ return body ? { body: body.slice(0, 200) } : {};
48
+ }
49
+ function parseOauthError(body) {
50
+ if (!body)
51
+ return null;
52
+ try {
53
+ const parsed = JSON.parse(body);
54
+ if (!parsed || typeof parsed !== 'object')
55
+ return null;
56
+ const obj = parsed;
57
+ const error = typeof obj.error === 'string' ? obj.error : undefined;
58
+ const error_description = typeof obj.error_description === 'string' ? obj.error_description : undefined;
59
+ if (!error && !error_description)
60
+ return null;
61
+ return {
62
+ ...(error !== undefined ? { error } : {}),
63
+ ...(error_description !== undefined ? { error_description } : {}),
64
+ };
65
+ }
66
+ catch {
67
+ return null; // opaque / HTML body — don't reflect it into the message
68
+ }
69
+ }
70
+ /**
71
+ * Parse a 2xx token-endpoint body as JSON. A provider (or an intercepting
72
+ * proxy / captive portal / load-balancer error page) can return HTTP 200 with
73
+ * a non-JSON or truncated body; an unguarded `res.json()` would reject with a
74
+ * native SyntaxError that escapes the MoxxyError boundary (no code/hint, and
75
+ * `isAuthRejection` can't classify it). Convert it to a typed error instead.
76
+ */
77
+ async function parseJsonBody(res, url) {
78
+ return (await res.json().catch(() => {
79
+ throw new MoxxyError({
80
+ code: 'PROVIDER_UNKNOWN_RESPONSE',
81
+ message: 'token endpoint returned a non-JSON success body',
82
+ context: { url },
83
+ });
84
+ }));
85
+ }
86
+ /**
87
+ * Refresh an access token using a stored refresh_token. Same token
88
+ * endpoint, different grant_type. Returns a new TokenSet — note that
89
+ * providers MAY or MAY NOT rotate the refresh_token (Google does
90
+ * not; Auth0 with rotation does). Caller should preserve the prior
91
+ * refresh_token if the response omits one.
92
+ */
93
+ export async function refreshAccessToken(input, fetchImpl = fetch) {
94
+ const body = new URLSearchParams();
95
+ body.set('grant_type', 'refresh_token');
96
+ body.set('refresh_token', input.refreshToken);
97
+ body.set('client_id', input.clientId);
98
+ if (input.clientSecret)
99
+ body.set('client_secret', input.clientSecret);
100
+ const res = await fetchImpl(input.tokenUrl, {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
103
+ body: body.toString(),
104
+ });
105
+ if (!res.ok) {
106
+ const text = await res.text().catch(() => '');
107
+ // 401/403 here typically means the refresh_token was revoked/expired —
108
+ // classifyHttpStatus maps those to AUTH_INVALID/AUTH_DENIED. Surface a
109
+ // refresh-specific fallback for the unmapped statuses.
110
+ throw (classifyHttpStatus(res.status, { url: input.tokenUrl, body: text }) ??
111
+ new MoxxyError({
112
+ code: 'AUTH_EXPIRED',
113
+ message: `Token refresh failed (HTTP ${res.status})${errorSummary(text)}`,
114
+ context: { status: res.status, url: input.tokenUrl, ...bodyContext(text) },
115
+ }));
116
+ }
117
+ const json = await parseJsonBody(res, input.tokenUrl);
118
+ return parseTokenResponse(json);
119
+ }
120
+ export function parseTokenResponse(json) {
121
+ const access = typeof json.access_token === 'string' ? json.access_token : null;
122
+ if (!access) {
123
+ throw new MoxxyError({
124
+ code: 'PROVIDER_UNKNOWN_RESPONSE',
125
+ message: `token response missing access_token: ${JSON.stringify(json).slice(0, 200)}`,
126
+ });
127
+ }
128
+ const refresh = typeof json.refresh_token === 'string' ? json.refresh_token : undefined;
129
+ const expiresIn = typeof json.expires_in === 'number' ? json.expires_in : null;
130
+ const scope = typeof json.scope === 'string' ? json.scope : undefined;
131
+ const tokenType = typeof json.token_type === 'string' ? json.token_type : 'Bearer';
132
+ const idToken = typeof json.id_token === 'string' ? json.id_token : undefined;
133
+ return {
134
+ accessToken: access,
135
+ ...(refresh !== undefined ? { refreshToken: refresh } : {}),
136
+ ...(expiresIn != null ? { expiresAt: Date.now() + expiresIn * 1000 } : {}),
137
+ ...(scope !== undefined ? { scope } : {}),
138
+ tokenType,
139
+ ...(idToken !== undefined ? { idToken } : {}),
140
+ };
141
+ }
142
+ //# sourceMappingURL=token-exchange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-exchange.js","sourceRoot":"","sources":["../../src/oauth/token-exchange.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ export interface OAuthFlowOptions {
2
+ /** Provider's authorization endpoint, e.g. https://accounts.google.com/o/oauth2/v2/auth */
3
+ readonly authUrl: string;
4
+ /** Provider's token endpoint, e.g. https://oauth2.googleapis.com/token */
5
+ readonly tokenUrl: string;
6
+ readonly clientId: string;
7
+ /** Confidential clients only; loopback flows usually omit. */
8
+ readonly clientSecret?: string;
9
+ readonly scopes: ReadonlyArray<string>;
10
+ /**
11
+ * Loopback redirect port. The redirect URI MUST be registered with
12
+ * the provider exactly (most providers require an exact match) — pick
13
+ * a value and tell the user to register it. Default 8765 to keep the
14
+ * Google Cloud Console setup deterministic across sessions.
15
+ */
16
+ readonly redirectPort?: number;
17
+ /**
18
+ * Loopback redirect path. Default `/callback`. Some providers reject
19
+ * paths other than `/`; tweak per provider. The full registered
20
+ * redirect URI must be `http://localhost:<port><path>` exactly.
21
+ */
22
+ readonly redirectPath?: string;
23
+ /**
24
+ * Provider-specific extra query parameters on the auth URL. Common
25
+ * cases: `access_type=offline` + `prompt=consent` for Google (forces
26
+ * the issuance of a refresh_token); `audience=...` for Auth0.
27
+ */
28
+ readonly extraAuthParams?: Readonly<Record<string, string>>;
29
+ /** How long to wait for the callback before giving up. Default 5min. */
30
+ readonly timeoutMs?: number;
31
+ /**
32
+ * Abort signal — when fired, shuts the local server and rejects the
33
+ * flow. Wire from `ctx.signal` so a turn cancel kills a pending auth.
34
+ */
35
+ readonly signal?: AbortSignal;
36
+ /**
37
+ * When true, do NOT auto-open the browser. The auth URL still
38
+ * gets handed to `onAuthUrl`; the caller is expected to print it
39
+ * for the user to visit on a host where the loopback callback is
40
+ * reachable (same machine, SSH tunnel, port-forward).
41
+ */
42
+ readonly noOpen?: boolean;
43
+ /**
44
+ * Fires with the built auth URL just before the browser-open step.
45
+ * Use to log / display the URL so the user sees it even when
46
+ * auto-open fails or `noOpen` is set.
47
+ */
48
+ readonly onAuthUrl?: (url: string) => void;
49
+ }
50
+ export interface DeviceFlowOptions {
51
+ /** Provider's device-authorization endpoint (RFC 8628 §3.1). */
52
+ readonly deviceUrl: string;
53
+ readonly tokenUrl: string;
54
+ readonly clientId: string;
55
+ readonly clientSecret?: string;
56
+ readonly scopes: ReadonlyArray<string>;
57
+ /** Hard cap; the device-code's own expires_in usually drives the timer. */
58
+ readonly timeoutMs?: number;
59
+ readonly signal?: AbortSignal;
60
+ /**
61
+ * Fired ONCE with the user-facing prompt info as soon as the
62
+ * device endpoint returns. Channels should surface this prominently
63
+ * — the whole flow stalls until the user finishes on the URL.
64
+ */
65
+ readonly onPrompt: (info: DevicePrompt) => void;
66
+ }
67
+ export interface DevicePrompt {
68
+ /** Short code the user types into the verification page. */
69
+ readonly userCode: string;
70
+ /** URL the user opens on any device. */
71
+ readonly verificationUri: string;
72
+ /** Some providers return a URL that already includes the user_code. */
73
+ readonly verificationUriComplete?: string;
74
+ /** Seconds until the device_code expires (from the provider). */
75
+ readonly expiresIn: number;
76
+ /** Poll interval the provider wants us to use, in seconds. */
77
+ readonly interval: number;
78
+ }
79
+ export interface TokenSet {
80
+ readonly accessToken: string;
81
+ readonly refreshToken?: string;
82
+ /** Epoch ms when the access_token expires. */
83
+ readonly expiresAt?: number;
84
+ /** Granted scopes — provider may grant less than requested. */
85
+ readonly scope?: string;
86
+ readonly tokenType: string;
87
+ /** OIDC id_token if the provider returned one (Google does for `openid` scope). */
88
+ readonly idToken?: string;
89
+ }
90
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/oauth/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,uEAAuE;IACvE,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/oauth/types.ts"],"names":[],"mappings":""}