@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,167 @@
1
+ /**
2
+ * Vault key convention:
3
+ * oauth/<provider>/access_token
4
+ * oauth/<provider>/refresh_token
5
+ * oauth/<provider>/expires_at (epoch ms, string)
6
+ * oauth/<provider>/scope
7
+ * oauth/<provider>/token_type
8
+ * oauth/<provider>/id_token (OIDC providers only)
9
+ * oauth/<provider>/client_id (so refresh doesn't need the model to re-pass it)
10
+ * oauth/<provider>/client_secret (confidential clients only)
11
+ * oauth/<provider>/token_url (so refresh works without re-supplying)
12
+ * oauth/<provider>/extras (JSON map of provider-specific fields, e.g. account_id)
13
+ *
14
+ * Provider names are namespaced with a `/` so the vault's `list-by-tag`
15
+ * UI groups them. Provider names MUST be `[a-z0-9._-]+`; the schema in
16
+ * the tool layer enforces this.
17
+ */
18
+ const PROVIDER_RE = /^[a-z0-9._-]+$/;
19
+ export function validateProvider(name) {
20
+ if (!PROVIDER_RE.test(name)) {
21
+ throw new Error(`oauth provider name "${name}" must match ${PROVIDER_RE.source} (lowercase, digits, ._-)`);
22
+ }
23
+ }
24
+ export async function storeTokenSet(vault, provider, tokens, meta) {
25
+ validateProvider(provider);
26
+ const tag = `oauth:${provider}`;
27
+ const base = `oauth/${provider}`;
28
+ // Write each optional field when present, but DELETE the stored key when the
29
+ // new TokenSet omits it, so the persisted bundle exactly mirrors the live
30
+ // TokenSet. A refresh response that omits expires_in/scope/id_token (RFC 6749
31
+ // §5.1 permits this) must not leave a stale expires_at (which would make
32
+ // isExpired() loop forever or trust a token off dead data) or a stale OIDC
33
+ // id_token (which extractAccountId/extractExtras might re-derive identity
34
+ // from). Callers preserve a rotated-or-prior refresh_token BEFORE calling us
35
+ // (RFC 6749 §6: a refresh MAY omit refresh_token), so an undefined
36
+ // refreshToken here genuinely means "no refresh token" — mirror that too.
37
+ const upsert = async (key, value) => {
38
+ if (value !== undefined) {
39
+ await vault.set(`${base}/${key}`, value, [tag]);
40
+ }
41
+ else {
42
+ await vault.delete?.(`${base}/${key}`);
43
+ }
44
+ };
45
+ await vault.set(`${base}/access_token`, tokens.accessToken, [tag]);
46
+ await upsert('refresh_token', tokens.refreshToken);
47
+ await upsert('expires_at', tokens.expiresAt !== undefined ? String(tokens.expiresAt) : undefined);
48
+ await upsert('scope', tokens.scope);
49
+ await vault.set(`${base}/token_type`, tokens.tokenType, [tag]);
50
+ await upsert('id_token', tokens.idToken);
51
+ await vault.set(`${base}/client_id`, meta.clientId, [tag]);
52
+ if (meta.clientSecret) {
53
+ await vault.set(`${base}/client_secret`, meta.clientSecret, [tag]);
54
+ }
55
+ await vault.set(`${base}/token_url`, meta.tokenUrl, [tag]);
56
+ if (meta.extras && Object.keys(meta.extras).length > 0) {
57
+ await vault.set(`${base}/extras`, JSON.stringify(meta.extras), [tag]);
58
+ }
59
+ }
60
+ export async function readStoredCreds(vault, provider) {
61
+ validateProvider(provider);
62
+ const base = `oauth/${provider}`;
63
+ const access = await vault.get(`${base}/access_token`);
64
+ if (!access)
65
+ return null;
66
+ // The remaining nine keys are independent reads with no ordering dependency;
67
+ // ensureFreshTokens runs this before every upstream LLM call, so fetch them
68
+ // concurrently instead of serializing ~9 vault round-trips on the hot path.
69
+ const [refresh, expiresStr, scope, tokenTypeRaw, idToken, clientId, clientSecret, tokenUrl, extrasRaw] = await Promise.all([
70
+ vault.get(`${base}/refresh_token`),
71
+ vault.get(`${base}/expires_at`),
72
+ vault.get(`${base}/scope`),
73
+ vault.get(`${base}/token_type`),
74
+ vault.get(`${base}/id_token`),
75
+ vault.get(`${base}/client_id`),
76
+ vault.get(`${base}/client_secret`),
77
+ vault.get(`${base}/token_url`),
78
+ vault.get(`${base}/extras`),
79
+ ]);
80
+ const tokenType = tokenTypeRaw ?? 'Bearer';
81
+ if (!clientId || !tokenUrl) {
82
+ // Missing setup-meta means a partial store — refresh impossible. Treat as absent.
83
+ return null;
84
+ }
85
+ // A stored `expires_at` that doesn't parse to a finite number (corrupt vault
86
+ // write, a hand-edited value, a partial flush) is poison: carried through as
87
+ // NaN it makes `isExpired` return false (NaN comparisons are always false),
88
+ // so the token would read as ETERNALLY fresh and never refresh — the user
89
+ // silently keeps presenting a possibly-dead token. Drop the unparseable value
90
+ // and let `expiresAt` go undefined; `isExpired` then defends the
91
+ // no-known-expiry case explicitly (see below).
92
+ const expiresAtParsed = expiresStr !== null ? Number.parseInt(expiresStr, 10) : null;
93
+ const expiresAt = expiresAtParsed !== null && Number.isFinite(expiresAtParsed) ? expiresAtParsed : undefined;
94
+ return {
95
+ tokenSet: {
96
+ accessToken: access,
97
+ ...(refresh !== null ? { refreshToken: refresh } : {}),
98
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
99
+ ...(scope !== null ? { scope } : {}),
100
+ tokenType,
101
+ ...(idToken !== null ? { idToken } : {}),
102
+ },
103
+ clientId,
104
+ clientSecret,
105
+ tokenUrl,
106
+ extras: parseExtras(extrasRaw),
107
+ };
108
+ }
109
+ export async function clearStoredCreds(vault, provider) {
110
+ validateProvider(provider);
111
+ const base = `oauth/${provider}`;
112
+ const keys = [
113
+ `${base}/access_token`,
114
+ `${base}/refresh_token`,
115
+ `${base}/expires_at`,
116
+ `${base}/scope`,
117
+ `${base}/token_type`,
118
+ `${base}/id_token`,
119
+ `${base}/client_id`,
120
+ `${base}/client_secret`,
121
+ `${base}/token_url`,
122
+ `${base}/extras`,
123
+ ];
124
+ let removed = 0;
125
+ for (const k of keys) {
126
+ if (await vault.delete?.(k))
127
+ removed += 1;
128
+ }
129
+ return removed;
130
+ }
131
+ /**
132
+ * True when the access token has expired (or is within `skewMs` of doing so).
133
+ *
134
+ * A token with no `expiresAt` is treated as non-expiring (some providers issue
135
+ * tokens without `expires_in`). A non-FINITE `expiresAt` (NaN/Infinity — corrupt
136
+ * data, an out-of-range parse) is treated as EXPIRED, never as fresh: a bare
137
+ * `>=` against NaN is always false, which would otherwise mark a token eternally
138
+ * valid and suppress every refresh. Forcing the expired branch degrades to a
139
+ * refresh (or a clear AUTH_EXPIRED if no refresh_token) instead of silently
140
+ * serving a token whose real expiry is unknown.
141
+ */
142
+ export function isExpired(tokens, skewMs = 60_000) {
143
+ if (tokens.expiresAt === undefined)
144
+ return false;
145
+ if (!Number.isFinite(tokens.expiresAt))
146
+ return true;
147
+ return Date.now() + skewMs >= tokens.expiresAt;
148
+ }
149
+ function parseExtras(raw) {
150
+ if (!raw)
151
+ return {};
152
+ try {
153
+ const parsed = JSON.parse(raw);
154
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
155
+ return {};
156
+ const out = {};
157
+ for (const [k, v] of Object.entries(parsed)) {
158
+ if (typeof v === 'string')
159
+ out[k] = v;
160
+ }
161
+ return out;
162
+ }
163
+ catch {
164
+ return {};
165
+ }
166
+ }
167
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { VaultStore } from '@moxxy/plugin-vault';
2
+ export interface OAuthToolDeps {
3
+ readonly vault: VaultStore;
4
+ }
5
+ export declare function buildOauthAuthorizeTool(deps: OAuthToolDeps): import("@moxxy/sdk").ToolDef;
6
+ export declare function buildOauthGetTokenTool(deps: OAuthToolDeps): import("@moxxy/sdk").ToolDef;
7
+ export declare function buildOauthClearTool(deps: OAuthToolDeps): import("@moxxy/sdk").ToolDef;
8
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAmBtD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAWD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,gCAuK1D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,gCAkEzD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,gCActD"}
package/dist/tools.js ADDED
@@ -0,0 +1,283 @@
1
+ import { defineTool, MoxxyError, z } from '@moxxy/sdk';
2
+ import { buildAuthUrl, runAuthorizationCodeFlow, runDeviceCodeFlow, } from './flow.js';
3
+ import { generateCodeVerifier, generateState } from './pkce.js';
4
+ import { clearStoredCreds, isExpired, readStoredCreds, storeTokenSet, validateProvider, } from './storage.js';
5
+ import { withCredentialLock } from './credential-lock.js';
6
+ import { refreshAndStore } from './ensure-fresh.js';
7
+ const providerNameField = z
8
+ .string()
9
+ .min(1)
10
+ .max(60)
11
+ .describe('Stable provider key used as the vault namespace (e.g. "google", "github", ' +
12
+ '"notion"). Lowercase letters, digits, dot/dash/underscore only.');
13
+ export function buildOauthAuthorizeTool(deps) {
14
+ return defineTool({
15
+ name: 'oauth_authorize',
16
+ description: 'Run an OAuth 2.0 authorization-code flow (PKCE) against any provider, ' +
17
+ 'or RFC 8628 device-code flow for headless hosts. Opens the user\'s ' +
18
+ 'browser (loopback mode), or prints a code to type on another device ' +
19
+ '(device mode). Stores the resulting tokens in the vault under ' +
20
+ '`oauth/<provider>/*`. Subsequent `oauth_get_token` calls auto-refresh ' +
21
+ 'when the access token expires. Returns when the user finishes the dance.',
22
+ inputSchema: z.object({
23
+ provider: providerNameField,
24
+ authUrl: z
25
+ .string()
26
+ .url()
27
+ .optional()
28
+ .describe('Provider authorization endpoint. Required for `mode: "loopback"`. ' +
29
+ 'E.g. https://accounts.google.com/o/oauth2/v2/auth'),
30
+ tokenUrl: z
31
+ .string()
32
+ .url()
33
+ .describe('Provider token endpoint. E.g. https://oauth2.googleapis.com/token'),
34
+ deviceUrl: z
35
+ .string()
36
+ .url()
37
+ .optional()
38
+ .describe('Provider device-authorization endpoint. Required for `mode: "device"`. ' +
39
+ 'E.g. https://oauth2.googleapis.com/device/code'),
40
+ clientId: z.string().min(1),
41
+ clientSecret: z
42
+ .string()
43
+ .optional()
44
+ .describe('Required for confidential clients (most server-side apps). Omit for native/installed apps using PKCE alone.'),
45
+ scopes: z.array(z.string().min(1)).min(1),
46
+ mode: z
47
+ .enum(['loopback', 'device'])
48
+ .optional()
49
+ .describe('Flow mode. "loopback" (default) opens a browser + local callback ' +
50
+ 'server. "device" prints a code for the user to type into another ' +
51
+ 'device — use when running headless (SSH session, CI, no display).'),
52
+ redirectPort: z
53
+ .number()
54
+ .int()
55
+ .positive()
56
+ .max(65535)
57
+ .optional()
58
+ .describe('Loopback-mode redirect port. Default 8765. MUST match what you registered with the provider.'),
59
+ redirectPath: z
60
+ .string()
61
+ .optional()
62
+ .describe('Loopback-mode redirect path. Default "/callback".'),
63
+ extraAuthParams: z
64
+ .record(z.string(), z.string())
65
+ .optional()
66
+ .describe('Provider-specific auth-URL params. Google wants `access_type=offline` + `prompt=consent` ' +
67
+ 'to issue a refresh_token; Auth0 may want `audience=...`.'),
68
+ noOpen: z
69
+ .boolean()
70
+ .optional()
71
+ .describe('Loopback-mode only: skip auto-opening the browser. The auth URL is ' +
72
+ 'logged via the tool logger so the user can open it manually on the ' +
73
+ 'host where the loopback server is reachable.'),
74
+ }),
75
+ permission: { action: 'prompt' },
76
+ async handler(input, ctx) {
77
+ validateProvider(input.provider);
78
+ const mode = input.mode ?? 'loopback';
79
+ let tokens;
80
+ if (mode === 'device') {
81
+ if (!input.deviceUrl) {
82
+ throw new MoxxyError({
83
+ code: 'TOOL_ERROR',
84
+ message: 'mode="device" requires `deviceUrl` (the provider\'s device-authorization endpoint)',
85
+ });
86
+ }
87
+ tokens = await runDeviceCodeFlow({
88
+ deviceUrl: input.deviceUrl,
89
+ tokenUrl: input.tokenUrl,
90
+ clientId: input.clientId,
91
+ ...(input.clientSecret ? { clientSecret: input.clientSecret } : {}),
92
+ scopes: input.scopes,
93
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
94
+ onPrompt: (info) => {
95
+ // Surface the prompt prominently via the tool logger so the
96
+ // channel renders it as a system notice. The model can also
97
+ // see this in the event log and echo it to the user.
98
+ ctx.logger.info('oauth_authorize: device flow — visit URL', {
99
+ verificationUri: info.verificationUri,
100
+ ...(info.verificationUriComplete ? { verificationUriComplete: info.verificationUriComplete } : {}),
101
+ userCode: info.userCode,
102
+ expiresInSec: info.expiresIn,
103
+ });
104
+ // Belt-and-suspenders: stderr in case the logger isn't
105
+ // wired to a visible surface in the current channel.
106
+ process.stderr.write(`\n Open ${info.verificationUri} and enter code: ${info.userCode}\n` +
107
+ (info.verificationUriComplete
108
+ ? ` (or visit directly: ${info.verificationUriComplete})\n`
109
+ : '') +
110
+ ` Code expires in ${Math.floor(info.expiresIn / 60)}m.\n\n`);
111
+ },
112
+ });
113
+ }
114
+ else {
115
+ if (!input.authUrl) {
116
+ throw new MoxxyError({
117
+ code: 'TOOL_ERROR',
118
+ message: 'mode="loopback" requires `authUrl` (the provider\'s authorization endpoint)',
119
+ });
120
+ }
121
+ if (input.noOpen) {
122
+ // Compute + log the URL without spawning a browser.
123
+ const verifier = generateCodeVerifier();
124
+ const state = generateState();
125
+ const { computeCodeChallenge } = await import('./pkce.js');
126
+ const challenge = computeCodeChallenge(verifier);
127
+ const port = input.redirectPort ?? 8765;
128
+ const path = input.redirectPath ?? '/callback';
129
+ const url = buildAuthUrl({
130
+ authUrl: input.authUrl,
131
+ clientId: input.clientId,
132
+ redirectUri: `http://localhost:${port}${path}`,
133
+ scopes: input.scopes,
134
+ codeChallenge: challenge,
135
+ state,
136
+ ...(input.extraAuthParams ? { extraAuthParams: input.extraAuthParams } : {}),
137
+ });
138
+ ctx.logger.info('oauth_authorize: open this URL manually', { url });
139
+ process.stderr.write(`\n Open this URL in a browser on this host:\n ${url}\n\n`);
140
+ }
141
+ tokens = await runAuthorizationCodeFlow({
142
+ authUrl: input.authUrl,
143
+ tokenUrl: input.tokenUrl,
144
+ clientId: input.clientId,
145
+ ...(input.clientSecret ? { clientSecret: input.clientSecret } : {}),
146
+ scopes: input.scopes,
147
+ ...(input.redirectPort !== undefined ? { redirectPort: input.redirectPort } : {}),
148
+ ...(input.redirectPath !== undefined ? { redirectPath: input.redirectPath } : {}),
149
+ ...(input.extraAuthParams ? { extraAuthParams: input.extraAuthParams } : {}),
150
+ ...(input.noOpen ? { noOpen: true } : {}),
151
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
152
+ onAuthUrl: (url) => {
153
+ ctx.logger.info('oauth_authorize: opening browser', { url });
154
+ },
155
+ });
156
+ }
157
+ await storeTokenSet(deps.vault, input.provider, tokens, {
158
+ clientId: input.clientId,
159
+ ...(input.clientSecret ? { clientSecret: input.clientSecret } : {}),
160
+ tokenUrl: input.tokenUrl,
161
+ });
162
+ return summarizeTokens(input.provider, tokens);
163
+ },
164
+ });
165
+ }
166
+ export function buildOauthGetTokenTool(deps) {
167
+ return defineTool({
168
+ name: 'oauth_get_token',
169
+ description: 'Read the stored access token for an OAuth provider, transparently ' +
170
+ 'refreshing it via refresh_token if it has expired. Returns the bearer ' +
171
+ 'token ready to drop into an Authorization header. Throws if no token ' +
172
+ 'has been stored — call `oauth_authorize` first.',
173
+ inputSchema: z.object({
174
+ provider: providerNameField,
175
+ forceRefresh: z
176
+ .boolean()
177
+ .optional()
178
+ .describe('Refresh even if the cached token has not expired yet.'),
179
+ includeRefresh: z
180
+ .boolean()
181
+ .optional()
182
+ .describe('Also return the refresh_token. Default false — the refresh_token ' +
183
+ 'is more sensitive than the access_token and most callers never ' +
184
+ 'need it. Set true when wiring an MCP server (or other long-lived ' +
185
+ 'subprocess) that needs to mint its own access tokens.'),
186
+ }),
187
+ permission: { action: 'prompt' },
188
+ async handler({ provider, forceRefresh, includeRefresh }, _ctx) {
189
+ const stored = await readStoredCreds(deps.vault, provider);
190
+ if (!stored) {
191
+ throw new MoxxyError({
192
+ code: 'AUTH_NO_CREDENTIALS',
193
+ message: `no stored OAuth credentials for "${provider}". Run oauth_authorize first.`,
194
+ context: { provider },
195
+ });
196
+ }
197
+ // Fast path: a still-fresh token needs no lock and no network. Refreshing
198
+ // a rotating single-use refresh_token, on the other hand, MUST serialize
199
+ // per credential (a second concurrent refresher — another oauth_get_token
200
+ // call OR a provider's ensureFreshTokens sharing this vault credential —
201
+ // would otherwise burn the now-rotated token and log the user out). Route
202
+ // the refresh+persist critical section through the same per-credential
203
+ // lock ensure-fresh.ts uses, re-reading inside the lock so a queued
204
+ // waiter reuses the winner's rotated token instead of re-racing.
205
+ if (!forceRefresh && !isExpired(stored.tokenSet)) {
206
+ return summarizeTokens(provider, stored.tokenSet, {
207
+ includeAccess: true,
208
+ ...(includeRefresh ? { includeRefresh: true } : {}),
209
+ });
210
+ }
211
+ const tokens = await withCredentialLock(`oauth-${provider}`, async () => {
212
+ // Re-read under the lock: another consumer/process may have refreshed
213
+ // while we queued. Reuse a now-fresh token even under forceRefresh,
214
+ // which exists for 401 recovery and is satisfied by ANY rotation.
215
+ const current = (await readStoredCreds(deps.vault, provider)) ?? stored;
216
+ const rotatedMeanwhile = current.tokenSet.accessToken !== stored.tokenSet.accessToken;
217
+ if (!isExpired(current.tokenSet) && (!forceRefresh || rotatedMeanwhile)) {
218
+ return current.tokenSet;
219
+ }
220
+ const { tokens } = await refreshAndStore(deps.vault, toolRefreshSpec(provider), current);
221
+ return tokens;
222
+ });
223
+ return summarizeTokens(provider, tokens, {
224
+ includeAccess: true,
225
+ ...(includeRefresh ? { includeRefresh: true } : {}),
226
+ });
227
+ },
228
+ });
229
+ }
230
+ export function buildOauthClearTool(deps) {
231
+ return defineTool({
232
+ name: 'oauth_clear_token',
233
+ description: 'Delete every stored credential for an OAuth provider — access token, ' +
234
+ 'refresh token, client config, etc. Use when the user wants to revoke ' +
235
+ 'the grant or re-do the flow with different scopes.',
236
+ inputSchema: z.object({ provider: providerNameField }),
237
+ permission: { action: 'prompt' },
238
+ async handler({ provider }) {
239
+ const removed = await clearStoredCreds(deps.vault, provider);
240
+ return { ok: true, provider, removedKeys: removed };
241
+ },
242
+ });
243
+ }
244
+ /**
245
+ * RefreshSpec for `oauth_get_token`, which has no provider profile — it works
246
+ * off raw StoredCreds. Mirrors the shared `refreshAndStore` critical section in
247
+ * ensure-fresh.ts (lock-guarded; preserves a rotated-or-prior refresh_token per
248
+ * RFC 6749 §6; recovers once from an invalid_grant when another process rotated
249
+ * our refresh_token away). Re-persists the stored extras so a store-layer change
250
+ * can't silently wipe account_id on a tool-driven refresh, and re-throws a
251
+ * non-network refresh failure verbatim (the tool has no friendlier wording).
252
+ */
253
+ function toolRefreshSpec(provider) {
254
+ return {
255
+ provider,
256
+ noRefreshTokenError: () => new MoxxyError({
257
+ code: 'AUTH_EXPIRED',
258
+ message: `OAuth token for "${provider}" expired and no refresh_token is stored. Re-run oauth_authorize.`,
259
+ context: { provider },
260
+ }),
261
+ wrapRefreshFailure: (err) => err,
262
+ resolveExtras: (_merged, storedExtras) => Object.keys(storedExtras).length > 0 ? { ...storedExtras } : undefined,
263
+ };
264
+ }
265
+ function summarizeTokens(provider, tokens, opts = {}) {
266
+ return {
267
+ provider,
268
+ tokenType: tokens.tokenType,
269
+ expiresAt: tokens.expiresAt ?? null,
270
+ scope: tokens.scope ?? null,
271
+ hasRefreshToken: tokens.refreshToken !== undefined,
272
+ // The actual access_token only flows through `oauth_get_token`
273
+ // (the model needs it to make API calls). For `oauth_authorize`
274
+ // the response is a confirmation; the token stays in the vault.
275
+ ...(opts.includeAccess
276
+ ? { accessToken: tokens.accessToken, ...(tokens.idToken ? { idToken: tokens.idToken } : {}) }
277
+ : {}),
278
+ ...(opts.includeRefresh && tokens.refreshToken !== undefined
279
+ ? { refreshToken: tokens.refreshToken }
280
+ : {}),
281
+ };
282
+ }
283
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@moxxy/plugin-oauth",
3
+ "version": "0.0.33",
4
+ "description": "Generic OAuth 2.0 + PKCE client. Loopback callback server, vault-backed token storage, refresh helpers. Bundled skills for Google Workspace + arbitrary providers.",
5
+ "keywords": [
6
+ "moxxy",
7
+ "oauth",
8
+ "pkce",
9
+ "auth",
10
+ "plugin"
11
+ ],
12
+ "homepage": "https://moxxy.ai",
13
+ "bugs": {
14
+ "url": "https://github.com/moxxy-ai/moxxy/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/moxxy-ai/moxxy.git",
19
+ "directory": "packages/plugin-oauth"
20
+ },
21
+ "author": "Michal Makowski <michal.makowski97@gmail.com>",
22
+ "license": "MIT",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ },
34
+ "./skills/*": "./skills/*"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src",
39
+ "skills"
40
+ ],
41
+ "moxxy": {
42
+ "plugin": {
43
+ "entry": "./dist/index.js",
44
+ "kind": "tools",
45
+ "skills": "./skills"
46
+ },
47
+ "requirements": [
48
+ {
49
+ "kind": "plugin",
50
+ "name": "@moxxy/plugin-vault",
51
+ "hint": "oauth resolves the vault from the service registry to store tokens; @moxxy/plugin-vault must load first"
52
+ }
53
+ ]
54
+ },
55
+ "dependencies": {
56
+ "zod": "^3.24.0",
57
+ "@moxxy/sdk": "0.21.1",
58
+ "@moxxy/plugin-vault": "0.0.33"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^22.10.0",
62
+ "typescript": "^5.7.3",
63
+ "vitest": "^2.1.8",
64
+ "@moxxy/tsconfig": "0.0.0",
65
+ "@moxxy/vitest-preset": "0.0.0"
66
+ },
67
+ "scripts": {
68
+ "build": "tsc -p tsconfig.json",
69
+ "typecheck": "tsc -p tsconfig.json --noEmit",
70
+ "test": "vitest run",
71
+ "clean": "rm -rf dist .turbo"
72
+ }
73
+ }