@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,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the user's default browser. Platform-specific:
|
|
3
|
+
* macOS → `open <url>`
|
|
4
|
+
* Linux → `xdg-open <url>`
|
|
5
|
+
* Win32 → `cmd /c start "" "<url>"` (the URL DOUBLE-QUOTED + args passed
|
|
6
|
+
* verbatim — see {@link browserOpenCommand})
|
|
7
|
+
*
|
|
8
|
+
* Returns once the helper process is spawned — it does NOT wait for the
|
|
9
|
+
* browser itself, since the user's flow continues asynchronously via
|
|
10
|
+
* the OAuth callback. Failures fall back to returning the URL string
|
|
11
|
+
* so the caller can print it for the user to paste manually.
|
|
12
|
+
*/
|
|
13
|
+
export declare function openInBrowser(url: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the platform's "open this URL in the default browser" command.
|
|
16
|
+
* Exported + `platform`-parameterized so the Windows quoting can be unit-tested
|
|
17
|
+
* without actually spawning a browser.
|
|
18
|
+
*
|
|
19
|
+
* Windows is the tricky one: `&` (and `|`, `<`, `>`, `^`) in a URL are cmd.exe
|
|
20
|
+
* command separators. `cmd /c start "" http://a?x=1&y=2` truncates the URL at
|
|
21
|
+
* the first `&`, so an OAuth authorize URL loses redirect_uri / code_challenge /
|
|
22
|
+
* state and the provider rejects the request ("authorization error"). Wrapping
|
|
23
|
+
* the URL in double quotes makes cmd treat it as one literal token; passing the
|
|
24
|
+
* args verbatim (`windowsVerbatimArguments`) stops Node from re-quoting and
|
|
25
|
+
* undoing that. The empty `""` is the required `start` window-title placeholder.
|
|
26
|
+
*/
|
|
27
|
+
export declare function browserOpenCommand(url: string, platform?: NodeJS.Platform): {
|
|
28
|
+
cmd: string;
|
|
29
|
+
args: string[];
|
|
30
|
+
verbatim?: boolean;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=open-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.d.ts","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAWrD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Open a URL in the user's default browser. Platform-specific:
|
|
4
|
+
* macOS → `open <url>`
|
|
5
|
+
* Linux → `xdg-open <url>`
|
|
6
|
+
* Win32 → `cmd /c start "" "<url>"` (the URL DOUBLE-QUOTED + args passed
|
|
7
|
+
* verbatim — see {@link browserOpenCommand})
|
|
8
|
+
*
|
|
9
|
+
* Returns once the helper process is spawned — it does NOT wait for the
|
|
10
|
+
* browser itself, since the user's flow continues asynchronously via
|
|
11
|
+
* the OAuth callback. Failures fall back to returning the URL string
|
|
12
|
+
* so the caller can print it for the user to paste manually.
|
|
13
|
+
*/
|
|
14
|
+
export async function openInBrowser(url) {
|
|
15
|
+
const { cmd, args, verbatim } = browserOpenCommand(url);
|
|
16
|
+
await new Promise((resolve, reject) => {
|
|
17
|
+
const child = spawn(cmd, args, {
|
|
18
|
+
stdio: 'ignore',
|
|
19
|
+
detached: true,
|
|
20
|
+
// Windows only: keep cmd.exe from re-quoting our already-quoted URL,
|
|
21
|
+
// which would otherwise break the `&` escaping (see below).
|
|
22
|
+
...(verbatim ? { windowsVerbatimArguments: true } : {}),
|
|
23
|
+
});
|
|
24
|
+
// Settle exactly once. An early spawn `error` rejects AND clears the pending
|
|
25
|
+
// 50ms timer (else it would fire a stray resolve later and hold a live timer
|
|
26
|
+
// handle on the event loop). After we've resolved, a LATE async spawn error
|
|
27
|
+
// must NOT be left unhandled: an 'error' event with no listener is fatal in
|
|
28
|
+
// Node (it re-throws as an uncaught exception). So we keep an error listener
|
|
29
|
+
// attached for the child's whole lifetime — it rejects before settle, and
|
|
30
|
+
// harmlessly swallows after.
|
|
31
|
+
let settled = false;
|
|
32
|
+
const onError = (err) => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return; // post-resolve late error: swallow, never crash
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
reject(err);
|
|
38
|
+
};
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
if (settled)
|
|
41
|
+
return;
|
|
42
|
+
settled = true;
|
|
43
|
+
resolve();
|
|
44
|
+
}, 50);
|
|
45
|
+
// Don't let the 50ms settle-timer keep the process alive on its own.
|
|
46
|
+
timer.unref?.();
|
|
47
|
+
// `on` (not `once`): the listener stays for the child's lifetime so any late
|
|
48
|
+
// 'error' is always handled (a listenerless 'error' would crash the process).
|
|
49
|
+
child.on('error', onError);
|
|
50
|
+
// `unref` so a misbehaving opener process can't keep the moxxy
|
|
51
|
+
// process alive past its own lifetime.
|
|
52
|
+
child.unref();
|
|
53
|
+
// Resolve after spawn — we never want to wait for the browser to
|
|
54
|
+
// close. A 50ms tick is enough to surface spawn-time errors.
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the platform's "open this URL in the default browser" command.
|
|
59
|
+
* Exported + `platform`-parameterized so the Windows quoting can be unit-tested
|
|
60
|
+
* without actually spawning a browser.
|
|
61
|
+
*
|
|
62
|
+
* Windows is the tricky one: `&` (and `|`, `<`, `>`, `^`) in a URL are cmd.exe
|
|
63
|
+
* command separators. `cmd /c start "" http://a?x=1&y=2` truncates the URL at
|
|
64
|
+
* the first `&`, so an OAuth authorize URL loses redirect_uri / code_challenge /
|
|
65
|
+
* state and the provider rejects the request ("authorization error"). Wrapping
|
|
66
|
+
* the URL in double quotes makes cmd treat it as one literal token; passing the
|
|
67
|
+
* args verbatim (`windowsVerbatimArguments`) stops Node from re-quoting and
|
|
68
|
+
* undoing that. The empty `""` is the required `start` window-title placeholder.
|
|
69
|
+
*/
|
|
70
|
+
export function browserOpenCommand(url, platform = process.platform) {
|
|
71
|
+
if (platform === 'darwin')
|
|
72
|
+
return { cmd: 'open', args: [url] };
|
|
73
|
+
if (platform === 'win32') {
|
|
74
|
+
return {
|
|
75
|
+
cmd: process.env.ComSpec || 'cmd.exe',
|
|
76
|
+
args: ['/c', 'start', '""', `"${url}"`],
|
|
77
|
+
verbatim: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Linux + everything else assumes a Freedesktop-compliant `xdg-open`.
|
|
81
|
+
return { cmd: 'xdg-open', args: [url] };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=open-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,IAAI;YACd,qEAAqE;YACrE,4DAA4D;YAC5D,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD,CAAC,CAAC;QACH,6EAA6E;QAC7E,6EAA6E;QAC7E,4EAA4E;QAC5E,4EAA4E;QAC5E,6EAA6E;QAC7E,0EAA0E;QAC1E,6BAA6B;QAC7B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,CAAC,GAAU,EAAQ,EAAE;YACnC,IAAI,OAAO;gBAAE,OAAO,CAAC,gDAAgD;YACrE,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,qEAAqE;QACrE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,6EAA6E;QAC7E,8EAA8E;QAC9E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3B,+DAA+D;QAC/D,uCAAuC;QACvC,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,iEAAiE;QACjE,6DAA6D;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAW,EACX,WAA4B,OAAO,CAAC,QAAQ;IAE5C,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/D,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO;YACL,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,SAAS;YACrC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,CAAC;YACvC,QAAQ,EAAE,IAAI;SACf,CAAC;IACJ,CAAC;IACD,sEAAsE;IACtE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAC1C,CAAC"}
|
package/dist/pkce.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a PKCE code verifier per RFC 7636 §4.1.
|
|
3
|
+
* Spec range: 43–128 chars from the unreserved set [A-Z a-z 0-9 - . _ ~].
|
|
4
|
+
* We use 96 bytes of entropy → 128 base64url chars (full length).
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateCodeVerifier(): string;
|
|
7
|
+
/**
|
|
8
|
+
* RFC 7636 §4.2 — SHA-256(verifier) then base64url-encode. The
|
|
9
|
+
* challenge_method is implied: callers MUST send `code_challenge_method=S256`
|
|
10
|
+
* to the authorization endpoint when using this helper.
|
|
11
|
+
*/
|
|
12
|
+
export declare function computeCodeChallenge(codeVerifier: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Opaque CSRF token included in the `state` query param. The callback
|
|
15
|
+
* server verifies that the returned state matches what we sent, so a
|
|
16
|
+
* cross-site forgery can't redirect the user back into our local
|
|
17
|
+
* server with attacker-controlled code.
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateState(): string;
|
|
20
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/pkce.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a PKCE code verifier per RFC 7636 §4.1.
|
|
4
|
+
* Spec range: 43–128 chars from the unreserved set [A-Z a-z 0-9 - . _ ~].
|
|
5
|
+
* We use 96 bytes of entropy → 128 base64url chars (full length).
|
|
6
|
+
*/
|
|
7
|
+
export function generateCodeVerifier() {
|
|
8
|
+
return base64urlEncode(randomBytes(96));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* RFC 7636 §4.2 — SHA-256(verifier) then base64url-encode. The
|
|
12
|
+
* challenge_method is implied: callers MUST send `code_challenge_method=S256`
|
|
13
|
+
* to the authorization endpoint when using this helper.
|
|
14
|
+
*/
|
|
15
|
+
export function computeCodeChallenge(codeVerifier) {
|
|
16
|
+
return base64urlEncode(createHash('sha256').update(codeVerifier).digest());
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Opaque CSRF token included in the `state` query param. The callback
|
|
20
|
+
* server verifies that the returned state matches what we sent, so a
|
|
21
|
+
* cross-site forgery can't redirect the user back into our local
|
|
22
|
+
* server with attacker-controlled code.
|
|
23
|
+
*/
|
|
24
|
+
export function generateState() {
|
|
25
|
+
return base64urlEncode(randomBytes(24));
|
|
26
|
+
}
|
|
27
|
+
function base64urlEncode(buf) {
|
|
28
|
+
return buf
|
|
29
|
+
.toString('base64')
|
|
30
|
+
.replace(/\+/g, '-')
|
|
31
|
+
.replace(/\//g, '_')
|
|
32
|
+
.replace(/=+$/, '');
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=pkce.js.map
|
package/dist/pkce.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAAoB;IACvD,OAAO,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,eAAe,CAAC,GAAW;IAClC,OAAO,GAAG;SACP,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-framework types. A new OAuth provider plugs in by exporting an
|
|
3
|
+
* `OAuthProviderProfile` and (if it supports device-flow) one of the bundled
|
|
4
|
+
* adapters — or a custom adapter for a non-standard dialect.
|
|
5
|
+
*
|
|
6
|
+
* The host then calls `runOauthLogin(profile, ctx)` to drive the full dance
|
|
7
|
+
* and `ensureFreshTokens(profile, vault)` before each upstream request to
|
|
8
|
+
* keep the access token fresh.
|
|
9
|
+
*/
|
|
10
|
+
import type { PollOutcome, PollState } from './oauth/poll-until.js';
|
|
11
|
+
import type { TokenSet } from './oauth/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Single source of truth for everything OAuth-related about a provider.
|
|
14
|
+
* Constants + extraction logic live here; the framework handles the
|
|
15
|
+
* actual HTTP / vault / refresh plumbing.
|
|
16
|
+
*/
|
|
17
|
+
export interface OAuthProviderProfile {
|
|
18
|
+
/**
|
|
19
|
+
* Stable namespace under `oauth/<id>/*` in the vault. Lowercase,
|
|
20
|
+
* digits, dot/dash/underscore only — same rules as `validateProvider`.
|
|
21
|
+
* Example: `'openai-codex'`, `'google'`, `'github'`.
|
|
22
|
+
*/
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** Authorization endpoint, e.g. https://accounts.google.com/o/oauth2/v2/auth */
|
|
25
|
+
readonly authUrl: string;
|
|
26
|
+
/** Token endpoint, e.g. https://oauth2.googleapis.com/token */
|
|
27
|
+
readonly tokenUrl: string;
|
|
28
|
+
readonly clientId: string;
|
|
29
|
+
/** Confidential clients only — loopback flows usually omit. */
|
|
30
|
+
readonly clientSecret?: string;
|
|
31
|
+
readonly scopes: ReadonlyArray<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Provider-specific auth-URL params. Google wants
|
|
34
|
+
* `access_type=offline` + `prompt=consent` for a refresh_token; Codex
|
|
35
|
+
* wants `id_token_add_organizations=true` etc.
|
|
36
|
+
*/
|
|
37
|
+
readonly extraAuthParams?: Readonly<Record<string, string>>;
|
|
38
|
+
/**
|
|
39
|
+
* Loopback callback config. Provider's registered redirect URI must
|
|
40
|
+
* match `http://localhost:<port><path>` EXACTLY.
|
|
41
|
+
*/
|
|
42
|
+
readonly redirect?: {
|
|
43
|
+
readonly port?: number;
|
|
44
|
+
readonly path?: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Device-flow adapter. Omit when the provider doesn't support a
|
|
48
|
+
* headless flow — `runOauthLogin` will reject `headless=true` with a
|
|
49
|
+
* helpful message.
|
|
50
|
+
*/
|
|
51
|
+
readonly deviceFlow?: DeviceFlowAdapter;
|
|
52
|
+
/**
|
|
53
|
+
* Pull a stable per-account identifier out of the token set. Codex
|
|
54
|
+
* extracts `chatgpt_account_id` from the id_token; Google could
|
|
55
|
+
* extract `email`. Returned value is shown to the user post-login and
|
|
56
|
+
* persisted under `extras.account_id`. Optional — providers that
|
|
57
|
+
* don't expose a useful identifier skip it.
|
|
58
|
+
*/
|
|
59
|
+
readonly extractAccountId?: (tokens: TokenSet) => string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Extract extra provider-specific fields to persist alongside the
|
|
62
|
+
* token set (e.g. `team_slug`, `org_id`). Merged with the account_id
|
|
63
|
+
* (if any) into the `extras` map under `oauth/<id>/extras`.
|
|
64
|
+
*/
|
|
65
|
+
readonly extractExtras?: (tokens: TokenSet) => Readonly<Record<string, string>>;
|
|
66
|
+
/**
|
|
67
|
+
* Human-readable name used in prompts and success messages, e.g.
|
|
68
|
+
* "ChatGPT Pro/Plus", "Google Workspace". Falls back to `id` when omitted.
|
|
69
|
+
*/
|
|
70
|
+
readonly displayName?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Pluggable device-flow contract. Implementations encode a single dialect
|
|
74
|
+
* (RFC 8628, OpenAI's flavor, ...). The framework calls `start()` once,
|
|
75
|
+
* surfaces the returned prompt to the user, then modes on `poll()` via
|
|
76
|
+
* the shared `pollUntil` primitive.
|
|
77
|
+
*/
|
|
78
|
+
export interface DeviceFlowAdapter {
|
|
79
|
+
start(args: DeviceFlowStartArgs): Promise<DeviceFlowInit>;
|
|
80
|
+
/**
|
|
81
|
+
* Single poll attempt against the provider's poll endpoint. May
|
|
82
|
+
* mutate `state.intervalMs` to honour a `slow_down` from the server.
|
|
83
|
+
* Resolves to `{done}` with the issued TokenSet, or `{pending: true}`
|
|
84
|
+
* to keep polling. Throws on fatal errors (denied / expired).
|
|
85
|
+
*/
|
|
86
|
+
poll(init: DeviceFlowInit, state: PollState): Promise<PollOutcome<TokenSet>>;
|
|
87
|
+
}
|
|
88
|
+
export interface DeviceFlowStartArgs {
|
|
89
|
+
readonly clientId: string;
|
|
90
|
+
readonly clientSecret?: string;
|
|
91
|
+
readonly scopes: ReadonlyArray<string>;
|
|
92
|
+
readonly signal?: AbortSignal;
|
|
93
|
+
}
|
|
94
|
+
export interface DeviceFlowInit {
|
|
95
|
+
/** Short code the user enters at `verificationUri`. */
|
|
96
|
+
readonly userCode: string;
|
|
97
|
+
readonly verificationUri: string;
|
|
98
|
+
/** Some providers return a URL that already embeds the user_code. */
|
|
99
|
+
readonly verificationUriComplete?: string;
|
|
100
|
+
/** Initial poll interval. The framework honours `slow_down` bumps. */
|
|
101
|
+
readonly intervalMs: number;
|
|
102
|
+
/** Hard deadline (ms from now) before the device_code dies. */
|
|
103
|
+
readonly expiresInMs: number;
|
|
104
|
+
/**
|
|
105
|
+
* Opaque data the adapter threads from `start()` to `poll()` —
|
|
106
|
+
* device_code, server-assigned ids, code_verifier, etc. The framework
|
|
107
|
+
* never inspects this.
|
|
108
|
+
*/
|
|
109
|
+
readonly providerData: unknown;
|
|
110
|
+
}
|
|
111
|
+
export interface RunOauthLoginCtx {
|
|
112
|
+
readonly vault: {
|
|
113
|
+
get(key: string): Promise<string | null>;
|
|
114
|
+
set(key: string, value: string, tags?: ReadonlyArray<string>): Promise<void>;
|
|
115
|
+
delete?(key: string): Promise<boolean>;
|
|
116
|
+
};
|
|
117
|
+
/** True ⇒ choose the device flow. False ⇒ open a browser. */
|
|
118
|
+
readonly headless: boolean;
|
|
119
|
+
/** Progress-message sink. The host wires this to its preferred renderer. */
|
|
120
|
+
readonly write: (chunk: string) => void;
|
|
121
|
+
readonly signal?: AbortSignal;
|
|
122
|
+
}
|
|
123
|
+
export interface RunOauthLoginResult {
|
|
124
|
+
readonly tokens: TokenSet;
|
|
125
|
+
/** Convenience surface for `ProviderOAuthResult.accountId`. */
|
|
126
|
+
readonly accountId?: string;
|
|
127
|
+
readonly extras: Readonly<Record<string, string>>;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-framework types. A new OAuth provider plugs in by exporting an
|
|
3
|
+
* `OAuthProviderProfile` and (if it supports device-flow) one of the bundled
|
|
4
|
+
* adapters — or a custom adapter for a non-standard dialect.
|
|
5
|
+
*
|
|
6
|
+
* The host then calls `runOauthLogin(profile, ctx)` to drive the full dance
|
|
7
|
+
* and `ensureFreshTokens(profile, vault)` before each upstream request to
|
|
8
|
+
* keep the access token fresh.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level orchestrator. Drives the full login dance for any provider
|
|
3
|
+
* declared via `OAuthProviderProfile` — picks browser or device flow based
|
|
4
|
+
* on `ctx.headless`, surfaces the user-facing prompt, persists the result
|
|
5
|
+
* (token set + extras) under `oauth/<profile.id>/*`.
|
|
6
|
+
*/
|
|
7
|
+
import type { OAuthProviderProfile, RunOauthLoginCtx, RunOauthLoginResult } from './profile.js';
|
|
8
|
+
export declare function runOauthLogin(profile: OAuthProviderProfile, ctx: RunOauthLoginCtx): Promise<RunOauthLoginResult>;
|
|
9
|
+
//# sourceMappingURL=run-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-login.d.ts","sourceRoot":"","sources":["../src/run-login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAGV,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAMtB,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAsB9B"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level orchestrator. Drives the full login dance for any provider
|
|
3
|
+
* declared via `OAuthProviderProfile` — picks browser or device flow based
|
|
4
|
+
* on `ctx.headless`, surfaces the user-facing prompt, persists the result
|
|
5
|
+
* (token set + extras) under `oauth/<profile.id>/*`.
|
|
6
|
+
*/
|
|
7
|
+
import { MoxxyError } from '@moxxy/sdk';
|
|
8
|
+
import { runAuthorizationCodeFlow } from './oauth/browser-flow.js';
|
|
9
|
+
import { pollUntil } from './oauth/poll-until.js';
|
|
10
|
+
import { storeTokenSet } from './storage.js';
|
|
11
|
+
const DEFAULT_BROWSER_TIMEOUT_MS = 5 * 60 * 1000;
|
|
12
|
+
const DEFAULT_DEVICE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
13
|
+
const DEVICE_POLL_SAFETY_MARGIN_MS = 3000;
|
|
14
|
+
export async function runOauthLogin(profile, ctx) {
|
|
15
|
+
const tokens = ctx.headless
|
|
16
|
+
? await runDeviceFlow(profile, ctx)
|
|
17
|
+
: await runBrowserFlow(profile, ctx);
|
|
18
|
+
const accountId = profile.extractAccountId?.(tokens);
|
|
19
|
+
const customExtras = profile.extractExtras?.(tokens) ?? {};
|
|
20
|
+
const extras = { ...customExtras };
|
|
21
|
+
if (accountId)
|
|
22
|
+
extras.account_id = accountId;
|
|
23
|
+
await storeTokenSet(ctx.vault, profile.id, tokens, {
|
|
24
|
+
clientId: profile.clientId,
|
|
25
|
+
...(profile.clientSecret ? { clientSecret: profile.clientSecret } : {}),
|
|
26
|
+
tokenUrl: profile.tokenUrl,
|
|
27
|
+
extras,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
tokens,
|
|
31
|
+
...(accountId ? { accountId } : {}),
|
|
32
|
+
extras,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function runBrowserFlow(profile, ctx) {
|
|
36
|
+
const port = profile.redirect?.port ?? 8765;
|
|
37
|
+
const path = profile.redirect?.path ?? '/callback';
|
|
38
|
+
const serviceName = profile.displayName ?? profile.id;
|
|
39
|
+
return runAuthorizationCodeFlow({
|
|
40
|
+
authUrl: profile.authUrl,
|
|
41
|
+
tokenUrl: profile.tokenUrl,
|
|
42
|
+
clientId: profile.clientId,
|
|
43
|
+
...(profile.clientSecret ? { clientSecret: profile.clientSecret } : {}),
|
|
44
|
+
scopes: profile.scopes,
|
|
45
|
+
redirectPort: port,
|
|
46
|
+
redirectPath: path,
|
|
47
|
+
...(profile.extraAuthParams ? { extraAuthParams: profile.extraAuthParams } : {}),
|
|
48
|
+
timeoutMs: DEFAULT_BROWSER_TIMEOUT_MS,
|
|
49
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
50
|
+
onAuthUrl: (url) => {
|
|
51
|
+
ctx.write(`\nSign in to ${serviceName} to authorize moxxy\n\n` +
|
|
52
|
+
`If your browser doesn't open automatically, paste this URL:\n\n ${url}\n\n` +
|
|
53
|
+
`Waiting for callback on http://localhost:${port}${path} (5 min timeout)…\n\n`);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async function runDeviceFlow(profile, ctx) {
|
|
58
|
+
if (!profile.deviceFlow) {
|
|
59
|
+
throw new MoxxyError({
|
|
60
|
+
code: 'OAUTH_FLOW_NOT_SUPPORTED',
|
|
61
|
+
message: `Provider "${profile.id}" doesn't expose a headless (device-code) flow.`,
|
|
62
|
+
hint: 'Run this command on a host with a browser, or ask the provider plugin author to ' +
|
|
63
|
+
'add a deviceFlow adapter to the profile.',
|
|
64
|
+
context: { provider: profile.id },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const adapter = profile.deviceFlow;
|
|
68
|
+
const init = await adapter.start({
|
|
69
|
+
clientId: profile.clientId,
|
|
70
|
+
...(profile.clientSecret ? { clientSecret: profile.clientSecret } : {}),
|
|
71
|
+
scopes: profile.scopes,
|
|
72
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
73
|
+
});
|
|
74
|
+
const serviceName = profile.displayName ?? profile.id;
|
|
75
|
+
ctx.write(`\nSign in to ${serviceName} (headless / device code flow)\n\n` +
|
|
76
|
+
` 1. On any browser-capable device, open:\n` +
|
|
77
|
+
` ${init.verificationUriComplete ?? init.verificationUri}\n\n` +
|
|
78
|
+
` 2. Enter this code:\n` +
|
|
79
|
+
` ${init.userCode}\n\n` +
|
|
80
|
+
`Polling every ${Math.round(init.intervalMs / 1000)}s ` +
|
|
81
|
+
`(${Math.round(init.expiresInMs / 60000)} min timeout)…\n\n`);
|
|
82
|
+
// Cap the polling deadline by the device_code's own expiry — once it
|
|
83
|
+
// dies, any further poll is wasted.
|
|
84
|
+
const timeoutMs = Math.min(init.expiresInMs, DEFAULT_DEVICE_TIMEOUT_MS);
|
|
85
|
+
return pollUntil((state) => adapter.poll(init, state), {
|
|
86
|
+
intervalMs: init.intervalMs + DEVICE_POLL_SAFETY_MARGIN_MS,
|
|
87
|
+
timeoutMs,
|
|
88
|
+
label: `${profile.id} device flow`,
|
|
89
|
+
leadingWait: true,
|
|
90
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=run-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-login.js","sourceRoot":"","sources":["../src/run-login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TokenSet } from './flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal vault shape this module uses. Both `@moxxy/plugin-vault`'s
|
|
4
|
+
* `VaultStore` and the SDK's `ProviderVault` structurally satisfy it, so
|
|
5
|
+
* callers can pass whichever they hold.
|
|
6
|
+
*/
|
|
7
|
+
export interface OAuthVault {
|
|
8
|
+
get(key: string): Promise<string | null>;
|
|
9
|
+
set(key: string, value: string, tags?: ReadonlyArray<string>): Promise<void>;
|
|
10
|
+
delete?(key: string): Promise<boolean>;
|
|
11
|
+
}
|
|
12
|
+
export declare function validateProvider(name: string): void;
|
|
13
|
+
export interface StoreTokenSetMeta {
|
|
14
|
+
readonly clientId: string;
|
|
15
|
+
readonly clientSecret?: string;
|
|
16
|
+
readonly tokenUrl: string;
|
|
17
|
+
/**
|
|
18
|
+
* Provider-specific extras the framework persists alongside the token
|
|
19
|
+
* set. Used for fields like `account_id`, `org_id`, `team_slug` that
|
|
20
|
+
* don't fit `TokenSet` but the caller wants to recover later.
|
|
21
|
+
*/
|
|
22
|
+
readonly extras?: Readonly<Record<string, string>>;
|
|
23
|
+
}
|
|
24
|
+
export interface StoredCreds {
|
|
25
|
+
readonly tokenSet: TokenSet;
|
|
26
|
+
readonly clientId: string;
|
|
27
|
+
readonly clientSecret: string | null;
|
|
28
|
+
readonly tokenUrl: string;
|
|
29
|
+
readonly extras: Readonly<Record<string, string>>;
|
|
30
|
+
}
|
|
31
|
+
export declare function storeTokenSet(vault: OAuthVault, provider: string, tokens: TokenSet, meta: StoreTokenSetMeta): Promise<void>;
|
|
32
|
+
export declare function readStoredCreds(vault: OAuthVault, provider: string): Promise<StoredCreds | null>;
|
|
33
|
+
export declare function clearStoredCreds(vault: OAuthVault, provider: string): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* True when the access token has expired (or is within `skewMs` of doing so).
|
|
36
|
+
*
|
|
37
|
+
* A token with no `expiresAt` is treated as non-expiring (some providers issue
|
|
38
|
+
* tokens without `expires_in`). A non-FINITE `expiresAt` (NaN/Infinity — corrupt
|
|
39
|
+
* data, an out-of-range parse) is treated as EXPIRED, never as fresh: a bare
|
|
40
|
+
* `>=` against NaN is always false, which would otherwise mark a token eternally
|
|
41
|
+
* valid and suppress every refresh. Forcing the expired branch degrades to a
|
|
42
|
+
* refresh (or a clear AUTH_EXPIRED if no refresh_token) instead of silently
|
|
43
|
+
* serving a token whose real expiry is unknown.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isExpired(tokens: TokenSet, skewMs?: number): boolean;
|
|
46
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAqB1C;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMnD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACnD;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,IAAI,CAAC,CAkCf;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAiD7B;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoB3F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,GAAG,OAAO,CAIpE"}
|