requestshield 0.1.4 → 0.1.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.
@@ -0,0 +1,81 @@
1
+ // @ts-check
2
+
3
+ import { CliError } from "../errors.mjs";
4
+ import { objectString } from "../api-client.mjs";
5
+
6
+ /**
7
+ * @param {{ appKey: string, from?: string, to?: string, granularity?: string }} options
8
+ * @param {{ api: { challengeVolume(accessToken: string, appKey: string, options: {from?: string, to?: string, granularity?: string}): Promise<{body: unknown}> }, sessions: { loadToken(): Promise<string> }, log: (message: string) => void }} deps
9
+ */
10
+ export async function showChallengeVolume(options, deps) {
11
+ const accessToken = await deps.sessions.loadToken();
12
+ const filters = {
13
+ ...(options.from ? { from: options.from } : {}),
14
+ ...(options.to ? { to: options.to } : {}),
15
+ ...(options.granularity ? { granularity: options.granularity } : {}),
16
+ };
17
+ const result = await deps.api.challengeVolume(accessToken, options.appKey, filters);
18
+ const volume = parseChallengeVolume(result.body, options);
19
+ deps.log(JSON.stringify({ ok: true, data: volume }, null, 2));
20
+ }
21
+
22
+ /**
23
+ * @param {unknown} body
24
+ * @param {{ appKey: string, from?: string, to?: string, granularity?: string }} requested
25
+ */
26
+ function parseChallengeVolume(body, requested) {
27
+ const ok = body && typeof body === "object" ?
28
+ Reflect.get(body, "ok") : undefined;
29
+
30
+ const data = body && typeof body === "object" ?
31
+ Reflect.get(body, "data") : undefined;
32
+
33
+ if (ok !== true || !data || typeof data !== "object" || Array.isArray(data)) {
34
+ throw invalidResponse("The challenge volume response did not contain ok=true and data");
35
+ }
36
+
37
+ const appKey = objectString(data, "app_key");
38
+ const from = objectString(data, "from");
39
+ const to = objectString(data, "to");
40
+ const granularity = objectString(data, "granularity");
41
+ const challengeCount = Reflect.get(data, "challenge_count");
42
+
43
+ if (appKey !== requested.appKey) {
44
+ throw invalidResponse("The challenge volume response did not match the requested App Key");
45
+ }
46
+ if (!from || !to || !isIsoTime(from) || !isIsoTime(to) || Date.parse(from) > Date.parse(to)) {
47
+ throw invalidResponse("The challenge volume response contained an invalid time range");
48
+ }
49
+ if (requested.from && Date.parse(from) !== Date.parse(requested.from)) {
50
+ throw invalidResponse("The challenge volume response did not match the requested start time");
51
+ }
52
+ if (requested.to && Date.parse(to) !== Date.parse(requested.to)) {
53
+ throw invalidResponse("The challenge volume response did not match the requested end time");
54
+ }
55
+ if (!granularity || (requested.granularity && granularity !== requested.granularity)) {
56
+ throw invalidResponse("The challenge volume response contained an unexpected granularity");
57
+ }
58
+ if (!Number.isSafeInteger(challengeCount) || /** @type {number} */ (challengeCount) < 0) {
59
+ throw invalidResponse("The challenge volume response contained an invalid challenge_count");
60
+ }
61
+
62
+ // Rebuild the response so unexpected API fields cannot reach command output.
63
+ return {
64
+ app_key: appKey,
65
+ from,
66
+ to,
67
+ granularity,
68
+ challenge_count: challengeCount,
69
+ };
70
+ }
71
+
72
+ /** @param {string} value */
73
+ function isIsoTime(value) {
74
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
75
+ && Number.isFinite(Date.parse(value));
76
+ }
77
+
78
+ /** @param {string} message */
79
+ function invalidResponse(message) {
80
+ return new CliError(message, { code: "INVALID_RESPONSE" });
81
+ }
@@ -0,0 +1,106 @@
1
+ // @ts-check
2
+
3
+ import { CliError } from "../errors.mjs";
4
+ import { objectString } from "../api-client.mjs";
5
+
6
+ const SENSITIVE_FIELDS = new Set([
7
+ "access_token",
8
+ "accesstoken",
9
+ "api_secret",
10
+ "apisecret",
11
+ "secret_key",
12
+ "secretkey",
13
+ "session_token",
14
+ "sessiontoken",
15
+ ]);
16
+
17
+ /**
18
+ * @param {{ api: { integrationContract(accessToken: string): Promise<{body: unknown}> }, sessions: { loadToken(): Promise<string> }, log: (message: string) => void }} deps
19
+ */
20
+ export async function showIntegrationContract(deps) {
21
+ const accessToken =
22
+ await deps.sessions.loadToken();
23
+
24
+ const result =
25
+ await deps.api.integrationContract(accessToken);
26
+
27
+ const data = parseIntegrationContract(result.body);
28
+
29
+ deps.log(JSON.stringify({ ok: true, data }, null, 2));
30
+ }
31
+
32
+ /** @param {unknown} body @returns {Record<string, unknown>} */
33
+ function parseIntegrationContract(body) {
34
+ const ok = body && typeof body === "object" ?
35
+ Reflect.get(body, "ok") : undefined;
36
+
37
+ const data = objectValue(body, "data");
38
+ if (ok !== true || !data) {
39
+ throw invalidResponse("The integration contract response did not contain ok=true and data");
40
+ }
41
+
42
+ const browser = objectValue(data, "browser");
43
+ const backend = objectValue(data, "backend");
44
+ const minJdk = backend ?
45
+ Reflect.get(backend, "min_jdk") : undefined;
46
+
47
+ if (
48
+ !objectString(data, "contract_version")
49
+ || !browser
50
+ || !objectString(browser, "script_url")
51
+ || !objectString(browser, "token_header")
52
+ || !stringArray(browser, "available_modes")
53
+ || !backend
54
+ || !stringArray(backend, "supported_languages")
55
+ || !Number.isInteger(minJdk)
56
+ || /** @type {number} */ (minJdk) < 1
57
+ || !objectString(data, "release_state")
58
+ ) {
59
+ throw invalidResponse("The integration contract response is missing required fields");
60
+ }
61
+
62
+ // Preserve future contract fields, but never relay credential-shaped fields to output.
63
+ assertNoSensitiveFields(data);
64
+ return data;
65
+ }
66
+
67
+ /** @param {unknown} value @param {string} property */
68
+ function objectValue(value, property) {
69
+ if (!value || typeof value !== "object")
70
+ return undefined;
71
+
72
+ const found = Reflect.get(value, property);
73
+
74
+ return found && typeof found === "object" && !Array.isArray(found) ? found : undefined;
75
+ }
76
+
77
+ /** @param {unknown} value @param {string} property */
78
+ function stringArray(value, property) {
79
+ if (!value || typeof value !== "object")
80
+ return false;
81
+
82
+ const found = Reflect.get(value, property);
83
+
84
+ return Array.isArray(found) && found.every((item) => typeof item === "string" && item !== "");
85
+ }
86
+
87
+ /** @param {unknown} value */
88
+ function assertNoSensitiveFields(value) {
89
+ if (Array.isArray(value)) {
90
+ for (const item of value) assertNoSensitiveFields(item);
91
+ return;
92
+ }
93
+ if (!value || typeof value !== "object")
94
+ return;
95
+ for (const [key, nestedValue] of Object.entries(value)) {
96
+ if (SENSITIVE_FIELDS.has(key.toLowerCase())) {
97
+ throw invalidResponse("The integration contract response contained a sensitive field");
98
+ }
99
+ assertNoSensitiveFields(nestedValue);
100
+ }
101
+ }
102
+
103
+ /** @param {string} message */
104
+ function invalidResponse(message) {
105
+ return new CliError(message, { code: "INVALID_RESPONSE" });
106
+ }
package/src/config.mjs ADDED
@@ -0,0 +1,8 @@
1
+ export const DEFAULT_MANAGEMENT_API_URL =
2
+ "https://api.intellifend.ai";
3
+
4
+ export const DEFAULT_MAVEN_REPOSITORY =
5
+ "https://sdk.intellifend.com/packages/maven";
6
+
7
+ export const DEFAULT_BROWSER_SCRIPT_URL =
8
+ "https://intellifend.ai/intellifend.js";
package/src/main.mjs CHANGED
@@ -1,25 +1,25 @@
1
1
  #!/usr/bin/env node
2
- // @ts-check
3
-
4
- import { run } from "./cli.mjs";
5
- import { CliError } from "./errors.mjs";
6
-
7
- async function main() {
8
- try {
9
- await run(process.argv.slice(2));
10
- } catch (error) {
11
- const message =
12
- error instanceof Error
13
- ? error.message
14
- : String(error);
15
-
16
- console.error(`requestshield: ${message}`);
17
-
18
- process.exitCode =
19
- error instanceof CliError
20
- ? error.exitCode
21
- : 1;
22
- }
23
- }
24
-
25
- void main();
2
+ // @ts-check
3
+
4
+ import { run } from "./cli.mjs";
5
+ import { CliError } from "./errors.mjs";
6
+
7
+ async function main() {
8
+ try {
9
+ await run(process.argv.slice(2));
10
+ } catch (error) {
11
+ const message =
12
+ error instanceof Error
13
+ ? error.message
14
+ : String(error);
15
+
16
+ console.error(`requestshield: ${message}`);
17
+
18
+ process.exitCode =
19
+ error instanceof CliError
20
+ ? error.exitCode
21
+ : 1;
22
+ }
23
+ }
24
+
25
+ void main();