1 | import { StringMap } from '@naturalcycles/js-lib'
|
2 | import * as fs from 'fs'
|
3 | import { base64ToString, Debug } from '..'
|
4 | import { decryptRandomIVBuffer } from './crypto.util'
|
5 |
|
6 | let loaded = false
|
7 |
|
8 |
|
9 | const getLog = () => Debug('nc:nodejs-lib:secret')
|
10 |
|
11 | const secretMap: StringMap = {}
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | export function loadSecretsFromEnv(): void {
|
20 | require('dotenv').config()
|
21 |
|
22 | const secrets: StringMap = {}
|
23 | Object.keys(process.env)
|
24 | .filter(k => k.toUpperCase().startsWith('SECRET_'))
|
25 | .forEach(k => {
|
26 | secrets[k.toUpperCase()] = process.env[k]!
|
27 | secretMap[k.toUpperCase()] = process.env[k]!
|
28 | delete process.env[k]
|
29 | })
|
30 |
|
31 | loaded = true
|
32 | getLog()(
|
33 | `${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(
|
34 | ', ',
|
35 | )}`,
|
36 | )
|
37 | }
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | export function removeSecretsFromEnv(): void {
|
43 | Object.keys(process.env)
|
44 | .filter(k => k.toUpperCase().startsWith('SECRET_'))
|
45 | .forEach(k => delete process.env[k])
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | export function loadSecretsFromJsonFile(filePath: string, SECRET_ENCRYPTION_KEY?: string): void {
|
54 | if (!fs.existsSync(filePath)) {
|
55 | throw new Error(`loadSecretsFromPlainJsonFile() cannot load from path: ${filePath}`)
|
56 | }
|
57 |
|
58 | let secrets: StringMap
|
59 |
|
60 | if (SECRET_ENCRYPTION_KEY) {
|
61 | const buf = fs.readFileSync(filePath)
|
62 | const plain = decryptRandomIVBuffer(buf, SECRET_ENCRYPTION_KEY).toString('utf8')
|
63 | secrets = JSON.parse(plain)
|
64 | } else {
|
65 | secrets = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
66 | }
|
67 |
|
68 | Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
|
69 |
|
70 | loaded = true
|
71 | getLog()(
|
72 | `${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
|
73 | .map(s => s.toUpperCase())
|
74 | .join(', ')}`,
|
75 | )
|
76 | }
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | export function secret<T = string>(k: string, json = false): T {
|
82 | const v = secretOptional(k, json)
|
83 | if (!v) {
|
84 | throw new Error(`secret(${k.toUpperCase()}) not found!`)
|
85 | }
|
86 |
|
87 | return v as any
|
88 | }
|
89 |
|
90 | export function secretOptional<T = string>(k: string, json = false): T | undefined {
|
91 | requireLoaded()
|
92 | const v = secretMap[k.toUpperCase()]
|
93 | return v && json ? JSON.parse(base64ToString(v)) : v
|
94 | }
|
95 |
|
96 | export function getSecretMap(): StringMap {
|
97 | requireLoaded()
|
98 | return secretMap
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | export function setSecretMap(map: StringMap): void {
|
105 | Object.keys(secretMap).forEach(k => delete secretMap[k])
|
106 | Object.entries(map).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
|
107 | getLog()(
|
108 | `setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
|
109 | .map(s => s.toUpperCase())
|
110 | .join(', ')}`,
|
111 | )
|
112 | }
|
113 |
|
114 | function requireLoaded(): void {
|
115 | if (!loaded) {
|
116 | throw new Error(`Secrets were not loaded! Call loadSecrets() before accessing secrets.`)
|
117 | }
|
118 | }
|