@sun-asterisk/sungen 3.2.3 → 3.2.5
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/dist/cli/commands/dashboard.js +2 -2
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +22 -5
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/feedback.d.ts +11 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -1
- package/dist/cli/commands/feedback.js +154 -57
- package/dist/cli/commands/feedback.js.map +1 -1
- package/dist/cli/commands/login.d.ts +19 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +76 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/qa-feedback.d.ts +14 -0
- package/dist/cli/commands/qa-feedback.d.ts.map +1 -0
- package/dist/cli/commands/qa-feedback.js +83 -0
- package/dist/cli/commands/qa-feedback.js.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts +3 -0
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +155 -52
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +23 -23
- package/dist/dashboard/types.d.ts +8 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +55 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -0
- package/dist/exporters/api-testcase-formatter.js +304 -0
- package/dist/exporters/api-testcase-formatter.js.map +1 -0
- package/dist/exporters/csv-exporter.d.ts +13 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +35 -6
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +4 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +7 -2
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +11 -3
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +21 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +52 -12
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +50 -2
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/spec-parser.js +1 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts +7 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +13 -0
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +13 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +131 -42
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +15 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +33 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +12 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +21 -1
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +10 -5
- package/dist/harness/script-check.js.map +1 -1
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +46 -4
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +14 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +58 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +7 -0
- package/dist/orchestrator/templates/ai-src/commands/feedback.md +6 -6
- package/dist/orchestrator/templates/ai-src/commands/locale.md +26 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
- package/dist/orchestrator/templates/wdio.conf.ts +39 -3
- package/dist/tools/sungen-api/edit-in-editor.d.ts +15 -0
- package/dist/tools/sungen-api/edit-in-editor.d.ts.map +1 -0
- package/dist/tools/sungen-api/edit-in-editor.js +85 -0
- package/dist/tools/sungen-api/edit-in-editor.js.map +1 -0
- package/dist/tools/sungen-api/feedback-client.d.ts +24 -0
- package/dist/tools/sungen-api/feedback-client.d.ts.map +1 -0
- package/dist/tools/sungen-api/feedback-client.js +53 -0
- package/dist/tools/sungen-api/feedback-client.js.map +1 -0
- package/dist/tools/sungen-api/read-stdin.d.ts +6 -0
- package/dist/tools/sungen-api/read-stdin.d.ts.map +1 -0
- package/dist/tools/sungen-api/read-stdin.js +19 -0
- package/dist/tools/sungen-api/read-stdin.js.map +1 -0
- package/dist/tools/sungen-auth/api-base-url.d.ts +14 -0
- package/dist/tools/sungen-auth/api-base-url.d.ts.map +1 -0
- package/dist/tools/sungen-auth/api-base-url.js +28 -0
- package/dist/tools/sungen-auth/api-base-url.js.map +1 -0
- package/dist/tools/sungen-auth/credentials-store.d.ts +19 -0
- package/dist/tools/sungen-auth/credentials-store.d.ts.map +1 -0
- package/dist/tools/sungen-auth/credentials-store.js +94 -0
- package/dist/tools/sungen-auth/credentials-store.js.map +1 -0
- package/dist/tools/sungen-auth/login-flow.d.ts +33 -0
- package/dist/tools/sungen-auth/login-flow.d.ts.map +1 -0
- package/dist/tools/sungen-auth/login-flow.js +107 -0
- package/dist/tools/sungen-auth/login-flow.js.map +1 -0
- package/dist/tools/sungen-auth/loopback-server.d.ts +20 -0
- package/dist/tools/sungen-auth/loopback-server.d.ts.map +1 -0
- package/dist/tools/sungen-auth/loopback-server.js +132 -0
- package/dist/tools/sungen-auth/loopback-server.js.map +1 -0
- package/dist/tools/sungen-auth/open-browser.d.ts +10 -0
- package/dist/tools/sungen-auth/open-browser.d.ts.map +1 -0
- package/dist/tools/sungen-auth/open-browser.js +43 -0
- package/dist/tools/sungen-auth/open-browser.js.map +1 -0
- package/dist/tools/sungen-auth/pkce.d.ts +13 -0
- package/dist/tools/sungen-auth/pkce.d.ts.map +1 -0
- package/dist/tools/sungen-auth/pkce.js +27 -0
- package/dist/tools/sungen-auth/pkce.js.map +1 -0
- package/dist/tools/sungen-auth/sungen-auth-types.d.ts +41 -0
- package/dist/tools/sungen-auth/sungen-auth-types.d.ts.map +1 -0
- package/dist/tools/sungen-auth/sungen-auth-types.js +7 -0
- package/dist/tools/sungen-auth/sungen-auth-types.js.map +1 -0
- package/package.json +4 -3
- package/src/cli/commands/dashboard.ts +2 -2
- package/src/cli/commands/delivery.ts +24 -6
- package/src/cli/commands/feedback.ts +123 -51
- package/src/cli/commands/login.ts +75 -0
- package/src/cli/commands/qa-feedback.ts +77 -0
- package/src/cli/index.ts +4 -0
- package/src/dashboard/snapshot-builder.ts +146 -46
- package/src/dashboard/templates/index.html +23 -23
- package/src/dashboard/types.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +325 -0
- package/src/exporters/csv-exporter.ts +46 -7
- package/src/exporters/feature-parser.ts +7 -2
- package/src/exporters/json-exporter.ts +11 -3
- package/src/exporters/playwright-report-parser.ts +50 -11
- package/src/exporters/scenario-merger.ts +49 -3
- package/src/exporters/spec-parser.ts +1 -1
- package/src/exporters/test-data-resolver.ts +13 -0
- package/src/exporters/types.ts +13 -0
- package/src/exporters/xlsx-exporter.ts +155 -49
- package/src/generators/gherkin-parser/index.ts +44 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
- package/src/generators/test-generator/patterns/index.ts +21 -1
- package/src/harness/script-check.ts +10 -5
- package/src/orchestrator/ai-skills/writer.ts +39 -4
- package/src/orchestrator/project-initializer.ts +59 -0
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
- package/src/orchestrator/templates/ai-src/commands/create-test.md +7 -0
- package/src/orchestrator/templates/ai-src/commands/feedback.md +6 -6
- package/src/orchestrator/templates/ai-src/commands/locale.md +26 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
- package/src/orchestrator/templates/wdio.conf.ts +39 -3
- package/src/tools/sungen-api/edit-in-editor.ts +48 -0
- package/src/tools/sungen-api/feedback-client.ts +62 -0
- package/src/tools/sungen-api/read-stdin.ts +15 -0
- package/src/tools/sungen-auth/api-base-url.ts +23 -0
- package/src/tools/sungen-auth/credentials-store.ts +57 -0
- package/src/tools/sungen-auth/login-flow.ts +135 -0
- package/src/tools/sungen-auth/loopback-server.ts +113 -0
- package/src/tools/sungen-auth/open-browser.ts +42 -0
- package/src/tools/sungen-auth/pkce.ts +27 -0
- package/src/tools/sungen-auth/sungen-auth-types.ts +45 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Read piped stdin to a string. Returns null when stdin is a TTY (interactive),
|
|
4
|
+
* so callers can distinguish "no piped input" from "empty pipe".
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.readStdin = readStdin;
|
|
8
|
+
function readStdin() {
|
|
9
|
+
if (process.stdin.isTTY)
|
|
10
|
+
return Promise.resolve(null);
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
let data = '';
|
|
13
|
+
process.stdin.setEncoding('utf8');
|
|
14
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
15
|
+
process.stdin.on('end', () => resolve(data));
|
|
16
|
+
process.stdin.on('error', reject);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=read-stdin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-stdin.js","sourceRoot":"","sources":["../../../src/tools/sungen-api/read-stdin.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,8BASC;AATD,SAAgB,SAAS;IACvB,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the feedback backend base URL with a fixed precedence so `login` and
|
|
3
|
+
* `feedback` agree on where to talk.
|
|
4
|
+
*
|
|
5
|
+
* Precedence: explicit flag > SUNGEN_API_BASE_URL env > stored credential's
|
|
6
|
+
* api_base_url > DEFAULT_API_BASE_URL.
|
|
7
|
+
*
|
|
8
|
+
* DEFAULT_API_BASE_URL is the deployed production host, so the common case needs
|
|
9
|
+
* no config; override via --api-base-url / SUNGEN_API_BASE_URL for dev/staging.
|
|
10
|
+
*/
|
|
11
|
+
/** Deployed feedback backend (production). Override with SUNGEN_API_BASE_URL locally. */
|
|
12
|
+
export declare const DEFAULT_API_BASE_URL = "https://sungen.sun-asterisk.vn";
|
|
13
|
+
export declare function resolveApiBaseUrl(flagValue?: string): string;
|
|
14
|
+
//# sourceMappingURL=api-base-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-base-url.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/api-base-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,mCAAmC,CAAC;AAErE,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the feedback backend base URL with a fixed precedence so `login` and
|
|
4
|
+
* `feedback` agree on where to talk.
|
|
5
|
+
*
|
|
6
|
+
* Precedence: explicit flag > SUNGEN_API_BASE_URL env > stored credential's
|
|
7
|
+
* api_base_url > DEFAULT_API_BASE_URL.
|
|
8
|
+
*
|
|
9
|
+
* DEFAULT_API_BASE_URL is the deployed production host, so the common case needs
|
|
10
|
+
* no config; override via --api-base-url / SUNGEN_API_BASE_URL for dev/staging.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DEFAULT_API_BASE_URL = void 0;
|
|
14
|
+
exports.resolveApiBaseUrl = resolveApiBaseUrl;
|
|
15
|
+
const credentials_store_1 = require("./credentials-store");
|
|
16
|
+
/** Deployed feedback backend (production). Override with SUNGEN_API_BASE_URL locally. */
|
|
17
|
+
exports.DEFAULT_API_BASE_URL = 'https://sungen.sun-asterisk.vn';
|
|
18
|
+
function resolveApiBaseUrl(flagValue) {
|
|
19
|
+
if (flagValue)
|
|
20
|
+
return flagValue;
|
|
21
|
+
if (process.env.SUNGEN_API_BASE_URL)
|
|
22
|
+
return process.env.SUNGEN_API_BASE_URL;
|
|
23
|
+
const stored = (0, credentials_store_1.loadCredentials)();
|
|
24
|
+
if (stored?.api_base_url)
|
|
25
|
+
return stored.api_base_url;
|
|
26
|
+
return exports.DEFAULT_API_BASE_URL;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=api-base-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-base-url.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/api-base-url.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAOH,8CAMC;AAXD,2DAAsD;AAEtD,yFAAyF;AAC5E,QAAA,oBAAoB,GAAG,gCAAgC,CAAC;AAErE,SAAgB,iBAAiB,CAAC,SAAkB;IAClD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC5E,MAAM,MAAM,GAAG,IAAA,mCAAe,GAAE,CAAC;IACjC,IAAI,MAAM,EAAE,YAAY;QAAE,OAAO,MAAM,CAAC,YAAY,CAAC;IACrD,OAAO,4BAAoB,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-level credential storage at ~/.sungen/credentials.json.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately user-scoped (not project-local like the Figma PAT in .env): the
|
|
5
|
+
* login token is tied to the person and works across every sungen project.
|
|
6
|
+
* File mode 0600, parent dir 0700 — the token is a bearer secret.
|
|
7
|
+
*/
|
|
8
|
+
import type { Credentials } from './sungen-auth-types';
|
|
9
|
+
/** Absolute path to the credentials file (~/.sungen/credentials.json). */
|
|
10
|
+
export declare function credentialsPath(): string;
|
|
11
|
+
/** Load stored credentials, or null when absent / unreadable / malformed. */
|
|
12
|
+
export declare function loadCredentials(): Credentials | null;
|
|
13
|
+
/** Persist credentials with restrictive permissions (dir 0700, file 0600). */
|
|
14
|
+
export declare function saveCredentials(creds: Credentials): void;
|
|
15
|
+
/** Remove the credentials file; no-op when absent. */
|
|
16
|
+
export declare function clearCredentials(): void;
|
|
17
|
+
/** True when the credential carries an expiry that is already past (no refresh token — prompt re-login on expiry). */
|
|
18
|
+
export declare function isExpired(creds: Credentials): boolean;
|
|
19
|
+
//# sourceMappingURL=credentials-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-store.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/credentials-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAKvD,0EAA0E;AAC1E,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CASpD;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAOxD;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAED,sHAAsH;AACtH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAGrD"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* User-level credential storage at ~/.sungen/credentials.json.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately user-scoped (not project-local like the Figma PAT in .env): the
|
|
6
|
+
* login token is tied to the person and works across every sungen project.
|
|
7
|
+
* File mode 0600, parent dir 0700 — the token is a bearer secret.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.credentialsPath = credentialsPath;
|
|
44
|
+
exports.loadCredentials = loadCredentials;
|
|
45
|
+
exports.saveCredentials = saveCredentials;
|
|
46
|
+
exports.clearCredentials = clearCredentials;
|
|
47
|
+
exports.isExpired = isExpired;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const DIR_NAME = '.sungen';
|
|
52
|
+
const FILE_NAME = 'credentials.json';
|
|
53
|
+
/** Absolute path to the credentials file (~/.sungen/credentials.json). */
|
|
54
|
+
function credentialsPath() {
|
|
55
|
+
return path.join(os.homedir(), DIR_NAME, FILE_NAME);
|
|
56
|
+
}
|
|
57
|
+
/** Load stored credentials, or null when absent / unreadable / malformed. */
|
|
58
|
+
function loadCredentials() {
|
|
59
|
+
try {
|
|
60
|
+
const raw = fs.readFileSync(credentialsPath(), 'utf8');
|
|
61
|
+
const parsed = JSON.parse(raw);
|
|
62
|
+
if (!parsed || typeof parsed.token !== 'string')
|
|
63
|
+
return null;
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Persist credentials with restrictive permissions (dir 0700, file 0600). */
|
|
71
|
+
function saveCredentials(creds) {
|
|
72
|
+
const dir = path.join(os.homedir(), DIR_NAME);
|
|
73
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
74
|
+
const fp = credentialsPath();
|
|
75
|
+
// Write then chmod so an existing looser-mode file is tightened too.
|
|
76
|
+
fs.writeFileSync(fp, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
77
|
+
fs.chmodSync(fp, 0o600);
|
|
78
|
+
}
|
|
79
|
+
/** Remove the credentials file; no-op when absent. */
|
|
80
|
+
function clearCredentials() {
|
|
81
|
+
try {
|
|
82
|
+
fs.unlinkSync(credentialsPath());
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* already gone */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** True when the credential carries an expiry that is already past (no refresh token — prompt re-login on expiry). */
|
|
89
|
+
function isExpired(creds) {
|
|
90
|
+
if (typeof creds.expires_at !== 'number')
|
|
91
|
+
return false;
|
|
92
|
+
return creds.expires_at * 1000 <= Date.now();
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=credentials-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-store.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/credentials-store.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWH,0CAEC;AAGD,0CASC;AAGD,0CAOC;AAGD,4CAMC;AAGD,8BAGC;AAhDD,uCAAyB;AACzB,uCAAyB;AACzB,2CAA6B;AAG7B,MAAM,QAAQ,GAAG,SAAS,CAAC;AAC3B,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC,0EAA0E;AAC1E,SAAgB,eAAe;IAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AACtD,CAAC;AAED,6EAA6E;AAC7E,SAAgB,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;QAC9C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC7D,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAgB,eAAe,CAAC,KAAkB;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC9C,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAC7B,qEAAqE;IACrE,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,sDAAsD;AACtD,SAAgB,gBAAgB;IAC9B,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;AACH,CAAC;AAED,sHAAsH;AACtH,SAAgB,SAAS,CAAC,KAAkB;IAC1C,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvD,OAAO,KAAK,CAAC,UAAU,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 6-call RFC 8252 CLI login flow against FastSchema v0.13.0.
|
|
3
|
+
*
|
|
4
|
+
* Base path is /api/auth (verified live against the running v0.13.0 server;
|
|
5
|
+
* the earlier /api/user/auth contract was wrong). PKCE-S256 + single-use
|
|
6
|
+
* one-time code; the JWT is minted server-side
|
|
7
|
+
* and only delivered via the server-to-server `exchange` — it never appears in
|
|
8
|
+
* the browser. This module does NO disk I/O so it stays unit-testable.
|
|
9
|
+
*/
|
|
10
|
+
import type { PkceMaterial } from './sungen-auth-types';
|
|
11
|
+
export interface LoginFlowDeps {
|
|
12
|
+
apiBaseUrl: string;
|
|
13
|
+
pkce: PkceMaterial;
|
|
14
|
+
correlation: string;
|
|
15
|
+
redirectUri: string;
|
|
16
|
+
/** Open the authorize URL; return false to trigger the manual-paste fallback. */
|
|
17
|
+
openBrowser: (url: string) => boolean;
|
|
18
|
+
/** Resolve with the one-time code once the loopback callback fires. */
|
|
19
|
+
waitForCode: (correlation: string, timeoutMs?: number) => Promise<string>;
|
|
20
|
+
/** Optional sink for user-facing progress lines (defaults to console.log). */
|
|
21
|
+
log?: (msg: string) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface LoginResult {
|
|
24
|
+
token: string;
|
|
25
|
+
email: string;
|
|
26
|
+
expires_at?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Run the full login flow. Throws with a clear, distinct message for each
|
|
30
|
+
* failure mode (feature-off, missing challenge, exchange-401, etc.).
|
|
31
|
+
*/
|
|
32
|
+
export declare function runLoginFlow(deps: LoginFlowDeps): Promise<LoginResult>;
|
|
33
|
+
//# sourceMappingURL=login-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-flow.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/login-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAIV,YAAY,EACb,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,uEAAuE;IACvE,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1E,8EAA8E;IAC9E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA6BD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAmE5E"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The 6-call RFC 8252 CLI login flow against FastSchema v0.13.0.
|
|
4
|
+
*
|
|
5
|
+
* Base path is /api/auth (verified live against the running v0.13.0 server;
|
|
6
|
+
* the earlier /api/user/auth contract was wrong). PKCE-S256 + single-use
|
|
7
|
+
* one-time code; the JWT is minted server-side
|
|
8
|
+
* and only delivered via the server-to-server `exchange` — it never appears in
|
|
9
|
+
* the browser. This module does NO disk I/O so it stays unit-testable.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.runLoginFlow = runLoginFlow;
|
|
13
|
+
/** Strip a trailing slash so path joins never double up. */
|
|
14
|
+
function normalizeBase(url) {
|
|
15
|
+
return url.replace(/\/+$/, '');
|
|
16
|
+
}
|
|
17
|
+
async function postJson(url, body) {
|
|
18
|
+
let res;
|
|
19
|
+
try {
|
|
20
|
+
res = await fetch(url, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
throw new Error(`Network error reaching ${url}: ${err?.message ?? 'unknown'}`);
|
|
28
|
+
}
|
|
29
|
+
let data = null;
|
|
30
|
+
try {
|
|
31
|
+
const json = await res.json();
|
|
32
|
+
// FastSchema wraps success payloads in { data: ... } and errors in { error: ... }.
|
|
33
|
+
data = (json && json.data !== undefined ? json.data : json);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* non-JSON body */
|
|
37
|
+
}
|
|
38
|
+
return { ok: res.ok, status: res.status, data };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Run the full login flow. Throws with a clear, distinct message for each
|
|
42
|
+
* failure mode (feature-off, missing challenge, exchange-401, etc.).
|
|
43
|
+
*/
|
|
44
|
+
async function runLoginFlow(deps) {
|
|
45
|
+
const base = normalizeBase(deps.apiBaseUrl);
|
|
46
|
+
const log = deps.log ?? ((m) => console.log(m));
|
|
47
|
+
// 1. initiate — exchange PKCE challenge + loopback redirect for a carrier.
|
|
48
|
+
const init = await postJson(`${base}/api/auth/cli/initiate`, {
|
|
49
|
+
redirect_uri: deps.redirectUri,
|
|
50
|
+
correlation: deps.correlation,
|
|
51
|
+
code_challenge: deps.pkce.challenge,
|
|
52
|
+
});
|
|
53
|
+
if (init.status === 403) {
|
|
54
|
+
throw new Error('CLI login is disabled on the server (AUTH_CLI_LOGIN_ENABLED=false).');
|
|
55
|
+
}
|
|
56
|
+
if (init.status === 400) {
|
|
57
|
+
throw new Error('Server rejected the login request (missing/invalid PKCE challenge or redirect).');
|
|
58
|
+
}
|
|
59
|
+
if (!init.ok || !init.data?.authorize_url) {
|
|
60
|
+
throw new Error(`Login initiate failed (HTTP ${init.status}).`);
|
|
61
|
+
}
|
|
62
|
+
// 2. open the dashboard login page (authorize_url is a path → join with base).
|
|
63
|
+
const authorizeUrl = `${base}${init.data.authorize_url}`;
|
|
64
|
+
// Always print the URL: openBrowser reports success even when the launcher
|
|
65
|
+
// fails asynchronously (e.g. headless box with no xdg-open), so the paste
|
|
66
|
+
// path must stay visible regardless.
|
|
67
|
+
const opened = deps.openBrowser(authorizeUrl);
|
|
68
|
+
if (opened) {
|
|
69
|
+
log(`Opened your browser to complete login. Waiting…\nIf it didn't open, visit:\n ${authorizeUrl}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
log(`Open this URL in your browser to continue login:\n ${authorizeUrl}`);
|
|
73
|
+
}
|
|
74
|
+
// 3. wait for the loopback callback (asserts state===correlation inside).
|
|
75
|
+
const code = await deps.waitForCode(deps.correlation);
|
|
76
|
+
// 4. exchange the one-time code + verifier for the JWT.
|
|
77
|
+
const exch = await postJson(`${base}/api/auth/exchange`, {
|
|
78
|
+
code,
|
|
79
|
+
code_verifier: deps.pkce.verifier,
|
|
80
|
+
});
|
|
81
|
+
if (exch.status === 401) {
|
|
82
|
+
throw new Error('Token exchange rejected (code expired or verifier mismatch). Try `sungen login` again.');
|
|
83
|
+
}
|
|
84
|
+
if (!exch.ok || !exch.data?.token) {
|
|
85
|
+
throw new Error(`Token exchange failed (HTTP ${exch.status}).`);
|
|
86
|
+
}
|
|
87
|
+
const token = exch.data.token;
|
|
88
|
+
// Backend reports expiry as an ISO `expires` string; accept a numeric
|
|
89
|
+
// `expires_at` epoch too. Absent → rely on 401-on-expiry (single token, no refresh).
|
|
90
|
+
const expires_at = exch.data.expires_at ??
|
|
91
|
+
(exch.data.expires ? Math.floor(Date.parse(exch.data.expires) / 1000) : undefined);
|
|
92
|
+
// 5. confirm identity (Bearer) and capture the email.
|
|
93
|
+
let me;
|
|
94
|
+
try {
|
|
95
|
+
me = await fetch(`${base}/api/auth/me`, { headers: { Authorization: `Bearer ${token}` } });
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
throw new Error(`Network error confirming identity: ${err?.message ?? 'unknown'}`);
|
|
99
|
+
}
|
|
100
|
+
if (!me.ok) {
|
|
101
|
+
throw new Error(`Logged in but identity check failed (HTTP ${me.status}).`);
|
|
102
|
+
}
|
|
103
|
+
const meJson = await me.json();
|
|
104
|
+
const meBody = (meJson && meJson.data !== undefined ? meJson.data : meJson);
|
|
105
|
+
return { token, email: meBody.email, expires_at };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=login-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-flow.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/login-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-shot loopback HTTP listener for the CLI login redirect.
|
|
3
|
+
*
|
|
4
|
+
* Binds 127.0.0.1 on an ephemeral port, waits for exactly one
|
|
5
|
+
* GET /callback?code=<otc>&state=<correlation>, asserts state===correlation
|
|
6
|
+
* (CSRF guard), shows a "you may close this tab" page, then closes.
|
|
7
|
+
*/
|
|
8
|
+
export interface Loopback {
|
|
9
|
+
/** The ephemeral port the listener bound to. */
|
|
10
|
+
port: number;
|
|
11
|
+
/** Full redirect_uri to hand to the backend (http://127.0.0.1:<port>/callback). */
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
/** Resolve with the one-time code once the browser hits /callback; reject on timeout/mismatch. */
|
|
14
|
+
waitForCode(correlation: string, timeoutMs?: number): Promise<string>;
|
|
15
|
+
/** Force-close the listener (idempotent). */
|
|
16
|
+
close(): void;
|
|
17
|
+
}
|
|
18
|
+
/** Start the loopback listener. Resolves once it is bound and ready. */
|
|
19
|
+
export declare function startLoopback(): Promise<Loopback>;
|
|
20
|
+
//# sourceMappingURL=loopback-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback-server.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/loopback-server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,WAAW,QAAQ;IACvB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtE,6CAA6C;IAC7C,KAAK,IAAI,IAAI,CAAC;CACf;AASD,wEAAwE;AACxE,wBAAgB,aAAa,IAAI,OAAO,CAAC,QAAQ,CAAC,CAkFjD"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Single-shot loopback HTTP listener for the CLI login redirect.
|
|
4
|
+
*
|
|
5
|
+
* Binds 127.0.0.1 on an ephemeral port, waits for exactly one
|
|
6
|
+
* GET /callback?code=<otc>&state=<correlation>, asserts state===correlation
|
|
7
|
+
* (CSRF guard), shows a "you may close this tab" page, then closes.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.startLoopback = startLoopback;
|
|
44
|
+
const http = __importStar(require("http"));
|
|
45
|
+
const CLOSE_PAGE = '<!doctype html><meta charset="utf-8"><title>sungen login</title>' +
|
|
46
|
+
'<body style="font-family:system-ui;padding:3rem;text-align:center">' +
|
|
47
|
+
'<h2>Login complete</h2><p>You may close this tab and return to the terminal.</p></body>';
|
|
48
|
+
const DEFAULT_TIMEOUT_MS = 180000;
|
|
49
|
+
/** Start the loopback listener. Resolves once it is bound and ready. */
|
|
50
|
+
function startLoopback() {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
let resolveCode = null;
|
|
53
|
+
let rejectCode = null;
|
|
54
|
+
const server = http.createServer((req, res) => {
|
|
55
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
56
|
+
if (url.pathname !== '/callback') {
|
|
57
|
+
res.writeHead(404, { Connection: 'close' }).end('not found');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Connection: close so the browser doesn't hold a keep-alive socket open —
|
|
61
|
+
// otherwise server.close() would never fire and the CLI would hang after login.
|
|
62
|
+
res.writeHead(200, { 'Content-Type': 'text/html', Connection: 'close' }).end(CLOSE_PAGE);
|
|
63
|
+
const code = url.searchParams.get('code');
|
|
64
|
+
const state = url.searchParams.get('state');
|
|
65
|
+
// Hand the parsed values to the pending waitForCode promise.
|
|
66
|
+
pending = { code, state };
|
|
67
|
+
drain();
|
|
68
|
+
});
|
|
69
|
+
// Track live sockets so shutdown() can destroy any keep-alive stragglers.
|
|
70
|
+
const sockets = new Set();
|
|
71
|
+
server.on('connection', (s) => {
|
|
72
|
+
sockets.add(s);
|
|
73
|
+
s.on('close', () => sockets.delete(s));
|
|
74
|
+
});
|
|
75
|
+
/** Close the server AND destroy lingering sockets so the event loop can drain. */
|
|
76
|
+
function shutdown() {
|
|
77
|
+
if (server.listening)
|
|
78
|
+
server.close();
|
|
79
|
+
for (const s of sockets)
|
|
80
|
+
s.destroy();
|
|
81
|
+
sockets.clear();
|
|
82
|
+
}
|
|
83
|
+
// Buffer the callback result until a waiter (with its correlation) arrives.
|
|
84
|
+
let pending = null;
|
|
85
|
+
let expectedCorrelation = null;
|
|
86
|
+
function drain() {
|
|
87
|
+
if (!pending || !resolveCode || !rejectCode || expectedCorrelation == null)
|
|
88
|
+
return;
|
|
89
|
+
const { code, state } = pending;
|
|
90
|
+
if (state !== expectedCorrelation) {
|
|
91
|
+
rejectCode(new Error('Login state mismatch — possible CSRF; aborting.'));
|
|
92
|
+
}
|
|
93
|
+
else if (!code) {
|
|
94
|
+
rejectCode(new Error('Login callback missing authorization code.'));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
resolveCode(code);
|
|
98
|
+
}
|
|
99
|
+
// Let the response flush first, then tear everything down.
|
|
100
|
+
setImmediate(shutdown);
|
|
101
|
+
}
|
|
102
|
+
server.on('error', reject);
|
|
103
|
+
server.listen(0, '127.0.0.1', () => {
|
|
104
|
+
const port = server.address().port;
|
|
105
|
+
resolve({
|
|
106
|
+
port,
|
|
107
|
+
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
108
|
+
waitForCode(correlation, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
109
|
+
expectedCorrelation = correlation;
|
|
110
|
+
return new Promise((res, rej) => {
|
|
111
|
+
resolveCode = res;
|
|
112
|
+
rejectCode = rej;
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
shutdown();
|
|
115
|
+
rej(new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for browser login.`));
|
|
116
|
+
}, timeoutMs);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
// Wrap to always clear the timer.
|
|
119
|
+
const origRes = resolveCode, origRej = rejectCode;
|
|
120
|
+
resolveCode = (c) => { clearTimeout(timer); origRes(c); };
|
|
121
|
+
rejectCode = (e) => { clearTimeout(timer); origRej(e); };
|
|
122
|
+
drain();
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
close() {
|
|
126
|
+
shutdown();
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=loopback-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback-server.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/loopback-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform "open this URL in the default browser" helper.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately dependency-free — a small platform switch instead of pulling in
|
|
5
|
+
* the `open` npm package. Returns false on failure so the caller can fall back
|
|
6
|
+
* to printing the URL for manual paste.
|
|
7
|
+
*/
|
|
8
|
+
/** Attempt to open `url` in the system browser. Returns true if a launcher was spawned. */
|
|
9
|
+
export declare function openBrowser(url: string): boolean;
|
|
10
|
+
//# sourceMappingURL=open-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/open-browser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA8BhD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-platform "open this URL in the default browser" helper.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately dependency-free — a small platform switch instead of pulling in
|
|
6
|
+
* the `open` npm package. Returns false on failure so the caller can fall back
|
|
7
|
+
* to printing the URL for manual paste.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.openBrowser = openBrowser;
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
/** Attempt to open `url` in the system browser. Returns true if a launcher was spawned. */
|
|
13
|
+
function openBrowser(url) {
|
|
14
|
+
let command;
|
|
15
|
+
let args;
|
|
16
|
+
switch (process.platform) {
|
|
17
|
+
case 'darwin':
|
|
18
|
+
command = 'open';
|
|
19
|
+
args = [url];
|
|
20
|
+
break;
|
|
21
|
+
case 'win32':
|
|
22
|
+
// `start` is a cmd builtin; the empty "" is the window-title placeholder.
|
|
23
|
+
command = 'cmd';
|
|
24
|
+
args = ['/c', 'start', '', url];
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
command = 'xdg-open';
|
|
28
|
+
args = [url];
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const child = (0, child_process_1.spawn)(command, args, { detached: true, stdio: 'ignore' });
|
|
33
|
+
// A missing launcher fails asynchronously here, after we've already returned
|
|
34
|
+
// true; that's why callers also print the URL rather than relying on this.
|
|
35
|
+
child.on('error', () => { });
|
|
36
|
+
child.unref();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=open-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/open-browser.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAKH,kCA8BC;AAjCD,iDAAsC;AAEtC,2FAA2F;AAC3F,SAAgB,WAAW,CAAC,GAAW;IACrC,IAAI,OAAe,CAAC;IACpB,IAAI,IAAc,CAAC;IAEnB,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,QAAQ;YACX,OAAO,GAAG,MAAM,CAAC;YACjB,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YACb,MAAM;QACR,KAAK,OAAO;YACV,0EAA0E;YAC1E,OAAO,GAAG,KAAK,CAAC;YAChB,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YAChC,MAAM;QACR;YACE,OAAO,GAAG,UAAU,CAAC;YACrB,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YACb,MAAM;IACV,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAA,qBAAK,EAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxE,6EAA6E;QAC7E,2EAA2E;QAC3E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAA4B,CAAC,CAAC,CAAC;QACtD,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE-S256 + correlation generation for the CLI login flow.
|
|
3
|
+
*
|
|
4
|
+
* The backend mandates S256 (plain is rejected). The verifier never leaves the
|
|
5
|
+
* process except inside the server-to-server `exchange` POST; the challenge is
|
|
6
|
+
* what travels to the browser side.
|
|
7
|
+
*/
|
|
8
|
+
import type { PkceMaterial } from './sungen-auth-types';
|
|
9
|
+
/** Generate a fresh PKCE verifier + S256 challenge pair. */
|
|
10
|
+
export declare function generatePkce(): PkceMaterial;
|
|
11
|
+
/** Random opaque value used to bind the loopback callback to this attempt (CSRF guard). */
|
|
12
|
+
export declare function randomCorrelation(): string;
|
|
13
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOxD,4DAA4D;AAC5D,wBAAgB,YAAY,IAAI,YAAY,CAI3C;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PKCE-S256 + correlation generation for the CLI login flow.
|
|
4
|
+
*
|
|
5
|
+
* The backend mandates S256 (plain is rejected). The verifier never leaves the
|
|
6
|
+
* process except inside the server-to-server `exchange` POST; the challenge is
|
|
7
|
+
* what travels to the browser side.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.generatePkce = generatePkce;
|
|
11
|
+
exports.randomCorrelation = randomCorrelation;
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
13
|
+
/** base64url with padding stripped, per RFC 7636 §A. */
|
|
14
|
+
function base64url(buf) {
|
|
15
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
16
|
+
}
|
|
17
|
+
/** Generate a fresh PKCE verifier + S256 challenge pair. */
|
|
18
|
+
function generatePkce() {
|
|
19
|
+
const verifier = base64url((0, crypto_1.randomBytes)(32));
|
|
20
|
+
const challenge = base64url((0, crypto_1.createHash)('sha256').update(verifier).digest());
|
|
21
|
+
return { verifier, challenge };
|
|
22
|
+
}
|
|
23
|
+
/** Random opaque value used to bind the loopback callback to this attempt (CSRF guard). */
|
|
24
|
+
function randomCorrelation() {
|
|
25
|
+
return (0, crypto_1.randomBytes)(16).toString('hex');
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/pkce.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAWH,oCAIC;AAGD,8CAEC;AAlBD,mCAAiD;AAGjD,wDAAwD;AACxD,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,4DAA4D;AAC5D,SAAgB,YAAY;IAC1B,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED,2FAA2F;AAC3F,SAAgB,iBAAiB;IAC/B,OAAO,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
|