opal-security 3.2.3 → 4.0.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/README.md +46 -61
- package/bin/dev +5 -5
- package/bin/run +2 -4
- package/build/commands/aws/identity.js +16 -0
- package/build/commands/clear-auth-config.d.ts +6 -0
- package/build/commands/clear-auth-config.js +22 -0
- package/{lib → build}/commands/groups/get.js +14 -16
- package/{lib → build}/commands/iam-roles/start.js +28 -30
- package/build/commands/kube-roles/start.js +71 -0
- package/{lib → build}/commands/login.d.ts +1 -0
- package/build/commands/login.js +379 -0
- package/build/commands/logout.js +22 -0
- package/{lib → build}/commands/postgres-instances/start.js +25 -27
- package/{lib → build}/commands/request/create.js +34 -36
- package/{lib → build}/commands/request/get.js +22 -24
- package/{lib → build}/commands/request/list.js +17 -19
- package/{lib → build}/commands/resources/get.js +15 -18
- package/build/commands/set-auth-config.d.ts +11 -0
- package/build/commands/set-auth-config.js +59 -0
- package/build/commands/set-custom-header.js +35 -0
- package/{lib → build}/commands/set-token.js +15 -17
- package/{lib → build}/commands/set-url.js +26 -28
- package/{lib → build}/commands/ssh/copyFrom.js +22 -24
- package/{lib → build}/commands/ssh/copyTo.js +22 -24
- package/{lib → build}/commands/ssh/start.js +30 -33
- package/build/commands/whoami.js +27 -0
- package/{lib → build}/graphql/fragment-masking.d.ts +1 -1
- package/{lib → build}/graphql/fragment-masking.js +3 -8
- package/{lib → build}/graphql/gql.d.ts +1 -1
- package/{lib → build}/graphql/gql.js +2 -5
- package/{lib → build}/graphql/graphql.js +256 -261
- package/build/graphql/index.d.ts +2 -0
- package/build/graphql/index.js +2 -0
- package/{lib → build}/handler.d.ts +1 -1
- package/build/handler.js +36 -0
- package/build/index.js +1 -0
- package/{lib → build}/labels.d.ts +1 -1
- package/build/labels.js +37 -0
- package/{lib → build}/lib/apollo.d.ts +2 -2
- package/{lib → build}/lib/apollo.js +62 -69
- package/build/lib/auth-success-template.d.ts +3 -0
- package/build/lib/auth-success-template.js +149 -0
- package/{lib → build}/lib/aws.js +2 -7
- package/{lib → build}/lib/cmd.d.ts +4 -4
- package/{lib → build}/lib/cmd.js +16 -20
- package/build/lib/config.js +46 -0
- package/{lib → build}/lib/credentials/index.d.ts +3 -2
- package/build/lib/credentials/index.js +85 -0
- package/{lib → build}/lib/credentials/keychain.js +4 -10
- package/{lib → build}/lib/credentials/localEncryption.js +12 -17
- package/{lib → build}/lib/flags.js +7 -10
- package/build/lib/local-auth-server.d.ts +5 -0
- package/build/lib/local-auth-server.js +69 -0
- package/build/lib/request/api/index.d.ts +6 -0
- package/build/lib/request/api/index.js +8 -0
- package/{lib → build}/lib/request/api/mutations/create-request.d.ts +2 -2
- package/{lib → build}/lib/request/api/mutations/create-request.js +3 -6
- package/{lib → build}/lib/request/api/queries/apps.d.ts +1 -1
- package/{lib → build}/lib/request/api/queries/apps.js +3 -6
- package/{lib → build}/lib/request/api/queries/assets.d.ts +2 -2
- package/{lib → build}/lib/request/api/queries/assets.js +7 -11
- package/{lib → build}/lib/request/api/queries/request-defaults.d.ts +2 -2
- package/{lib → build}/lib/request/api/queries/request-defaults.js +3 -6
- package/{lib → build}/lib/request/api/queries/requests.d.ts +3 -3
- package/{lib → build}/lib/request/api/queries/requests.js +10 -16
- package/{lib → build}/lib/request/api/queries/roles.d.ts +1 -1
- package/{lib → build}/lib/request/api/queries/roles.js +14 -18
- package/{lib → build}/lib/request/displays.d.ts +2 -2
- package/{lib → build}/lib/request/displays.js +27 -37
- package/{lib → build}/lib/request/prompts/apps-prompt.d.ts +1 -1
- package/build/lib/request/prompts/apps-prompt.js +33 -0
- package/{lib → build}/lib/request/prompts/asset-prompt.d.ts +1 -1
- package/build/lib/request/prompts/asset-prompt.js +61 -0
- package/{lib → build}/lib/request/prompts/duration-prompt.d.ts +1 -1
- package/{lib → build}/lib/request/prompts/duration-prompt.js +6 -10
- package/build/lib/request/prompts/index.d.ts +7 -0
- package/build/lib/request/prompts/index.js +8 -0
- package/{lib → build}/lib/request/prompts/reason-prompt.d.ts +1 -1
- package/{lib → build}/lib/request/prompts/reason-prompt.js +3 -6
- package/{lib → build}/lib/request/prompts/role-prompt.d.ts +1 -1
- package/build/lib/request/prompts/role-prompt.js +33 -0
- package/{lib → build}/lib/request/prompts/validate-prompt.d.ts +1 -1
- package/{lib → build}/lib/request/prompts/validate-prompt.js +9 -13
- package/{lib → build}/lib/request/request-utils.d.ts +2 -2
- package/{lib → build}/lib/request/request-utils.js +50 -62
- package/{lib → build}/lib/request/types.d.ts +1 -1
- package/build/lib/request/types.js +12 -0
- package/{lib → build}/lib/resources.d.ts +1 -1
- package/{lib → build}/lib/resources.js +18 -23
- package/{lib → build}/lib/sessions.d.ts +1 -1
- package/{lib → build}/lib/sessions.js +57 -32
- package/{lib → build}/lib/ssh.d.ts +1 -1
- package/{lib → build}/lib/ssh.js +6 -11
- package/{lib → build}/lib/util.js +7 -14
- package/{lib → build}/types.js +98 -101
- package/oclif.manifest.json +77 -98
- package/package.json +24 -14
- package/lib/commands/aws/identity.js +0 -18
- package/lib/commands/clear-auth-provider.d.ts +0 -9
- package/lib/commands/clear-auth-provider.js +0 -28
- package/lib/commands/curl-example.d.ts +0 -8
- package/lib/commands/curl-example.js +0 -34
- package/lib/commands/kube-roles/start.js +0 -73
- package/lib/commands/login.js +0 -286
- package/lib/commands/logout.js +0 -23
- package/lib/commands/set-auth-provider.d.ts +0 -11
- package/lib/commands/set-auth-provider.js +0 -44
- package/lib/commands/set-custom-header.js +0 -37
- package/lib/commands/whoami.js +0 -34
- package/lib/graphql/index.d.ts +0 -2
- package/lib/graphql/index.js +0 -5
- package/lib/handler.js +0 -41
- package/lib/index.js +0 -5
- package/lib/labels.js +0 -40
- package/lib/lib/config.js +0 -54
- package/lib/lib/credentials/index.js +0 -67
- package/lib/lib/request/api/index.d.ts +0 -6
- package/lib/lib/request/api/index.js +0 -20
- package/lib/lib/request/prompts/apps-prompt.js +0 -35
- package/lib/lib/request/prompts/asset-prompt.js +0 -81
- package/lib/lib/request/prompts/index.d.ts +0 -8
- package/lib/lib/request/prompts/index.js +0 -20
- package/lib/lib/request/prompts/role-prompt.js +0 -44
- package/lib/lib/request/types.js +0 -15
- /package/{lib → build}/commands/aws/identity.d.ts +0 -0
- /package/{lib → build}/commands/groups/get.d.ts +0 -0
- /package/{lib → build}/commands/iam-roles/start.d.ts +0 -0
- /package/{lib → build}/commands/kube-roles/start.d.ts +0 -0
- /package/{lib → build}/commands/logout.d.ts +0 -0
- /package/{lib → build}/commands/postgres-instances/start.d.ts +0 -0
- /package/{lib → build}/commands/request/create.d.ts +0 -0
- /package/{lib → build}/commands/request/get.d.ts +0 -0
- /package/{lib → build}/commands/request/list.d.ts +0 -0
- /package/{lib → build}/commands/resources/get.d.ts +0 -0
- /package/{lib → build}/commands/set-custom-header.d.ts +0 -0
- /package/{lib → build}/commands/set-token.d.ts +0 -0
- /package/{lib → build}/commands/set-url.d.ts +0 -0
- /package/{lib → build}/commands/ssh/copyFrom.d.ts +0 -0
- /package/{lib → build}/commands/ssh/copyTo.d.ts +0 -0
- /package/{lib → build}/commands/ssh/start.d.ts +0 -0
- /package/{lib → build}/commands/whoami.d.ts +0 -0
- /package/{lib → build}/graphql/graphql.d.ts +0 -0
- /package/{lib → build}/index.d.ts +0 -0
- /package/{lib → build}/lib/aws.d.ts +0 -0
- /package/{lib → build}/lib/config.d.ts +0 -0
- /package/{lib → build}/lib/credentials/keychain.d.ts +0 -0
- /package/{lib → build}/lib/credentials/localEncryption.d.ts +0 -0
- /package/{lib → build}/lib/flags.d.ts +0 -0
- /package/{lib → build}/lib/util.d.ts +0 -0
- /package/{lib → build}/types.d.ts +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const urlKey = "url";
|
|
4
|
+
export const defaultUrl = "https://app.opal.dev";
|
|
5
|
+
export const allowSelfSignedCertsKey = "allowSelfSignedCerts";
|
|
6
|
+
export const defaultAllowSelfSignedCerts = false;
|
|
7
|
+
export const customHttpHeaderKey = "customHttpHeader";
|
|
8
|
+
export const getOrCreateConfigData = (configDir) => {
|
|
9
|
+
if (!fs.existsSync(configDir)) {
|
|
10
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
const configFilePath = path.join(configDir, "config.json");
|
|
13
|
+
if (!fs.existsSync(configFilePath)) {
|
|
14
|
+
fs.writeFileSync(configFilePath, JSON.stringify({
|
|
15
|
+
[urlKey]: defaultUrl,
|
|
16
|
+
[allowSelfSignedCertsKey]: defaultAllowSelfSignedCerts,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
let configData = {};
|
|
20
|
+
try {
|
|
21
|
+
const configDataRaw = fs.readFileSync(configFilePath);
|
|
22
|
+
configData = JSON.parse(configDataRaw === null || configDataRaw === void 0 ? void 0 : configDataRaw.toString());
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error &&
|
|
26
|
+
typeof error === "object" &&
|
|
27
|
+
"code" in error &&
|
|
28
|
+
error.code !== "ENOENT") {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return configData;
|
|
33
|
+
};
|
|
34
|
+
export const writeConfigData = (configDir, newConfigData) => {
|
|
35
|
+
fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify(newConfigData), {
|
|
36
|
+
mode: 0o0600,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
export const isProduction = (configDir) => {
|
|
40
|
+
const configData = getOrCreateConfigData(configDir);
|
|
41
|
+
// Custom URLs are considered production since it includes on-prem
|
|
42
|
+
return (configData[urlKey] !== "https://dev.opal.dev" &&
|
|
43
|
+
configData[urlKey] !== "https://demo.opal.dev" &&
|
|
44
|
+
configData[urlKey] !== "https://staging.opal.dev" &&
|
|
45
|
+
!configData[urlKey].match(/https?:\/\/localhost/));
|
|
46
|
+
};
|
|
@@ -2,7 +2,7 @@ import type { Command } from "@oclif/core";
|
|
|
2
2
|
interface OpalCredentials {
|
|
3
3
|
email?: string;
|
|
4
4
|
organizationID?: string;
|
|
5
|
-
|
|
5
|
+
clientID?: string;
|
|
6
6
|
secret?: string;
|
|
7
7
|
secretType?: SecretType;
|
|
8
8
|
organizationName?: string;
|
|
@@ -11,7 +11,8 @@ export declare enum SecretType {
|
|
|
11
11
|
Cookie = "COOKIE",
|
|
12
12
|
ApiToken = "API_TOKEN"
|
|
13
13
|
}
|
|
14
|
-
export declare const setOpalCredentials: (command: Command, email: string | undefined, organizationID: string,
|
|
14
|
+
export declare const setOpalCredentials: (command: Command, email: string | undefined, organizationID: string | undefined, clientID: string | undefined, secret: string, secretType?: SecretType, organizationName?: string) => Promise<void>;
|
|
15
15
|
export declare const getOpalCredentials: (command: Command, includeAuthSecret?: boolean) => Promise<OpalCredentials>;
|
|
16
16
|
export declare const removeOpalCredentials: (command: Command) => Promise<void>;
|
|
17
|
+
export declare const removeAuthSecret: (command: Command) => Promise<void>;
|
|
17
18
|
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getOrCreateConfigData, writeConfigData } from "../config.js";
|
|
2
|
+
import { deleteSecretFromKeychain, getSecretFromKeychain, setSecretInKeychain, } from "./keychain.js";
|
|
3
|
+
import { getSecretFromConfig, setSecretInConfig } from "./localEncryption.js";
|
|
4
|
+
export var SecretType;
|
|
5
|
+
(function (SecretType) {
|
|
6
|
+
SecretType["Cookie"] = "COOKIE";
|
|
7
|
+
SecretType["ApiToken"] = "API_TOKEN";
|
|
8
|
+
})(SecretType || (SecretType = {}));
|
|
9
|
+
export const setOpalCredentials = async (command, email, organizationID, clientID, secret, secretType, organizationName) => {
|
|
10
|
+
const givenEmail = email || "email-unset";
|
|
11
|
+
const configData = getOrCreateConfigData(command.config.configDir);
|
|
12
|
+
configData.creds = {
|
|
13
|
+
email,
|
|
14
|
+
organizationID,
|
|
15
|
+
organizationName,
|
|
16
|
+
secretType,
|
|
17
|
+
};
|
|
18
|
+
writeConfigData(command.config.configDir, configData);
|
|
19
|
+
await writeSecret(command, configData, givenEmail, { clientID, secret });
|
|
20
|
+
};
|
|
21
|
+
export const getOpalCredentials = async (command, includeAuthSecret = true) => {
|
|
22
|
+
var _a, _b;
|
|
23
|
+
const creds = (_b = (_a = getOrCreateConfigData(command.config.configDir)) === null || _a === void 0 ? void 0 : _a.creds) !== null && _b !== void 0 ? _b : {};
|
|
24
|
+
if (!includeAuthSecret) {
|
|
25
|
+
return creds;
|
|
26
|
+
}
|
|
27
|
+
let secretStr = null;
|
|
28
|
+
if (process.platform === "darwin") {
|
|
29
|
+
secretStr = await getSecretFromKeychain((creds === null || creds === void 0 ? void 0 : creds.email) || "email-unset");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
secretStr = await getSecretFromConfig(creds);
|
|
33
|
+
}
|
|
34
|
+
if (secretStr) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(secretStr);
|
|
37
|
+
creds.secret = parsed.secret;
|
|
38
|
+
creds.clientID = parsed.clientID;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Fallback for old format where only the secret was stored as a plain string
|
|
42
|
+
creds.secret = secretStr;
|
|
43
|
+
}
|
|
44
|
+
// This is a fallback for users with stored credentials from before we converted to session auth with the CLITokenExchange mutation
|
|
45
|
+
// It will allow them to continue authenticating with an access token in an Authorization header, which will work until we remove support for that
|
|
46
|
+
if (!creds.secretType) {
|
|
47
|
+
creds.secretType = SecretType.ApiToken;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return creds;
|
|
51
|
+
};
|
|
52
|
+
// This removes *everything* in the creds object, and wipes out anything stored in the keychain
|
|
53
|
+
export const removeOpalCredentials = async (command) => {
|
|
54
|
+
var _a;
|
|
55
|
+
const configData = getOrCreateConfigData(command.config.configDir);
|
|
56
|
+
const email = ((_a = configData === null || configData === void 0 ? void 0 : configData.creds) === null || _a === void 0 ? void 0 : _a.email) || "email-unset";
|
|
57
|
+
// On linux, the access token is stored encrypted in configData.creds, so this effectively removes it
|
|
58
|
+
configData.creds = {};
|
|
59
|
+
writeConfigData(command.config.configDir, configData);
|
|
60
|
+
// but on OSX, we need an extra step to delete the token from the keychain
|
|
61
|
+
if (process.platform === "darwin") {
|
|
62
|
+
await deleteSecretFromKeychain(email);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// This just removes the auth secret, if it exists
|
|
66
|
+
export const removeAuthSecret = async (command) => {
|
|
67
|
+
const configData = getOrCreateConfigData(command.config.configDir);
|
|
68
|
+
const creds = await getOpalCredentials(command, true);
|
|
69
|
+
if (creds.email) {
|
|
70
|
+
const secretCreds = {};
|
|
71
|
+
if (creds.clientID) {
|
|
72
|
+
secretCreds.clientID = creds.clientID;
|
|
73
|
+
}
|
|
74
|
+
await writeSecret(command, configData, creds.email, secretCreds);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const writeSecret = async (command, configData, email, secretCreds) => {
|
|
78
|
+
const secretStr = JSON.stringify(secretCreds);
|
|
79
|
+
if (process.platform === "darwin") {
|
|
80
|
+
await setSecretInKeychain(email, secretStr);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
await setSecretInConfig(command, configData, secretStr);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteSecretFromKeychain = exports.getSecretFromKeychain = exports.setSecretInKeychain = void 0;
|
|
4
|
-
const keychain = require("keychain");
|
|
1
|
+
import keychain from "keychain";
|
|
5
2
|
const OPAL_KEYCHAIN_SERVICE = "opal-cli";
|
|
6
3
|
// On OSX, we can easily work with the system Keychain to store the access token.
|
|
7
|
-
const setSecretInKeychain = async (email, secret) => {
|
|
4
|
+
export const setSecretInKeychain = async (email, secret) => {
|
|
8
5
|
return new Promise((resolve, reject) => {
|
|
9
6
|
keychain.setPassword({ account: email, service: OPAL_KEYCHAIN_SERVICE, password: secret }, (err) => {
|
|
10
7
|
if (err) {
|
|
@@ -16,8 +13,7 @@ const setSecretInKeychain = async (email, secret) => {
|
|
|
16
13
|
});
|
|
17
14
|
});
|
|
18
15
|
};
|
|
19
|
-
|
|
20
|
-
const getSecretFromKeychain = async (email) => {
|
|
16
|
+
export const getSecretFromKeychain = async (email) => {
|
|
21
17
|
return new Promise((resolve, reject) => {
|
|
22
18
|
keychain.getPassword({ account: email, service: OPAL_KEYCHAIN_SERVICE }, (err, password) => {
|
|
23
19
|
if (err && (err === null || err === void 0 ? void 0 : err.type) !== "PasswordNotFoundError") {
|
|
@@ -29,8 +25,7 @@ const getSecretFromKeychain = async (email) => {
|
|
|
29
25
|
});
|
|
30
26
|
});
|
|
31
27
|
};
|
|
32
|
-
|
|
33
|
-
const deleteSecretFromKeychain = async (email) => {
|
|
28
|
+
export const deleteSecretFromKeychain = async (email) => {
|
|
34
29
|
return new Promise((resolve) => {
|
|
35
30
|
keychain.deletePassword({ service: OPAL_KEYCHAIN_SERVICE, account: email }, () => {
|
|
36
31
|
// we might get errors here if the password doesn't exist, which we want to swallow
|
|
@@ -38,4 +33,3 @@ const deleteSecretFromKeychain = async (email) => {
|
|
|
38
33
|
});
|
|
39
34
|
});
|
|
40
35
|
};
|
|
41
|
-
exports.deleteSecretFromKeychain = deleteSecretFromKeychain;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const argon2 = require("argon2");
|
|
6
|
-
const inquirer = require("inquirer");
|
|
7
|
-
const config_1 = require("../config");
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
2
|
+
import * as argon2 from "argon2";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import { writeConfigData } from "../config.js";
|
|
8
5
|
const ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
9
6
|
/**
|
|
10
7
|
* On linux, windows, and WSL, we can't easily work with the system keychain
|
|
@@ -28,18 +25,18 @@ const promptForPassword = async (msg) => {
|
|
|
28
25
|
]);
|
|
29
26
|
return customKey;
|
|
30
27
|
};
|
|
31
|
-
const setSecretInConfig = async (command, configData, secret) => {
|
|
28
|
+
export const setSecretInConfig = async (command, configData, secret) => {
|
|
32
29
|
if (!password) {
|
|
33
30
|
password = await promptForPassword("Enter a password to encrypt your credentials: ");
|
|
34
31
|
}
|
|
35
|
-
const salt =
|
|
32
|
+
const salt = randomBytes(24);
|
|
36
33
|
const key = await argon2.hash(password, {
|
|
37
34
|
hashLength: 32,
|
|
38
35
|
raw: true,
|
|
39
36
|
salt: salt,
|
|
40
37
|
});
|
|
41
|
-
const iv =
|
|
42
|
-
const cipher =
|
|
38
|
+
const iv = randomBytes(12);
|
|
39
|
+
const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
43
40
|
let secretEncrypted = cipher.update(secret, "utf8", "base64");
|
|
44
41
|
secretEncrypted += cipher.final("base64");
|
|
45
42
|
// In addition to storing the encrypted text, we need to store the argon2 salt, and AES authTag + IV so we can decrypt
|
|
@@ -49,10 +46,9 @@ const setSecretInConfig = async (command, configData, secret) => {
|
|
|
49
46
|
salt: salt.toString("base64"),
|
|
50
47
|
iv: iv.toString("base64"),
|
|
51
48
|
};
|
|
52
|
-
|
|
49
|
+
writeConfigData(command.config.configDir, configData);
|
|
53
50
|
};
|
|
54
|
-
|
|
55
|
-
const getSecretFromConfig = async (credsConfig) => {
|
|
51
|
+
export const getSecretFromConfig = async (credsConfig) => {
|
|
56
52
|
let secret = "";
|
|
57
53
|
if (credsConfig === null || credsConfig === void 0 ? void 0 : credsConfig.accessTokenDetails) {
|
|
58
54
|
if (!password) {
|
|
@@ -66,12 +62,12 @@ const getSecretFromConfig = async (credsConfig) => {
|
|
|
66
62
|
salt: salt,
|
|
67
63
|
});
|
|
68
64
|
try {
|
|
69
|
-
const decipher =
|
|
65
|
+
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
70
66
|
decipher.setAuthTag(Buffer.from(credsConfig.accessTokenDetails.authTag, "base64"));
|
|
71
67
|
secret = decipher.update(credsConfig.accessTokenDetails.encryptedText, "base64", "utf8");
|
|
72
68
|
secret += decipher.final("utf8");
|
|
73
69
|
}
|
|
74
|
-
catch (
|
|
70
|
+
catch (_a) {
|
|
75
71
|
console.error("ERROR: Failed to decrypt local credentials.\n" +
|
|
76
72
|
" Check that you entered the correct password, or re-authenticate with `opal login`");
|
|
77
73
|
process.exit(1);
|
|
@@ -81,4 +77,3 @@ const getSecretFromConfig = async (credsConfig) => {
|
|
|
81
77
|
}
|
|
82
78
|
return secret;
|
|
83
79
|
};
|
|
84
|
-
exports.getSecretFromConfig = getSecretFromConfig;
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
exports.SHARED_FLAGS = {
|
|
6
|
-
help: core_1.Flags.help({ char: "h" }),
|
|
7
|
-
id: core_1.Flags.string({
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
export const SHARED_FLAGS = {
|
|
3
|
+
help: Flags.help({ char: "h" }),
|
|
4
|
+
id: Flags.string({
|
|
8
5
|
multiple: false,
|
|
9
6
|
char: "i",
|
|
10
7
|
description: "The Opal ID of the asset. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
|
|
11
8
|
}),
|
|
12
|
-
accessLevelRemoteId:
|
|
9
|
+
accessLevelRemoteId: Flags.string({
|
|
13
10
|
multiple: false,
|
|
14
11
|
char: "a",
|
|
15
12
|
description: "The remote ID of the access level with which to access the resource.",
|
|
16
13
|
}),
|
|
17
|
-
sessionId:
|
|
14
|
+
sessionId: Flags.string({
|
|
18
15
|
multiple: false,
|
|
19
16
|
char: "s",
|
|
20
17
|
description: "The Opal ID of the session to connect to. Uses an existing session that was created via the web flow.",
|
|
21
18
|
}),
|
|
22
|
-
refresh:
|
|
19
|
+
refresh: Flags.boolean({
|
|
23
20
|
char: "r",
|
|
24
21
|
description: "Starts a new session even if one already exists. Useful if a session is about to expire.",
|
|
25
22
|
}),
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import { authErrorHtml, authMissingCodeHtml, authSuccessHtml, } from "./auth-success-template.js";
|
|
3
|
+
/**
|
|
4
|
+
* Starts a local HTTP server on port 8080 to handle OAuth callback.
|
|
5
|
+
* Returns a promise that resolves with the full callback URL when authentication succeeds.
|
|
6
|
+
*/
|
|
7
|
+
export function startLocalServer() {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const server = http.createServer(async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(req.url || "", "http://127.0.0.1:8080");
|
|
12
|
+
if (url.pathname === "/callback") {
|
|
13
|
+
const error = url.searchParams.get("error");
|
|
14
|
+
if (error) {
|
|
15
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
16
|
+
res.end(authErrorHtml(error));
|
|
17
|
+
server.closeAllConnections();
|
|
18
|
+
server.close(() => {
|
|
19
|
+
reject(new Error(`Authentication failed: ${error}`));
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (req.url) {
|
|
24
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
25
|
+
res.end(authSuccessHtml);
|
|
26
|
+
const fullUrl = `http://127.0.0.1:8080${req.url}`;
|
|
27
|
+
server.closeAllConnections();
|
|
28
|
+
server.close(() => {
|
|
29
|
+
resolve(fullUrl);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
34
|
+
res.end(authMissingCodeHtml);
|
|
35
|
+
server.closeAllConnections();
|
|
36
|
+
server.close(() => {
|
|
37
|
+
reject(new Error("Missing authorization code"));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
res.writeHead(404);
|
|
43
|
+
res.end();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
res.writeHead(400);
|
|
48
|
+
res.end();
|
|
49
|
+
server.closeAllConnections();
|
|
50
|
+
server.close(() => {
|
|
51
|
+
reject(err);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
server.listen(8080, "127.0.0.1", () => {
|
|
56
|
+
console.log("Local server started on http://127.0.0.1:8080");
|
|
57
|
+
});
|
|
58
|
+
server.on("error", (err) => {
|
|
59
|
+
reject(err);
|
|
60
|
+
});
|
|
61
|
+
// Timeout after 5 minutes
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
server.closeAllConnections();
|
|
64
|
+
server.close(() => {
|
|
65
|
+
reject(new Error("Authentication timeout"));
|
|
66
|
+
});
|
|
67
|
+
}, 5 * 60 * 1000);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { queryRequestableApps } from "./queries/apps.js";
|
|
2
|
+
export { queryRequestableAssets, queryCatalogItems } from "./queries/assets.js";
|
|
3
|
+
export { queryAssetRoles, queryAssociatedItems } from "./queries/roles.js";
|
|
4
|
+
export { queryRequestDefaults } from "./queries/request-defaults.js";
|
|
5
|
+
export { queryRequest, queryRequests } from "./queries/requests.js";
|
|
6
|
+
export { createRequest } from "./mutations/create-request.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Query exports
|
|
2
|
+
export { queryRequestableApps } from "./queries/apps.js";
|
|
3
|
+
export { queryRequestableAssets, queryCatalogItems } from "./queries/assets.js";
|
|
4
|
+
export { queryAssetRoles, queryAssociatedItems } from "./queries/roles.js";
|
|
5
|
+
export { queryRequestDefaults } from "./queries/request-defaults.js";
|
|
6
|
+
export { queryRequest, queryRequests } from "./queries/requests.js";
|
|
7
|
+
// Mutation exports
|
|
8
|
+
export { createRequest } from "./mutations/create-request.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
2
|
import type { Command } from "@oclif/core";
|
|
3
|
-
import type { RequestedGroupInput, RequestedResourceInput } from "../../../../graphql/graphql";
|
|
3
|
+
import type { RequestedGroupInput, RequestedResourceInput } from "../../../../graphql/graphql.js";
|
|
4
4
|
export declare function createRequest(cmd: Command, client: ApolloClient, requestedResources: RequestedResourceInput[], requestedGroups: RequestedGroupInput[], reason: string, durationInMinutes?: number): Promise<{
|
|
5
5
|
__typename?: "Request";
|
|
6
6
|
id: string;
|
|
7
|
-
status: import("../../../../graphql/graphql").RequestStatus;
|
|
7
|
+
status: import("../../../../graphql/graphql.js").RequestStatus;
|
|
8
8
|
} | undefined>;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.createRequest = createRequest;
|
|
4
|
-
const graphql_1 = require("../../../../graphql");
|
|
5
|
-
const CREATE_REQUEST_MUTATION = (0, graphql_1.graphql)(`
|
|
1
|
+
import { graphql } from "../../../../graphql/index.js";
|
|
2
|
+
const CREATE_REQUEST_MUTATION = graphql(`
|
|
6
3
|
mutation CreateRequest(
|
|
7
4
|
$requestedResources: [RequestedResourceInput!]!
|
|
8
5
|
$requestedGroups: [RequestedGroupInput!]!
|
|
@@ -86,7 +83,7 @@ const CREATE_REQUEST_MUTATION = (0, graphql_1.graphql)(`
|
|
|
86
83
|
}
|
|
87
84
|
}
|
|
88
85
|
`);
|
|
89
|
-
async function createRequest(cmd, client, requestedResources, requestedGroups, reason, durationInMinutes) {
|
|
86
|
+
export async function createRequest(cmd, client, requestedResources, requestedGroups, reason, durationInMinutes) {
|
|
90
87
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
91
88
|
try {
|
|
92
89
|
const resp = await client.mutate({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
2
|
import type { Command } from "@oclif/core";
|
|
3
|
-
import type { PromptChoice } from "../../types";
|
|
3
|
+
import type { PromptChoice } from "../../types.js";
|
|
4
4
|
export declare function queryRequestableApps(cmd: Command, client: ApolloClient, input: string | undefined): Promise<PromptChoice[] | undefined>;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.queryRequestableApps = queryRequestableApps;
|
|
4
|
-
const graphql_1 = require("../../../../graphql");
|
|
1
|
+
import { graphql } from "../../../../graphql/index.js";
|
|
5
2
|
// TODO: add pagination ability from CLI. (Load more...) option
|
|
6
|
-
const GET_REQUESTABLE_APPS_QUERY =
|
|
3
|
+
const GET_REQUESTABLE_APPS_QUERY = graphql(`
|
|
7
4
|
query GetRequestableAppsQuery($searchQuery: String) {
|
|
8
5
|
appsV2(
|
|
9
6
|
filters: {
|
|
@@ -32,7 +29,7 @@ const GET_REQUESTABLE_APPS_QUERY = (0, graphql_1.graphql)(`
|
|
|
32
29
|
}
|
|
33
30
|
}
|
|
34
31
|
`);
|
|
35
|
-
async function queryRequestableApps(cmd, client, input) {
|
|
32
|
+
export async function queryRequestableApps(cmd, client, input) {
|
|
36
33
|
var _a, _b;
|
|
37
34
|
try {
|
|
38
35
|
const resp = await client.query({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
2
|
import type { Command } from "@oclif/core";
|
|
3
|
-
import type { GetCatalogItemQuery } from "../../../../graphql/graphql";
|
|
4
|
-
import { type PromptChoice } from "../../types";
|
|
3
|
+
import type { GetCatalogItemQuery } from "../../../../graphql/graphql.js";
|
|
4
|
+
import { type PromptChoice } from "../../types.js";
|
|
5
5
|
export declare function queryRequestableAssets(cmd: Command, client: ApolloClient, appId: string, input: string | undefined): Promise<PromptChoice[] | undefined>;
|
|
6
6
|
export declare function queryCatalogItems(cmd: Command, client: ApolloClient, assetId: string): Promise<ApolloClient.QueryResult<GetCatalogItemQuery>>;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.queryCatalogItems = queryCatalogItems;
|
|
5
|
-
const graphql_1 = require("../../../../graphql");
|
|
6
|
-
const types_1 = require("../../types");
|
|
7
|
-
const GET_ASSETS_QUERY = (0, graphql_1.graphql)(`
|
|
1
|
+
import { graphql } from "../../../../graphql/index.js";
|
|
2
|
+
import { entityTypeFromString } from "../../types.js";
|
|
3
|
+
const GET_ASSETS_QUERY = graphql(`
|
|
8
4
|
query PaginatedEntityDropdown(
|
|
9
5
|
$id: UUID!
|
|
10
6
|
$searchQuery: String
|
|
@@ -40,7 +36,7 @@ const GET_ASSETS_QUERY = (0, graphql_1.graphql)(`
|
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
38
|
`);
|
|
43
|
-
async function queryRequestableAssets(cmd, client, appId, input) {
|
|
39
|
+
export async function queryRequestableAssets(cmd, client, appId, input) {
|
|
44
40
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
45
41
|
try {
|
|
46
42
|
const resp = await client.query({
|
|
@@ -65,7 +61,7 @@ async function queryRequestableAssets(cmd, client, appId, input) {
|
|
|
65
61
|
value: {
|
|
66
62
|
name: name || "",
|
|
67
63
|
id: id || "",
|
|
68
|
-
type:
|
|
64
|
+
type: entityTypeFromString(((_g = item.resource) === null || _g === void 0 ? void 0 : _g.__typename) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.__typename)),
|
|
69
65
|
},
|
|
70
66
|
};
|
|
71
67
|
});
|
|
@@ -82,7 +78,7 @@ async function queryRequestableAssets(cmd, client, appId, input) {
|
|
|
82
78
|
}
|
|
83
79
|
}
|
|
84
80
|
}
|
|
85
|
-
const CATALOG_ITEM =
|
|
81
|
+
const CATALOG_ITEM = graphql(`
|
|
86
82
|
query GetCatalogItem($uuid: UUID!) {
|
|
87
83
|
catalogItem(id: $uuid) {
|
|
88
84
|
__typename
|
|
@@ -120,7 +116,7 @@ const CATALOG_ITEM = (0, graphql_1.graphql)(`
|
|
|
120
116
|
}
|
|
121
117
|
}
|
|
122
118
|
`);
|
|
123
|
-
async function queryCatalogItems(cmd, client, assetId) {
|
|
119
|
+
export async function queryCatalogItems(cmd, client, assetId) {
|
|
124
120
|
try {
|
|
125
121
|
return await client.query({
|
|
126
122
|
query: CATALOG_ITEM,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
2
|
import type { Command } from "@oclif/core";
|
|
3
|
-
import type { RequestConfigurationGroupInput, RequestConfigurationResourceInput } from "../../../../graphql/graphql";
|
|
4
|
-
import type { RequestDefaults } from "../../types";
|
|
3
|
+
import type { RequestConfigurationGroupInput, RequestConfigurationResourceInput } from "../../../../graphql/graphql.js";
|
|
4
|
+
import type { RequestDefaults } from "../../types.js";
|
|
5
5
|
export declare function queryRequestDefaults(cmd: Command, client: ApolloClient, requestedResources: RequestConfigurationResourceInput[], requestedGroups: RequestConfigurationGroupInput[]): Promise<RequestDefaults | undefined>;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.queryRequestDefaults = queryRequestDefaults;
|
|
4
|
-
const graphql_1 = require("../../../../graphql");
|
|
5
|
-
const REQUEST_DEFAULTS_QUERY = (0, graphql_1.graphql)(`
|
|
1
|
+
import { graphql } from "../../../../graphql/index.js";
|
|
2
|
+
const REQUEST_DEFAULTS_QUERY = graphql(`
|
|
6
3
|
query RequestDefaults(
|
|
7
4
|
$requestedResources: [RequestConfigurationResourceInput!]!
|
|
8
5
|
$requestedGroups: [RequestConfigurationGroupInput!]!
|
|
@@ -31,7 +28,7 @@ const REQUEST_DEFAULTS_QUERY = (0, graphql_1.graphql)(`
|
|
|
31
28
|
}
|
|
32
29
|
}
|
|
33
30
|
}`);
|
|
34
|
-
async function queryRequestDefaults(cmd, client, requestedResources, requestedGroups) {
|
|
31
|
+
export async function queryRequestDefaults(cmd, client, requestedResources, requestedGroups) {
|
|
35
32
|
var _a;
|
|
36
33
|
try {
|
|
37
34
|
const resp = await client.query({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
|
-
export declare const queryRequest: (client: ApolloClient, requestId: string) => Promise<ApolloClient.QueryResult<import("../../../../graphql/graphql").GetRequestQuery>>;
|
|
3
|
-
export declare const queryRequests: (client: ApolloClient, pageSize: number, showPendingOnly: boolean) => Promise<ApolloClient.QueryResult<import("../../../../graphql/graphql").GetRequestsQuery>>;
|
|
4
|
-
export declare const queryBundle: (client: ApolloClient, bundleId: string) => Promise<ApolloClient.QueryResult<import("../../../../graphql/graphql").GetBundleQuery>>;
|
|
2
|
+
export declare const queryRequest: (client: ApolloClient, requestId: string) => Promise<ApolloClient.QueryResult<import("../../../../graphql/graphql.js").GetRequestQuery>>;
|
|
3
|
+
export declare const queryRequests: (client: ApolloClient, pageSize: number, showPendingOnly: boolean) => Promise<ApolloClient.QueryResult<import("../../../../graphql/graphql.js").GetRequestsQuery>>;
|
|
4
|
+
export declare const queryBundle: (client: ApolloClient, bundleId: string) => Promise<ApolloClient.QueryResult<import("../../../../graphql/graphql.js").GetBundleQuery>>;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.queryBundle = exports.queryRequests = exports.queryRequest = void 0;
|
|
4
|
-
const graphql_1 = require("../../../../graphql");
|
|
5
|
-
const RESOURCE_FRAGMENT = (0, graphql_1.graphql)(`
|
|
1
|
+
import { graphql } from "../../../../graphql/index.js";
|
|
2
|
+
const RESOURCE_FRAGMENT = graphql(`
|
|
6
3
|
fragment ResourceFields on Resource {
|
|
7
4
|
displayName
|
|
8
5
|
id
|
|
@@ -13,7 +10,7 @@ const RESOURCE_FRAGMENT = (0, graphql_1.graphql)(`
|
|
|
13
10
|
}
|
|
14
11
|
}
|
|
15
12
|
`);
|
|
16
|
-
const GROUP_FRAGMENT =
|
|
13
|
+
const GROUP_FRAGMENT = graphql(`
|
|
17
14
|
fragment GroupFields on Group {
|
|
18
15
|
name
|
|
19
16
|
id
|
|
@@ -24,7 +21,7 @@ const GROUP_FRAGMENT = (0, graphql_1.graphql)(`
|
|
|
24
21
|
}
|
|
25
22
|
}
|
|
26
23
|
`);
|
|
27
|
-
const REQUEST_FIELDS_FRAGMENT =
|
|
24
|
+
const REQUEST_FIELDS_FRAGMENT = graphql(`
|
|
28
25
|
fragment RequestFields on Request {
|
|
29
26
|
id
|
|
30
27
|
createdAt
|
|
@@ -57,7 +54,7 @@ const REQUEST_FIELDS_FRAGMENT = (0, graphql_1.graphql)(`
|
|
|
57
54
|
}
|
|
58
55
|
}
|
|
59
56
|
`);
|
|
60
|
-
const GET_REQUEST =
|
|
57
|
+
const GET_REQUEST = graphql(`
|
|
61
58
|
query GetRequest(
|
|
62
59
|
$id: RequestId!
|
|
63
60
|
) {
|
|
@@ -74,7 +71,7 @@ const GET_REQUEST = (0, graphql_1.graphql)(`
|
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
`);
|
|
77
|
-
const queryRequest = async (client, requestId) => {
|
|
74
|
+
export const queryRequest = async (client, requestId) => {
|
|
78
75
|
const resp = await client.query({
|
|
79
76
|
query: GET_REQUEST,
|
|
80
77
|
variables: {
|
|
@@ -84,9 +81,8 @@ const queryRequest = async (client, requestId) => {
|
|
|
84
81
|
});
|
|
85
82
|
return resp;
|
|
86
83
|
};
|
|
87
|
-
exports.queryRequest = queryRequest;
|
|
88
84
|
// TODO: Add date filters, search query,
|
|
89
|
-
const GET_OUTGOING_REQUESTS =
|
|
85
|
+
const GET_OUTGOING_REQUESTS = graphql(`
|
|
90
86
|
query GetRequests($showPendingOnly: Boolean!, $pageSize: Int) {
|
|
91
87
|
requests(input: {
|
|
92
88
|
requestType: OUTGOING
|
|
@@ -106,7 +102,7 @@ const GET_OUTGOING_REQUESTS = (0, graphql_1.graphql)(`
|
|
|
106
102
|
|
|
107
103
|
}
|
|
108
104
|
}`);
|
|
109
|
-
const queryRequests = async (client, pageSize, showPendingOnly) => {
|
|
105
|
+
export const queryRequests = async (client, pageSize, showPendingOnly) => {
|
|
110
106
|
const resp = await client.query({
|
|
111
107
|
query: GET_OUTGOING_REQUESTS,
|
|
112
108
|
variables: {
|
|
@@ -117,8 +113,7 @@ const queryRequests = async (client, pageSize, showPendingOnly) => {
|
|
|
117
113
|
});
|
|
118
114
|
return resp;
|
|
119
115
|
};
|
|
120
|
-
|
|
121
|
-
const GET_BUNDLE = (0, graphql_1.graphql)(`query GetBundle($id: BundleId!) {
|
|
116
|
+
const GET_BUNDLE = graphql(`query GetBundle($id: BundleId!) {
|
|
122
117
|
bundle(input: { id: $id }) {
|
|
123
118
|
__typename
|
|
124
119
|
... on BundleResult {
|
|
@@ -150,7 +145,7 @@ const GET_BUNDLE = (0, graphql_1.graphql)(`query GetBundle($id: BundleId!) {
|
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
147
|
}`);
|
|
153
|
-
const queryBundle = async (client, bundleId) => {
|
|
148
|
+
export const queryBundle = async (client, bundleId) => {
|
|
154
149
|
const resp = await client.query({
|
|
155
150
|
query: GET_BUNDLE,
|
|
156
151
|
variables: {
|
|
@@ -160,4 +155,3 @@ const queryBundle = async (client, bundleId) => {
|
|
|
160
155
|
});
|
|
161
156
|
return resp;
|
|
162
157
|
};
|
|
163
|
-
exports.queryBundle = queryBundle;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApolloClient } from "@apollo/client";
|
|
2
2
|
import type { Command } from "@oclif/core";
|
|
3
|
-
import type { PromptChoice } from "../../types";
|
|
3
|
+
import type { PromptChoice } from "../../types.js";
|
|
4
4
|
export declare function queryAssetRoles(cmd: Command, client: ApolloClient, assetType: string, assetId: string): Promise<PromptChoice[] | undefined>;
|
|
5
5
|
export declare function queryAssociatedItems(cmd: Command, client: ApolloClient, id: string, input: string | undefined): Promise<PromptChoice[] | undefined>;
|