requestshield 0.1.6 → 0.1.8
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 +177 -68
- package/config/.env.prod +1 -0
- package/package.json +1 -1
- package/skills/requestshield/SKILL.md +20 -17
- package/skills/requestshield/assets/AGENTS.codex.md +5 -3
- package/skills/requestshield/references/backend-java-core.md +3 -3
- package/skills/requestshield/references/backend-spring-boot.md +3 -3
- package/skills/requestshield/references/browser-manual.md +1 -1
- package/skills/requestshield/references/cli.md +92 -28
- package/skills/requestshield/references/integration-planning.md +10 -8
- package/skills/requestshield/references/troubleshooting.md +2 -2
- package/src/api-client.mjs +1 -1
- package/src/args.mjs +98 -106
- package/src/cli.mjs +69 -243
- package/src/command-registry.mjs +97 -0
- package/src/commands/agent-setup.mjs +24 -17
- package/src/commands/agent-status.mjs +60 -0
- package/src/commands/application-mutations.mjs +8 -4
- package/src/commands/apps-get.mjs +12 -4
- package/src/commands/apps-list.mjs +20 -13
- package/src/commands/contract.mjs +25 -0
- package/src/commands/keys-create.mjs +5 -2
- package/src/commands/mutation-support.mjs +26 -12
- package/src/commands/output.mjs +21 -0
- package/src/commands/secret-commands.mjs +13 -6
- package/src/commands/signin.mjs +17 -8
- package/src/commands/signout.mjs +7 -3
- package/src/commands/update-check.mjs +97 -43
- package/src/config.mjs +29 -3
- package/src/entrypoint.mjs +20 -13
- package/src/integration-contract-client.mjs +81 -0
- package/src/integration-contract.mjs +104 -0
- package/src/oauth-client.mjs +2 -2
- package/src/oauth-loopback.mjs +1 -1
- package/src/session-files.mjs +4 -4
- package/src/session-store.mjs +2 -2
package/src/config.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { CliError } from "./errors.mjs";
|
|
|
7
7
|
|
|
8
8
|
/** @typedef {'qat' | 'stg' | 'prod'} Profile */
|
|
9
9
|
const PROFILES = new Set(["qat", "stg", "prod"]);
|
|
10
|
-
const CONFIG_KEYS = new Set(["API_URL", "OAUTH_ISSUER", "OAUTH_CLIENT_ID", "OAUTH_AUTHORIZATION_ISSUER"]);
|
|
10
|
+
const CONFIG_KEYS = new Set(["API_URL", "DOCS_URL", "OAUTH_ISSUER", "OAUTH_CLIENT_ID", "OAUTH_AUTHORIZATION_ISSUER"]);
|
|
11
11
|
|
|
12
12
|
export const DEFAULT_MAVEN_REPOSITORY =
|
|
13
13
|
"https://sdk.intellifend.com/packages/maven";
|
|
@@ -73,6 +73,32 @@ export function getApiUrl(profile = "prod") {
|
|
|
73
73
|
return configuredApiUrl(readProfileConfig(profile), profile);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/** Public documentation selection is independent of API and OAuth settings.
|
|
77
|
+
* @param {Profile} [profile]
|
|
78
|
+
*/
|
|
79
|
+
export function getDocsUrl(profile = "prod") {
|
|
80
|
+
let values;
|
|
81
|
+
try { values = readProfileConfig(profile); }
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new CliError(error instanceof CliError ? error.message : "Cannot read the RequestShield documentation configuration", {code: "CONTRACT_CONFIG_INVALID", exitCode: 2});
|
|
84
|
+
}
|
|
85
|
+
if (!values.DOCS_URL) {
|
|
86
|
+
throw new CliError(`Documentation is not configured for ${getCommandName(profile)}. Set DOCS_URL in config/.env.${profile}.`, {code: "CONTRACT_CONFIG_INVALID", exitCode: 2});
|
|
87
|
+
}
|
|
88
|
+
return validateDocsUrl(values.DOCS_URL);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @param {unknown} value */
|
|
92
|
+
export function validateDocsUrl(value) {
|
|
93
|
+
try {
|
|
94
|
+
if (typeof value !== "string" || !/^[\x21-\x7e]+$/.test(value) || /[\\?#]/.test(value)) throw new Error();
|
|
95
|
+
return validatedUrl(value, "DOCS_URL").replace(/\/+$/, "");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new CliError("DOCS_URL must be a valid HTTPS URL without credentials, query or fragment (HTTP is allowed only on loopback)", {code: "CONTRACT_CONFIG_INVALID", exitCode: 2});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
/** @param {Record<string, string | undefined>} values @param {Profile} profile */
|
|
77
103
|
function configuredApiUrl(values, profile) {
|
|
78
104
|
if (!values.API_URL) {
|
|
@@ -112,7 +138,7 @@ function readProfileConfig(profile) {
|
|
|
112
138
|
throw invalidConfig(`Cannot read config/.env.${profile} for ${getCommandName(profile)}. Restore the package configuration file.`);
|
|
113
139
|
}
|
|
114
140
|
if (Object.keys(values).some(key => !CONFIG_KEYS.has(key))) {
|
|
115
|
-
throw invalidConfig(`config/.env.${profile} accepts only API_URL, OAUTH_ISSUER, OAUTH_CLIENT_ID and OAUTH_AUTHORIZATION_ISSUER; do not store credentials in this file.`);
|
|
141
|
+
throw invalidConfig(`config/.env.${profile} accepts only API_URL, DOCS_URL, OAUTH_ISSUER, OAUTH_CLIENT_ID and OAUTH_AUTHORIZATION_ISSUER; do not store credentials in this file.`);
|
|
116
142
|
}
|
|
117
143
|
return values;
|
|
118
144
|
}
|
|
@@ -120,7 +146,7 @@ function readProfileConfig(profile) {
|
|
|
120
146
|
/** @param {unknown} value @returns {string} */
|
|
121
147
|
export function validateBearerToken(value) {
|
|
122
148
|
if (typeof value !== "string" || value.length > 16_384 || !/^[A-Za-z0-9._~+/-]+=*$/.test(value)) {
|
|
123
|
-
throw new CliError("The access token is invalid; run `requestshield
|
|
149
|
+
throw new CliError("The access token is invalid; run `requestshield login` again", {
|
|
124
150
|
code: "INVALID_ACCESS_TOKEN", exitCode: 3,
|
|
125
151
|
});
|
|
126
152
|
}
|
package/src/entrypoint.mjs
CHANGED
|
@@ -3,22 +3,29 @@ import { run } from "./cli.mjs";
|
|
|
3
3
|
import { CliError } from "./errors.mjs";
|
|
4
4
|
import { getCommandInvocation, getCommandName } from "./config.mjs";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
6
|
+
/** Render exactly one result document on JSON failures. Unknown failures are redacted.
|
|
7
|
+
* @param {string[]} argv @param {Parameters<typeof run>[1]} [deps] */
|
|
8
|
+
export async function execute(argv, deps = {}) {
|
|
9
|
+
const profile = deps.profile ?? "prod";
|
|
10
10
|
try {
|
|
11
|
-
await run(
|
|
11
|
+
await run(argv, deps);
|
|
12
|
+
return 0;
|
|
12
13
|
} catch (error) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.replaceAll("`requestshield
|
|
18
|
-
.replaceAll("`requestshield signout`", `\`${invocation} signout\``)
|
|
14
|
+
const known = error instanceof CliError;
|
|
15
|
+
const invocation = getCommandInvocation(profile);
|
|
16
|
+
const message = (known ? error.message : "The command failed unexpectedly. Please try again.")
|
|
17
|
+
.replaceAll("`requestshield login`", `\`${invocation} login\``)
|
|
18
|
+
.replaceAll("`requestshield logout`", `\`${invocation} logout\``)
|
|
19
19
|
.replaceAll("Usage: requestshield ", `Usage: ${invocation} `)
|
|
20
20
|
.replace(/^ requestshield /gm, ` ${invocation} `);
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const code = known ? error.code : "CLI_ERROR";
|
|
22
|
+
if (argv.includes("--json")) (deps.log ?? console.log)(JSON.stringify({error: {code, message}}, null, 2));
|
|
23
|
+
else (deps.warn ?? console.error)(`${getCommandName(profile)}: ${message}\nCode: ${code}`);
|
|
24
|
+
return known ? error.exitCode : 1;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
/** @param {import('./config.mjs').Profile} profile */
|
|
29
|
+
export async function main(profile) {
|
|
30
|
+
process.exitCode = await execute(process.argv.slice(2), {profile});
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { CliError } from "./errors.mjs";
|
|
3
|
+
import { validateDocsUrl } from "./config.mjs";
|
|
4
|
+
import { validateIntegrationContract } from "./integration-contract.mjs";
|
|
5
|
+
|
|
6
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
7
|
+
|
|
8
|
+
/** Fetch only public metadata. Never attach OAuth credentials or use Management APIs. */
|
|
9
|
+
export class IntegrationContractClient {
|
|
10
|
+
/** @param {{docsUrl: string, environment: import('./config.mjs').Profile, fetchImpl?: typeof fetch, timeoutMs?: number}} options */
|
|
11
|
+
constructor({docsUrl, environment, fetchImpl = fetch, timeoutMs = 15_000}) {
|
|
12
|
+
this.url = `${validateDocsUrl(docsUrl)}/integration-contract.json`;
|
|
13
|
+
this.environment = environment;
|
|
14
|
+
this.fetchImpl = fetchImpl;
|
|
15
|
+
this.timeoutMs = timeoutMs;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getContract() {
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
/** @type {ReadableStreamDefaultReader<Uint8Array> | undefined} */
|
|
21
|
+
let reader;
|
|
22
|
+
let expired = false;
|
|
23
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
24
|
+
let timer;
|
|
25
|
+
const deadline = new Promise((_, reject) => {
|
|
26
|
+
timer = setTimeout(() => {
|
|
27
|
+
expired = true;
|
|
28
|
+
controller.abort();
|
|
29
|
+
reject(timeout());
|
|
30
|
+
}, this.timeoutMs);
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const operation = (async () => {
|
|
34
|
+
const response = await this.fetchImpl(this.url, {
|
|
35
|
+
method: "GET", headers: {Accept: "application/json"},
|
|
36
|
+
credentials: "omit", redirect: "error", cache: "no-store", signal: controller.signal,
|
|
37
|
+
});
|
|
38
|
+
if (expired) { void response.body?.cancel().catch(() => {}); throw timeout(); }
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
void response.body?.cancel().catch(() => {});
|
|
41
|
+
throw new CliError(response.status === 404 ? "The public integration contract has not been published for this environment (HTTP 404)." : `The public integration contract request failed: HTTP ${response.status}.`, {code: response.status === 404 ? "CONTRACT_NOT_PUBLISHED" : "CONTRACT_FETCH_FAILED", exitCode: 1, httpStatus: response.status});
|
|
42
|
+
}
|
|
43
|
+
const mediaType = response.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
|
|
44
|
+
if (!mediaType || !/^application\/(?:json|[a-z0-9!#$&^_.+-]+\+json)$/.test(mediaType)
|
|
45
|
+
|| Number(response.headers.get("content-length")) > MAX_BODY_BYTES) {
|
|
46
|
+
void response.body?.cancel().catch(() => {});
|
|
47
|
+
throw invalidResponse();
|
|
48
|
+
}
|
|
49
|
+
const chunks = [];
|
|
50
|
+
let size = 0;
|
|
51
|
+
if (response.body) {
|
|
52
|
+
reader = response.body.getReader();
|
|
53
|
+
while (true) {
|
|
54
|
+
const {done, value} = await reader.read();
|
|
55
|
+
if (expired) throw timeout();
|
|
56
|
+
if (done) break;
|
|
57
|
+
size += value.byteLength;
|
|
58
|
+
if (size > MAX_BODY_BYTES) throw invalidResponse();
|
|
59
|
+
chunks.push(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const body = JSON.parse(new TextDecoder("utf-8", {fatal: true}).decode(Buffer.concat(chunks)));
|
|
64
|
+
return validateIntegrationContract(body, this.environment);
|
|
65
|
+
} catch { throw invalidResponse(); }
|
|
66
|
+
})();
|
|
67
|
+
return await Promise.race([operation, /** @type {Promise<never>} */ (deadline)]);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (expired) throw timeout();
|
|
70
|
+
if (error instanceof CliError) throw error;
|
|
71
|
+
throw new CliError("Could not retrieve the public integration contract. Check connectivity and try again.", {code: "CONTRACT_FETCH_FAILED", exitCode: 1});
|
|
72
|
+
} finally {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
if (reader) void reader.cancel().catch(() => {});
|
|
75
|
+
controller.abort();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function timeout() { return new CliError("The public integration contract request timed out. Check connectivity and try again.", {code: "CONTRACT_FETCH_FAILED", exitCode: 1}); }
|
|
81
|
+
function invalidResponse() { return new CliError("The public integration contract is invalid, oversized or does not match this environment.", {code: "CONTRACT_INVALID", exitCode: 1}); }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {{groupId: string, artifactId: string}} MavenArtifact */
|
|
4
|
+
/** @typedef {{
|
|
5
|
+
* schemaVersion: 1, contractVersion: string, environment: 'qat' | 'stg' | 'prod',
|
|
6
|
+
* services: {challengeUrl: string},
|
|
7
|
+
* browser: {sdkVersion: string, scriptUrl: string, integrity: string,
|
|
8
|
+
* tokenHeader: 'X-IntelliFend-Token', availableModes: Array<'manual' | 'seamless'>,
|
|
9
|
+
* cspAdditions: {scriptSrc: string[], connectSrc: string[], workerSrc: string[]}},
|
|
10
|
+
* backend: {sdkVersion: string, minJdk: number, mavenRepository: string,
|
|
11
|
+
* core: MavenArtifact, springBoot3: MavenArtifact & {framework: 'Spring Boot 3 MVC'}},
|
|
12
|
+
* documentation: {browserSdk: string, javaSdk: string, domainsAndCsp: string}
|
|
13
|
+
* }} IntegrationContract */
|
|
14
|
+
|
|
15
|
+
const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
16
|
+
const ENVIRONMENTS = new Set(['qat', 'stg', 'prod']);
|
|
17
|
+
|
|
18
|
+
/** Shared by the CLI reader and customer-docs build checks. This module performs
|
|
19
|
+
* no I/O and includes no release data; customer-docs owns the selected releases.
|
|
20
|
+
* Schema 1 rejects extra fields so unreviewed content never reaches CLI output.
|
|
21
|
+
* @param {unknown} value
|
|
22
|
+
* @param {string} expectedEnvironment
|
|
23
|
+
* @returns {{data: IntegrationContract}}
|
|
24
|
+
*/
|
|
25
|
+
export function validateIntegrationContract(value, expectedEnvironment) {
|
|
26
|
+
const envelope = object(value, ['data']);
|
|
27
|
+
const data = object(envelope.data, ['schemaVersion', 'contractVersion', 'environment', 'services', 'browser', 'backend', 'documentation']);
|
|
28
|
+
check(data.schemaVersion === 1 && ENVIRONMENTS.has(expectedEnvironment) && data.environment === expectedEnvironment);
|
|
29
|
+
version(data.contractVersion);
|
|
30
|
+
|
|
31
|
+
const services = object(data.services, ['challengeUrl']);
|
|
32
|
+
const challenge = publicUrl(services.challengeUrl);
|
|
33
|
+
const browser = object(data.browser, ['sdkVersion', 'scriptUrl', 'integrity', 'tokenHeader', 'availableModes', 'cspAdditions']);
|
|
34
|
+
version(browser.sdkVersion);
|
|
35
|
+
const script = publicUrl(browser.scriptUrl);
|
|
36
|
+
check(script.pathname.endsWith(`/requestshield/v${browser.sdkVersion}/intellifend.js`));
|
|
37
|
+
check(typeof browser.integrity === 'string' && /^sha384-[A-Za-z0-9+/]{64}$/.test(browser.integrity));
|
|
38
|
+
check(browser.tokenHeader === 'X-IntelliFend-Token');
|
|
39
|
+
const modes = stringList(browser.availableModes, 2);
|
|
40
|
+
check(modes.includes('manual') && modes.every(mode => mode === 'manual' || mode === 'seamless'));
|
|
41
|
+
|
|
42
|
+
const csp = object(browser.cspAdditions, ['scriptSrc', 'connectSrc', 'workerSrc']);
|
|
43
|
+
const scriptSources = origins(csp.scriptSrc);
|
|
44
|
+
const connectSources = origins(csp.connectSrc);
|
|
45
|
+
check(scriptSources.includes(script.origin) && connectSources.includes(challenge.origin));
|
|
46
|
+
const workerSources = stringList(csp.workerSrc, 1);
|
|
47
|
+
check(workerSources.length === 1 && workerSources[0] === 'blob:');
|
|
48
|
+
|
|
49
|
+
const backend = object(data.backend, ['sdkVersion', 'minJdk', 'mavenRepository', 'core', 'springBoot3']);
|
|
50
|
+
version(backend.sdkVersion);
|
|
51
|
+
check(typeof backend.minJdk === 'number' && Number.isSafeInteger(backend.minJdk) && backend.minJdk >= 17 && backend.minJdk <= 100);
|
|
52
|
+
publicUrl(backend.mavenRepository);
|
|
53
|
+
const core = object(backend.core, ['groupId', 'artifactId']);
|
|
54
|
+
const starter = object(backend.springBoot3, ['groupId', 'artifactId', 'framework']);
|
|
55
|
+
for (const artifact of [core, starter]) {
|
|
56
|
+
check(typeof artifact.groupId === 'string' && /^[A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)+$/.test(artifact.groupId) && artifact.groupId.length <= 200);
|
|
57
|
+
check(typeof artifact.artifactId === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(artifact.artifactId));
|
|
58
|
+
}
|
|
59
|
+
check(starter.framework === 'Spring Boot 3 MVC');
|
|
60
|
+
const docs = object(data.documentation, ['browserSdk', 'javaSdk', 'domainsAndCsp']);
|
|
61
|
+
for (const url of Object.values(docs)) publicUrl(url);
|
|
62
|
+
return /** @type {{data: IntegrationContract}} */ (value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @param {unknown} condition @returns {asserts condition} */
|
|
66
|
+
function check(condition) {
|
|
67
|
+
if (!condition) throw new Error('Invalid integration contract');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @param {unknown} value @param {string[]} keys @returns {Record<string, unknown>} */
|
|
71
|
+
function object(value, keys) {
|
|
72
|
+
check(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
73
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
74
|
+
check(Object.keys(record).length === keys.length && keys.every(key => Object.hasOwn(record, key)));
|
|
75
|
+
return record;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @param {unknown} value */
|
|
79
|
+
function version(value) {
|
|
80
|
+
check(typeof value === 'string' && value.length <= 64 && VERSION.test(value));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @param {unknown} value @returns {URL} */
|
|
84
|
+
function publicUrl(value) {
|
|
85
|
+
check(typeof value === 'string' && value.length <= 2048 && /^[\x21-\x7e]+$/.test(value) && !/[?#\\]/.test(value));
|
|
86
|
+
let url;
|
|
87
|
+
try { url = new URL(value); } catch { throw new Error('Invalid integration contract'); }
|
|
88
|
+
check(url.protocol === 'https:' && !url.username && !url.password && !url.search && !url.hash);
|
|
89
|
+
return url;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @param {unknown} value @param {number} maximum @returns {string[]} */
|
|
93
|
+
function stringList(value, maximum) {
|
|
94
|
+
check(Array.isArray(value) && value.length >= 1 && value.length <= maximum);
|
|
95
|
+
check(value.every(item => typeof item === 'string') && new Set(value).size === value.length);
|
|
96
|
+
return /** @type {string[]} */ (value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @param {unknown} value @returns {string[]} */
|
|
100
|
+
function origins(value) {
|
|
101
|
+
const list = stringList(value, 10);
|
|
102
|
+
for (const item of list) check(publicUrl(item).origin === item);
|
|
103
|
+
return list;
|
|
104
|
+
}
|
package/src/oauth-client.mjs
CHANGED
|
@@ -58,7 +58,7 @@ export class OAuthClient {
|
|
|
58
58
|
return this.#credentials(body);
|
|
59
59
|
} catch (error) {
|
|
60
60
|
if (error instanceof CliError && ["OAUTH_INVALID_GRANT", "OAUTH_REJECTED"].includes(error.code)) throw error;
|
|
61
|
-
throw new CliError("The refresh result is uncertain; run `requestshield
|
|
61
|
+
throw new CliError("The refresh result is uncertain; run `requestshield login` again", {code: "OAUTH_REFRESH_UNCERTAIN", exitCode: 3});
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -126,7 +126,7 @@ export class OAuthClient {
|
|
|
126
126
|
} catch { throw invalidResponse(); }
|
|
127
127
|
if (!response.ok) {
|
|
128
128
|
if (body.error === "invalid_grant" || ["idp_refresh_token_invalid", "idp_authorization_code_invalid", "invalid_grant"].includes(String(body.error_type))) {
|
|
129
|
-
throw new CliError("Authorization was rejected; run `requestshield
|
|
129
|
+
throw new CliError("Authorization was rejected; run `requestshield login` again", {code: "OAUTH_INVALID_GRANT", exitCode: 3});
|
|
130
130
|
}
|
|
131
131
|
if (response.status >= 400 && response.status < 500) {
|
|
132
132
|
throw new CliError("The identity provider rejected the request; check the OAuth configuration or sign in again", {code: "OAUTH_REJECTED", exitCode: 3});
|
package/src/oauth-loopback.mjs
CHANGED
|
@@ -68,7 +68,7 @@ export async function createLoopbackReceiver({state, issuer, signal}) {
|
|
|
68
68
|
else socket.destroy();
|
|
69
69
|
});
|
|
70
70
|
const aborted = () => {
|
|
71
|
-
rejectResult(new CliError("Sign-in was cancelled or expired; run `requestshield
|
|
71
|
+
rejectResult(new CliError("Sign-in was cancelled or expired; run `requestshield login` again", {code: "SIGNIN_CANCELLED", exitCode: 3}));
|
|
72
72
|
if (pendingResponse && !pendingResponse.writableEnded) pendingResponse.writeHead(400).end("Sign-in was not completed. Return to your terminal.");
|
|
73
73
|
server.close();
|
|
74
74
|
server.closeAllConnections();
|
package/src/session-files.mjs
CHANGED
|
@@ -79,7 +79,7 @@ export class SessionFiles {
|
|
|
79
79
|
} catch (error) {
|
|
80
80
|
if (error instanceof CliError) throw error;
|
|
81
81
|
if (errorCode(error) === "ENOENT") {
|
|
82
|
-
throw new CliError("Not signed in. Run `requestshield
|
|
82
|
+
throw new CliError("Not signed in. Run `requestshield login` first.", { code: "NOT_SIGNED_IN", exitCode: 3 });
|
|
83
83
|
}
|
|
84
84
|
throw storageFailure();
|
|
85
85
|
} finally { await handle?.close().catch(() => undefined); }
|
|
@@ -108,7 +108,7 @@ export class SessionFiles {
|
|
|
108
108
|
return true;
|
|
109
109
|
} catch (error) {
|
|
110
110
|
if (removed) {
|
|
111
|
-
throw new CliError("The local session was removed, but its durability could not be confirmed. Run `requestshield
|
|
111
|
+
throw new CliError("The local session was removed, but its durability could not be confirmed. Run `requestshield logout` again to confirm removal.", {
|
|
112
112
|
code: "SESSION_COMMIT_UNCERTAIN", exitCode: 7,
|
|
113
113
|
});
|
|
114
114
|
}
|
|
@@ -142,7 +142,7 @@ export class SessionFiles {
|
|
|
142
142
|
await this.syncDirectory();
|
|
143
143
|
} catch (error) {
|
|
144
144
|
if (replaced) {
|
|
145
|
-
throw new CliError("The session was replaced, but its durability could not be confirmed. Run `requestshield
|
|
145
|
+
throw new CliError("The session was replaced, but its durability could not be confirmed. Run `requestshield login` again if the next command cannot load it.", {
|
|
146
146
|
code: "SESSION_COMMIT_UNCERTAIN", exitCode: 7,
|
|
147
147
|
});
|
|
148
148
|
}
|
|
@@ -209,5 +209,5 @@ function permissionFailure() {
|
|
|
209
209
|
return new CliError("Could not protect the RequestShield session for the current operating-system user. No new credentials were written.", { code: "SESSION_PERMISSION_ERROR", exitCode: 7 });
|
|
210
210
|
}
|
|
211
211
|
function invalidFile() {
|
|
212
|
-
return new CliError("The saved RequestShield session is invalid. Run `requestshield
|
|
212
|
+
return new CliError("The saved RequestShield session is invalid. Run `requestshield login` again.", { code: "INVALID_SESSION", exitCode: 3 });
|
|
213
213
|
}
|
package/src/session-store.mjs
CHANGED
|
@@ -180,12 +180,12 @@ function checkedCredentials(value) {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
function invalidSession() {
|
|
183
|
-
return new CliError("The saved RequestShield session is invalid or belongs to different OAuth configuration. Run `requestshield
|
|
183
|
+
return new CliError("The saved RequestShield session is invalid or belongs to different OAuth configuration. Run `requestshield login` again.", {
|
|
184
184
|
code: "INVALID_SESSION", exitCode: 3,
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
function signinRequired() {
|
|
188
|
-
return new CliError("The RequestShield session could not be safely refreshed. Run `requestshield
|
|
188
|
+
return new CliError("The RequestShield session could not be safely refreshed. Run `requestshield login` again.", {
|
|
189
189
|
code: "SESSION_SIGNIN_REQUIRED", exitCode: 3,
|
|
190
190
|
});
|
|
191
191
|
}
|