@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,122 @@
1
+ /**
2
+ * Pre-request token freshness helper. Reads stored creds, refreshes when
3
+ * within `skewMs` of expiry (or forced), persists the rotated tokens BEFORE
4
+ * returning so a crash mid-flight can't strand a single-use refresh_token.
5
+ *
6
+ * The refresh + persist critical section runs under the per-credential lock
7
+ * (`withCredentialLock`): concurrent consumers — in this process or another
8
+ * moxxy process — coalesce into a single IdP refresh, with the followers
9
+ * re-reading the winner's rotated tokens from the vault instead of burning
10
+ * the (single-use, rotating) refresh_token a second time.
11
+ *
12
+ * Returns the live TokenSet + extras the provider can use to build headers
13
+ * (e.g. ChatGPT-Account-Id). Throws when no credential is stored or the
14
+ * refresh fails permanently.
15
+ */
16
+ import { MoxxyError, classifyNetworkError } from '@moxxy/sdk';
17
+ import { isExpired, readStoredCreds, storeTokenSet } from './storage.js';
18
+ import { refreshAccessToken } from './oauth/token-exchange.js';
19
+ import { isAuthRejection, withCredentialLock } from './credential-lock.js';
20
+ export async function refreshAndStore(vault, spec, stored, retried = false) {
21
+ if (!stored.tokenSet.refreshToken) {
22
+ throw spec.noRefreshTokenError();
23
+ }
24
+ let refreshed;
25
+ try {
26
+ refreshed = await refreshAccessToken({
27
+ tokenUrl: stored.tokenUrl,
28
+ clientId: stored.clientId,
29
+ ...(stored.clientSecret ? { clientSecret: stored.clientSecret } : {}),
30
+ refreshToken: stored.tokenSet.refreshToken,
31
+ });
32
+ }
33
+ catch (err) {
34
+ // Rotation-race recovery: an invalid_grant-style rejection with a
35
+ // DIFFERENT refresh_token now on disk means another process rotated ours
36
+ // away after we read it. Retry once with the fresher token before
37
+ // declaring re-auth necessary. Transient (network/5xx) failures are not
38
+ // recovered here — they aren't evidence of rotation.
39
+ if (!retried && isAuthRejection(err)) {
40
+ const latest = await readStoredCreds(vault, spec.provider);
41
+ if (latest?.tokenSet.refreshToken &&
42
+ latest.tokenSet.refreshToken !== stored.tokenSet.refreshToken) {
43
+ return refreshAndStore(vault, spec, latest, true);
44
+ }
45
+ }
46
+ const net = classifyNetworkError(err, { url: stored.tokenUrl, provider: spec.provider });
47
+ if (net)
48
+ throw net;
49
+ throw spec.wrapRefreshFailure(err);
50
+ }
51
+ // Providers MAY rotate refresh_token — preserve the prior one if not.
52
+ const merged = {
53
+ ...refreshed,
54
+ refreshToken: refreshed.refreshToken ?? stored.tokenSet.refreshToken,
55
+ };
56
+ const extras = spec.resolveExtras(merged, stored.extras);
57
+ await storeTokenSet(vault, spec.provider, merged, {
58
+ clientId: stored.clientId,
59
+ ...(stored.clientSecret ? { clientSecret: stored.clientSecret } : {}),
60
+ tokenUrl: stored.tokenUrl,
61
+ ...(extras ? { extras } : {}),
62
+ });
63
+ return { tokens: merged, extras: extras ?? stored.extras };
64
+ }
65
+ export async function ensureFreshTokens(profile, vault, opts = {}) {
66
+ const stored = await readStoredCreds(vault, profile.id);
67
+ if (!stored) {
68
+ throw new MoxxyError({
69
+ code: 'AUTH_NO_CREDENTIALS',
70
+ message: `No stored OAuth credentials for "${profile.id}".`,
71
+ hint: `Run \`moxxy login ${profile.id}\` to sign in.`,
72
+ context: { provider: profile.id },
73
+ });
74
+ }
75
+ if (!opts.force && !isExpired(stored.tokenSet, opts.skewMs)) {
76
+ return { tokens: stored.tokenSet, extras: stored.extras };
77
+ }
78
+ return withCredentialLock(`oauth-${profile.id}`, async () => {
79
+ // Re-read under the lock: another consumer/process may have refreshed
80
+ // while we waited. If the vault now holds a different, still-fresh access
81
+ // token, reuse it — even under `force`, which exists for 401 recovery and
82
+ // is satisfied by ANY rotation, not specifically ours.
83
+ const current = (await readStoredCreds(vault, profile.id)) ?? stored;
84
+ const rotatedMeanwhile = current.tokenSet.accessToken !== stored.tokenSet.accessToken;
85
+ if (!isExpired(current.tokenSet, opts.skewMs) && (!opts.force || rotatedMeanwhile)) {
86
+ return { tokens: current.tokenSet, extras: current.extras };
87
+ }
88
+ return refreshAndStore(vault, profileRefreshSpec(profile), current);
89
+ });
90
+ }
91
+ /**
92
+ * RefreshSpec for a provider profile: friendly `moxxy login`-flavored errors
93
+ * and extras re-derived from the fresh tokens (id_token re-issuance varies by
94
+ * provider, so fall back to stored extras for fields like account_id).
95
+ */
96
+ function profileRefreshSpec(profile) {
97
+ return {
98
+ provider: profile.id,
99
+ noRefreshTokenError: () => new MoxxyError({
100
+ code: 'AUTH_EXPIRED',
101
+ message: `OAuth token for "${profile.id}" expired and no refresh_token is stored.`,
102
+ hint: `Re-run \`moxxy login ${profile.id}\` to sign in again.`,
103
+ context: { provider: profile.id },
104
+ }),
105
+ wrapRefreshFailure: (err) => new MoxxyError({
106
+ code: 'AUTH_EXPIRED',
107
+ message: `Couldn't refresh the OAuth token for "${profile.id}".`,
108
+ hint: `Re-run \`moxxy login ${profile.id}\` to sign in again.`,
109
+ context: { provider: profile.id },
110
+ cause: err,
111
+ }),
112
+ resolveExtras: (merged, storedExtras) => {
113
+ const freshAccountId = profile.extractAccountId?.(merged);
114
+ const freshExtras = profile.extractExtras?.(merged) ?? {};
115
+ const mergedExtras = { ...storedExtras, ...freshExtras };
116
+ if (freshAccountId)
117
+ mergedExtras.account_id = freshAccountId;
118
+ return mergedExtras;
119
+ },
120
+ };
121
+ }
122
+ //# sourceMappingURL=ensure-fresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure-fresh.js","sourceRoot":"","sources":["../src/ensure-fresh.ts"],"names":[],"mappings":"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"}
package/dist/flow.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { buildAuthUrl, runAuthorizationCodeFlow, type BuildAuthUrlInput } from './oauth/browser-flow.js';
2
+ export { runDeviceCodeFlow } from './oauth/device-flow.js';
3
+ export { exchangeCodeForToken, parseTokenResponse, refreshAccessToken, } from './oauth/token-exchange.js';
4
+ export type { DeviceFlowOptions, DevicePrompt, OAuthFlowOptions, TokenSet } from './oauth/types.js';
5
+ export { pollUntil, type PollOutcome, type PollState, type PollUntilOpts } from './oauth/poll-until.js';
6
+ //# sourceMappingURL=flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACzG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/flow.js ADDED
@@ -0,0 +1,5 @@
1
+ export { buildAuthUrl, runAuthorizationCodeFlow } from './oauth/browser-flow.js';
2
+ export { runDeviceCodeFlow } from './oauth/device-flow.js';
3
+ export { exchangeCodeForToken, parseTokenResponse, refreshAccessToken, } from './oauth/token-exchange.js';
4
+ export { pollUntil } from './oauth/poll-until.js';
5
+ //# sourceMappingURL=flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.js","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAA0B,MAAM,yBAAyB,CAAC;AACzG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,SAAS,EAAwD,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { type Plugin } from '@moxxy/sdk';
2
+ import type { VaultStore } from '@moxxy/plugin-vault';
3
+ export { buildAuthUrl, exchangeCodeForToken, parseTokenResponse, refreshAccessToken, runAuthorizationCodeFlow, runDeviceCodeFlow, pollUntil, } from './flow.js';
4
+ export type { BuildAuthUrlInput, DeviceFlowOptions, DevicePrompt, OAuthFlowOptions, PollOutcome, PollState, PollUntilOpts, TokenSet, } from './flow.js';
5
+ export { computeCodeChallenge, generateCodeVerifier, generateState } from './pkce.js';
6
+ export { openInBrowser } from './open-browser.js';
7
+ export { clearStoredCreds, isExpired, readStoredCreds, storeTokenSet, validateProvider, type OAuthVault, type StoreTokenSetMeta, type StoredCreds, } from './storage.js';
8
+ export type { DeviceFlowAdapter, DeviceFlowInit, DeviceFlowStartArgs, OAuthProviderProfile, RunOauthLoginCtx, RunOauthLoginResult, } from './profile.js';
9
+ export { runOauthLogin } from './run-login.js';
10
+ export { ensureFreshTokens, type EnsureFreshOptions, type EnsureFreshResult, } from './ensure-fresh.js';
11
+ export { isAuthRejection, withCredentialLock, type CredentialLockOptions, } from './credential-lock.js';
12
+ export { rfc8628DeviceFlow, type Rfc8628AdapterOpts } from './adapters/rfc8628-device-flow.js';
13
+ export { openaiDeviceFlow, type OpenaiDeviceFlowOpts } from './adapters/openai-device-flow.js';
14
+ export { buildOauthAuthorizeTool, buildOauthClearTool, buildOauthGetTokenTool, type OAuthToolDeps, } from './tools.js';
15
+ export interface BuildOauthPluginOpts {
16
+ readonly vault: VaultStore;
17
+ }
18
+ /**
19
+ * `@moxxy/plugin-oauth` — generic OAuth 2.0 + PKCE provider framework.
20
+ *
21
+ * Two surfaces:
22
+ * 1. **Tools** — `oauth_authorize`, `oauth_get_token`, `oauth_clear_token`.
23
+ * Model-callable; suits ad-hoc usage and MCP server wiring.
24
+ * 2. **Provider framework** — declare an `OAuthProviderProfile`, plug in
25
+ * (or write) a `DeviceFlowAdapter`, drive with `runOauthLogin(profile, ctx)`
26
+ * and `ensureFreshTokens(profile, vault)`. Suits LLM providers (e.g.
27
+ * `@moxxy/plugin-provider-openai-codex`) that own their auth lifecycle.
28
+ *
29
+ * Tokens persist in the vault under `oauth/<provider>/*`. Bundled device
30
+ * adapters: `rfc8628DeviceFlow` (standards-compliant), `openaiDeviceFlow`
31
+ * (OpenAI's non-standard flavor). New dialects ship their own adapter.
32
+ */
33
+ export declare function buildOauthPlugin(opts: BuildOauthPluginOpts): Plugin;
34
+ /**
35
+ * Discovery-loadable default export: resolves the vault from the inter-plugin
36
+ * service registry in `onInit` (the vault plugin publishes `'vault'`). Requires
37
+ * `@moxxy/plugin-vault` to load first (declared in `package.json`
38
+ * `moxxy.requirements`), so its `onInit` registers the service before this one
39
+ * consumes it. The tools read `deps.vault` lazily via a getter, so they resolve
40
+ * the store at call time — after `onInit` has wired it.
41
+ */
42
+ export declare const oauthPlugin: Plugin;
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAStD,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,aAAa,EACb,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlD,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AAItB,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAG/F,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CAGnE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAgBtB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,82 @@
1
+ import { definePlugin } from '@moxxy/sdk';
2
+ import { buildOauthAuthorizeTool, buildOauthClearTool, buildOauthGetTokenTool, } from './tools.js';
3
+ // Low-level flow primitives — useful for ad-hoc usage or custom adapters.
4
+ export { buildAuthUrl, exchangeCodeForToken, parseTokenResponse, refreshAccessToken, runAuthorizationCodeFlow, runDeviceCodeFlow, pollUntil, } from './flow.js';
5
+ export { computeCodeChallenge, generateCodeVerifier, generateState } from './pkce.js';
6
+ export { openInBrowser } from './open-browser.js';
7
+ // Storage (vault-backed; extras-aware).
8
+ export { clearStoredCreds, isExpired, readStoredCreds, storeTokenSet, validateProvider, } from './storage.js';
9
+ export { runOauthLogin } from './run-login.js';
10
+ export { ensureFreshTokens, } from './ensure-fresh.js';
11
+ // Per-credential refresh serialization (in-process mutex + best-effort
12
+ // cross-process lockfile) for providers with single-use rotating
13
+ // refresh tokens.
14
+ export { isAuthRejection, withCredentialLock, } from './credential-lock.js';
15
+ // Bundled device-flow adapters. Custom dialects implement `DeviceFlowAdapter`.
16
+ export { rfc8628DeviceFlow } from './adapters/rfc8628-device-flow.js';
17
+ export { openaiDeviceFlow } from './adapters/openai-device-flow.js';
18
+ // Tools layer — `oauth_authorize`, `oauth_get_token`, `oauth_clear_token`.
19
+ export { buildOauthAuthorizeTool, buildOauthClearTool, buildOauthGetTokenTool, } from './tools.js';
20
+ /**
21
+ * `@moxxy/plugin-oauth` — generic OAuth 2.0 + PKCE provider framework.
22
+ *
23
+ * Two surfaces:
24
+ * 1. **Tools** — `oauth_authorize`, `oauth_get_token`, `oauth_clear_token`.
25
+ * Model-callable; suits ad-hoc usage and MCP server wiring.
26
+ * 2. **Provider framework** — declare an `OAuthProviderProfile`, plug in
27
+ * (or write) a `DeviceFlowAdapter`, drive with `runOauthLogin(profile, ctx)`
28
+ * and `ensureFreshTokens(profile, vault)`. Suits LLM providers (e.g.
29
+ * `@moxxy/plugin-provider-openai-codex`) that own their auth lifecycle.
30
+ *
31
+ * Tokens persist in the vault under `oauth/<provider>/*`. Bundled device
32
+ * adapters: `rfc8628DeviceFlow` (standards-compliant), `openaiDeviceFlow`
33
+ * (OpenAI's non-standard flavor). New dialects ship their own adapter.
34
+ */
35
+ export function buildOauthPlugin(opts) {
36
+ // Host-injected vault (available immediately).
37
+ return makeOauthPlugin(() => opts.vault);
38
+ }
39
+ /**
40
+ * Discovery-loadable default export: resolves the vault from the inter-plugin
41
+ * service registry in `onInit` (the vault plugin publishes `'vault'`). Requires
42
+ * `@moxxy/plugin-vault` to load first (declared in `package.json`
43
+ * `moxxy.requirements`), so its `onInit` registers the service before this one
44
+ * consumes it. The tools read `deps.vault` lazily via a getter, so they resolve
45
+ * the store at call time — after `onInit` has wired it.
46
+ */
47
+ export const oauthPlugin = (() => {
48
+ let resolved = null;
49
+ const getVault = () => {
50
+ if (!resolved) {
51
+ throw new Error('@moxxy/plugin-oauth: the "vault" service is unavailable — @moxxy/plugin-vault must load first');
52
+ }
53
+ return resolved;
54
+ };
55
+ const hooks = {
56
+ onInit: (ctx) => {
57
+ resolved = ctx.services.require('vault');
58
+ },
59
+ };
60
+ return makeOauthPlugin(getVault, hooks);
61
+ })();
62
+ function makeOauthPlugin(getVault, hooks) {
63
+ // The getter satisfies `OAuthToolDeps.vault` while deferring resolution to call
64
+ // time, so the same tools work whether the vault is host-injected or resolved
65
+ // from the service registry in onInit.
66
+ const deps = {
67
+ get vault() {
68
+ return getVault();
69
+ },
70
+ };
71
+ return definePlugin({
72
+ name: '@moxxy/plugin-oauth',
73
+ version: '0.0.0',
74
+ tools: [
75
+ buildOauthAuthorizeTool(deps),
76
+ buildOauthGetTokenTool(deps),
77
+ buildOauthClearTool(deps),
78
+ ],
79
+ ...(hooks ? { hooks } : {}),
80
+ });
81
+ }
82
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAoC,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AAEpB,0EAA0E;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,SAAS,GACV,MAAM,WAAW,CAAC;AAYnB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,wCAAwC;AACxC,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,aAAa,EACb,gBAAgB,GAIjB,MAAM,cAAc,CAAC;AAYtB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EACL,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAE3B,uEAAuE;AACvE,iEAAiE;AACjE,kBAAkB;AAClB,OAAO,EACL,eAAe,EACf,kBAAkB,GAEnB,MAAM,sBAAsB,CAAC;AAE9B,+EAA+E;AAC/E,OAAO,EAAE,iBAAiB,EAA2B,MAAM,mCAAmC,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAA6B,MAAM,kCAAkC,CAAC;AAE/F,2EAA2E;AAC3E,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AAMpB;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA0B;IACzD,+CAA+C;IAC/C,OAAO,eAAe,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAW,CAAC,GAAG,EAAE;IACvC,IAAI,QAAQ,GAAsB,IAAI,CAAC;IACvC,MAAM,QAAQ,GAAG,GAAe,EAAE;QAChC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CACb,+FAA+F,CAChG,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;IACF,MAAM,KAAK,GAAmB;QAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;YACd,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAa,OAAO,CAAC,CAAC;QACvD,CAAC;KACF,CAAC;IACF,OAAO,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC1C,CAAC,CAAC,EAAE,CAAC;AAEL,SAAS,eAAe,CAAC,QAA0B,EAAE,KAAsB;IACzE,gFAAgF;IAChF,8EAA8E;IAC9E,uCAAuC;IACvC,MAAM,IAAI,GAAkB;QAC1B,IAAI,KAAK;YACP,OAAO,QAAQ,EAAE,CAAC;QACpB,CAAC;KACF,CAAC;IACF,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE;YACL,uBAAuB,CAAC,IAAI,CAAC;YAC7B,sBAAsB,CAAC,IAAI,CAAC;YAC5B,mBAAmB,CAAC,IAAI,CAAC;SAC1B;QACD,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { OAuthFlowOptions, TokenSet } from './types.js';
2
+ export interface BuildAuthUrlInput {
3
+ readonly authUrl: string;
4
+ readonly clientId: string;
5
+ readonly redirectUri: string;
6
+ readonly scopes: ReadonlyArray<string>;
7
+ readonly codeChallenge: string;
8
+ readonly state: string;
9
+ readonly extraAuthParams?: Readonly<Record<string, string>>;
10
+ }
11
+ /** Pure URL builder, exported separately so tests can assert on it. */
12
+ export declare function buildAuthUrl(input: BuildAuthUrlInput): string;
13
+ /**
14
+ * Run the full authorization-code-with-PKCE dance:
15
+ * 1. Bind a loopback HTTP server on `redirectPort`.
16
+ * 2. Build the auth URL (PKCE challenge, CSRF state, scopes, etc.).
17
+ * 3. Open the URL in the user's default browser.
18
+ * 4. Wait for the provider to redirect back with `code` + `state`.
19
+ * 5. Verify state, POST the code to the token endpoint with the
20
+ * verifier, return the parsed token set.
21
+ */
22
+ export declare function runAuthorizationCodeFlow(opts: OAuthFlowOptions): Promise<TokenSet>;
23
+ //# sourceMappingURL=browser-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-flow.d.ts","sourceRoot":"","sources":["../../src/oauth/browser-flow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7D;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAa7D;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsDxF"}
@@ -0,0 +1,82 @@
1
+ import { computeCodeChallenge, generateCodeVerifier, generateState } from '../pkce.js';
2
+ import { openInBrowser } from '../open-browser.js';
3
+ import { waitForCallback } from './callback-server.js';
4
+ import { exchangeCodeForToken } from './token-exchange.js';
5
+ /** Pure URL builder, exported separately so tests can assert on it. */
6
+ export function buildAuthUrl(input) {
7
+ const url = new URL(input.authUrl);
8
+ url.searchParams.set('client_id', input.clientId);
9
+ url.searchParams.set('redirect_uri', input.redirectUri);
10
+ url.searchParams.set('response_type', 'code');
11
+ url.searchParams.set('scope', input.scopes.join(' '));
12
+ url.searchParams.set('state', input.state);
13
+ url.searchParams.set('code_challenge', input.codeChallenge);
14
+ url.searchParams.set('code_challenge_method', 'S256');
15
+ for (const [k, v] of Object.entries(input.extraAuthParams ?? {})) {
16
+ url.searchParams.set(k, v);
17
+ }
18
+ return url.toString();
19
+ }
20
+ /**
21
+ * Run the full authorization-code-with-PKCE dance:
22
+ * 1. Bind a loopback HTTP server on `redirectPort`.
23
+ * 2. Build the auth URL (PKCE challenge, CSRF state, scopes, etc.).
24
+ * 3. Open the URL in the user's default browser.
25
+ * 4. Wait for the provider to redirect back with `code` + `state`.
26
+ * 5. Verify state, POST the code to the token endpoint with the
27
+ * verifier, return the parsed token set.
28
+ */
29
+ export async function runAuthorizationCodeFlow(opts) {
30
+ const port = opts.redirectPort ?? 8765;
31
+ const path = opts.redirectPath ?? '/callback';
32
+ const codeVerifier = generateCodeVerifier();
33
+ const codeChallenge = computeCodeChallenge(codeVerifier);
34
+ const state = generateState();
35
+ const redirectUri = `http://localhost:${port}${path}`;
36
+ const authUrl = buildAuthUrl({
37
+ authUrl: opts.authUrl,
38
+ clientId: opts.clientId,
39
+ redirectUri,
40
+ scopes: opts.scopes,
41
+ codeChallenge,
42
+ state,
43
+ ...(opts.extraAuthParams ? { extraAuthParams: opts.extraAuthParams } : {}),
44
+ });
45
+ // Start the server BEFORE opening the browser — otherwise the user
46
+ // could complete the consent screen before we're listening and the
47
+ // redirect would 404.
48
+ const codePromise = waitForCallback({
49
+ port,
50
+ path,
51
+ expectedState: state,
52
+ timeoutMs: opts.timeoutMs ?? 300_000,
53
+ ...(opts.signal ? { signal: opts.signal } : {}),
54
+ });
55
+ if (opts.onAuthUrl)
56
+ opts.onAuthUrl(authUrl);
57
+ if (!opts.noOpen) {
58
+ try {
59
+ await openInBrowser(authUrl);
60
+ }
61
+ catch {
62
+ // Failed to open the browser — not fatal; the user can visit
63
+ // the URL surfaced via onAuthUrl. The loopback server is still
64
+ // listening.
65
+ }
66
+ }
67
+ const code = await codePromise;
68
+ // Thread the abort signal into the exchange too: a turn cancelled AFTER the
69
+ // browser redirect arrives but DURING the token POST must cancel the in-flight
70
+ // fetch, not leak a hung request past the cancel. (waitForCallback already
71
+ // honours the same signal for the listening phase.)
72
+ return exchangeCodeForToken({
73
+ tokenUrl: opts.tokenUrl,
74
+ code,
75
+ redirectUri,
76
+ clientId: opts.clientId,
77
+ ...(opts.clientSecret ? { clientSecret: opts.clientSecret } : {}),
78
+ codeVerifier,
79
+ ...(opts.signal ? { signal: opts.signal } : {}),
80
+ });
81
+ }
82
+ //# sourceMappingURL=browser-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-flow.js","sourceRoot":"","sources":["../../src/oauth/browser-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ interface WaitForCallbackOpts {
2
+ readonly port: number;
3
+ readonly path: string;
4
+ readonly expectedState: string;
5
+ readonly timeoutMs: number;
6
+ readonly signal?: AbortSignal;
7
+ }
8
+ export declare function waitForCallback(opts: WaitForCallbackOpts): Promise<string>;
9
+ export {};
10
+ //# sourceMappingURL=callback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.d.ts","sourceRoot":"","sources":["../../src/oauth/callback-server.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwL1E"}
@@ -0,0 +1,158 @@
1
+ import { createServer } from 'node:http';
2
+ import { MoxxyError } from '@moxxy/sdk';
3
+ export function waitForCallback(opts) {
4
+ return new Promise((resolve, reject) => {
5
+ // Bind BOTH loopback stacks. The redirect_uri uses `localhost`, which
6
+ // Windows resolves to `::1` (IPv6) first while macOS/Linux use `127.0.0.1`
7
+ // (IPv4). A v4-only listener therefore never receives the redirect on
8
+ // Windows → the login hangs on "waiting for the browser". IPv4 is required;
9
+ // IPv6 is best-effort (a host without it still works, since there
10
+ // `localhost` is IPv4 anyway).
11
+ const servers = [];
12
+ let settled = false;
13
+ const settle = (fn) => {
14
+ if (settled)
15
+ return;
16
+ settled = true;
17
+ // Single cleanup chokepoint: clear the timeout AND detach the abort
18
+ // listener on every exit (success/timeout/abort/error), so neither a
19
+ // dangling timer nor a lingering signal listener is left behind. Guard
20
+ // each close()/fn() so a Server.close() that throws ERR_SERVER_NOT_RUNNING
21
+ // (server pushed before its listen completed) can never strand the
22
+ // remaining servers or skip the resolve/reject.
23
+ clearTimeout(timer);
24
+ opts.signal?.removeEventListener('abort', onAbort);
25
+ try {
26
+ for (const s of servers) {
27
+ try {
28
+ s.close();
29
+ }
30
+ catch {
31
+ /* not yet listening — nothing to close */
32
+ }
33
+ }
34
+ }
35
+ finally {
36
+ fn();
37
+ }
38
+ };
39
+ const timer = setTimeout(() => {
40
+ settle(() => reject(new MoxxyError({
41
+ code: 'OAUTH_FLOW_TIMEOUT',
42
+ message: `OAuth callback timed out after ${Math.round(opts.timeoutMs / 1000)}s.`,
43
+ hint: 'Re-run the login command and complete the consent screen before the timeout.',
44
+ context: { port: opts.port, path: opts.path, timeout_ms: opts.timeoutMs },
45
+ })));
46
+ }, opts.timeoutMs);
47
+ timer.unref?.();
48
+ const onAbort = () => {
49
+ settle(() => reject(new MoxxyError({
50
+ code: 'NETWORK_ABORTED',
51
+ message: 'OAuth flow was aborted.',
52
+ })));
53
+ };
54
+ opts.signal?.addEventListener('abort', onAbort, { once: true });
55
+ // A signal that was ALREADY aborted before entry never fires `abort`, so
56
+ // the listener above won't run — without this guard we'd bind two HTTP
57
+ // servers and block for the full timeout, ignoring the cancel. (pollUntil
58
+ // guards the same case.) settle() detaches the listener and skips binding.
59
+ if (opts.signal?.aborted) {
60
+ settle(() => reject(new MoxxyError({
61
+ code: 'NETWORK_ABORTED',
62
+ message: 'OAuth flow was aborted.',
63
+ })));
64
+ return;
65
+ }
66
+ const handleRequest = (req, res) => {
67
+ const url = new URL(req.url ?? '/', `http://localhost:${opts.port}`);
68
+ if (url.pathname !== opts.path) {
69
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
70
+ res.end('not found');
71
+ return;
72
+ }
73
+ const err = url.searchParams.get('error');
74
+ const errDesc = url.searchParams.get('error_description');
75
+ if (err) {
76
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
77
+ res.end(htmlPage('OAuth error', `${err}${errDesc ? `: ${errDesc}` : ''}`));
78
+ const denied = err === 'access_denied';
79
+ settle(() => reject(new MoxxyError({
80
+ code: denied ? 'OAUTH_FLOW_DENIED' : 'AUTH_INVALID',
81
+ message: denied
82
+ ? 'You declined the authorization request.'
83
+ : `Authorization server returned an error: ${err}${errDesc ? ` — ${errDesc}` : ''}.`,
84
+ ...(denied
85
+ ? { hint: 'Re-run the login command and approve the consent screen to continue.' }
86
+ : {}),
87
+ context: { provider_error: err, ...(errDesc ? { description: errDesc } : {}) },
88
+ })));
89
+ return;
90
+ }
91
+ const code = url.searchParams.get('code');
92
+ const returnedState = url.searchParams.get('state');
93
+ if (!code || !returnedState) {
94
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
95
+ res.end(htmlPage('OAuth error', 'callback was missing code or state'));
96
+ settle(() => reject(new MoxxyError({
97
+ code: 'AUTH_INVALID',
98
+ message: 'OAuth callback was missing code or state — the upstream redirect is malformed.',
99
+ hint: 'Re-run the login command. If this persists, the provider may have rejected the request.',
100
+ })));
101
+ return;
102
+ }
103
+ if (returnedState !== opts.expectedState) {
104
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
105
+ res.end(htmlPage('OAuth error', 'state mismatch — possible CSRF, refusing'));
106
+ settle(() => reject(new MoxxyError({
107
+ code: 'OAUTH_FLOW_STATE_MISMATCH',
108
+ message: 'OAuth state mismatch — possible CSRF attempt, refusing to continue.',
109
+ hint: 'Make sure no other moxxy login is running at the same time, and re-run the command. ' +
110
+ 'If this keeps happening, your browser or a proxy may be tampering with redirects.',
111
+ })));
112
+ return;
113
+ }
114
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
115
+ res.end(htmlPage('Authorized', 'You can close this window — moxxy received the token.'));
116
+ settle(() => resolve(code));
117
+ };
118
+ const onFatalError = (e) => {
119
+ if (e.code === 'EADDRINUSE') {
120
+ settle(() => reject(new MoxxyError({
121
+ code: 'OAUTH_FLOW_PORT_BUSY',
122
+ message: `OAuth callback port ${opts.port} is already in use.`,
123
+ hint: 'Another moxxy login may already be running, or the port is occupied by something else. ' +
124
+ `Stop the other process, or set a different port for this provider's redirect.`,
125
+ context: { port: opts.port },
126
+ cause: e,
127
+ })));
128
+ return;
129
+ }
130
+ settle(() => reject(MoxxyError.wrap(e, {
131
+ code: 'INTERNAL',
132
+ message: `OAuth callback server failed: ${e.message}`,
133
+ })));
134
+ };
135
+ // IPv4 loopback — the required listener; its bind/port errors are fatal.
136
+ const v4 = createServer(handleRequest);
137
+ v4.on('error', onFatalError);
138
+ v4.listen(opts.port, '127.0.0.1');
139
+ servers.push(v4);
140
+ // IPv6 loopback — Windows resolves `localhost` to ::1 first, so the
141
+ // redirect lands here. Best-effort: swallow bind errors (host without IPv6,
142
+ // or ::1 already bound), since the IPv4 listener above is the guaranteed
143
+ // path and on such hosts `localhost` is IPv4 anyway.
144
+ const v6 = createServer(handleRequest);
145
+ v6.on('error', () => undefined);
146
+ v6.listen(opts.port, '::1');
147
+ servers.push(v6);
148
+ });
149
+ }
150
+ function htmlPage(title, body) {
151
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(title)}</title>
152
+ <style>body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#111;color:#eee;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;margin:0}h1{font-weight:300}</style>
153
+ </head><body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(body)}</p></body></html>`;
154
+ }
155
+ function escapeHtml(s) {
156
+ return s.replace(/[&<>"']/g, (c) => c === '&' ? '&amp;' : c === '<' ? '&lt;' : c === '>' ? '&gt;' : c === '"' ? '&quot;' : '&#39;');
157
+ }
158
+ //# sourceMappingURL=callback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.js","sourceRoot":"","sources":["../../src/oauth/callback-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { PollOutcome } from './poll-until.js';
2
+ import type { TokenSet } from './types.js';
3
+ /**
4
+ * Parsed RFC 8628 device-authorization response. Times are kept in seconds (as
5
+ * the wire reports them); callers convert to ms where their API needs it.
6
+ */
7
+ export interface DeviceAuthorization {
8
+ readonly deviceCode: string;
9
+ readonly userCode: string;
10
+ readonly verificationUri: string;
11
+ readonly verificationUriComplete?: string;
12
+ readonly expiresInSec: number;
13
+ readonly intervalSec: number;
14
+ }
15
+ /**
16
+ * POST the RFC 8628 device-authorization request and parse the response. The
17
+ * single home for the request + field parsing + error handling shared by the
18
+ * legacy `runDeviceCodeFlow` and the `rfc8628DeviceFlow` adapter.
19
+ */
20
+ export declare function requestDeviceAuthorization(args: {
21
+ readonly deviceUrl: string;
22
+ readonly clientId: string;
23
+ readonly scopes: ReadonlyArray<string>;
24
+ readonly signal?: AbortSignal;
25
+ }): Promise<DeviceAuthorization>;
26
+ /**
27
+ * Classify a device-flow token-poll response per RFC 8628 §3.5: success →
28
+ * `done`; `authorization_pending` → keep waiting; `slow_down` → +5s and wait;
29
+ * `access_denied` / `expired_token` / other → throw a MoxxyError. Shared by both
30
+ * poll loops; each caller builds its own request body (the legacy flow appends
31
+ * client_id/client_secret) and hands the response + parsed JSON here.
32
+ */
33
+ export declare function classifyDeviceTokenResponse(res: {
34
+ readonly ok: boolean;
35
+ readonly status: number;
36
+ }, json: Record<string, unknown>, state: {
37
+ intervalMs: number;
38
+ }): PollOutcome<TokenSet>;
39
+ //# sourceMappingURL=device-flow-shared.d.ts.map