@orion-js/env 4.0.0 → 4.0.1
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/index.js +287 -0
- package/package.json +3 -3
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import colors from "colors/safe";
|
|
6
|
+
|
|
7
|
+
// src/cli/init/index.ts
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
|
|
10
|
+
// src/crypto/tweetnacl.ts
|
|
11
|
+
import nacl from "tweetnacl-es6";
|
|
12
|
+
|
|
13
|
+
// src/crypto/util.ts
|
|
14
|
+
function validateBase64(s) {
|
|
15
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(s)) {
|
|
16
|
+
throw new TypeError("invalid encoding");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
var decodeUTF8 = (s) => {
|
|
20
|
+
if (typeof s !== "string") throw new TypeError("expected string");
|
|
21
|
+
let i;
|
|
22
|
+
const d = unescape(encodeURIComponent(s));
|
|
23
|
+
const b = new Uint8Array(d.length);
|
|
24
|
+
for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);
|
|
25
|
+
return b;
|
|
26
|
+
};
|
|
27
|
+
var encodeUTF8 = (arr) => {
|
|
28
|
+
let i;
|
|
29
|
+
const s = [];
|
|
30
|
+
for (i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i]));
|
|
31
|
+
return decodeURIComponent(escape(s.join("")));
|
|
32
|
+
};
|
|
33
|
+
var encodeBase64 = (arr) => Buffer.from(arr).toString("base64");
|
|
34
|
+
var decodeBase64 = (s) => {
|
|
35
|
+
validateBase64(s);
|
|
36
|
+
return new Uint8Array(Array.prototype.slice.call(Buffer.from(s, "base64"), 0));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/crypto/tweetnacl.ts
|
|
40
|
+
var newNonce = () => nacl.randomBytes(nacl.box.nonceLength);
|
|
41
|
+
var generateKeyPair = () => nacl.box.keyPair();
|
|
42
|
+
var encrypt = (bSecretKey, aPublicKey, message) => {
|
|
43
|
+
const nonce = newNonce();
|
|
44
|
+
const messageUint8 = decodeUTF8(message);
|
|
45
|
+
const encrypted = nacl.box(messageUint8, nonce, aPublicKey, bSecretKey);
|
|
46
|
+
const fullMessage = new Uint8Array(nonce.length + encrypted.length);
|
|
47
|
+
fullMessage.set(nonce);
|
|
48
|
+
fullMessage.set(encrypted, nonce.length);
|
|
49
|
+
const base64FullMessage = encodeBase64(fullMessage);
|
|
50
|
+
return base64FullMessage;
|
|
51
|
+
};
|
|
52
|
+
var decrypt = (aSecretKey, bPublicKey, messageWithNonce) => {
|
|
53
|
+
const messageWithNonceAsUint8Array = decodeBase64(messageWithNonce);
|
|
54
|
+
const nonce = messageWithNonceAsUint8Array.slice(0, nacl.box.nonceLength);
|
|
55
|
+
const message = messageWithNonceAsUint8Array.slice(nacl.box.nonceLength, messageWithNonce.length);
|
|
56
|
+
const decrypted = nacl.box.open(message, nonce, bPublicKey, aSecretKey);
|
|
57
|
+
if (!decrypted) {
|
|
58
|
+
throw new Error("Could not decrypt message");
|
|
59
|
+
}
|
|
60
|
+
const base64DecryptedMessage = encodeUTF8(decrypted);
|
|
61
|
+
return base64DecryptedMessage;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/crypto/index.ts
|
|
65
|
+
function generateKeys() {
|
|
66
|
+
const { publicKey, secretKey } = generateKeyPair();
|
|
67
|
+
const encryptKeyHex = encodeBase64(publicKey);
|
|
68
|
+
const decryptKeyHex = encodeBase64(secretKey);
|
|
69
|
+
return {
|
|
70
|
+
encryptKey: encryptKeyHex,
|
|
71
|
+
decryptKey: decryptKeyHex
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function encrypt2(encryptKey, message) {
|
|
75
|
+
const encryptPublicKey = decodeBase64(encryptKey);
|
|
76
|
+
const tempPair = generateKeyPair();
|
|
77
|
+
const encrypted = encrypt(tempPair.secretKey, encryptPublicKey, message);
|
|
78
|
+
const hexTempPublic = encodeBase64(tempPair.publicKey);
|
|
79
|
+
return `${hexTempPublic}:${encrypted}`;
|
|
80
|
+
}
|
|
81
|
+
function decrypt2(decryptKey, encrypted) {
|
|
82
|
+
const decryptSecretKey = decodeBase64(decryptKey);
|
|
83
|
+
const [messagePubKeyHex, encryptedMessage] = encrypted.split(":");
|
|
84
|
+
const messagePubKey = decodeBase64(messagePubKeyHex);
|
|
85
|
+
return decrypt(decryptSecretKey, messagePubKey, encryptedMessage);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/files/index.ts
|
|
89
|
+
import fs from "node:fs";
|
|
90
|
+
import path from "node:path";
|
|
91
|
+
function readFile(filePath) {
|
|
92
|
+
if (!fs.existsSync(filePath)) return null;
|
|
93
|
+
return fs.readFileSync(filePath).toString();
|
|
94
|
+
}
|
|
95
|
+
function writeFile(path2, content) {
|
|
96
|
+
ensureDirectory(path2);
|
|
97
|
+
fs.writeFileSync(path2, content);
|
|
98
|
+
}
|
|
99
|
+
function ensureDirectory(filePath) {
|
|
100
|
+
const dirname = path.dirname(filePath);
|
|
101
|
+
if (fs.existsSync(dirname)) return true;
|
|
102
|
+
ensureDirectory(dirname);
|
|
103
|
+
fs.mkdirSync(dirname);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/cli/init/index.ts
|
|
107
|
+
async function envInit({ path: path2 }) {
|
|
108
|
+
if (!path2) {
|
|
109
|
+
path2 = ".env.local.yml";
|
|
110
|
+
}
|
|
111
|
+
const keypair = generateKeys();
|
|
112
|
+
const envFile = {
|
|
113
|
+
version: "1.0",
|
|
114
|
+
publicKey: keypair.encryptKey,
|
|
115
|
+
cleanKeys: {},
|
|
116
|
+
encryptedKeys: {},
|
|
117
|
+
readFromSecret: {}
|
|
118
|
+
};
|
|
119
|
+
const text = YAML.stringify(envFile);
|
|
120
|
+
writeFile(path2, text);
|
|
121
|
+
console.log("");
|
|
122
|
+
console.log(
|
|
123
|
+
`Environment file created. You need to use the following key to decrypt the environment variables:`
|
|
124
|
+
);
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log(keypair.decryptKey);
|
|
127
|
+
console.log("");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/cli/add/encryptValue.ts
|
|
131
|
+
var encryptValue = (key, value, config) => {
|
|
132
|
+
config.encryptedKeys[key] = encrypt2(config.publicKey, value);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/cli/add/getConfig.ts
|
|
136
|
+
import YAML2 from "yaml";
|
|
137
|
+
var getConfig = (envPath) => {
|
|
138
|
+
const configFile = readFile(envPath);
|
|
139
|
+
if (!configFile) {
|
|
140
|
+
throw new Error("No config file found at path " + envPath);
|
|
141
|
+
}
|
|
142
|
+
return YAML2.parse(configFile);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/cli/add/getParams.ts
|
|
146
|
+
import prompts from "prompts";
|
|
147
|
+
var getParams = async (config) => {
|
|
148
|
+
const response = await prompts([
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
name: "key",
|
|
152
|
+
message: "Key"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
name: "value",
|
|
157
|
+
message: "Value"
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
return {
|
|
161
|
+
key: response.key,
|
|
162
|
+
value: response.value
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/cli/add/index.ts
|
|
167
|
+
import YAML3 from "yaml";
|
|
168
|
+
var sortObjectByKeys = (object) => {
|
|
169
|
+
if (!object) return {};
|
|
170
|
+
const sorted = {};
|
|
171
|
+
Object.keys(object).sort().forEach((key) => {
|
|
172
|
+
sorted[key] = object[key];
|
|
173
|
+
});
|
|
174
|
+
return sorted;
|
|
175
|
+
};
|
|
176
|
+
async function envAdd({ path: path2 }) {
|
|
177
|
+
if (!path2) {
|
|
178
|
+
path2 = ".env.local.yml";
|
|
179
|
+
}
|
|
180
|
+
const config = getConfig(path2);
|
|
181
|
+
const { key, value } = await getParams(config);
|
|
182
|
+
if (!value) return;
|
|
183
|
+
encryptValue(key, value, config);
|
|
184
|
+
config.cleanKeys = sortObjectByKeys(config.cleanKeys);
|
|
185
|
+
config.encryptedKeys = sortObjectByKeys(config.encryptedKeys);
|
|
186
|
+
config.readFromSecret = sortObjectByKeys(config.readFromSecret);
|
|
187
|
+
const text = YAML3.stringify(config);
|
|
188
|
+
writeFile(path2, text);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/environment/getVariables.ts
|
|
192
|
+
function readSecrets(readFromSecret) {
|
|
193
|
+
const variables = {};
|
|
194
|
+
let secretKey = null;
|
|
195
|
+
if (!readFromSecret) return { variables, secretKey };
|
|
196
|
+
for (const secretName in readFromSecret) {
|
|
197
|
+
const keys = readFromSecret[secretName];
|
|
198
|
+
if (!process.env[secretName]) {
|
|
199
|
+
console.warn(
|
|
200
|
+
`@orion/env could not find the secret "${secretName}" in the environment. Related variables will be undefined.`
|
|
201
|
+
);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const values = JSON.parse(process.env[secretName]);
|
|
206
|
+
if (values.ORION_ENV_SECRET_KEY) {
|
|
207
|
+
secretKey = values.ORION_ENV_SECRET_KEY;
|
|
208
|
+
}
|
|
209
|
+
for (const key of keys) {
|
|
210
|
+
if (values[key]) {
|
|
211
|
+
variables[key] = values[key];
|
|
212
|
+
} else {
|
|
213
|
+
console.warn(
|
|
214
|
+
`@orion/env could not find the variable "${key}" in the secret "${secretName}". Related variables will be undefined.`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn(
|
|
220
|
+
`'@orion/env found a the secret "${secretName}" variable in the environment but it is not a valid JSON. Related variables will be undefined.'`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { variables, secretKey };
|
|
225
|
+
}
|
|
226
|
+
function getVariables(config, secretKey) {
|
|
227
|
+
const { cleanKeys, encryptedKeys, readFromSecret } = config;
|
|
228
|
+
const { variables, secretKey: foundSecretKey } = readSecrets(readFromSecret);
|
|
229
|
+
let decryptKey = foundSecretKey || secretKey;
|
|
230
|
+
if (!decryptKey) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
"Orion encrypted env was passed but process.env.ORION_ENV_SECRET_KEY is not defined"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
for (const key in cleanKeys) {
|
|
236
|
+
const value = cleanKeys[key];
|
|
237
|
+
variables[key] = value;
|
|
238
|
+
}
|
|
239
|
+
for (const key in encryptedKeys) {
|
|
240
|
+
const encrypted = encryptedKeys[key];
|
|
241
|
+
try {
|
|
242
|
+
variables[key] = decrypt2(decryptKey, encrypted);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`Orion encrypted env was passed but process.env.ORION_ENV_SECRET_KEY is not the right key for "${key}"`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return variables;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/cli/read/index.ts
|
|
253
|
+
async function envRead({ path: path2, key, secret }) {
|
|
254
|
+
if (!path2) {
|
|
255
|
+
path2 = ".env.local.yml";
|
|
256
|
+
}
|
|
257
|
+
if (!secret) {
|
|
258
|
+
throw new Error("Secret is required");
|
|
259
|
+
}
|
|
260
|
+
const config = getConfig(path2);
|
|
261
|
+
const variables = getVariables(config, secret);
|
|
262
|
+
if (key) {
|
|
263
|
+
console.log(variables[key]);
|
|
264
|
+
} else {
|
|
265
|
+
console.log(JSON.stringify(variables, null, 2));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/cli/index.ts
|
|
270
|
+
var program = new Command();
|
|
271
|
+
var run = function(action) {
|
|
272
|
+
return async function(...args) {
|
|
273
|
+
try {
|
|
274
|
+
await action(...args);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
console.error(colors.red("Error: " + e.message));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
program.command("init").description("Creates a new encrypted env file").option("--path <path>", "Specify the env file name").action(run(envInit));
|
|
281
|
+
program.command("add").description("Adds a new environment to the encrypted env file").option("--path <path>", "Specify the env file name").action(run(envAdd));
|
|
282
|
+
program.command("read").description("Prints the value of the env file in JSON or a specific variable in plain text").option("--path <path>", "Specify the env file name").option("--key <key>", "Prints the value of a specific variable in plain text").option("--secret <secret>", "The password to decrypt the keys").action(run(envRead));
|
|
283
|
+
program.parse(process.argv);
|
|
284
|
+
if (!process.argv.slice(2).length) {
|
|
285
|
+
program.outputHelp();
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orion-js/env",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"main": "./dist/index.cjs",
|
|
5
5
|
"author": "nicolaslopezj",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
8
|
-
"orion-env": "./
|
|
8
|
+
"orion-env": "./dist-cli/index.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"colors": "^1.4.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"test": "vitest run",
|
|
40
40
|
"clean": "rm -rf ./dist",
|
|
41
|
-
"build": "tsup",
|
|
41
|
+
"build": "tsup --config tsup.config.ts && tsup --config cli.tsup.config.ts",
|
|
42
42
|
"dev": "tsup --watch"
|
|
43
43
|
}
|
|
44
44
|
}
|