@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.
- package/LICENSE +21 -0
- package/dist/adapters/openai-device-flow.d.ts +34 -0
- package/dist/adapters/openai-device-flow.d.ts.map +1 -0
- package/dist/adapters/openai-device-flow.js +136 -0
- package/dist/adapters/openai-device-flow.js.map +1 -0
- package/dist/adapters/rfc8628-device-flow.d.ts +20 -0
- package/dist/adapters/rfc8628-device-flow.d.ts.map +1 -0
- package/dist/adapters/rfc8628-device-flow.js +54 -0
- package/dist/adapters/rfc8628-device-flow.js.map +1 -0
- package/dist/credential-lock.d.ts +60 -0
- package/dist/credential-lock.d.ts.map +1 -0
- package/dist/credential-lock.js +149 -0
- package/dist/credential-lock.js.map +1 -0
- package/dist/ensure-fresh.d.ts +63 -0
- package/dist/ensure-fresh.d.ts.map +1 -0
- package/dist/ensure-fresh.js +122 -0
- package/dist/ensure-fresh.js.map +1 -0
- package/dist/flow.d.ts +6 -0
- package/dist/flow.d.ts.map +1 -0
- package/dist/flow.js +5 -0
- package/dist/flow.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/browser-flow.d.ts +23 -0
- package/dist/oauth/browser-flow.d.ts.map +1 -0
- package/dist/oauth/browser-flow.js +82 -0
- package/dist/oauth/browser-flow.js.map +1 -0
- package/dist/oauth/callback-server.d.ts +10 -0
- package/dist/oauth/callback-server.d.ts.map +1 -0
- package/dist/oauth/callback-server.js +158 -0
- package/dist/oauth/callback-server.js.map +1 -0
- package/dist/oauth/device-flow-shared.d.ts +39 -0
- package/dist/oauth/device-flow-shared.d.ts.map +1 -0
- package/dist/oauth/device-flow-shared.js +101 -0
- package/dist/oauth/device-flow-shared.js.map +1 -0
- package/dist/oauth/device-flow.d.ts +29 -0
- package/dist/oauth/device-flow.d.ts.map +1 -0
- package/dist/oauth/device-flow.js +74 -0
- package/dist/oauth/device-flow.js.map +1 -0
- package/dist/oauth/poll-until.d.ts +41 -0
- package/dist/oauth/poll-until.d.ts.map +1 -0
- package/dist/oauth/poll-until.js +70 -0
- package/dist/oauth/poll-until.js.map +1 -0
- package/dist/oauth/token-exchange.d.ts +28 -0
- package/dist/oauth/token-exchange.d.ts.map +1 -0
- package/dist/oauth/token-exchange.js +142 -0
- package/dist/oauth/token-exchange.js.map +1 -0
- package/dist/oauth/types.d.ts +90 -0
- package/dist/oauth/types.d.ts.map +1 -0
- package/dist/oauth/types.js +2 -0
- package/dist/oauth/types.js.map +1 -0
- package/dist/open-browser.d.ts +32 -0
- package/dist/open-browser.d.ts.map +1 -0
- package/dist/open-browser.js +83 -0
- package/dist/open-browser.js.map +1 -0
- package/dist/pkce.d.ts +20 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +34 -0
- package/dist/pkce.js.map +1 -0
- package/dist/profile.d.ts +129 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +11 -0
- package/dist/profile.js.map +1 -0
- package/dist/run-login.d.ts +9 -0
- package/dist/run-login.d.ts.map +1 -0
- package/dist/run-login.js +93 -0
- package/dist/run-login.js.map +1 -0
- package/dist/storage.d.ts +46 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +167 -0
- package/dist/storage.js.map +1 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +283 -0
- package/dist/tools.js.map +1 -0
- package/package.json +73 -0
- package/skills/google-oauth.md +313 -0
- package/skills/oauth-flow.md +132 -0
- package/src/adapters/openai-device-flow.test.ts +191 -0
- package/src/adapters/openai-device-flow.ts +179 -0
- package/src/adapters/rfc8628-device-flow.ts +75 -0
- package/src/credential-lock.ts +183 -0
- package/src/discovery.test.ts +35 -0
- package/src/ensure-fresh.test.ts +279 -0
- package/src/ensure-fresh.ts +178 -0
- package/src/flow.ts +9 -0
- package/src/index.ts +153 -0
- package/src/oauth/browser-flow.ts +96 -0
- package/src/oauth/callback-server.test.ts +90 -0
- package/src/oauth/callback-server.ts +208 -0
- package/src/oauth/device-flow-shared.test.ts +142 -0
- package/src/oauth/device-flow-shared.ts +129 -0
- package/src/oauth/device-flow.test.ts +102 -0
- package/src/oauth/device-flow.ts +82 -0
- package/src/oauth/poll-until.test.ts +63 -0
- package/src/oauth/poll-until.ts +100 -0
- package/src/oauth/token-exchange.test.ts +224 -0
- package/src/oauth/token-exchange.ts +169 -0
- package/src/oauth/types.ts +92 -0
- package/src/oauth.test.ts +185 -0
- package/src/open-browser-spawn.test.ts +69 -0
- package/src/open-browser.test.ts +30 -0
- package/src/open-browser.ts +84 -0
- package/src/pkce.ts +37 -0
- package/src/profile.test.ts +331 -0
- package/src/profile.ts +135 -0
- package/src/run-login.ts +123 -0
- package/src/storage.test.ts +191 -0
- package/src/storage.ts +208 -0
- package/src/tools.test.ts +239 -0
- 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
|
package/dist/flow.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 === '&' ? '&' : c === '<' ? '<' : c === '>' ? '>' : c === '"' ? '"' : ''');
|
|
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
|