@gbuerk/gcrypt 0.1.0
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/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/commands.d.ts +49 -0
- package/dist/commands.js +333 -0
- package/dist/commands.js.map +1 -0
- package/dist/crypto.d.ts +18 -0
- package/dist/crypto.js +134 -0
- package/dist/crypto.js.map +1 -0
- package/dist/document.d.ts +45 -0
- package/dist/document.js +437 -0
- package/dist/document.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +285 -0
- package/dist/index.js.map +1 -0
- package/dist/key-storage.d.ts +6 -0
- package/dist/key-storage.js +41 -0
- package/dist/key-storage.js.map +1 -0
- package/dist/member-setup-code.d.ts +6 -0
- package/dist/member-setup-code.js +39 -0
- package/dist/member-setup-code.js.map +1 -0
- package/dist/node-types.d.ts +1 -0
- package/dist/node-types.js +3 -0
- package/dist/node-types.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
interface Prompt {
|
|
4
|
+
ask(question: string): Promise<string>;
|
|
5
|
+
confirm(question: string, defaultValue?: boolean): Promise<boolean>;
|
|
6
|
+
close(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function findEncryptedFiles(rootDirectory?: string): Promise<string[]>;
|
|
9
|
+
export declare function createProgram(providedPrompt?: Prompt): Command;
|
|
10
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { stdin, stdout } from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
import { createEncryptedDocument, decryptEncryptedDocument, editEncryptedDocument, executeDotenv, grantMaintainer, grantRecipient, maintainerSetup, revokeMaintainer, revokeRecipient, setup, verifyEncryptedDocument, } from './commands.js';
|
|
11
|
+
import { parseEncryptedDocument } from './document.js';
|
|
12
|
+
function terminalPrompt() {
|
|
13
|
+
const readline = createInterface({ input: stdin, output: stdout });
|
|
14
|
+
return {
|
|
15
|
+
ask: (question) => readline.question(question),
|
|
16
|
+
async confirm(question, defaultValue = false) {
|
|
17
|
+
const answer = (await readline.question(`${question} ${defaultValue ? '[Y/n]' : '[y/N]'} `)).trim();
|
|
18
|
+
return answer === '' ? defaultValue : /^(y|yes)$/iu.test(answer);
|
|
19
|
+
},
|
|
20
|
+
close: () => readline.close(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function selectedFile(file, getPrompt) {
|
|
24
|
+
if (file)
|
|
25
|
+
return file;
|
|
26
|
+
const files = await findEncryptedFiles();
|
|
27
|
+
if (files.length > 0 && stdin.isTTY) {
|
|
28
|
+
const manual = '__gcrypt_manual_path__';
|
|
29
|
+
const { selected } = await inquirer.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: 'select',
|
|
32
|
+
name: 'selected',
|
|
33
|
+
message: 'Select an encrypted file:',
|
|
34
|
+
pageSize: 12,
|
|
35
|
+
choices: [
|
|
36
|
+
...files.map((candidate) => ({ name: candidate, value: candidate })),
|
|
37
|
+
new inquirer.Separator(),
|
|
38
|
+
{ name: 'Enter a path manually', value: manual },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
if (selected !== manual)
|
|
43
|
+
return selected;
|
|
44
|
+
}
|
|
45
|
+
const value = (await getPrompt().ask('Encrypted file path: ')).trim();
|
|
46
|
+
if (!value)
|
|
47
|
+
throw new Error('An encrypted file path is required');
|
|
48
|
+
if (!value.endsWith('.enc'))
|
|
49
|
+
throw new Error('Encrypted files must end in .enc');
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
async function selectedMemberId(filePath, message, eligible) {
|
|
53
|
+
const source = await readFile(filePath, 'utf8');
|
|
54
|
+
const format = source.trimStart().startsWith('{') ? 'json' : 'dotenv';
|
|
55
|
+
const members = parseEncryptedDocument(source, format).metadata.members.filter(eligible);
|
|
56
|
+
if (members.length === 0)
|
|
57
|
+
throw new Error('No eligible members are listed');
|
|
58
|
+
const { memberId } = await inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: 'select',
|
|
61
|
+
name: 'memberId',
|
|
62
|
+
message,
|
|
63
|
+
pageSize: 12,
|
|
64
|
+
choices: members.map((member) => ({ name: `${member.id} (${member.recipient})`, value: member.id })),
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
return memberId;
|
|
68
|
+
}
|
|
69
|
+
export async function findEncryptedFiles(rootDirectory = process.cwd()) {
|
|
70
|
+
const files = [];
|
|
71
|
+
const ignoredDirectories = new Set(['.git', 'node_modules']);
|
|
72
|
+
async function visit(directory) {
|
|
73
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
74
|
+
await Promise.all(entries.map(async (entry) => {
|
|
75
|
+
const filePath = join(directory, entry.name);
|
|
76
|
+
if (entry.isDirectory() && !ignoredDirectories.has(entry.name))
|
|
77
|
+
await visit(filePath);
|
|
78
|
+
else if (entry.isFile() && entry.name.endsWith('.enc'))
|
|
79
|
+
files.push(relative(rootDirectory, filePath));
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
await visit(rootDirectory);
|
|
83
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
84
|
+
}
|
|
85
|
+
function reportPrivateKey(result, identity) {
|
|
86
|
+
stdout.write(`${result.created ? 'Created' : 'Using'} private ${identity} (never share) at ${result.path}\n`);
|
|
87
|
+
}
|
|
88
|
+
async function runSetup() {
|
|
89
|
+
const result = await setup();
|
|
90
|
+
reportPrivateKey(result.age, 'access key');
|
|
91
|
+
reportPrivateKey(result.signing, 'signing key');
|
|
92
|
+
stdout.write(`Share this member setup code with a maintainer:\n${result.memberSetupCode}\n`);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
async function runMaintainerSetup() {
|
|
96
|
+
reportPrivateKey(await maintainerSetup(), 'signing key');
|
|
97
|
+
}
|
|
98
|
+
async function initialMemberId(prompt) {
|
|
99
|
+
const memberId = (await prompt.ask('Initial maintainer ID: ')).trim();
|
|
100
|
+
if (!memberId || /\s/u.test(memberId))
|
|
101
|
+
throw new Error('Initial maintainer ID must be a non-empty string without whitespace');
|
|
102
|
+
return memberId;
|
|
103
|
+
}
|
|
104
|
+
async function initializeProject(file, getPrompt) {
|
|
105
|
+
const packagePath = join(process.cwd(), 'package.json');
|
|
106
|
+
let packageJson;
|
|
107
|
+
try {
|
|
108
|
+
packageJson = JSON.parse(await readFile(packagePath, 'utf8'));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
throw new Error('init requires package.json in the current directory');
|
|
112
|
+
}
|
|
113
|
+
const target = file ?? '.env.dev.enc';
|
|
114
|
+
if (!target.endsWith('.enc'))
|
|
115
|
+
throw new Error('Encrypted files must end in .enc');
|
|
116
|
+
try {
|
|
117
|
+
await access(target);
|
|
118
|
+
throw new Error(`Refusing to overwrite existing encrypted file: ${target}`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error.code !== 'ENOENT')
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
const scripts = { ...packageJson.scripts };
|
|
125
|
+
if (scripts.dev) {
|
|
126
|
+
const prompt = getPrompt();
|
|
127
|
+
if (!await prompt.confirm(`Move the current dev command to dev:app exactly as written?\n${scripts.dev}`, true))
|
|
128
|
+
throw new Error('init cancelled');
|
|
129
|
+
if (scripts['dev:app'] && !await prompt.confirm('Replace existing dev:app script?'))
|
|
130
|
+
throw new Error('init cancelled');
|
|
131
|
+
scripts['dev:app'] = scripts.dev;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const prompt = getPrompt();
|
|
135
|
+
const command = (await prompt.ask('Project development command: ')).trim();
|
|
136
|
+
if (!command)
|
|
137
|
+
throw new Error('A project development command is required');
|
|
138
|
+
scripts['dev:app'] = command;
|
|
139
|
+
}
|
|
140
|
+
scripts.dev = 'node scripts/dev.mjs';
|
|
141
|
+
for (const [name, command] of Object.entries({ 'dev:setup': 'gcrypt setup', secrets: 'gcrypt edit', 'secrets:grant': 'gcrypt grant', 'secrets:revoke': 'gcrypt revoke', 'secrets:maintainer:grant': 'gcrypt maintainer grant', 'secrets:maintainer:revoke': 'gcrypt maintainer revoke', 'secrets:init': 'gcrypt secrets init', 'secrets:verify': 'gcrypt verify' })) {
|
|
142
|
+
if (!scripts[name])
|
|
143
|
+
scripts[name] = command;
|
|
144
|
+
else if (scripts[name] !== command && await getPrompt().confirm(`Replace existing ${name} script?`))
|
|
145
|
+
scripts[name] = command;
|
|
146
|
+
}
|
|
147
|
+
packageJson.scripts = scripts;
|
|
148
|
+
await mkdir(join(process.cwd(), 'scripts'), { recursive: true });
|
|
149
|
+
await writeFile(join(process.cwd(), 'scripts', 'dev.mjs'), `import { spawn } from 'node:child_process'\n\nconst child = spawn('gcrypt', ['exec', ${JSON.stringify(target)}, '--', 'npm', 'run', 'dev:app'], { stdio: 'inherit' })\nchild.on('exit', (code) => process.exit(code ?? 1))\n`);
|
|
150
|
+
await writeFile(packagePath, `${JSON.stringify(packageJson, undefined, 2)}\n`);
|
|
151
|
+
if (!packageJson.dependencies?.['@gbuerk/gcrypt'] && !packageJson.devDependencies?.['@gbuerk/gcrypt']) {
|
|
152
|
+
await installGcrypt(localPackageSource());
|
|
153
|
+
}
|
|
154
|
+
const identities = await runSetup();
|
|
155
|
+
const memberId = await initialMemberId(getPrompt());
|
|
156
|
+
const examplePath = '.env.example';
|
|
157
|
+
try {
|
|
158
|
+
await access(examplePath);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
await writeFile(examplePath, '');
|
|
162
|
+
}
|
|
163
|
+
await createEncryptedDocument(target, '', {
|
|
164
|
+
members: [{ id: memberId, recipient: identities.age.recipient, signingKey: identities.signing.publicKey, isMaintainer: true }],
|
|
165
|
+
});
|
|
166
|
+
stdout.write(`Created ${target}. Run npm run secrets to add values.\n`);
|
|
167
|
+
}
|
|
168
|
+
async function installGcrypt(source) {
|
|
169
|
+
const packageManager = await exists('pnpm-lock.yaml') ? 'pnpm' : await exists('yarn.lock') ? 'yarn' : 'npm';
|
|
170
|
+
const packageReference = source ?? '@gbuerk/gcrypt';
|
|
171
|
+
const arguments_ = packageManager === 'yarn' ? ['add', '--dev', packageReference] : ['install', '--save-dev', packageReference];
|
|
172
|
+
const status = await new Promise((resolve, reject) => {
|
|
173
|
+
const child = spawn(packageManager, arguments_, { stdio: 'inherit' });
|
|
174
|
+
child.once('error', reject);
|
|
175
|
+
child.once('exit', resolve);
|
|
176
|
+
});
|
|
177
|
+
if (status !== 0)
|
|
178
|
+
throw new Error(`${packageManager} failed to install @gbuerk/gcrypt`);
|
|
179
|
+
}
|
|
180
|
+
async function exists(path) { try {
|
|
181
|
+
await access(path);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
} }
|
|
187
|
+
function localPackageSource() {
|
|
188
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
189
|
+
return packageRoot.includes(`${sep}node_modules${sep}`) ? undefined : packageRoot;
|
|
190
|
+
}
|
|
191
|
+
export function createProgram(providedPrompt) {
|
|
192
|
+
let prompt = providedPrompt;
|
|
193
|
+
const getPrompt = () => {
|
|
194
|
+
prompt ??= terminalPrompt();
|
|
195
|
+
return prompt;
|
|
196
|
+
};
|
|
197
|
+
const program = new Command();
|
|
198
|
+
program.name('gcrypt').description('Safely share app settings with your team').version('0.1.0').exitOverride();
|
|
199
|
+
program.hook('postAction', () => { prompt?.close(); });
|
|
200
|
+
program.command('setup').description('Set up your local access and signing keys').action(async () => { await runSetup(); });
|
|
201
|
+
const maintainer = program.command('maintainer').description('Manage who can approve changes');
|
|
202
|
+
maintainer.command('setup').action(() => runMaintainerSetup());
|
|
203
|
+
maintainer.command('grant [file]').action(async (file) => {
|
|
204
|
+
const selected = await selectedFile(file, getPrompt);
|
|
205
|
+
await grantMaintainer(selected, {
|
|
206
|
+
prompt: {
|
|
207
|
+
memberId: () => selectedMemberId(selected, 'Select a member who can approve changes:', (member) => !member.isMaintainer),
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
maintainer.command('revoke [file]').action(async (file) => {
|
|
212
|
+
const selected = await selectedFile(file, getPrompt);
|
|
213
|
+
await revokeMaintainer(selected, { prompt: { memberId: () => selectedMemberId(selected, 'Select a member who can no longer approve changes:', (member) => member.isMaintainer) } });
|
|
214
|
+
});
|
|
215
|
+
program.command('grant [file]').action(async (file) => {
|
|
216
|
+
const selected = await selectedFile(file, getPrompt);
|
|
217
|
+
const activePrompt = getPrompt();
|
|
218
|
+
await grantRecipient(selected, { prompt: { memberId: () => activePrompt.ask('Member ID: '), memberSetupCode: () => activePrompt.ask('Member setup code: ') } });
|
|
219
|
+
});
|
|
220
|
+
program.command('revoke [file]').action(async (file) => {
|
|
221
|
+
const selected = await selectedFile(file, getPrompt);
|
|
222
|
+
await revokeRecipient(selected, { prompt: { memberId: () => selectedMemberId(selected, 'Select a member to revoke:', () => true) } });
|
|
223
|
+
});
|
|
224
|
+
program.command('verify [file]').action(async (file) => {
|
|
225
|
+
await verifyEncryptedDocument(await selectedFile(file, getPrompt));
|
|
226
|
+
stdout.write('This file is valid and has maintainer approval.\n');
|
|
227
|
+
});
|
|
228
|
+
program.command('decrypt [file]').action(async (file) => {
|
|
229
|
+
stdout.write(await decryptEncryptedDocument(await selectedFile(file, getPrompt)));
|
|
230
|
+
});
|
|
231
|
+
program.command('edit [file]').action(async (file) => {
|
|
232
|
+
const selected = await selectedFile(file, getPrompt);
|
|
233
|
+
const editor = process.env.VISUAL || process.env.EDITOR;
|
|
234
|
+
if (!editor)
|
|
235
|
+
throw new Error('Set VISUAL or EDITOR to edit secrets');
|
|
236
|
+
await editEncryptedDocument(selected, editor);
|
|
237
|
+
});
|
|
238
|
+
program.command('exec <file> [command...]').allowUnknownOption().action(async (file, command) => {
|
|
239
|
+
const status = await executeDotenv(file, command, undefined);
|
|
240
|
+
process.exitCode = status;
|
|
241
|
+
});
|
|
242
|
+
program.command('init [file]').action((file) => initializeProject(file, getPrompt));
|
|
243
|
+
const secrets = program.command('secrets').description('Manage encrypted secrets');
|
|
244
|
+
secrets.command('init [file]').action(async (file) => {
|
|
245
|
+
const target = file ?? '.env.dev.enc';
|
|
246
|
+
try {
|
|
247
|
+
await access(target);
|
|
248
|
+
throw new Error(`Refusing to overwrite existing encrypted file: ${target}`);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error.code !== 'ENOENT')
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
const identities = await runSetup();
|
|
255
|
+
await createEncryptedDocument(target, '', {
|
|
256
|
+
members: [{ id: await initialMemberId(getPrompt()), recipient: identities.age.recipient, signingKey: identities.signing.publicKey, isMaintainer: true }],
|
|
257
|
+
});
|
|
258
|
+
stdout.write(`Created ${target}. Run npm run secrets to add values.\n`);
|
|
259
|
+
});
|
|
260
|
+
return program;
|
|
261
|
+
}
|
|
262
|
+
async function main() {
|
|
263
|
+
try {
|
|
264
|
+
await createProgram().parseAsync(process.argv);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
stderr(error);
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function stderr(error) { process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); }
|
|
272
|
+
function invokedAsCli() {
|
|
273
|
+
if (!process.argv[1])
|
|
274
|
+
return false;
|
|
275
|
+
try {
|
|
276
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (invokedAsCli())
|
|
283
|
+
void main();
|
|
284
|
+
import { spawn } from 'node:child_process';
|
|
285
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function defaultAgeIdentityPath(homeDirectory?: string): string;
|
|
2
|
+
export declare function defaultSigningIdentityPath(homeDirectory?: string): string;
|
|
3
|
+
export declare function writeDefaultAgeIdentity(identity: string, homeDirectory?: string): Promise<string>;
|
|
4
|
+
export declare function readDefaultAgeIdentity(homeDirectory?: string): Promise<string>;
|
|
5
|
+
export declare function writeDefaultSigningIdentity(identity: string, homeDirectory?: string): Promise<string>;
|
|
6
|
+
export declare function readDefaultSigningIdentity(homeDirectory?: string): Promise<string>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const configDirectoryName = '.config/gcrypt';
|
|
5
|
+
export function defaultAgeIdentityPath(homeDirectory = homedir()) {
|
|
6
|
+
return join(homeDirectory, configDirectoryName, 'keys', 'default.txt');
|
|
7
|
+
}
|
|
8
|
+
export function defaultSigningIdentityPath(homeDirectory = homedir()) {
|
|
9
|
+
return join(homeDirectory, configDirectoryName, 'signing', 'default.txt');
|
|
10
|
+
}
|
|
11
|
+
export async function writeDefaultAgeIdentity(identity, homeDirectory = homedir()) {
|
|
12
|
+
return writePrivateKey(defaultAgeIdentityPath(homeDirectory), identity);
|
|
13
|
+
}
|
|
14
|
+
export async function readDefaultAgeIdentity(homeDirectory = homedir()) {
|
|
15
|
+
return readPrivateKey(defaultAgeIdentityPath(homeDirectory));
|
|
16
|
+
}
|
|
17
|
+
export async function writeDefaultSigningIdentity(identity, homeDirectory = homedir()) {
|
|
18
|
+
return writePrivateKey(defaultSigningIdentityPath(homeDirectory), identity);
|
|
19
|
+
}
|
|
20
|
+
export async function readDefaultSigningIdentity(homeDirectory = homedir()) {
|
|
21
|
+
return readPrivateKey(defaultSigningIdentityPath(homeDirectory));
|
|
22
|
+
}
|
|
23
|
+
async function writePrivateKey(filePath, value) {
|
|
24
|
+
const configDirectory = dirname(dirname(filePath));
|
|
25
|
+
const directory = dirname(filePath);
|
|
26
|
+
await mkdir(configDirectory, { mode: 0o700, recursive: true });
|
|
27
|
+
await chmod(configDirectory, 0o700);
|
|
28
|
+
await mkdir(directory, { mode: 0o700, recursive: true });
|
|
29
|
+
await chmod(directory, 0o700);
|
|
30
|
+
await writeFile(filePath, `${value}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
31
|
+
await chmod(filePath, 0o600);
|
|
32
|
+
return filePath;
|
|
33
|
+
}
|
|
34
|
+
async function readPrivateKey(filePath) {
|
|
35
|
+
const fileStatus = await stat(filePath);
|
|
36
|
+
if ((fileStatus.mode & 0o077) !== 0) {
|
|
37
|
+
throw new Error(`Refusing group- or world-readable private key file: ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
return (await readFile(filePath, 'utf8')).trim();
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=key-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-storage.js","sourceRoot":"","sources":["../src/key-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,mBAAmB,GAAG,gBAAgB,CAAA;AAE5C,MAAM,UAAU,sBAAsB,CAAC,aAAa,GAAG,OAAO,EAAE;IAC5D,OAAO,IAAI,CAAC,aAAa,EAAE,mBAAmB,EAAE,MAAM,EAAE,aAAa,CAAC,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,aAAa,GAAG,OAAO,EAAE;IAChE,OAAO,IAAI,CAAC,aAAa,EAAE,mBAAmB,EAAE,SAAS,EAAE,aAAa,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,QAAgB,EAAE,aAAa,GAAG,OAAO,EAAE;IACrF,OAAO,eAAe,CAAC,sBAAsB,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,CAAA;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,aAAa,GAAG,OAAO,EAAE;IAClE,OAAO,cAAc,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC,CAAA;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,QAAgB,EAAE,aAAa,GAAG,OAAO,EAAE;IACzF,OAAO,eAAe,CAAC,0BAA0B,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,CAAA;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,aAAa,GAAG,OAAO,EAAE;IACtE,OAAO,cAAc,CAAC,0BAA0B,CAAC,aAAa,CAAC,CAAC,CAAA;AACpE,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,KAAa;IAC1D,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnC,MAAM,KAAK,CAAC,eAAe,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9D,MAAM,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAA;IACnC,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACxD,MAAM,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC7B,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1E,MAAM,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC5B,OAAO,QAAQ,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,QAAgB;IAC1C,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAA;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,uDAAuD,QAAQ,EAAE,CAAC,CAAA;IACtF,CAAC;IACD,OAAO,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AACpD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { validateAgeRecipient, validateSigningPublicKey } from './crypto.js';
|
|
2
|
+
const prefix = 'gcrypt-member-v1.';
|
|
3
|
+
export function encodeMemberSetupCode({ recipient, signingKey }) {
|
|
4
|
+
validateAgeRecipient(recipient);
|
|
5
|
+
validateSigningPublicKey(signingKey);
|
|
6
|
+
return `${prefix}${Buffer.from(JSON.stringify({ recipient, signingKey })).toString('base64url')}`;
|
|
7
|
+
}
|
|
8
|
+
export function decodeMemberSetupCode(value) {
|
|
9
|
+
try {
|
|
10
|
+
const code = value.trim();
|
|
11
|
+
if (!code.startsWith(prefix))
|
|
12
|
+
throw new Error();
|
|
13
|
+
const encoded = code.slice(prefix.length);
|
|
14
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(encoded) || Buffer.from(encoded, 'base64url').toString('base64url') !== encoded)
|
|
15
|
+
throw new Error();
|
|
16
|
+
const parsed = JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
17
|
+
if (!isMemberSetupCode(parsed))
|
|
18
|
+
throw new Error();
|
|
19
|
+
validateAgeRecipient(parsed.recipient);
|
|
20
|
+
validateSigningPublicKey(parsed.signingKey);
|
|
21
|
+
if (encodeMemberSetupCode(parsed) !== code)
|
|
22
|
+
throw new Error();
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error('Invalid member setup code');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isMemberSetupCode(value) {
|
|
30
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
const record = value;
|
|
33
|
+
const entries = Object.entries(record);
|
|
34
|
+
return entries.length === 2
|
|
35
|
+
&& typeof record.recipient === 'string'
|
|
36
|
+
&& typeof record.signingKey === 'string'
|
|
37
|
+
&& entries.every(([key]) => key === 'recipient' || key === 'signingKey');
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=member-setup-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"member-setup-code.js","sourceRoot":"","sources":["../src/member-setup-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAE5E,MAAM,MAAM,GAAG,mBAAmB,CAAA;AAOlC,MAAM,UAAU,qBAAqB,CAAC,EAAE,SAAS,EAAE,UAAU,EAAmB;IAC5E,oBAAoB,CAAC,SAAS,CAAC,CAAA;IAC/B,wBAAwB,CAAC,UAAU,CAAC,CAAA;IACpC,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAA;AACrG,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAa;IAC/C,IAAI,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,EAAE,CAAA;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,EAAE,CAAA;QAChI,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;QACtF,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,EAAE,CAAA;QACjD,oBAAoB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QACtC,wBAAwB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;QAC3C,IAAI,qBAAqB,CAAC,MAAM,CAAC,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,EAAE,CAAA;QAC7D,OAAO,MAAM,CAAA;IACjB,CAAC;IAAC,MAAM,CAAC;QACL,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;IAChD,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACrF,MAAM,MAAM,GAAG,KAAgC,CAAA;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACtC,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC;WACpB,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;WACpC,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;WACrC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,YAAY,CAAC,CAAA;AAChF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-types.js","sourceRoot":"","sources":["../src/node-types.ts"],"names":[],"mappings":";AAAA,8BAA8B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gbuerk/gcrypt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Git-friendly encrypted dotenv and JSON configuration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"secrets",
|
|
8
|
+
"encryption",
|
|
9
|
+
"dotenv",
|
|
10
|
+
"configuration",
|
|
11
|
+
"age",
|
|
12
|
+
"git"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/gbuerk/gcrypt.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/gbuerk/gcrypt#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/gbuerk/gcrypt/issues"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"gcrypt": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": "^22.13.0 || >=23.5.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"dev": "tsx src/index.ts",
|
|
38
|
+
"lint": "eslint .",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"prepack": "npm run build",
|
|
42
|
+
"prepublishOnly": "npm run lint && npm test"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"age-encryption": "^0.3.1",
|
|
46
|
+
"commander": "^15.0.0",
|
|
47
|
+
"dotenv": "^17.2.3",
|
|
48
|
+
"inquirer": "^14.0.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^25.9.3",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.59.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.59.0",
|
|
54
|
+
"eslint": "^9.39.4",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"vitest": "^4.1.0"
|
|
58
|
+
}
|
|
59
|
+
}
|