@atlasauth/cli 0.7.0 → 0.9.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/CHANGELOG.md +36 -0
- package/dist/args.d.ts +155 -0
- package/dist/args.js +66 -1
- package/dist/args.js.map +1 -1
- package/dist/commands/auth.js +5 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/config.d.ts +56 -1
- package/dist/commands/config.js +188 -29
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/csp.d.ts +32 -0
- package/dist/commands/csp.js +180 -0
- package/dist/commands/csp.js.map +1 -0
- package/dist/commands/import-better-auth.d.ts +3 -0
- package/dist/commands/import-better-auth.js +164 -0
- package/dist/commands/import-better-auth.js.map +1 -0
- package/dist/commands/import.js +7 -0
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/info.js +1 -1
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.js +26 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/integrations.d.ts +8 -0
- package/dist/commands/integrations.js +112 -0
- package/dist/commands/integrations.js.map +1 -0
- package/dist/commands/listen.d.ts +17 -0
- package/dist/commands/listen.js +223 -0
- package/dist/commands/listen.js.map +1 -0
- package/dist/commands/migrate.js +40 -1
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/move-origin.d.ts +11 -0
- package/dist/commands/move-origin.js +95 -0
- package/dist/commands/move-origin.js.map +1 -0
- package/dist/commands/operator.d.ts +26 -0
- package/dist/commands/operator.js +198 -0
- package/dist/commands/operator.js.map +1 -0
- package/dist/commands/project-commands.d.ts +12 -0
- package/dist/commands/project-commands.js +130 -30
- package/dist/commands/project-commands.js.map +1 -1
- package/dist/commands/referrals.d.ts +4 -4
- package/dist/commands/referrals.js +203 -33
- package/dist/commands/referrals.js.map +1 -1
- package/dist/commands/shared.d.ts +6 -0
- package/dist/commands/shared.js +10 -1
- package/dist/commands/shared.js.map +1 -1
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +77 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/webhooks.d.ts +29 -2
- package/dist/commands/webhooks.js +257 -24
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +13 -0
- package/dist/context.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.js +221 -17
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate/better-auth/integration.d.ts +59 -0
- package/dist/migrate/better-auth/integration.js +81 -0
- package/dist/migrate/better-auth/integration.js.map +1 -0
- package/dist/migrate/better-auth/orchestrator.d.ts +43 -0
- package/dist/migrate/better-auth/orchestrator.js +525 -0
- package/dist/migrate/better-auth/orchestrator.js.map +1 -0
- package/dist/migrate/better-auth/output.d.ts +20 -0
- package/dist/migrate/better-auth/output.js +146 -0
- package/dist/migrate/better-auth/output.js.map +1 -0
- package/dist/migrate/better-auth/translate.d.ts +77 -0
- package/dist/migrate/better-auth/translate.js +1989 -0
- package/dist/migrate/better-auth/translate.js.map +1 -0
- package/dist/run.js +14 -0
- package/dist/run.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.moveOrigin = moveOrigin;
|
|
4
|
+
const args_1 = require("../args");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const output_1 = require("../output");
|
|
7
|
+
const project_1 = require("../project");
|
|
8
|
+
const shared_1 = require("./shared");
|
|
9
|
+
const COLUMNS = [
|
|
10
|
+
{ header: 'OBJECT', get: (c) => `${c.object} ${c.id}` },
|
|
11
|
+
{ header: 'FIELD', get: (c) => c.field },
|
|
12
|
+
{ header: 'BEFORE', get: (c) => c.before },
|
|
13
|
+
{ header: 'AFTER', get: (c) => c.after },
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* `atlas move-origin --from <origin> --to <origin> [--dry-run] [--yes]`: move an
|
|
17
|
+
* app to a new domain in one step. The server rewrites every origin-bearing
|
|
18
|
+
* field (allowed origins, OAuth client redirect/logout URIs, webhook URLs,
|
|
19
|
+
* redirect allowlist, branding links, Sign in with Atlas apps). The CLI always
|
|
20
|
+
* shows the diff first, asks before writing (skip with --yes), and after a real
|
|
21
|
+
* move sends a signed test delivery to each webhook endpoint that moved.
|
|
22
|
+
*/
|
|
23
|
+
async function moveOrigin(ctx, args) {
|
|
24
|
+
const v = args.values;
|
|
25
|
+
const from = (0, args_1.str)(v, 'from');
|
|
26
|
+
const to = (0, args_1.str)(v, 'to');
|
|
27
|
+
if (!from || !to)
|
|
28
|
+
throw new errors_1.CliError('move-origin needs --from <origin> and --to <origin>, e.g. --from https://old.example --to https://new.example.');
|
|
29
|
+
const json = (0, shared_1.wantsJson)(v);
|
|
30
|
+
const auth = (0, shared_1.resolveAuth)(ctx, v);
|
|
31
|
+
const http = (0, project_1.httpFor)(ctx, { secretKey: auth.secretKey, apiUrl: auth.apiUrl });
|
|
32
|
+
const path = '/v1/instance/move_origin';
|
|
33
|
+
const plan = await http.post(path, { from, to, dry_run: true });
|
|
34
|
+
if (!json) {
|
|
35
|
+
if (plan.changes.length === 0)
|
|
36
|
+
ctx.io.out(`Nothing on ${plan.from} to move: no field of this instance uses that origin.`);
|
|
37
|
+
else {
|
|
38
|
+
ctx.io.out(`${plan.from} → ${plan.to}: ${plan.changes.length} field(s) would change.`);
|
|
39
|
+
ctx.io.out((0, output_1.renderTable)(COLUMNS, plan.changes));
|
|
40
|
+
}
|
|
41
|
+
for (const p of plan.problems)
|
|
42
|
+
ctx.io.out(`problem: ${p.object} ${p.id} ${p.field}: ${p.message} (${p.url})`);
|
|
43
|
+
}
|
|
44
|
+
if (v['dry-run'] === true) {
|
|
45
|
+
if (json)
|
|
46
|
+
(0, output_1.printJson)(ctx.io, plan);
|
|
47
|
+
else
|
|
48
|
+
ctx.io.out('Dry run: nothing was written.');
|
|
49
|
+
return plan.problems.length > 0 ? 1 : 0;
|
|
50
|
+
}
|
|
51
|
+
if (plan.problems.length > 0) {
|
|
52
|
+
if (json)
|
|
53
|
+
(0, output_1.printJson)(ctx.io, plan);
|
|
54
|
+
throw new errors_1.CliError(`${plan.problems.length} rewritten value(s) would be refused (see above); fix them and run again. Nothing was written.`);
|
|
55
|
+
}
|
|
56
|
+
if (plan.changes.length === 0) {
|
|
57
|
+
if (json)
|
|
58
|
+
(0, output_1.printJson)(ctx.io, plan);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
if (v.yes !== true) {
|
|
62
|
+
if (json)
|
|
63
|
+
throw new errors_1.CliError('Pass --yes to apply a move with --json (the confirmation prompt is interactive).');
|
|
64
|
+
const answer = (await ctx.prompt.text('Apply these changes? [y/N]', { default: 'n' })).trim().toLowerCase();
|
|
65
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
66
|
+
ctx.io.out('Cancelled: nothing was written.');
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const done = await http.post(path, { from, to });
|
|
71
|
+
const hooks = [...new Set(done.changes.filter((c) => c.object === 'webhook_endpoint').map((c) => c.id))];
|
|
72
|
+
const tests = [];
|
|
73
|
+
for (const id of hooks) {
|
|
74
|
+
try {
|
|
75
|
+
const res = await http.post(`/v1/webhook_endpoints/${encodeURIComponent(id)}/test?wait=10s`, {});
|
|
76
|
+
const status = res.http_status ?? null;
|
|
77
|
+
tests.push({ endpoint: id, ok: status !== null && status >= 200 && status < 300, http_status: status, error: res.error ?? null });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
tests.push({ endpoint: id, ok: false, http_status: null, error: err instanceof Error ? err.message : String(err) });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (json) {
|
|
84
|
+
(0, output_1.printJson)(ctx.io, { ...done, webhook_tests: tests });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
ctx.io.out(`Moved ${done.changes.length} field(s) from ${done.from} to ${done.to}.`);
|
|
88
|
+
for (const t of tests) {
|
|
89
|
+
ctx.io.out(`webhook ${t.endpoint}: ${t.ok ? `test delivery answered ${t.http_status}` : `test delivery did not get a 2xx${t.http_status ? ` (${t.http_status})` : ''}${t.error ? `: ${t.error}` : ''}`}`);
|
|
90
|
+
}
|
|
91
|
+
ctx.io.out("Next: update your app's own config (env vars, DNS, deploy) to the new origin, then run 'atlas preflight'.");
|
|
92
|
+
}
|
|
93
|
+
return tests.some((t) => !t.ok) ? 1 : 0;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=move-origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move-origin.js","sourceRoot":"","sources":["../../src/commands/move-origin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ParsedArgs } from '../args';
|
|
2
|
+
import type { RunContext } from '../context';
|
|
3
|
+
/**
|
|
4
|
+
* `atlas login --device` — sign in as an OPERATOR through Atlas's own sign-in
|
|
5
|
+
* (passkey, MFA) with the OAuth device flow, instead of pasting a key. Only
|
|
6
|
+
* short-lived tokens are stored: the operator token (to swap later with
|
|
7
|
+
* `atlas token`) and, with `--resource`, a one-hour token bound to that
|
|
8
|
+
* instance or app, used as the profile credential. No long-lived key is written.
|
|
9
|
+
*/
|
|
10
|
+
export declare function deviceLogin(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* `atlas token --resource <ins_…|app_…|acct_…> --scope "<scopes>"` — swap the
|
|
13
|
+
* stored operator token for a one-hour Atlas token bound to that resource.
|
|
14
|
+
* Prints the token (or JSON with --json); `--profile` stores it as that
|
|
15
|
+
* profile's credential instead.
|
|
16
|
+
*/
|
|
17
|
+
export declare function token(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
18
|
+
/**
|
|
19
|
+
* `atlas keys whoami|list|create|rotate|revoke|revoke-leaked`.
|
|
20
|
+
*
|
|
21
|
+
* create / rotate ask for the new secret SEALED to a key pair generated here
|
|
22
|
+
* (X-Secret-Recipient), open it locally and write it to a 0600 file
|
|
23
|
+
* (`--out`, default `.atlas/secret-key-<id>.env`), so the plaintext never
|
|
24
|
+
* crosses a log or terminal. `--print-secret` prints it instead.
|
|
25
|
+
*/
|
|
26
|
+
export declare function keys(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deviceLogin = deviceLogin;
|
|
4
|
+
exports.token = token;
|
|
5
|
+
exports.keys = keys;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const backend_1 = require("@atlasauth/backend");
|
|
9
|
+
const args_1 = require("../args");
|
|
10
|
+
const errors_1 = require("../errors");
|
|
11
|
+
const output_1 = require("../output");
|
|
12
|
+
const shared_1 = require("./shared");
|
|
13
|
+
/**
|
|
14
|
+
* `atlas login --device` — sign in as an OPERATOR through Atlas's own sign-in
|
|
15
|
+
* (passkey, MFA) with the OAuth device flow, instead of pasting a key. Only
|
|
16
|
+
* short-lived tokens are stored: the operator token (to swap later with
|
|
17
|
+
* `atlas token`) and, with `--resource`, a one-hour token bound to that
|
|
18
|
+
* instance or app, used as the profile credential. No long-lived key is written.
|
|
19
|
+
*/
|
|
20
|
+
async function deviceLogin(ctx, args) {
|
|
21
|
+
const { values } = args;
|
|
22
|
+
const profile = (0, args_1.str)(values, 'profile') || 'default';
|
|
23
|
+
const apiUrl = (0, args_1.str)(values, 'url') || (0, shared_1.envApiUrl)(ctx.env) || backend_1.DEFAULT_API_URL;
|
|
24
|
+
const opts = { apiUrl, fetch: ctx.fetch };
|
|
25
|
+
let start;
|
|
26
|
+
try {
|
|
27
|
+
start = await (0, backend_1.startOperatorLogin)(opts);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw new errors_1.CliError(err instanceof Error ? err.message : String(err));
|
|
31
|
+
}
|
|
32
|
+
ctx.io.out(`Open ${start.verification_uri_complete}`);
|
|
33
|
+
ctx.io.out(`and confirm the code ${start.user_code} (it expires in ${Math.round(start.expires_in / 60)} minutes).`);
|
|
34
|
+
let token;
|
|
35
|
+
try {
|
|
36
|
+
token = await (0, backend_1.pollOperatorLogin)(start, { fetch: ctx.fetch, sleep: ctx.sleep });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err instanceof backend_1.OAuthFlowError)
|
|
40
|
+
throw new errors_1.CliError(`Sign-in did not complete: ${err.message}`);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const file = ctx.config.load();
|
|
45
|
+
const entry = file.profiles[profile] ?? { api_url: apiUrl, secret_key: '' };
|
|
46
|
+
entry.api_url = apiUrl;
|
|
47
|
+
entry.operator = { access_token: token.access_token, expires_at: now + token.expires_in * 1000, issuer: start.config.issuer };
|
|
48
|
+
const resource = (0, args_1.str)(values, 'resource');
|
|
49
|
+
if (resource) {
|
|
50
|
+
const scope = (0, args_1.str)(values, 'scope') || 'read';
|
|
51
|
+
const bound = await exchangeOrExplain(token.access_token, resource, scope, opts);
|
|
52
|
+
entry.secret_key = bound.access_token;
|
|
53
|
+
entry.expires_at = now + bound.expires_in * 1000;
|
|
54
|
+
entry.resource = resource;
|
|
55
|
+
entry.scope = bound.scope;
|
|
56
|
+
}
|
|
57
|
+
file.profiles[profile] = entry;
|
|
58
|
+
file.active_profile = profile;
|
|
59
|
+
ctx.config.save(file);
|
|
60
|
+
ctx.io.out(`Signed in to ${start.config.issuer} as an operator (profile '${profile}').`);
|
|
61
|
+
if (resource)
|
|
62
|
+
ctx.io.out(`Profile credential: a one-hour token for ${resource} (${entry.scope}).`);
|
|
63
|
+
else
|
|
64
|
+
ctx.io.out("Run 'atlas token --resource <ins_… | app_…> --scope <scopes>' for a token bound to one instance or app.");
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
async function exchangeOrExplain(subject, resource, scope, opts) {
|
|
68
|
+
try {
|
|
69
|
+
return await (0, backend_1.exchangeOperatorToken)(subject, { resource, scope }, opts);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err instanceof backend_1.OAuthFlowError)
|
|
73
|
+
throw new errors_1.CliError(`Token exchange refused (${err.error}): ${err.message}`);
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `atlas token --resource <ins_…|app_…|acct_…> --scope "<scopes>"` — swap the
|
|
79
|
+
* stored operator token for a one-hour Atlas token bound to that resource.
|
|
80
|
+
* Prints the token (or JSON with --json); `--profile` stores it as that
|
|
81
|
+
* profile's credential instead.
|
|
82
|
+
*/
|
|
83
|
+
async function token(ctx, args) {
|
|
84
|
+
const { values } = args;
|
|
85
|
+
const file = ctx.config.load();
|
|
86
|
+
const profileName = (0, args_1.str)(values, 'profile') || file.active_profile || 'default';
|
|
87
|
+
const profile = file.profiles[profileName];
|
|
88
|
+
if (!profile?.operator || profile.operator.expires_at <= Date.now()) {
|
|
89
|
+
throw new errors_1.CliError("No current operator sign-in. Run 'atlas login --device' first.");
|
|
90
|
+
}
|
|
91
|
+
const resource = (0, args_1.str)(values, 'resource');
|
|
92
|
+
if (!resource)
|
|
93
|
+
throw new errors_1.CliError('--resource is required (an ins_…, app_… or acct_… id).');
|
|
94
|
+
const scope = (0, args_1.str)(values, 'scope') || 'read';
|
|
95
|
+
const bound = await exchangeOrExplain(profile.operator.access_token, resource, scope, { apiUrl: profile.api_url, fetch: ctx.fetch });
|
|
96
|
+
if ((0, shared_1.wantsJson)(values)) {
|
|
97
|
+
(0, output_1.printJson)(ctx.io, bound);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
ctx.io.out(bound.access_token);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
/** Write a secret to a 0600 file (directories 0700). */
|
|
104
|
+
function writeSecret(path, name, secret) {
|
|
105
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true, mode: 0o700 });
|
|
106
|
+
(0, node_fs_1.writeFileSync)(path, `${name}=${secret}\n`, { mode: 0o600 });
|
|
107
|
+
(0, node_fs_1.chmodSync)(path, 0o600);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* `atlas keys whoami|list|create|rotate|revoke|revoke-leaked`.
|
|
111
|
+
*
|
|
112
|
+
* create / rotate ask for the new secret SEALED to a key pair generated here
|
|
113
|
+
* (X-Secret-Recipient), open it locally and write it to a 0600 file
|
|
114
|
+
* (`--out`, default `.atlas/secret-key-<id>.env`), so the plaintext never
|
|
115
|
+
* crosses a log or terminal. `--print-secret` prints it instead.
|
|
116
|
+
*/
|
|
117
|
+
async function keys(ctx, args) {
|
|
118
|
+
const { values, positionals } = args;
|
|
119
|
+
const sub = positionals[1];
|
|
120
|
+
if (sub === 'revoke-leaked') {
|
|
121
|
+
const secret = positionals[2] ?? (await ctx.prompt.secret('Leaked secret'));
|
|
122
|
+
if (!secret)
|
|
123
|
+
throw new errors_1.CliError('Pass the leaked secret (or enter it at the prompt).');
|
|
124
|
+
const apiUrl = (0, args_1.str)(values, 'url') || (0, shared_1.envApiUrl)(ctx.env) || backend_1.DEFAULT_API_URL;
|
|
125
|
+
const res = await (0, backend_1.revokeLeakedKeys)([secret], { apiUrl, fetch: ctx.fetch });
|
|
126
|
+
ctx.io.out(`Result: ${res.results[0]?.outcome ?? 'unknown'}`);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
const auth = (0, shared_1.resolveAuth)(ctx, values);
|
|
130
|
+
const pair = (0, backend_1.generateSealKeyPair)();
|
|
131
|
+
const atlas = (0, backend_1.createAtlasClient)({ secretKey: auth.secretKey, apiUrl: auth.apiUrl, fetch: ctx.fetch, secretRecipient: pair.secretRecipient });
|
|
132
|
+
const emit = (data, line) => ((0, shared_1.wantsJson)(values) ? (0, output_1.printJson)(ctx.io, data) : line());
|
|
133
|
+
const deliver = (id, sealed, plain) => {
|
|
134
|
+
const secret = sealed ? (0, backend_1.openSealedSecret)(sealed, pair) : plain;
|
|
135
|
+
if (!secret)
|
|
136
|
+
throw new errors_1.CliError('The response carried no secret.');
|
|
137
|
+
if (values['print-secret'] === true)
|
|
138
|
+
return ctx.io.out(secret);
|
|
139
|
+
const path = (0, args_1.str)(values, 'out') || `.atlas/secret-key-${id}.env`;
|
|
140
|
+
writeSecret(path, 'ATLAS_SECRET_KEY', secret);
|
|
141
|
+
ctx.io.out(`Secret written to ${path} (mode 0600).`);
|
|
142
|
+
};
|
|
143
|
+
switch (sub) {
|
|
144
|
+
case 'whoami': {
|
|
145
|
+
const me = await atlas.whoami();
|
|
146
|
+
emit(me, () => {
|
|
147
|
+
ctx.io.out(`Key: ${me.key.id} (${me.key.type}, ${me.key.prefix ?? ''}…)`);
|
|
148
|
+
ctx.io.out(`Scopes: ${me.key.scopes.join(' ')}`);
|
|
149
|
+
ctx.io.out(`Instance: ${me.instance?.id ?? '-'} Account: ${me.account.id ?? '-'}`);
|
|
150
|
+
if (me.key.expires_at)
|
|
151
|
+
ctx.io.out(`Expires: ${new Date(me.key.expires_at).toISOString()}`);
|
|
152
|
+
});
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
case 'list': {
|
|
156
|
+
const page = await atlas.keys.list();
|
|
157
|
+
emit(page, () => {
|
|
158
|
+
for (const k of page.data) {
|
|
159
|
+
ctx.io.out(`${k.id} ${k.kind.padEnd(14)} ${k.prefix.padEnd(20)} ${k.status.padEnd(9)} owner=${k.owner ?? '-'} last_used=${k.last_used_at ? new Date(k.last_used_at).toISOString() : 'never'}`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
case 'create': {
|
|
165
|
+
const scopes = ((0, args_1.str)(values, 'scopes') || '').split(/[ ,]+/).filter(Boolean);
|
|
166
|
+
if (scopes.length === 0)
|
|
167
|
+
throw new errors_1.CliError('--scopes is required, e.g. --scopes "instance:read webhooks:read" or --scopes read_only.');
|
|
168
|
+
const created = await atlas.secretKeys.create({
|
|
169
|
+
scopes,
|
|
170
|
+
name: (0, args_1.str)(values, 'name') || undefined,
|
|
171
|
+
owner: (0, args_1.str)(values, 'owner') || undefined,
|
|
172
|
+
expires_at: (0, args_1.str)(values, 'expires-at') || undefined,
|
|
173
|
+
allowed_ips: (0, args_1.str)(values, 'allowed-ips') ? ((0, args_1.str)(values, 'allowed-ips') ?? '').split(',').map((s) => s.trim()) : undefined,
|
|
174
|
+
});
|
|
175
|
+
ctx.io.out(`Created ${created.id} (${created.prefix}…) with ${created.scopes.join(' ')}.`);
|
|
176
|
+
deliver(created.id, created.sealed_secret, created.secret);
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
case 'rotate': {
|
|
180
|
+
const id = (0, shared_1.requireArg)(positionals, 2, 'key id');
|
|
181
|
+
const overlap = (0, args_1.str)(values, 'overlap');
|
|
182
|
+
const rotated = await atlas.secretKeys.rotate(id, overlap ? { overlap_seconds: Number(overlap) } : {});
|
|
183
|
+
ctx.io.out(`Rotated ${id} → ${rotated.id}.` +
|
|
184
|
+
(rotated.previous_key_expires_at ? ` The old key works until ${new Date(rotated.previous_key_expires_at).toISOString()}.` : ' The old key was revoked.'));
|
|
185
|
+
deliver(rotated.id, rotated.sealed_secret, rotated.secret);
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
case 'revoke': {
|
|
189
|
+
const id = (0, shared_1.requireArg)(positionals, 2, 'key id');
|
|
190
|
+
await atlas.secretKeys.revoke(id);
|
|
191
|
+
ctx.io.out(`Revoked ${id}.`);
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
default:
|
|
195
|
+
throw new errors_1.CliError('Usage: atlas keys whoami|list|create|rotate <id>|revoke <id>|revoke-leaked [secret]');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=operator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator.js","sourceRoot":"","sources":["../../src/commands/operator.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +20,19 @@ export declare function instanceCredential(ctx: RunContext, args: ParsedArgs): {
|
|
|
20
20
|
};
|
|
21
21
|
/** `atlas preflight` — run the instance's setup checks; exit 1 on errors (or warnings with --fail-on warning). */
|
|
22
22
|
export declare function preflight(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
23
|
+
/** `atlas doctor` — the integration-health report; exit 1 when a check fails. */
|
|
24
|
+
export declare function doctor(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
23
25
|
/** Public values, then each secret as a reference to its file — never the value. */
|
|
24
26
|
export declare function envCommand(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Kubernetes output: a ConfigMap with the public values, `kubectl create secret`
|
|
29
|
+
* commands that read each secret straight from its 0600 file (so a value never
|
|
30
|
+
* passes through this output), and the container `envFrom` / `secretKeyRef`
|
|
31
|
+
* snippet that wires both in.
|
|
32
|
+
*/
|
|
33
|
+
export declare function k8sManifest(product: string, environment: string, pub: Record<string, string>, secrets: Array<{
|
|
34
|
+
key: string;
|
|
35
|
+
file: string;
|
|
36
|
+
}>): string;
|
|
25
37
|
/** `atlas client rotate <id> [--now | --overlap <seconds>]` — the new secret goes straight to its file. */
|
|
26
38
|
export declare function client(ctx: RunContext, args: ParsedArgs): Promise<number>;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.instanceCredential = instanceCredential;
|
|
4
4
|
exports.preflight = preflight;
|
|
5
|
+
exports.doctor = doctor;
|
|
5
6
|
exports.envCommand = envCommand;
|
|
7
|
+
exports.k8sManifest = k8sManifest;
|
|
6
8
|
exports.client = client;
|
|
7
9
|
const backend_1 = require("@atlasauth/backend");
|
|
8
10
|
const args_1 = require("../args");
|
|
@@ -33,7 +35,7 @@ function instanceCredential(ctx, args) {
|
|
|
33
35
|
}
|
|
34
36
|
return {
|
|
35
37
|
secretKey: (0, project_1.instanceSecretFromFile)(found.instance),
|
|
36
|
-
apiUrl: ctx.env
|
|
38
|
+
apiUrl: (0, shared_1.envApiUrl)(ctx.env) || found.manifest.api_url,
|
|
37
39
|
manifest: found.manifest,
|
|
38
40
|
instance: found.instance,
|
|
39
41
|
};
|
|
@@ -42,7 +44,7 @@ function instanceCredential(ctx, args) {
|
|
|
42
44
|
const { manifest, instance } = projectInstance(ctx, args, product);
|
|
43
45
|
return {
|
|
44
46
|
secretKey: (0, project_1.instanceSecretFromFile)(instance),
|
|
45
|
-
apiUrl: ctx.env
|
|
47
|
+
apiUrl: (0, shared_1.envApiUrl)(ctx.env) || manifest.api_url,
|
|
46
48
|
manifest,
|
|
47
49
|
instance,
|
|
48
50
|
};
|
|
@@ -108,43 +110,104 @@ async function preflight(ctx, args) {
|
|
|
108
110
|
}
|
|
109
111
|
return 0;
|
|
110
112
|
}
|
|
113
|
+
const HEALTH_COLUMNS = [
|
|
114
|
+
{ header: 'STATE', get: (c) => (c.ok === true ? 'ok' : c.ok === false ? 'FAIL' : '-') },
|
|
115
|
+
{ header: 'CHECK', get: (c) => c.id },
|
|
116
|
+
{ header: 'HOP', get: (c) => c.hop ?? '' },
|
|
117
|
+
{ header: 'DETAIL', get: (c) => c.detail },
|
|
118
|
+
];
|
|
119
|
+
/** `atlas doctor` — the integration-health report; exit 1 when a check fails. */
|
|
120
|
+
async function doctor(ctx, args) {
|
|
121
|
+
const cred = instanceCredential(ctx, args);
|
|
122
|
+
const q = new URLSearchParams();
|
|
123
|
+
const only = (0, args_1.str)(args.values, 'only');
|
|
124
|
+
if (only)
|
|
125
|
+
q.set('only', only);
|
|
126
|
+
if (args.values['no-probe'] === true)
|
|
127
|
+
q.set('probe', 'false');
|
|
128
|
+
const qs = q.toString();
|
|
129
|
+
const report = await (0, project_1.httpFor)(ctx, cred).get(`/v1/integration-health${qs ? `?${qs}` : ''}`);
|
|
130
|
+
if ((0, shared_1.wantsJson)(args.values)) {
|
|
131
|
+
(0, output_1.printJson)(ctx.io, report);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const rows = [...report.checks].sort((a, b) => (a.ok === false ? 0 : a.ok === null ? 2 : 1) - (b.ok === false ? 0 : b.ok === null ? 2 : 1));
|
|
135
|
+
ctx.io.out((0, output_1.renderTable)(HEALTH_COLUMNS, rows));
|
|
136
|
+
const failing = rows.filter((c) => c.ok === false && c.fix);
|
|
137
|
+
if (failing.length) {
|
|
138
|
+
ctx.io.out('');
|
|
139
|
+
for (const c of failing)
|
|
140
|
+
ctx.io.out(`${c.id}: ${c.fix}`);
|
|
141
|
+
}
|
|
142
|
+
ctx.io.out('');
|
|
143
|
+
ctx.io.out(`${report.summary.failing} failing, ${report.summary.passing} passing, ${report.summary.unknown} not known or not configured.`);
|
|
144
|
+
}
|
|
145
|
+
if (!report.ok) {
|
|
146
|
+
ctx.io.err(`atlas: ${report.summary.failing} integration check(s) failing.`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
111
151
|
// ---- atlas env ----------------------------------------------------------------
|
|
112
|
-
const FORMATS = ['dotenv', 'coolify', 'vercel'];
|
|
152
|
+
const FORMATS = ['dotenv', 'coolify', 'vercel', 'k8s'];
|
|
113
153
|
/** Public values, then each secret as a reference to its file — never the value. */
|
|
114
154
|
async function envCommand(ctx, args) {
|
|
115
155
|
const format = ((0, args_1.str)(args.values, 'format') ?? 'dotenv');
|
|
116
156
|
if (!FORMATS.includes(format))
|
|
117
157
|
throw new errors_1.CliError(`--format must be one of: ${FORMATS.join(', ')} (got '${format}').`);
|
|
118
158
|
const product = (0, args_1.str)(args.values, 'product');
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
pub.
|
|
128
|
-
|
|
129
|
-
pub.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
secrets.push({ key: '
|
|
159
|
+
const pub = {};
|
|
160
|
+
const secrets = [];
|
|
161
|
+
let environment;
|
|
162
|
+
let label;
|
|
163
|
+
if (product) {
|
|
164
|
+
const { manifest, instance } = projectInstance(ctx, args, product);
|
|
165
|
+
environment = instance.environment;
|
|
166
|
+
label = product;
|
|
167
|
+
pub.ATLAS_PUBLISHABLE_KEY = instance.publishable_key;
|
|
168
|
+
pub.ATLAS_API_BASE = manifest.api_url;
|
|
169
|
+
pub.ATLAS_API_URL = manifest.api_url;
|
|
170
|
+
if (instance.frontend_api_host)
|
|
171
|
+
pub.ATLAS_FRONTEND_API = `https://${instance.frontend_api_host}`;
|
|
172
|
+
if (instance.issuer)
|
|
173
|
+
pub.ATLAS_ISSUER = instance.issuer;
|
|
174
|
+
if (instance.discovery_url)
|
|
175
|
+
pub.ATLAS_DISCOVERY_URL = instance.discovery_url;
|
|
176
|
+
if (instance.jwks_uri)
|
|
177
|
+
pub.ATLAS_JWKS_URI = instance.jwks_uri;
|
|
178
|
+
if (instance.oauth_client)
|
|
179
|
+
pub.ATLAS_CLIENT_ID = instance.oauth_client.client_id;
|
|
180
|
+
secrets.push({ key: 'ATLAS_SECRET_KEY', file: instance.secret_file });
|
|
181
|
+
if (instance.oauth_client?.secret_file)
|
|
182
|
+
secrets.push({ key: 'ATLAS_CLIENT_SECRET', file: instance.oauth_client.secret_file });
|
|
183
|
+
if (instance.webhook?.secret_file)
|
|
184
|
+
secrets.push({ key: 'ATLAS_WEBHOOK_SECRET', file: instance.webhook.secret_file });
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// No project manifest: read the public values straight from the instance the credential belongs to.
|
|
188
|
+
const cred = instanceCredential(ctx, args);
|
|
189
|
+
const live = await (0, project_1.httpFor)(ctx, cred).get('/v1/instance');
|
|
190
|
+
environment = live.environment;
|
|
191
|
+
label = live.id;
|
|
192
|
+
pub.ATLAS_PUBLISHABLE_KEY = live.publishable_key;
|
|
193
|
+
pub.ATLAS_API_BASE = cred.apiUrl;
|
|
194
|
+
pub.ATLAS_API_URL = cred.apiUrl;
|
|
195
|
+
if (live.frontend_api_host)
|
|
196
|
+
pub.ATLAS_FRONTEND_API = `https://${live.frontend_api_host}`;
|
|
197
|
+
if (live.issuer)
|
|
198
|
+
pub.ATLAS_ISSUER = live.issuer;
|
|
199
|
+
if (live.discovery_url)
|
|
200
|
+
pub.ATLAS_DISCOVERY_URL = live.discovery_url;
|
|
201
|
+
if (live.jwks_uri)
|
|
202
|
+
pub.ATLAS_JWKS_URI = live.jwks_uri;
|
|
203
|
+
}
|
|
141
204
|
if ((0, shared_1.wantsJson)(args.values)) {
|
|
142
|
-
(0, output_1.printJson)(ctx.io, { product, environment
|
|
205
|
+
(0, output_1.printJson)(ctx.io, { product: product ?? null, environment, public: pub, secret_files: Object.fromEntries(secrets.map((s) => [s.key, s.file])) });
|
|
143
206
|
return 0;
|
|
144
207
|
}
|
|
145
208
|
const out = (l) => ctx.io.out(l);
|
|
146
209
|
if (format === 'dotenv') {
|
|
147
|
-
out(`# ${
|
|
210
|
+
out(`# ${label} (${environment}): public values`);
|
|
148
211
|
for (const [k, v] of Object.entries(pub))
|
|
149
212
|
out(`${k}=${v}`);
|
|
150
213
|
out('# Secrets are not printed. Each *_FILE variable names the 0600 file that holds it.');
|
|
@@ -152,16 +215,19 @@ async function envCommand(ctx, args) {
|
|
|
152
215
|
out(`${s.key}_FILE=${s.file}`);
|
|
153
216
|
}
|
|
154
217
|
else if (format === 'coolify') {
|
|
155
|
-
out(`# ${
|
|
218
|
+
out(`# ${label} (${environment}): paste into Coolify → Environment Variables (Developer view).`);
|
|
156
219
|
for (const [k, v] of Object.entries(pub))
|
|
157
220
|
out(`${k}=${v}`);
|
|
158
221
|
out('# Secrets: copy each value from its file (mark them as secret in Coolify):');
|
|
159
222
|
for (const s of secrets)
|
|
160
223
|
out(`${s.key}=@${s.file}`);
|
|
161
224
|
}
|
|
225
|
+
else if (format === 'k8s') {
|
|
226
|
+
out(k8sManifest(label, environment, pub, secrets));
|
|
227
|
+
}
|
|
162
228
|
else {
|
|
163
|
-
out(`# ${
|
|
164
|
-
const target =
|
|
229
|
+
out(`# ${label} (${environment}): Vercel CLI commands. Secrets are piped from their files, never echoed.`);
|
|
230
|
+
const target = environment === 'production' ? 'production' : 'preview';
|
|
165
231
|
for (const [k, v] of Object.entries(pub))
|
|
166
232
|
out(`printf '%s' '${v.replace(/'/g, "'\\''")}' | vercel env add ${k} ${target}`);
|
|
167
233
|
for (const s of secrets)
|
|
@@ -169,6 +235,40 @@ async function envCommand(ctx, args) {
|
|
|
169
235
|
}
|
|
170
236
|
return 0;
|
|
171
237
|
}
|
|
238
|
+
/** DNS-1123 name fragment for a Kubernetes resource. */
|
|
239
|
+
function k8sName(raw) {
|
|
240
|
+
return raw.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'atlas';
|
|
241
|
+
}
|
|
242
|
+
/** YAML double-quoted scalar (JSON strings are valid YAML). */
|
|
243
|
+
const yamlStr = (v) => JSON.stringify(v);
|
|
244
|
+
/**
|
|
245
|
+
* Kubernetes output: a ConfigMap with the public values, `kubectl create secret`
|
|
246
|
+
* commands that read each secret straight from its 0600 file (so a value never
|
|
247
|
+
* passes through this output), and the container `envFrom` / `secretKeyRef`
|
|
248
|
+
* snippet that wires both in.
|
|
249
|
+
*/
|
|
250
|
+
function k8sManifest(product, environment, pub, secrets) {
|
|
251
|
+
const base = `${k8sName(product)}-atlas`;
|
|
252
|
+
const lines = [
|
|
253
|
+
`# ${product} (${environment}): Kubernetes. Public values go in a ConfigMap; secrets are created from their 0600 files.`,
|
|
254
|
+
'apiVersion: v1',
|
|
255
|
+
'kind: ConfigMap',
|
|
256
|
+
'metadata:',
|
|
257
|
+
` name: ${base}`,
|
|
258
|
+
'data:',
|
|
259
|
+
...Object.entries(pub).map(([k, v]) => ` ${k}: ${yamlStr(v)}`),
|
|
260
|
+
'---',
|
|
261
|
+
'# Create each secret from its file (nothing is printed or stored in this manifest):',
|
|
262
|
+
];
|
|
263
|
+
for (const s of secrets) {
|
|
264
|
+
lines.push(`# kubectl create secret generic ${base}-${k8sName(s.key)} --from-env-file='${s.file.replace(/'/g, "'\\''")}'`);
|
|
265
|
+
}
|
|
266
|
+
lines.push('---', '# Container spec fragment:', '# envFrom:', `# - configMapRef: { name: ${base} }`, '# env:');
|
|
267
|
+
for (const s of secrets) {
|
|
268
|
+
lines.push(`# - name: ${s.key}`, `# valueFrom: { secretKeyRef: { name: ${base}-${k8sName(s.key)}, key: ${s.key} } }`);
|
|
269
|
+
}
|
|
270
|
+
return lines.join('\n');
|
|
271
|
+
}
|
|
172
272
|
// ---- atlas client rotate ----------------------------------------------------------
|
|
173
273
|
/** `atlas client rotate <id> [--now | --overlap <seconds>]` — the new secret goes straight to its file. */
|
|
174
274
|
async function client(ctx, args) {
|