latchkey 2.17.2 → 2.19.0
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/README.md +1 -1
- package/dist/skills/generic/latchkey/SKILL.md +1 -1
- package/dist/skills/openclaw/latchkey/SKILL.md +1 -1
- package/dist/src/cli.js +0 -0
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/cliCommands.js +36 -4
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/gateway/latchkeyEndpoint.d.ts +25 -0
- package/dist/src/gateway/latchkeyEndpoint.d.ts.map +1 -1
- package/dist/src/gateway/latchkeyEndpoint.js +13 -2
- package/dist/src/gateway/latchkeyEndpoint.js.map +1 -1
- package/dist/src/serviceRegistry.d.ts.map +1 -1
- package/dist/src/serviceRegistry.js +2 -1
- package/dist/src/serviceRegistry.js.map +1 -1
- package/dist/src/services/core/base.d.ts +44 -0
- package/dist/src/services/core/base.d.ts.map +1 -1
- package/dist/src/services/core/base.js +55 -0
- package/dist/src/services/core/base.js.map +1 -1
- package/dist/src/services/google/base.d.ts +21 -0
- package/dist/src/services/google/base.d.ts.map +1 -1
- package/dist/src/services/google/base.js +34 -6
- package/dist/src/services/google/base.js.map +1 -1
- package/dist/src/services/index.d.ts +2 -1
- package/dist/src/services/index.d.ts.map +1 -1
- package/dist/src/services/index.js +2 -1
- package/dist/src/services/index.js.map +1 -1
- package/dist/src/services/notion-mcp.d.ts +21 -0
- package/dist/src/services/notion-mcp.d.ts.map +1 -1
- package/dist/src/services/notion-mcp.js +21 -1
- package/dist/src/services/notion-mcp.js.map +1 -1
- package/dist/src/services/ramp.d.ts +46 -0
- package/dist/src/services/ramp.d.ts.map +1 -0
- package/dist/src/services/ramp.js +185 -0
- package/dist/src/services/ramp.js.map +1 -0
- package/dist/src/sharedOperations.d.ts +11 -0
- package/dist/src/sharedOperations.d.ts.map +1 -1
- package/dist/src/sharedOperations.js +28 -1
- package/dist/src/sharedOperations.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/tests/cli.test.js +81 -0
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/latchkeyEndpoint.test.js +57 -0
- package/dist/tests/latchkeyEndpoint.test.js.map +1 -1
- package/dist/tests/oauthUtils.test.js +15 -4
- package/dist/tests/oauthUtils.test.js.map +1 -1
- package/dist/tests/ramp-session.test.d.ts +10 -0
- package/dist/tests/ramp-session.test.d.ts.map +1 -0
- package/dist/tests/ramp-session.test.js +43 -0
- package/dist/tests/ramp-session.test.js.map +1 -0
- package/dist/tests/serviceErrors.test.d.ts +2 -0
- package/dist/tests/serviceErrors.test.d.ts.map +1 -0
- package/dist/tests/serviceErrors.test.js +19 -0
- package/dist/tests/serviceErrors.test.js.map +1 -0
- package/dist/tests/serviceRegistry.test.js +3 -1
- package/dist/tests/serviceRegistry.test.js.map +1 -1
- package/dist/tests/sharedOperations.test.js +88 -3
- package/dist/tests/sharedOperations.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ramp service implementation (browser / AI agent-key pathway, production only).
|
|
3
|
+
*
|
|
4
|
+
* `latchkey auth browser ramp` runs the OAuth 2.0 authorization-code + PKCE flow
|
|
5
|
+
* against Ramp's public client (a fixed client ID, no secret). The hosted consent
|
|
6
|
+
* screen (auth_level=auto) mints an "AI agent key"; latchkey catches the loopback
|
|
7
|
+
* callback, exchanges the code for a bearer + refresh token at
|
|
8
|
+
* `.../developer/v1/token/pkce`, and stores them as OAuthCredentials (auto-refreshed).
|
|
9
|
+
*
|
|
10
|
+
* Agent keys use the agent-tools endpoints -- POST https://api.ramp.com/developer/v1/
|
|
11
|
+
* agent-tools/<tool> with a {"rationale": ...} body (they are auth-level barred from
|
|
12
|
+
* the standard REST endpoints). Spec: https://api.ramp.com/v1/public/agent-tools/spec/.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import { OAuthCredentials } from '../apiCredentials/base.js';
|
|
16
|
+
import { exchangeCodeForTokens, generateCodeChallenge, generateCodeVerifier, refreshAccessToken, startOAuthCallbackServer, } from '../oauthUtils.js';
|
|
17
|
+
import { isBrowserClosedError, LoginCancelledError, Service, ServiceSession } from './core/base.js';
|
|
18
|
+
/** Ramp's public OAuth client (PKCE, no secret), from ramp-cli. */
|
|
19
|
+
const RAMP_OAUTH_CLIENT_ID = 'ramp_id_6pKvd0IR3d8Kuzp82SV6YgpVCZOlz68Px6s3wVsr';
|
|
20
|
+
/** Hosted authorize endpoint (where the user signs in / approves the agent key). */
|
|
21
|
+
const RAMP_AUTHORIZE_URL = 'https://app.ramp.com/v1/authorize';
|
|
22
|
+
/** PKCE token endpoint (code exchange + refresh). */
|
|
23
|
+
const RAMP_PKCE_TOKEN_ENDPOINT = 'https://api.ramp.com/developer/v1/token/pkce';
|
|
24
|
+
/** Loopback callback path; matches ramp-cli's `/callback`. */
|
|
25
|
+
const RAMP_OAUTH_CALLBACK_PATH = '/callback';
|
|
26
|
+
/** Time to wait for the user to finish the hosted login + agent-key approval. */
|
|
27
|
+
const RAMP_LOGIN_TIMEOUT_MS = 300_000;
|
|
28
|
+
/**
|
|
29
|
+
* Scopes requested on the authorize URL: exactly the scopes Ramp's agent-tools
|
|
30
|
+
* OpenAPI declares (no regular-REST-only scopes -- agent keys can't use the standard
|
|
31
|
+
* REST API anyway). Ramp grants only the subset the signed-in user is entitled to
|
|
32
|
+
* (returned in the token's `scope`), so over-requesting is harmless, but omitting a
|
|
33
|
+
* scope an endpoint needs fails at call time with DEVELOPER_7100.
|
|
34
|
+
*/
|
|
35
|
+
const RAMP_OAUTH_SCOPES = [
|
|
36
|
+
'accounting:read',
|
|
37
|
+
'ai_spend:read',
|
|
38
|
+
'approvals:write',
|
|
39
|
+
'bills:read',
|
|
40
|
+
'cards:read_agentic',
|
|
41
|
+
'cards:write',
|
|
42
|
+
'comments:write',
|
|
43
|
+
'funds:write',
|
|
44
|
+
'limits:read',
|
|
45
|
+
'limits:write',
|
|
46
|
+
'memos:read',
|
|
47
|
+
'purchase_orders:read',
|
|
48
|
+
'receipts:write',
|
|
49
|
+
'reimbursements:read',
|
|
50
|
+
'reimbursements:write',
|
|
51
|
+
'tasks:read',
|
|
52
|
+
'transactions:read',
|
|
53
|
+
'transactions:write',
|
|
54
|
+
'treasury:read',
|
|
55
|
+
'trips:read',
|
|
56
|
+
'trips:write',
|
|
57
|
+
'unified_requests:read',
|
|
58
|
+
'users:read',
|
|
59
|
+
'vendors:read',
|
|
60
|
+
'vendors:write',
|
|
61
|
+
'x402:write',
|
|
62
|
+
].join(' ');
|
|
63
|
+
/**
|
|
64
|
+
* Browser login session: runs the OAuth authorization-code + PKCE flow in a
|
|
65
|
+
* Playwright browser and returns OAuthCredentials. login() is overridden wholesale
|
|
66
|
+
* (the base template's static loginUrl + response-watching model doesn't fit a
|
|
67
|
+
* per-session authorize URL with a localhost callback), mirroring NotionMcpSession.
|
|
68
|
+
*/
|
|
69
|
+
class RampOAuthServiceSession extends ServiceSession {
|
|
70
|
+
onResponse(_response) {
|
|
71
|
+
// Not used -- login completion is signalled by the OAuth callback, not by
|
|
72
|
+
// inspecting page responses.
|
|
73
|
+
}
|
|
74
|
+
isLoginComplete() {
|
|
75
|
+
// Not used -- login() is overridden entirely.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
finalizeCredentials(_browser, _context, _oldCredentials) {
|
|
79
|
+
// Not used -- login() is overridden entirely.
|
|
80
|
+
return Promise.resolve(null);
|
|
81
|
+
}
|
|
82
|
+
async login(encryptedStorage, launchOptions = {}, _oldCredentials) {
|
|
83
|
+
const { withTempBrowserContext } = await import('../playwrightUtils.js');
|
|
84
|
+
const clientId = RAMP_OAUTH_CLIENT_ID;
|
|
85
|
+
return withTempBrowserContext(encryptedStorage, launchOptions, async ({ context }) => {
|
|
86
|
+
const page = await context.newPage();
|
|
87
|
+
const abortController = new AbortController();
|
|
88
|
+
const closeHandler = () => {
|
|
89
|
+
abortController.abort();
|
|
90
|
+
};
|
|
91
|
+
page.on('close', closeHandler);
|
|
92
|
+
context.on('close', closeHandler);
|
|
93
|
+
try {
|
|
94
|
+
// 1. Stand up the localhost callback server (random port; Ramp's public
|
|
95
|
+
// client allows arbitrary loopback ports per RFC 8252).
|
|
96
|
+
const { port, codePromise } = await startOAuthCallbackServer(RAMP_LOGIN_TIMEOUT_MS, abortController.signal, RAMP_OAUTH_CALLBACK_PATH);
|
|
97
|
+
const redirectUri = `http://localhost:${port.toString()}${RAMP_OAUTH_CALLBACK_PATH}`;
|
|
98
|
+
// 2. PKCE verifier/challenge.
|
|
99
|
+
const codeVerifier = generateCodeVerifier();
|
|
100
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
101
|
+
// 3. Open Ramp's hosted authorize screen. auth_level=auto triggers the
|
|
102
|
+
// "create/approve an AI agent key" prompt.
|
|
103
|
+
const authUrl = new URL(RAMP_AUTHORIZE_URL);
|
|
104
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
105
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
106
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
107
|
+
authUrl.searchParams.set('scope', RAMP_OAUTH_SCOPES);
|
|
108
|
+
authUrl.searchParams.set('state', randomUUID());
|
|
109
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
110
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
111
|
+
authUrl.searchParams.set('auth_level', 'auto');
|
|
112
|
+
await page.goto(authUrl.toString());
|
|
113
|
+
// 4. Wait for the user to finish and the callback to deliver the code.
|
|
114
|
+
const code = await codePromise;
|
|
115
|
+
// 5. Exchange the code for tokens (public client: no secret).
|
|
116
|
+
const tokens = exchangeCodeForTokens(RAMP_PKCE_TOKEN_ENDPOINT, code, clientId, '', redirectUri, codeVerifier);
|
|
117
|
+
const accessTokenExpiresAt = new Date(Date.now() + tokens.expires_in * 1000).toISOString();
|
|
118
|
+
await page.close();
|
|
119
|
+
// Public client: clientSecret is stored as '' so refresh sends client_id only.
|
|
120
|
+
return new OAuthCredentials(clientId, '', tokens.access_token, tokens.refresh_token, accessTokenExpiresAt);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof Error && isBrowserClosedError(error)) {
|
|
124
|
+
throw new LoginCancelledError();
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
page.off('close', closeHandler);
|
|
130
|
+
context.off('close', closeHandler);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export class Ramp extends Service {
|
|
136
|
+
name = 'ramp';
|
|
137
|
+
displayName = 'Ramp';
|
|
138
|
+
baseApiUrls = ['https://api.ramp.com/'];
|
|
139
|
+
loginUrl = 'https://app.ramp.com/';
|
|
140
|
+
info = 'Ramp agent-tools API; the REST API is not supported. ' +
|
|
141
|
+
'Docs: https://api.ramp.com/v1/public/agent-tools/spec/.';
|
|
142
|
+
// Validate credentials against `search-help-center-snippets`: the one agent-tools
|
|
143
|
+
// endpoint that requires only a valid token and no specific scope (`security:
|
|
144
|
+
// [{oauth2: []}]` in the spec), so the check works regardless of which scopes the
|
|
145
|
+
// signed-in user's agent key was granted. It's a POST taking a required
|
|
146
|
+
// {query, rationale} body; a bad token returns a non-200 (404 DEVELOPER_7002).
|
|
147
|
+
credentialCheckCurlArguments = [
|
|
148
|
+
'-X',
|
|
149
|
+
'POST',
|
|
150
|
+
'-H',
|
|
151
|
+
'Content-Type: application/json',
|
|
152
|
+
'-d',
|
|
153
|
+
'{"query":"ping","rationale":"latchkey credential check"}',
|
|
154
|
+
'https://api.ramp.com/developer/v1/agent-tools/search-help-center-snippets',
|
|
155
|
+
];
|
|
156
|
+
setCredentialsExample(serviceName) {
|
|
157
|
+
return `latchkey auth browser ${serviceName}`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Browser login: run the OAuth authorization-code + PKCE flow and store the
|
|
161
|
+
* resulting bearer + refresh token.
|
|
162
|
+
*/
|
|
163
|
+
getSession(appNamePrefix) {
|
|
164
|
+
return new RampOAuthServiceSession(this, appNamePrefix);
|
|
165
|
+
}
|
|
166
|
+
refreshCredentials(apiCredentials) {
|
|
167
|
+
// Refresh the PKCE access token with the (rotating) refresh token against the
|
|
168
|
+
// `/token/pkce` endpoint, mirroring ramp-cli.
|
|
169
|
+
if (!(apiCredentials instanceof OAuthCredentials)) {
|
|
170
|
+
return Promise.resolve(null);
|
|
171
|
+
}
|
|
172
|
+
if (apiCredentials.refreshToken === undefined || apiCredentials.refreshToken === '') {
|
|
173
|
+
return Promise.resolve(null);
|
|
174
|
+
}
|
|
175
|
+
const tokens = refreshAccessToken(RAMP_PKCE_TOKEN_ENDPOINT, apiCredentials.refreshToken, apiCredentials.clientId, apiCredentials.clientSecret);
|
|
176
|
+
if (tokens === null) {
|
|
177
|
+
return Promise.resolve(null);
|
|
178
|
+
}
|
|
179
|
+
const accessTokenExpiresAt = new Date(Date.now() + tokens.expires_in * 1000).toISOString();
|
|
180
|
+
// Ramp rotates refresh tokens; keep the old one only if none came back.
|
|
181
|
+
return Promise.resolve(new OAuthCredentials(apiCredentials.clientId, apiCredentials.clientSecret, tokens.access_token, tokens.refresh_token ?? apiCredentials.refreshToken, accessTokenExpiresAt, apiCredentials.refreshTokenExpiresAt));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export const RAMP = new Ramp();
|
|
185
|
+
//# sourceMappingURL=ramp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ramp.js","sourceRoot":"","sources":["../../../src/services/ramp.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,4 +41,15 @@ export interface AuthBrowserPrepareResult {
|
|
|
41
41
|
readonly alreadyPrepared: boolean;
|
|
42
42
|
}
|
|
43
43
|
export declare function authBrowserPrepare(registry: ServiceRegistry, apiCredentialStore: ApiCredentialStore, encryptedStorage: EncryptedStorage, config: Config, serviceName: string): Promise<AuthBrowserPrepareResult>;
|
|
44
|
+
export interface PrepareServiceResult {
|
|
45
|
+
readonly serviceName: string;
|
|
46
|
+
readonly credentialType: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Store credentials for a service from a validated JSON payload
|
|
50
|
+
* (`latchkey auth prepare <service> <json>`). The whole operation is rejected — and
|
|
51
|
+
* nothing is stored — if the JSON is malformed, fails the service's schema, or
|
|
52
|
+
* the service does not support prepare.
|
|
53
|
+
*/
|
|
54
|
+
export declare function prepareService(registry: ServiceRegistry, apiCredentialStore: ApiCredentialStore, serviceName: string, json: string): PrepareServiceResult;
|
|
44
55
|
//# sourceMappingURL=sharedOperations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedOperations.d.ts","sourceRoot":"","sources":["../../src/sharedOperations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAO9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"sharedOperations.d.ts","sourceRoot":"","sources":["../../src/sharedOperations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAO9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAY5D,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,WAAW,EAAE,MAAM;CAMhC;AAED,qBAAa,yBAA0B,SAAQ,KAAK;;CAKnD;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,WAAW,EAAE,MAAM;CAOhC;AAkCD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/C,SAAS,MAAM,EAAE,CAoBnB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IAC/C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,UAAQ,GACd,OAAO,CAAC,kBAAkB,CAAC,CAyB7B;AAED,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAAC,CAwB5F;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,gBAAgB,EAAE,gBAAgB,EAClC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,gBAAgB,EAAE,gBAAgB,EAClC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,wBAAwB,CAAC,CA8BnC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,oBAAoB,CAyBtB"}
|
|
@@ -9,7 +9,7 @@ import { ApiCredentialStatus } from './apiCredentials/base.js';
|
|
|
9
9
|
import { getCredentialStatus } from './apiCredentials/utils.js';
|
|
10
10
|
import { loadBrowserConfig } from './configDataStore.js';
|
|
11
11
|
import { BrowserDisabledError, BrowserFlowsNotSupportedError, GraphicalEnvironmentNotFoundError, hasGraphicalEnvironment, } from './playwrightUtils.js';
|
|
12
|
-
import { isBrowserClosedError, LoginCancelledError } from './services/core/base.js';
|
|
12
|
+
import { isBrowserClosedError, LoginCancelledError, PrepareInputInvalidError, PrepareNotSupportedError, } from './services/core/base.js';
|
|
13
13
|
import { RegisteredService } from './services/core/registered.js';
|
|
14
14
|
// Error classes
|
|
15
15
|
export class UnknownServiceError extends Error {
|
|
@@ -142,4 +142,31 @@ export async function authBrowserPrepare(registry, apiCredentialStore, encrypted
|
|
|
142
142
|
apiCredentialStore.save(service.name, apiCredentials);
|
|
143
143
|
return { alreadyPrepared: false };
|
|
144
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Store credentials for a service from a validated JSON payload
|
|
147
|
+
* (`latchkey auth prepare <service> <json>`). The whole operation is rejected — and
|
|
148
|
+
* nothing is stored — if the JSON is malformed, fails the service's schema, or
|
|
149
|
+
* the service does not support prepare.
|
|
150
|
+
*/
|
|
151
|
+
export function prepareService(registry, apiCredentialStore, serviceName, json) {
|
|
152
|
+
const service = lookupService(registry, serviceName);
|
|
153
|
+
// Services opt in to prepare by implementing prepareFromJson; absence is the
|
|
154
|
+
// default "not supported" state.
|
|
155
|
+
if (service.prepareFromJson === undefined) {
|
|
156
|
+
throw new PrepareNotSupportedError(serviceName);
|
|
157
|
+
}
|
|
158
|
+
let parsedJson;
|
|
159
|
+
try {
|
|
160
|
+
parsedJson = JSON.parse(json);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw new PrepareInputInvalidError(serviceName, `not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
|
+
}
|
|
165
|
+
// prepareFromJson validates against the service's schema and throws
|
|
166
|
+
// PrepareInputInvalidError on any mismatch, so a store only happens once the
|
|
167
|
+
// input is fully valid.
|
|
168
|
+
const credentials = service.prepareFromJson(parsedJson);
|
|
169
|
+
apiCredentialStore.save(service.name, credentials);
|
|
170
|
+
return { serviceName: service.name, credentialType: credentials.objectType };
|
|
171
|
+
}
|
|
145
172
|
//# sourceMappingURL=sharedOperations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedOperations.js","sourceRoot":"","sources":["../../src/sharedOperations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EACL,oBAAoB,EACpB,6BAA6B,EAC7B,iCAAiC,EACjC,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,
|
|
1
|
+
{"version":3,"file":"sharedOperations.js","sourceRoot":"","sources":["../../src/sharedOperations.ts"],"names":[],"mappings":"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"}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.19.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/src/version.js
CHANGED
package/dist/tests/cli.test.js
CHANGED
|
@@ -18,6 +18,7 @@ import { GITLAB } from '../src/services/gitlab.js';
|
|
|
18
18
|
import { GITHUB } from '../src/services/github.js';
|
|
19
19
|
import { derivePermissionsOverrideSigningKey, verifyPermissionsOverrideJwt, } from '../src/gateway/permissionsOverride.js';
|
|
20
20
|
import { TELEGRAM } from '../src/services/telegram.js';
|
|
21
|
+
import { GOOGLE_GMAIL } from '../src/services/google/gmail.js';
|
|
21
22
|
import { deleteRegisteredService, loadRegisteredServices, saveRegisteredService, } from '../src/configDataStore.js';
|
|
22
23
|
import { loadRegisteredServicesIntoServiceRegistry } from '../src/serviceRegistry.js';
|
|
23
24
|
// Use a fixed test key for deterministic test behavior (32 bytes = 256 bits, base64 encoded)
|
|
@@ -948,6 +949,55 @@ describe('CLI commands with dependency injection', () => {
|
|
|
948
949
|
expect(exitCode).toBe(1);
|
|
949
950
|
});
|
|
950
951
|
});
|
|
952
|
+
describe('prepare command', () => {
|
|
953
|
+
it('stores an OAuth client for a Google service and reports success', async () => {
|
|
954
|
+
const storePath = join(tempDir, 'credentials.json');
|
|
955
|
+
writeSecureFile(storePath, '{}');
|
|
956
|
+
const deps = createMockDependencies({
|
|
957
|
+
registry: new ServiceRegistry([GOOGLE_GMAIL]),
|
|
958
|
+
});
|
|
959
|
+
await runCommand(['auth', 'prepare', 'google-gmail', '{"clientId":"cid","clientSecret":"csecret"}'], deps);
|
|
960
|
+
expect(logs).toContain('Done');
|
|
961
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
962
|
+
expect(storedData['google-gmail']).toEqual({
|
|
963
|
+
objectType: 'oauth',
|
|
964
|
+
clientId: 'cid',
|
|
965
|
+
clientSecret: 'csecret',
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
it('returns an error for an unknown service', async () => {
|
|
969
|
+
const deps = createMockDependencies();
|
|
970
|
+
await runCommand(['auth', 'prepare', 'unknown-service', '{}'], deps);
|
|
971
|
+
expect(exitCode).toBe(1);
|
|
972
|
+
});
|
|
973
|
+
it('returns an error for a service that does not support prepare', async () => {
|
|
974
|
+
const deps = createMockDependencies();
|
|
975
|
+
await runCommand(['auth', 'prepare', 'slack', '{"clientId":"a","clientSecret":"b"}'], deps);
|
|
976
|
+
expect(exitCode).toBe(1);
|
|
977
|
+
});
|
|
978
|
+
it('returns an error and stores nothing for malformed JSON', async () => {
|
|
979
|
+
const storePath = join(tempDir, 'credentials.json');
|
|
980
|
+
writeSecureFile(storePath, '{}');
|
|
981
|
+
const deps = createMockDependencies({
|
|
982
|
+
registry: new ServiceRegistry([GOOGLE_GMAIL]),
|
|
983
|
+
});
|
|
984
|
+
await runCommand(['auth', 'prepare', 'google-gmail', '{not valid'], deps);
|
|
985
|
+
expect(exitCode).toBe(1);
|
|
986
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
987
|
+
expect(storedData['google-gmail']).toBeUndefined();
|
|
988
|
+
});
|
|
989
|
+
it('returns an error for input that fails the schema', async () => {
|
|
990
|
+
const storePath = join(tempDir, 'credentials.json');
|
|
991
|
+
writeSecureFile(storePath, '{}');
|
|
992
|
+
const deps = createMockDependencies({
|
|
993
|
+
registry: new ServiceRegistry([GOOGLE_GMAIL]),
|
|
994
|
+
});
|
|
995
|
+
await runCommand(['auth', 'prepare', 'google-gmail', '{"clientId":"only-id"}'], deps);
|
|
996
|
+
expect(exitCode).toBe(1);
|
|
997
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
998
|
+
expect(storedData['google-gmail']).toBeUndefined();
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
951
1001
|
describe('curl command', () => {
|
|
952
1002
|
it('should pass arguments to subprocess', async () => {
|
|
953
1003
|
const storePath = join(tempDir, 'credentials.json');
|
|
@@ -1018,6 +1068,18 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1018
1068
|
await runCommand(['curl', 'https://unknown-api.example.com'], deps);
|
|
1019
1069
|
expect(exitCode).toBe(1);
|
|
1020
1070
|
});
|
|
1071
|
+
it('should return error when OAuth credentials lack an access token', async () => {
|
|
1072
|
+
const storePath = join(tempDir, 'credentials.json');
|
|
1073
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1074
|
+
'google-gmail': { objectType: 'oauth', clientId: 'cid', clientSecret: 'csecret' },
|
|
1075
|
+
}));
|
|
1076
|
+
const deps = createMockDependencies({
|
|
1077
|
+
registry: new ServiceRegistry([GOOGLE_GMAIL]),
|
|
1078
|
+
});
|
|
1079
|
+
await runCommand(['curl', 'https://gmail.googleapis.com/gmail/v1/users/me/profile'], deps);
|
|
1080
|
+
expect(exitCode).toBe(1);
|
|
1081
|
+
expect(errorLogs.length).toBeGreaterThan(0);
|
|
1082
|
+
});
|
|
1021
1083
|
it('should pass through unknown service when passthroughUnknown is enabled', async () => {
|
|
1022
1084
|
const deps = createMockDependencies({
|
|
1023
1085
|
config: createMockConfig({ passthroughUnknown: true }),
|
|
@@ -1860,12 +1922,31 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1860
1922
|
await runCommand(['auth', 'browser-prepare', 'slack'], deps);
|
|
1861
1923
|
expect(logs).toContain('Already prepared.');
|
|
1862
1924
|
});
|
|
1925
|
+
it('forwards `prepare` to the gateway /latchkey endpoint', async () => {
|
|
1926
|
+
const fetchMock = makeFetchMock(new Response(JSON.stringify({ result: { serviceName: 'google-gmail', credentialType: 'oauth' } }), { status: 200 }));
|
|
1927
|
+
const deps = createMockDependencies({
|
|
1928
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1929
|
+
});
|
|
1930
|
+
await runCommand(['auth', 'prepare', 'google-gmail', '{"clientId":"cid","clientSecret":"csecret"}'], deps);
|
|
1931
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
1932
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
1933
|
+
expect(url).toBe(`${GATEWAY_URL}/latchkey`);
|
|
1934
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
1935
|
+
command: 'auth prepare',
|
|
1936
|
+
params: {
|
|
1937
|
+
serviceName: 'google-gmail',
|
|
1938
|
+
json: '{"clientId":"cid","clientSecret":"csecret"}',
|
|
1939
|
+
},
|
|
1940
|
+
});
|
|
1941
|
+
expect(logs).toContain('Done');
|
|
1942
|
+
});
|
|
1863
1943
|
it.each([
|
|
1864
1944
|
['services list', ['services', 'list']],
|
|
1865
1945
|
['services info', ['services', 'info', 'foo']],
|
|
1866
1946
|
['auth list', ['auth', 'list']],
|
|
1867
1947
|
['auth browser', ['auth', 'browser', 'slack']],
|
|
1868
1948
|
['auth browser-prepare', ['auth', 'browser-prepare', 'slack']],
|
|
1949
|
+
['auth prepare', ['auth', 'prepare', 'foo', '{}']],
|
|
1869
1950
|
])('reports gateway errors on stderr (not stdout) for `%s`', async (_name, argv) => {
|
|
1870
1951
|
makeFetchMock(new Response(JSON.stringify({ error: 'Unknown service: foo.' }), { status: 400 }));
|
|
1871
1952
|
const deps = createMockDependencies({
|