ssh-mcp-pro 1.2.3 → 1.4.2
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/CHANGELOG.md +71 -0
- package/MIGRATION.md +2 -2
- package/README.md +34 -13
- package/SECURITY_DECISIONS.md +19 -0
- package/dist/agent-bin.js +5 -2
- package/dist/agent-bin.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +38 -47
- package/dist/cli.js.map +1 -1
- package/dist/config-security-env.d.ts +4 -0
- package/dist/config-security-env.d.ts.map +1 -0
- package/dist/config-security-env.js +64 -0
- package/dist/config-security-env.js.map +1 -0
- package/dist/config-types.d.ts +82 -0
- package/dist/config-types.d.ts.map +1 -0
- package/dist/config-types.js +2 -0
- package/dist/config-types.js.map +1 -0
- package/dist/config.d.ts +3 -82
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -67
- package/dist/config.js.map +1 -1
- package/dist/connector-profile.d.ts +1 -1
- package/dist/connector-profile.d.ts.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +226 -192
- package/dist/detect.js.map +1 -1
- package/dist/ensure-package-service.d.ts +9 -0
- package/dist/ensure-package-service.d.ts.map +1 -0
- package/dist/ensure-package-service.js +146 -0
- package/dist/ensure-package-service.js.map +1 -0
- package/dist/ensure-pkg.js +4 -4
- package/dist/ensure-pkg.js.map +1 -1
- package/dist/ensure.d.ts.map +1 -1
- package/dist/ensure.js +15 -144
- package/dist/ensure.js.map +1 -1
- package/dist/fs-sftp.js +1 -1
- package/dist/fs-sftp.js.map +1 -1
- package/dist/fs-stat.d.ts +10 -0
- package/dist/fs-stat.d.ts.map +1 -0
- package/dist/fs-stat.js +62 -0
- package/dist/fs-stat.js.map +1 -0
- package/dist/fs-tools.d.ts.map +1 -1
- package/dist/fs-tools.js +6 -58
- package/dist/fs-tools.js.map +1 -1
- package/dist/http-security.d.ts.map +1 -1
- package/dist/http-security.js +6 -6
- package/dist/http-security.js.map +1 -1
- package/dist/http-server-runtime.d.ts.map +1 -1
- package/dist/http-server-runtime.js +41 -7
- package/dist/http-server-runtime.js.map +1 -1
- package/dist/index.js +7 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp-http-web.d.ts +8 -0
- package/dist/mcp-http-web.d.ts.map +1 -0
- package/dist/mcp-http-web.js +35 -0
- package/dist/mcp-http-web.js.map +1 -0
- package/dist/mcp.d.ts +6 -2
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +22 -14
- package/dist/mcp.js.map +1 -1
- package/dist/policy.d.ts +8 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +147 -105
- package/dist/policy.js.map +1 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +11 -7
- package/dist/process.js.map +1 -1
- package/dist/remote/agent-activity-log.d.ts +26 -0
- package/dist/remote/agent-activity-log.d.ts.map +1 -0
- package/dist/remote/agent-activity-log.js +211 -0
- package/dist/remote/agent-activity-log.js.map +1 -0
- package/dist/remote/agent-cli.d.ts.map +1 -1
- package/dist/remote/agent-cli.js +153 -33
- package/dist/remote/agent-cli.js.map +1 -1
- package/dist/remote/agent-executor.d.ts.map +1 -1
- package/dist/remote/agent-executor.js +22 -10
- package/dist/remote/agent-executor.js.map +1 -1
- package/dist/remote/agent-handler.d.ts +2 -0
- package/dist/remote/agent-handler.d.ts.map +1 -1
- package/dist/remote/agent-handler.js +4 -0
- package/dist/remote/agent-handler.js.map +1 -1
- package/dist/remote/config.d.ts.map +1 -1
- package/dist/remote/config.js +12 -1
- package/dist/remote/config.js.map +1 -1
- package/dist/remote/control-plane.d.ts +12 -0
- package/dist/remote/control-plane.d.ts.map +1 -1
- package/dist/remote/control-plane.js +164 -56
- package/dist/remote/control-plane.js.map +1 -1
- package/dist/remote/crypto.d.ts +9 -4
- package/dist/remote/crypto.d.ts.map +1 -1
- package/dist/remote/crypto.js +41 -9
- package/dist/remote/crypto.js.map +1 -1
- package/dist/remote/http-util.d.ts.map +1 -1
- package/dist/remote/http-util.js +14 -7
- package/dist/remote/http-util.js.map +1 -1
- package/dist/remote/mcp-tools.d.ts +7 -0
- package/dist/remote/mcp-tools.d.ts.map +1 -1
- package/dist/remote/mcp-tools.js +28 -0
- package/dist/remote/mcp-tools.js.map +1 -1
- package/dist/remote/oauth-handler.d.ts +30 -3
- package/dist/remote/oauth-handler.d.ts.map +1 -1
- package/dist/remote/oauth-handler.js +232 -18
- package/dist/remote/oauth-handler.js.map +1 -1
- package/dist/remote/policy.js +2 -2
- package/dist/remote/policy.js.map +1 -1
- package/dist/remote/schemas.d.ts +9 -9
- package/dist/remote/schemas.js +6 -6
- package/dist/remote/schemas.js.map +1 -1
- package/dist/remote/store.d.ts +38 -1
- package/dist/remote/store.d.ts.map +1 -1
- package/dist/remote/store.js +149 -11
- package/dist/remote/store.js.map +1 -1
- package/dist/remote/types.d.ts +14 -0
- package/dist/remote/types.d.ts.map +1 -1
- package/dist/remote/types.js.map +1 -1
- package/dist/remote/web-dashboard-client-config.d.ts +77 -0
- package/dist/remote/web-dashboard-client-config.d.ts.map +1 -0
- package/dist/remote/web-dashboard-client-config.js +81 -0
- package/dist/remote/web-dashboard-client-config.js.map +1 -0
- package/dist/remote/web-dashboard-html.d.ts +2 -0
- package/dist/remote/web-dashboard-html.d.ts.map +1 -0
- package/dist/remote/web-dashboard-html.js +451 -0
- package/dist/remote/web-dashboard-html.js.map +1 -0
- package/dist/remote/web-dashboard-oauth-users.d.ts +4 -0
- package/dist/remote/web-dashboard-oauth-users.d.ts.map +1 -0
- package/dist/remote/web-dashboard-oauth-users.js +78 -0
- package/dist/remote/web-dashboard-oauth-users.js.map +1 -0
- package/dist/remote/web-dashboard-static.d.ts +4 -0
- package/dist/remote/web-dashboard-static.d.ts.map +1 -0
- package/dist/remote/web-dashboard-static.js +19 -0
- package/dist/remote/web-dashboard-static.js.map +1 -0
- package/dist/remote/web-dashboard-styles.d.ts +2 -0
- package/dist/remote/web-dashboard-styles.d.ts.map +1 -0
- package/dist/remote/web-dashboard-styles.js +206 -0
- package/dist/remote/web-dashboard-styles.js.map +1 -0
- package/dist/remote/web-dashboard.d.ts +62 -0
- package/dist/remote/web-dashboard.d.ts.map +1 -0
- package/dist/remote/web-dashboard.js +467 -0
- package/dist/remote/web-dashboard.js.map +1 -0
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +6 -3
- package/dist/retry.js.map +1 -1
- package/dist/safety.d.ts.map +1 -1
- package/dist/safety.js +56 -6
- package/dist/safety.js.map +1 -1
- package/dist/session-auth.js +7 -7
- package/dist/session-auth.js.map +1 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +44 -40
- package/dist/session.js.map +1 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +5 -4
- package/dist/shell.js.map +1 -1
- package/dist/ssh-config.d.ts +2 -2
- package/dist/ssh-config.d.ts.map +1 -1
- package/dist/ssh-config.js +24 -20
- package/dist/ssh-config.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +9 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/tools/connector.provider.d.ts +1 -1
- package/dist/tools/connector.provider.d.ts.map +1 -1
- package/dist/tools/ensure.provider.d.ts +1 -1
- package/dist/tools/ensure.provider.d.ts.map +1 -1
- package/dist/tools/fs.provider.d.ts +1 -1
- package/dist/tools/fs.provider.d.ts.map +1 -1
- package/dist/tools/metadata.d.ts +1 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/output-schemas.d.ts +27 -135
- package/dist/tools/output-schemas.d.ts.map +1 -1
- package/dist/tools/process.provider.d.ts +1 -1
- package/dist/tools/process.provider.d.ts.map +1 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +16 -13
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/session.provider.d.ts +1 -1
- package/dist/tools/session.provider.d.ts.map +1 -1
- package/dist/tools/system.provider.d.ts +1 -1
- package/dist/tools/system.provider.d.ts.map +1 -1
- package/dist/tools/transfer.provider.d.ts +1 -1
- package/dist/tools/transfer.provider.d.ts.map +1 -1
- package/dist/tools/tunnel.provider.d.ts +1 -1
- package/dist/tools/tunnel.provider.d.ts.map +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/transfer.js +2 -2
- package/dist/transfer.js.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/docs/adding-a-device.md +39 -17
- package/docs/agent-activity-monitor.md +34 -0
- package/docs/coolify.md +29 -0
- package/docs/development/dependency-management.md +1 -1
- package/docs/development/local-setup.md +1 -1
- package/docs/development/mcp-protocol-2026.md +33 -0
- package/docs/docker.md +4 -4
- package/docs/doppler-guide.md +34 -0
- package/docs/openssf-evidence.md +11 -7
- package/docs/openssf-gap-analysis.md +7 -8
- package/docs/reference/cli.md +17 -6
- package/docs/reference/compatibility.md +1 -1
- package/docs/security/dependency-audit-exceptions.md +3 -7
- package/docs/security/hosted-data-inventory.md +81 -0
- package/docs/sonarcloud-analysis.md +9 -0
- package/docs/tutorials/getting-started.md +1 -1
- package/mcp.json +1 -1
- package/package.json +26 -21
- package/registry/ssh-mcp-pro/mcp.json +1 -1
- package/server.json +3 -3
package/dist/remote/http-util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPublicKey } from "node:crypto";
|
|
2
2
|
import { URL } from "node:url";
|
|
3
|
-
import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS
|
|
3
|
+
import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/server";
|
|
4
4
|
import { sha256Base64Url } from "./crypto.js";
|
|
5
5
|
import { parseScopes } from "./scopes.js";
|
|
6
6
|
import { REMOTE_SCOPES } from "./types.js";
|
|
@@ -20,10 +20,11 @@ export function negotiateMcpProtocolVersion(params) {
|
|
|
20
20
|
: LATEST_PROTOCOL_VERSION;
|
|
21
21
|
}
|
|
22
22
|
export function quotePosixArg(value) {
|
|
23
|
-
|
|
23
|
+
const escaped = value.replaceAll("'", "'\"'\"'");
|
|
24
|
+
return `'${escaped}'`;
|
|
24
25
|
}
|
|
25
26
|
export function quotePowerShellArg(value) {
|
|
26
|
-
return `'${value.
|
|
27
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
27
28
|
}
|
|
28
29
|
export function asStringArray(value) {
|
|
29
30
|
if (!Array.isArray(value)) {
|
|
@@ -99,6 +100,15 @@ export async function readJson(req) {
|
|
|
99
100
|
}
|
|
100
101
|
return parsed;
|
|
101
102
|
}
|
|
103
|
+
function formFieldString(value) {
|
|
104
|
+
if (value === null || value === undefined) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
108
|
+
return String(value);
|
|
109
|
+
}
|
|
110
|
+
throw safeError("INVALID_CLIENT", "Expected a scalar JSON form value", 400);
|
|
111
|
+
}
|
|
102
112
|
export async function readJsonOrForm(req) {
|
|
103
113
|
const raw = await readBody(req);
|
|
104
114
|
const contentType = req.headers["content-type"] ?? "";
|
|
@@ -107,10 +117,7 @@ export async function readJsonOrForm(req) {
|
|
|
107
117
|
if (!isRecord(parsed)) {
|
|
108
118
|
throw safeError("INTERNAL_ERROR", "Expected JSON object");
|
|
109
119
|
}
|
|
110
|
-
return Object.fromEntries(Object.entries(parsed).map(([key, value]) => [
|
|
111
|
-
key,
|
|
112
|
-
typeof value === "string" ? value : String(value ?? ""),
|
|
113
|
-
]));
|
|
120
|
+
return Object.fromEntries(Object.entries(parsed).map(([key, value]) => [key, formFieldString(value)]));
|
|
114
121
|
}
|
|
115
122
|
try {
|
|
116
123
|
return formDecode(raw);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-util.js","sourceRoot":"","sources":["../../src/remote/http-util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,
|
|
1
|
+
{"version":3,"file":"http-util.js","sourceRoot":"","sources":["../../src/remote/http-util.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { RemoteCapability, RemoteToolName } from "./types.js";
|
|
2
|
+
export interface ToolAnnotations {
|
|
3
|
+
readOnlyHint?: boolean;
|
|
4
|
+
destructiveHint?: boolean;
|
|
5
|
+
openWorldHint?: boolean;
|
|
6
|
+
}
|
|
2
7
|
interface RemoteToolDescriptor {
|
|
3
8
|
name: RemoteToolName;
|
|
4
9
|
description: string;
|
|
5
10
|
capability: RemoteCapability;
|
|
6
11
|
inputSchema: Record<string, unknown>;
|
|
12
|
+
annotations?: ToolAnnotations;
|
|
7
13
|
}
|
|
14
|
+
export declare function remoteToolAnnotations(name: RemoteToolName): ToolAnnotations;
|
|
8
15
|
export declare function listRemoteToolDescriptors(capabilities: readonly RemoteCapability[]): RemoteToolDescriptor[];
|
|
9
16
|
export {};
|
|
10
17
|
//# sourceMappingURL=mcp-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.d.ts","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGnE,UAAU,oBAAoB;IAC5B,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-tools.d.ts","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGnE,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,UAAU,oBAAoB;IAC5B,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAyND,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,CAQ3E;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,SAAS,gBAAgB,EAAE,GACxC,oBAAoB,EAAE,CAQxB"}
|
package/dist/remote/mcp-tools.js
CHANGED
|
@@ -190,11 +190,39 @@ const descriptors = {
|
|
|
190
190
|
},
|
|
191
191
|
},
|
|
192
192
|
};
|
|
193
|
+
const READ_ONLY_REMOTE_TOOLS = new Set([
|
|
194
|
+
"list_hosts",
|
|
195
|
+
"list_agents",
|
|
196
|
+
"get_agent_install_command",
|
|
197
|
+
"get_system_status",
|
|
198
|
+
"tail_logs",
|
|
199
|
+
"docker_ps",
|
|
200
|
+
"docker_logs",
|
|
201
|
+
"file_read",
|
|
202
|
+
"get_audit_events",
|
|
203
|
+
]);
|
|
204
|
+
const DESTRUCTIVE_REMOTE_TOOLS = new Set([
|
|
205
|
+
"run_shell_as_root",
|
|
206
|
+
"revoke_agent",
|
|
207
|
+
"file_write",
|
|
208
|
+
"restart_service",
|
|
209
|
+
"docker_restart",
|
|
210
|
+
]);
|
|
211
|
+
export function remoteToolAnnotations(name) {
|
|
212
|
+
const isReadOnly = READ_ONLY_REMOTE_TOOLS.has(name);
|
|
213
|
+
const isDestructive = DESTRUCTIVE_REMOTE_TOOLS.has(name);
|
|
214
|
+
return {
|
|
215
|
+
readOnlyHint: isReadOnly,
|
|
216
|
+
destructiveHint: isDestructive,
|
|
217
|
+
openWorldHint: !isReadOnly,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
193
220
|
export function listRemoteToolDescriptors(capabilities) {
|
|
194
221
|
const allowed = new Set(capabilities);
|
|
195
222
|
return REMOTE_TOOLS.filter((name) => allowed.has(TOOL_CAPABILITY_MAP[name])).map((name) => ({
|
|
196
223
|
name,
|
|
197
224
|
capability: TOOL_CAPABILITY_MAP[name],
|
|
225
|
+
annotations: remoteToolAnnotations(name),
|
|
198
226
|
...descriptors[name],
|
|
199
227
|
}));
|
|
200
228
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { type JwtKeyPair } from "./crypto.js";
|
|
3
3
|
import { RemoteStore } from "./store.js";
|
|
4
|
-
import type { AuditEvent, RemoteConfig } from "./types.js";
|
|
4
|
+
import type { AuditEvent, GitHubUser, GoogleUser, RemoteConfig } from "./types.js";
|
|
5
5
|
/** Pending OAuth authorization transaction. */
|
|
6
6
|
export interface PendingAuthorize {
|
|
7
7
|
clientId: string;
|
|
@@ -32,16 +32,43 @@ export declare class OAuthHandler {
|
|
|
32
32
|
handleRegister(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
33
33
|
/** GET /oauth/authorize */
|
|
34
34
|
handleAuthorize(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Avoid chaining browser form submissions through third-party OAuth redirects
|
|
37
|
+
* and then back to the MCP client. Chromium applies form-action to redirects;
|
|
38
|
+
* the final client callback is deliberately not a permitted form destination.
|
|
39
|
+
* A new document starts an ordinary navigation instead.
|
|
40
|
+
*/
|
|
41
|
+
private continueToGitHub;
|
|
35
42
|
/** Explicit browser approval binds this client to the current login transaction. */
|
|
36
43
|
handleApprove(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
37
44
|
private validateAuthorizeParams;
|
|
38
45
|
private restoreChatGptClient;
|
|
39
46
|
private addChatGptRedirectUri;
|
|
47
|
+
/** Consume one-use OAuth transaction and preserve the associated PKCE request. */
|
|
48
|
+
private consumeOAuthCallback;
|
|
49
|
+
private completeOAuthCallback;
|
|
40
50
|
/** GET /oauth/callback/github */
|
|
41
51
|
handleGitHubCallback(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
52
|
+
/** GET /oauth/callback/google */
|
|
53
|
+
handleGoogleCallback(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
54
|
+
fetchGoogleUser(code: string): Promise<GoogleUser>;
|
|
55
|
+
/** Fixture identities must never bypass provider login on public-facing hosts. */
|
|
56
|
+
private allowFixtureIdentity;
|
|
57
|
+
testGoogleUser(): GoogleUser | undefined;
|
|
58
|
+
upsertGoogleUser(googleUser: GoogleUser): {
|
|
59
|
+
id: string;
|
|
60
|
+
googleId: string;
|
|
61
|
+
email: string;
|
|
62
|
+
displayName: string;
|
|
63
|
+
};
|
|
64
|
+
isGoogleUserAllowed(user: GoogleUser): boolean;
|
|
42
65
|
private fetchGitHubUser;
|
|
43
|
-
|
|
44
|
-
|
|
66
|
+
testGitHubUser(): GitHubUser | undefined;
|
|
67
|
+
upsertGitHubUser(githubUser: GitHubUser): {
|
|
68
|
+
id: string;
|
|
69
|
+
githubId: string;
|
|
70
|
+
githubLogin: string;
|
|
71
|
+
};
|
|
45
72
|
private isGitHubUserAllowed;
|
|
46
73
|
private issueAuthorizationCode;
|
|
47
74
|
/** POST /oauth/token */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-handler.d.ts","sourceRoot":"","sources":["../../src/remote/oauth-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,EAOL,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAarB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EACV,UAAU,
|
|
1
|
+
{"version":3,"file":"oauth-handler.d.ts","sourceRoot":"","sources":["../../src/remote/oauth-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,6 +42,7 @@ export class OAuthHandler {
|
|
|
42
42
|
authorizationServerMetadata() {
|
|
43
43
|
return {
|
|
44
44
|
issuer: this.config.publicBaseUrl,
|
|
45
|
+
authorization_response_iss_parameter_supported: true,
|
|
45
46
|
authorization_endpoint: `${this.config.publicBaseUrl}/oauth/authorize`,
|
|
46
47
|
token_endpoint: `${this.config.publicBaseUrl}/oauth/token`,
|
|
47
48
|
registration_endpoint: `${this.config.publicBaseUrl}/oauth/register`,
|
|
@@ -126,44 +127,139 @@ export class OAuthHandler {
|
|
|
126
127
|
const code = this.issueAuthorizationCode(pending, user.id);
|
|
127
128
|
const destination = new URL(redirectUri);
|
|
128
129
|
destination.searchParams.set("code", code);
|
|
130
|
+
destination.searchParams.set("iss", this.config.publicBaseUrl);
|
|
129
131
|
if (state) {
|
|
130
132
|
destination.searchParams.set("state", state);
|
|
131
133
|
}
|
|
132
134
|
redirect(res, destination.toString());
|
|
133
135
|
return;
|
|
134
136
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
const testGoogle = this.testGoogleUser();
|
|
138
|
+
if (testGoogle) {
|
|
139
|
+
const user = this.upsertGoogleUser(testGoogle);
|
|
140
|
+
const code = this.issueAuthorizationCode(pending, user.id);
|
|
141
|
+
const destination = new URL(redirectUri);
|
|
142
|
+
destination.searchParams.set("code", code);
|
|
143
|
+
destination.searchParams.set("iss", this.config.publicBaseUrl);
|
|
144
|
+
if (state) {
|
|
145
|
+
destination.searchParams.set("state", state);
|
|
146
|
+
}
|
|
147
|
+
redirect(res, destination.toString());
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const hasGitHub = Boolean(this.config.githubClientId && this.config.githubClientSecret);
|
|
151
|
+
const hasGoogle = Boolean(this.config.googleClientId && this.config.googleClientSecret);
|
|
152
|
+
if (!hasGitHub && !hasGoogle) {
|
|
153
|
+
throw safeError("FORBIDDEN", "Neither GitHub nor Google OAuth is configured", 503);
|
|
137
154
|
}
|
|
138
155
|
const transactionId = id("code");
|
|
139
156
|
this.authorizeTransactions.set(transactionId, pending);
|
|
140
157
|
const hostname = escapeHtml(new URL(redirectUri).hostname);
|
|
158
|
+
// Chromium also checks form-action on the redirect after POST /oauth/approve.
|
|
159
|
+
// Permit only the configured identity providers, not arbitrary OAuth clients.
|
|
160
|
+
const formActions = [
|
|
161
|
+
"'self'",
|
|
162
|
+
...(hasGoogle ? ["https://accounts.google.com"] : []),
|
|
163
|
+
...(hasGitHub ? ["https://github.com"] : []),
|
|
164
|
+
].join(" ");
|
|
141
165
|
res.writeHead(200, {
|
|
142
166
|
"Content-Type": "text/html; charset=utf-8",
|
|
143
167
|
"Cache-Control": "no-store",
|
|
144
|
-
"Content-Security-Policy":
|
|
168
|
+
"Content-Security-Policy": `default-src 'none'; style-src 'unsafe-inline'; form-action ${formActions}; frame-ancestors 'none'; base-uri 'none'`,
|
|
145
169
|
"X-Content-Type-Options": "nosniff",
|
|
146
170
|
"Set-Cookie": this.transactionCookie(transactionId),
|
|
147
171
|
});
|
|
172
|
+
const buttons = [];
|
|
173
|
+
if (hasGoogle) {
|
|
174
|
+
buttons.push(`<button type="submit" name="provider" value="google" style="display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:12px;margin-bottom:10px;background:#ffffff;color:#1f2937;border:1px solid #d1d5db;border-radius:8px;font-weight:600;font-size:15px;cursor:pointer;">
|
|
175
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#EA4335" d="M12 5c1.7 0 3 .7 3.9 1.5l2.9-2.9C17 2 14.7 1 12 1 7.5 1 3.7 3.6 1.9 7.3l3.7 2.9C6.5 7.4 9 5 12 5z"/><path fill="#4285F4" d="M23.5 12.3c0-.8-.1-1.6-.2-2.3H12v4.5h6.5c-.3 1.5-1.1 2.8-2.4 3.7l3.7 2.9c2.2-2 3.7-5 3.7-8.8z"/><path fill="#FBBC05" d="M5.6 14.8c-.2-.7-.4-1.5-.4-2.3s.2-1.6.4-2.3L1.9 7.3C.7 9.7 0 12.3 0 15.2c0 2.8.7 5.5 1.9 7.8l3.7-2.9z"/><path fill="#34A853" d="M12 23.5c3.2 0 6-1.1 8-3l-3.7-2.9c-1.1.7-2.5 1.2-4.3 1.2-3 0-5.5-2.4-6.4-5.2L1.9 16.5C3.7 20.3 7.5 23.5 12 23.5z"/></svg>
|
|
176
|
+
Continue with Google
|
|
177
|
+
</button>`);
|
|
178
|
+
}
|
|
179
|
+
if (hasGitHub) {
|
|
180
|
+
buttons.push(`<button type="submit" name="provider" value="github" style="display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:12px;margin-bottom:10px;background:#24292f;color:#ffffff;border:none;border-radius:8px;font-weight:600;font-size:15px;cursor:pointer;">
|
|
181
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
|
|
182
|
+
Continue with GitHub
|
|
183
|
+
</button>`);
|
|
184
|
+
}
|
|
148
185
|
res.end([
|
|
149
|
-
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Authorize SSH MCP</title
|
|
150
|
-
|
|
186
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Authorize SSH MCP Pro</title>',
|
|
187
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
188
|
+
'<style>body{background:#0d1117;color:#e6edf3;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:16px;}',
|
|
189
|
+
".card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:32px;max-width:440px;width:100%;box-shadow:0 10px 30px rgba(0,0,0,0.5);text-align:center;}",
|
|
190
|
+
"h1{font-size:22px;margin:0 0 12px;color:#f0f6fc;}p{font-size:14px;color:#8b949e;line-height:1.5;margin:0 0 24px;}",
|
|
191
|
+
".badge{background:#21262d;border:1px solid #30363d;border-radius:6px;padding:4px 8px;font-family:monospace;font-size:12px;color:#58a6ff;}",
|
|
192
|
+
"</style></head><body>",
|
|
193
|
+
'<div class="card">',
|
|
194
|
+
'<div style="margin-bottom:16px;"><span style="font-size:32px;">🔐</span></div>',
|
|
195
|
+
"<h1>Authorize SSH MCP Pro</h1>",
|
|
196
|
+
'<p>Allow client at <strong style="color:#f0f6fc;">',
|
|
151
197
|
hostname,
|
|
152
|
-
" to request
|
|
198
|
+
"</strong> to request access to:</p>",
|
|
199
|
+
'<p><span class="badge">',
|
|
153
200
|
escapeHtml(scope),
|
|
154
|
-
|
|
201
|
+
"</span></p>",
|
|
202
|
+
'<form method="post" action="/oauth/approve"><input type="hidden" name="transaction" value="',
|
|
155
203
|
escapeHtml(transactionId),
|
|
156
|
-
'"
|
|
204
|
+
'">',
|
|
205
|
+
buttons.join(""),
|
|
206
|
+
"</form>",
|
|
207
|
+
'<div style="font-size:12px;color:#484f58;margin-top:20px;">Zero-Trust SSH Model Context Protocol Gateway</div>',
|
|
208
|
+
"</div></body></html>",
|
|
209
|
+
].join(""));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Avoid chaining browser form submissions through third-party OAuth redirects
|
|
213
|
+
* and then back to the MCP client. Chromium applies form-action to redirects;
|
|
214
|
+
* the final client callback is deliberately not a permitted form destination.
|
|
215
|
+
* A new document starts an ordinary navigation instead.
|
|
216
|
+
*/
|
|
217
|
+
continueToGitHub(res, githubUrl) {
|
|
218
|
+
const href = escapeHtml(githubUrl.toString());
|
|
219
|
+
res.writeHead(200, {
|
|
220
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
221
|
+
"Cache-Control": "no-store",
|
|
222
|
+
"Referrer-Policy": "no-referrer",
|
|
223
|
+
"X-Content-Type-Options": "nosniff",
|
|
224
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'",
|
|
225
|
+
});
|
|
226
|
+
res.end([
|
|
227
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8">',
|
|
228
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
229
|
+
"<title>Continue with GitHub — SSH MCP Pro</title>",
|
|
230
|
+
'<meta http-equiv="refresh" content="0;url=',
|
|
231
|
+
href,
|
|
232
|
+
'">',
|
|
233
|
+
"</head><body>",
|
|
234
|
+
'<p>Continue to GitHub to sign in.</p><a href="',
|
|
235
|
+
href,
|
|
236
|
+
'">Continue with GitHub</a>',
|
|
237
|
+
"</body></html>",
|
|
157
238
|
].join(""));
|
|
158
239
|
}
|
|
159
240
|
/** Explicit browser approval binds this client to the current login transaction. */
|
|
160
241
|
async handleApprove(req, res) {
|
|
161
242
|
const form = await readJsonOrForm(req);
|
|
162
243
|
const transactionId = form.transaction ?? "";
|
|
244
|
+
const provider = asString(form.provider) ?? "github";
|
|
163
245
|
const pending = this.authorizeTransactions.get(transactionId);
|
|
164
246
|
if (!pending || pending.expiresAt < Date.now() || !this.matchingBrowser(req, transactionId)) {
|
|
165
247
|
throw safeError("INVALID_TOKEN", "OAuth transaction is missing or expired", 403);
|
|
166
248
|
}
|
|
249
|
+
if (provider === "google") {
|
|
250
|
+
if (!this.config.googleClientId || !this.config.googleClientSecret) {
|
|
251
|
+
throw safeError("FORBIDDEN", "Google OAuth is not configured", 503);
|
|
252
|
+
}
|
|
253
|
+
pending.approved = true;
|
|
254
|
+
const googleUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth");
|
|
255
|
+
googleUrl.searchParams.set("client_id", this.config.googleClientId);
|
|
256
|
+
googleUrl.searchParams.set("redirect_uri", this.config.googleCallbackUrl);
|
|
257
|
+
googleUrl.searchParams.set("response_type", "code");
|
|
258
|
+
googleUrl.searchParams.set("scope", "openid email profile");
|
|
259
|
+
googleUrl.searchParams.set("state", transactionId);
|
|
260
|
+
redirect(res, googleUrl.toString());
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
167
263
|
if (!this.config.githubClientId || !this.config.githubClientSecret) {
|
|
168
264
|
throw safeError("FORBIDDEN", "GitHub OAuth is not configured", 503);
|
|
169
265
|
}
|
|
@@ -173,7 +269,7 @@ export class OAuthHandler {
|
|
|
173
269
|
githubUrl.searchParams.set("redirect_uri", this.config.githubCallbackUrl);
|
|
174
270
|
githubUrl.searchParams.set("scope", "read:user");
|
|
175
271
|
githubUrl.searchParams.set("state", transactionId);
|
|
176
|
-
|
|
272
|
+
this.continueToGitHub(res, githubUrl);
|
|
177
273
|
}
|
|
178
274
|
validateAuthorizeParams(clientId, redirectUri, responseType, codeChallenge, codeChallengeMethod, resource, scope) {
|
|
179
275
|
let client = this.store.getClient(clientId);
|
|
@@ -255,9 +351,9 @@ export class OAuthHandler {
|
|
|
255
351
|
});
|
|
256
352
|
return { ...client, redirectUris: updatedUris };
|
|
257
353
|
}
|
|
258
|
-
/**
|
|
259
|
-
|
|
260
|
-
const url = new URL(req.url ??
|
|
354
|
+
/** Consume one-use OAuth transaction and preserve the associated PKCE request. */
|
|
355
|
+
consumeOAuthCallback(req, provider) {
|
|
356
|
+
const url = new URL(req.url ?? `/oauth/callback/${provider}`, this.config.publicBaseUrl);
|
|
261
357
|
const code = url.searchParams.get("code") ?? "";
|
|
262
358
|
const state = url.searchParams.get("state") ?? "";
|
|
263
359
|
const pending = this.authorizeTransactions.get(state);
|
|
@@ -268,17 +364,113 @@ export class OAuthHandler {
|
|
|
268
364
|
!this.matchingBrowser(req, state)) {
|
|
269
365
|
throw safeError("INVALID_TOKEN", "OAuth transaction is missing or expired");
|
|
270
366
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
367
|
+
return { code, pending };
|
|
368
|
+
}
|
|
369
|
+
completeOAuthCallback(res, pending, userId) {
|
|
370
|
+
const authCode = this.issueAuthorizationCode(pending, userId);
|
|
274
371
|
const destination = new URL(pending.redirectUri);
|
|
275
372
|
destination.searchParams.set("code", authCode);
|
|
276
|
-
|
|
373
|
+
destination.searchParams.set("iss", this.config.publicBaseUrl);
|
|
374
|
+
if (pending.state)
|
|
277
375
|
destination.searchParams.set("state", pending.state);
|
|
278
|
-
}
|
|
279
376
|
res.setHeader("Set-Cookie", this.transactionCookie("").replace("Max-Age=300", "Max-Age=0"));
|
|
280
377
|
redirect(res, destination.toString());
|
|
281
378
|
}
|
|
379
|
+
/** GET /oauth/callback/github */
|
|
380
|
+
async handleGitHubCallback(req, res) {
|
|
381
|
+
const { code, pending } = this.consumeOAuthCallback(req, "github");
|
|
382
|
+
const user = this.upsertGitHubUser(await this.fetchGitHubUser(code));
|
|
383
|
+
this.completeOAuthCallback(res, pending, user.id);
|
|
384
|
+
}
|
|
385
|
+
/** GET /oauth/callback/google */
|
|
386
|
+
async handleGoogleCallback(req, res) {
|
|
387
|
+
const { code, pending } = this.consumeOAuthCallback(req, "google");
|
|
388
|
+
const user = this.upsertGoogleUser(await this.fetchGoogleUser(code));
|
|
389
|
+
this.completeOAuthCallback(res, pending, user.id);
|
|
390
|
+
}
|
|
391
|
+
async fetchGoogleUser(code) {
|
|
392
|
+
const testUser = this.testGoogleUser();
|
|
393
|
+
if (testUser) {
|
|
394
|
+
return testUser;
|
|
395
|
+
}
|
|
396
|
+
if (!this.config.googleClientId || !this.config.googleClientSecret) {
|
|
397
|
+
throw safeError("FORBIDDEN", "Google OAuth is not configured", 503);
|
|
398
|
+
}
|
|
399
|
+
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
402
|
+
body: new URLSearchParams({
|
|
403
|
+
code,
|
|
404
|
+
client_id: this.config.googleClientId,
|
|
405
|
+
client_secret: this.config.googleClientSecret,
|
|
406
|
+
redirect_uri: this.config.googleCallbackUrl,
|
|
407
|
+
grant_type: "authorization_code",
|
|
408
|
+
}).toString(),
|
|
409
|
+
});
|
|
410
|
+
const tokenPayload = (await tokenResponse.json());
|
|
411
|
+
const accessToken = asString(tokenPayload.access_token);
|
|
412
|
+
if (!accessToken) {
|
|
413
|
+
throw safeError("INVALID_TOKEN", "Google OAuth token exchange failed", 502);
|
|
414
|
+
}
|
|
415
|
+
const userResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
|
|
416
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
417
|
+
});
|
|
418
|
+
const userPayload = (await userResponse.json());
|
|
419
|
+
const googleSub = asString(userPayload.sub);
|
|
420
|
+
const email = asString(userPayload.email);
|
|
421
|
+
const name = asString(userPayload.name) ?? email ?? "Google User";
|
|
422
|
+
const picture = asString(userPayload.picture);
|
|
423
|
+
if (!googleSub || !email) {
|
|
424
|
+
throw safeError("INVALID_TOKEN", "Google OAuth user response missing valid identity", 502);
|
|
425
|
+
}
|
|
426
|
+
return { id: googleSub, email, name, picture };
|
|
427
|
+
}
|
|
428
|
+
/** Fixture identities must never bypass provider login on public-facing hosts. */
|
|
429
|
+
allowFixtureIdentity() {
|
|
430
|
+
if (process.env.NODE_ENV !== "test")
|
|
431
|
+
return false;
|
|
432
|
+
try {
|
|
433
|
+
const baseUrl = new URL(this.config.publicBaseUrl);
|
|
434
|
+
return (baseUrl.protocol === "http:" &&
|
|
435
|
+
["localhost", "127.0.0.1", "[::1]"].includes(baseUrl.hostname));
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
testGoogleUser() {
|
|
442
|
+
if (!this.allowFixtureIdentity())
|
|
443
|
+
return undefined;
|
|
444
|
+
const idValue = process.env.SSHAUTOMATOR_TEST_GOOGLE_ID;
|
|
445
|
+
const email = process.env.SSHAUTOMATOR_TEST_GOOGLE_EMAIL;
|
|
446
|
+
const name = process.env.SSHAUTOMATOR_TEST_GOOGLE_NAME ?? "Test Google User";
|
|
447
|
+
return idValue && email ? { id: idValue, email, name } : undefined;
|
|
448
|
+
}
|
|
449
|
+
upsertGoogleUser(googleUser) {
|
|
450
|
+
if (!this.isGoogleUserAllowed(googleUser)) {
|
|
451
|
+
throw safeError("FORBIDDEN", "Google user is not allowed");
|
|
452
|
+
}
|
|
453
|
+
const existing = this.store.getUserByGoogleId(googleUser.id);
|
|
454
|
+
const internalId = existing?.id ?? `google:${googleUser.id}`;
|
|
455
|
+
this.store.upsertGoogleUser({ ...googleUser, internalId, now: nowIso() });
|
|
456
|
+
this.audit({
|
|
457
|
+
userId: internalId,
|
|
458
|
+
eventType: "user_login",
|
|
459
|
+
severity: "info",
|
|
460
|
+
metadata: { google_id: googleUser.id, email: googleUser.email, provider: "google" },
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
id: internalId,
|
|
464
|
+
googleId: googleUser.id,
|
|
465
|
+
email: googleUser.email,
|
|
466
|
+
displayName: googleUser.name,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
isGoogleUserAllowed(user) {
|
|
470
|
+
return (this.config.allowAllUsers ||
|
|
471
|
+
this.config.allowedGoogleSubIds.includes(user.id) ||
|
|
472
|
+
this.config.allowedGoogleEmails.includes(user.email));
|
|
473
|
+
}
|
|
282
474
|
async fetchGitHubUser(code) {
|
|
283
475
|
const tokenResponse = await fetch("https://github.com/login/oauth/access_token", {
|
|
284
476
|
method: "POST",
|
|
@@ -299,9 +491,20 @@ export class OAuthHandler {
|
|
|
299
491
|
headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/vnd.github+json" },
|
|
300
492
|
});
|
|
301
493
|
const userPayload = (await userResponse.json());
|
|
302
|
-
|
|
494
|
+
const githubId = userPayload.id;
|
|
495
|
+
const githubLogin = userPayload.login;
|
|
496
|
+
if (typeof githubId !== "number" ||
|
|
497
|
+
!Number.isSafeInteger(githubId) ||
|
|
498
|
+
githubId <= 0 ||
|
|
499
|
+
typeof githubLogin !== "string" ||
|
|
500
|
+
!githubLogin) {
|
|
501
|
+
throw safeError("INVALID_TOKEN", "GitHub OAuth user response missing valid identity", 502);
|
|
502
|
+
}
|
|
503
|
+
return { id: String(githubId), login: githubLogin };
|
|
303
504
|
}
|
|
304
505
|
testGitHubUser() {
|
|
506
|
+
if (!this.allowFixtureIdentity())
|
|
507
|
+
return undefined;
|
|
305
508
|
const idValue = process.env.SSHAUTOMATOR_TEST_GITHUB_ID;
|
|
306
509
|
const login = process.env.SSHAUTOMATOR_TEST_GITHUB_LOGIN;
|
|
307
510
|
return idValue && login ? { id: idValue, login } : undefined;
|
|
@@ -394,6 +597,17 @@ export class OAuthHandler {
|
|
|
394
597
|
return user;
|
|
395
598
|
}
|
|
396
599
|
}
|
|
600
|
+
if (userId.startsWith("google:")) {
|
|
601
|
+
const googleId = userId.slice("google:".length);
|
|
602
|
+
const user = this.store.getUserByGoogleId(googleId);
|
|
603
|
+
if (user) {
|
|
604
|
+
return user;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const user = this.store.getUserById(userId);
|
|
608
|
+
if (user) {
|
|
609
|
+
return user;
|
|
610
|
+
}
|
|
397
611
|
throw safeError("UNAUTHORIZED", "User no longer exists", 401);
|
|
398
612
|
}
|
|
399
613
|
/** Remove expired pending authorization transactions. */
|