@nebulr-group/bridge-cli 0.4.4 → 0.5.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/README.md +56 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/auth/logout.command.d.ts +10 -3
- package/dist/commands/auth/logout.command.d.ts.map +1 -1
- package/dist/commands/auth/logout.command.js +58 -18
- package/dist/commands/auth/logout.command.js.map +1 -1
- package/dist/commands/auth/status.command.d.ts +4 -0
- package/dist/commands/auth/status.command.d.ts.map +1 -1
- package/dist/commands/auth/status.command.js +50 -19
- package/dist/commands/auth/status.command.js.map +1 -1
- package/dist/commands/auth/use.command.d.ts +17 -0
- package/dist/commands/auth/use.command.d.ts.map +1 -0
- package/dist/commands/auth/use.command.js +47 -0
- package/dist/commands/auth/use.command.js.map +1 -0
- package/dist/commands/auth.command.d.ts +1 -0
- package/dist/commands/auth.command.d.ts.map +1 -1
- package/dist/commands/auth.command.js +3 -0
- package/dist/commands/auth.command.js.map +1 -1
- package/dist/config.d.ts +20 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +120 -32
- package/dist/config.js.map +1 -1
- package/dist/credentials.d.ts +68 -5
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +202 -11
- package/dist/credentials.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,10 +27,48 @@ bridge auth login
|
|
|
27
27
|
Opens your default browser, runs through a PKCE-secured loopback flow (RFC 8252), and stores a 10-day token at `~/.config/bridge/credentials.json` (mode `0600`). After that, every subsequent `bridge` command picks the token up automatically — no env vars needed.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
bridge auth status # show
|
|
31
|
-
bridge auth logout # revoke the token and
|
|
30
|
+
bridge auth status # show every stored app, and which one is active
|
|
31
|
+
bridge auth logout # revoke the active token and remove it locally
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
#### Several apps at once
|
|
35
|
+
|
|
36
|
+
The credentials file holds one credential per app, so you can log in to your
|
|
37
|
+
local, stage and prod apps and move between them without a browser round-trip —
|
|
38
|
+
the tokens are already on disk with days left on them.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bridge auth login --label northwhistle-local # once per app
|
|
42
|
+
bridge auth login --label northwhistle-prod
|
|
43
|
+
|
|
44
|
+
bridge auth status # lists both, marks the active one
|
|
45
|
+
bridge auth use northwhistle-local # switch the default, offline
|
|
46
|
+
bridge --profile northwhistle-prod app get # target ONE command, default untouched
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A profile is selected by its `--label`, its app id, or its app name, in that
|
|
50
|
+
order. An ambiguous name is an error rather than a guess. `BRIDGE_PROFILE` does
|
|
51
|
+
the same thing as `--profile` for a whole shell session.
|
|
52
|
+
|
|
53
|
+
Prefer `--profile` over `bridge auth use` in scripts and long-running agent
|
|
54
|
+
sessions: `use` mutates state another process may be reading, `--profile` does
|
|
55
|
+
not.
|
|
56
|
+
|
|
57
|
+
Every command prints one line to **stderr** naming the app that answered:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
bridge: northwhistle-prod (606b4416fabdc800087d09ec) · https://api.thebridge.dev · via saved default
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
stdout stays pure JSON, so this does not disturb anything parsing it. Silence it
|
|
64
|
+
with `BRIDGE_NO_BANNER=true` if you are already certain which app you are on.
|
|
65
|
+
|
|
66
|
+
`bridge auth logout` removes the active app only. Use `--profile <name>` for a
|
|
67
|
+
specific one, or `--all` to clear the file.
|
|
68
|
+
|
|
69
|
+
Existing single-app credentials files keep working and are upgraded in place the
|
|
70
|
+
next time something writes.
|
|
71
|
+
|
|
34
72
|
#### `bridge auth login` flags
|
|
35
73
|
|
|
36
74
|
| Flag | Description |
|
|
@@ -56,7 +94,17 @@ For non-interactive contexts (CI/CD pipelines, Docker images, headless agents) s
|
|
|
56
94
|
export BRIDGE_API_KEY=<your-api-token>
|
|
57
95
|
```
|
|
58
96
|
|
|
59
|
-
If you have logged in via `bridge auth login`, the credentials file always wins — a fresh login takes effect immediately, even when `BRIDGE_API_KEY` is still exported in your shell. `BRIDGE_API_KEY` is only used when no credentials file is present (the typical CI runner shape). To switch back to env-var auth on a developer machine, run `bridge auth logout` first.
|
|
97
|
+
If you have logged in via `bridge auth login`, the credentials file always wins — a fresh login takes effect immediately, even when `BRIDGE_API_KEY` is still exported in your shell. `BRIDGE_API_KEY` is only used when no credentials file is present (the typical CI runner shape). To switch back to env-var auth on a developer machine, run `bridge auth logout --all` first.
|
|
98
|
+
|
|
99
|
+
Because that trips people up, the CLI now says so at the moment it happens: with
|
|
100
|
+
both present you get a line on stderr telling you `BRIDGE_API_KEY` is set and
|
|
101
|
+
being ignored, and what to do instead. To point the CLI at a different app, use
|
|
102
|
+
`--profile`, not the env vars.
|
|
103
|
+
|
|
104
|
+
**`BRIDGE_APP_ID` has no effect on any path.** The app is carried by the
|
|
105
|
+
credential itself — a login token and an API key are both already scoped to one
|
|
106
|
+
app, so there is nothing for it to change. It is called out on stderr whenever it
|
|
107
|
+
is set, rather than being quietly ignored.
|
|
60
108
|
|
|
61
109
|
Optional configuration (applies to both auth paths):
|
|
62
110
|
|
|
@@ -139,8 +187,11 @@ bridge auth login # default: open browser, complete PKCE flow
|
|
|
139
187
|
bridge auth login --app acme # pin to a specific app
|
|
140
188
|
bridge auth login --label "work laptop"
|
|
141
189
|
bridge auth login --no-browser # print the URL (headless / SSH)
|
|
142
|
-
bridge auth status # show
|
|
143
|
-
bridge auth
|
|
190
|
+
bridge auth status # show every stored app, active one marked
|
|
191
|
+
bridge auth use <label|app-id> # switch the default app, no browser
|
|
192
|
+
bridge auth logout # revoke + remove the active app
|
|
193
|
+
bridge auth logout --all # ...or every stored app
|
|
194
|
+
bridge --profile <label> <cmd> # target one app for one command
|
|
144
195
|
```
|
|
145
196
|
|
|
146
197
|
### Auth Configuration (app-level — separate from `auth login`)
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyBpC,eAAO,MAAM,OAAO,SAAgB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerInfoCommands } from './commands/info.command.js';
|
|
|
18
18
|
import { registerGuideCommands } from './commands/guide.command.js';
|
|
19
19
|
import { registerOpsCommands } from './commands/ops.command.js';
|
|
20
20
|
import { registerStripeCommands } from './commands/stripe.command.js';
|
|
21
|
+
import { setProfileOverride } from './config.js';
|
|
21
22
|
// Read version from package.json so `bridge --version` never drifts from the
|
|
22
23
|
// published package. dist/cli.js lives at <pkg>/dist/cli.js, so `../package.json`
|
|
23
24
|
// resolves to the package root in both the published tarball and during dev.
|
|
@@ -27,7 +28,18 @@ export const program = new Command();
|
|
|
27
28
|
program
|
|
28
29
|
.name('bridge')
|
|
29
30
|
.description('Bridge platform CLI — optimized for AI coding agents')
|
|
30
|
-
.version(version)
|
|
31
|
+
.version(version)
|
|
32
|
+
// TBP-628 — target another stored app for ONE command, without touching the
|
|
33
|
+
// persisted default another process may be reading. Named `--profile` rather
|
|
34
|
+
// than `--app` because `bridge auth login --app` already exists and means
|
|
35
|
+
// something different (which app to authorise), and because `--profile` is
|
|
36
|
+
// what aws/npm users already reach for.
|
|
37
|
+
.option('--profile <label|app-id|app-name>', 'Use a specific stored credential for this command (env: BRIDGE_PROFILE)');
|
|
38
|
+
// Runs before any subcommand action, so `getManagementClient()` sees the
|
|
39
|
+
// override wherever in the tree it is eventually called from.
|
|
40
|
+
program.hook('preAction', (thisCommand) => {
|
|
41
|
+
setProfileOverride(thisCommand.opts().profile);
|
|
42
|
+
});
|
|
31
43
|
registerAppCommands(program);
|
|
32
44
|
registerTenantCommands(program);
|
|
33
45
|
registerUserCommands(program);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Behavior:
|
|
5
5
|
* - No credentials file → "Not logged in." (exit 0).
|
|
6
|
-
* -
|
|
6
|
+
* - Otherwise → call `POST /v1/auth/cli/revoke` with `x-api-key`,
|
|
7
7
|
* treat 401 as success (token already gone), then
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* remove it locally. Network errors → warn but still
|
|
9
|
+
* remove (the local file is the source of truth for
|
|
10
10
|
* "logged in" on this machine).
|
|
11
|
+
*
|
|
12
|
+
* TBP-628 — the file can hold several apps, so logout now has a scope.
|
|
13
|
+
* Bare `logout` removes the ACTIVE credential and leaves the others; the
|
|
14
|
+
* alternative, wiping every app because you finished with one of them, throws
|
|
15
|
+
* away valid tokens and forces browser round-trips to get them back, which is
|
|
16
|
+
* the friction this ticket exists to remove. `--profile` names one, `--all`
|
|
17
|
+
* asks for the old whole-file behaviour explicitly.
|
|
11
18
|
*/
|
|
12
19
|
import type { Command } from 'commander';
|
|
13
20
|
export declare function registerAuthLogoutCommand(auth: Command): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logout.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/logout.command.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"logout.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/logout.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAgB7D"}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { credentialsPath,
|
|
1
|
+
import { credentialsPath, CredentialSelectorError, deleteAllCredentials, findCredential, listCredentials, readCredentials, removeCredential, } from '../../credentials.js';
|
|
2
2
|
import { resetManagementClient } from '../../config.js';
|
|
3
3
|
import { createCliApiClient, CliAuthApiError } from '../../auth/api-client.js';
|
|
4
4
|
export function registerAuthLogoutCommand(auth) {
|
|
5
5
|
auth
|
|
6
6
|
.command('logout')
|
|
7
|
-
.description('Revoke the
|
|
8
|
-
.
|
|
7
|
+
.description('Revoke the active CLI token and remove it locally')
|
|
8
|
+
.option('--profile <label|app-id|app-name>', 'Log out of one specific stored app')
|
|
9
|
+
.option('--all', 'Log out of every stored app and delete the credentials file')
|
|
10
|
+
.action(async (opts) => {
|
|
9
11
|
try {
|
|
10
|
-
await runLogout();
|
|
12
|
+
await runLogout(opts);
|
|
11
13
|
process.exitCode = 0;
|
|
12
14
|
}
|
|
13
15
|
catch (err) {
|
|
@@ -17,41 +19,79 @@ export function registerAuthLogoutCommand(auth) {
|
|
|
17
19
|
}
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
|
-
async function runLogout() {
|
|
22
|
+
async function runLogout(opts = {}) {
|
|
21
23
|
const stdout = process.stdout;
|
|
22
24
|
const stderr = process.stderr;
|
|
23
|
-
let
|
|
25
|
+
let entries;
|
|
24
26
|
try {
|
|
25
|
-
|
|
27
|
+
entries = listCredentials();
|
|
26
28
|
}
|
|
27
29
|
catch (err) {
|
|
28
|
-
// Malformed credentials file — still
|
|
30
|
+
// Malformed credentials file — still delete it so the user gets unstuck.
|
|
29
31
|
stderr.write(`Warning: existing credentials file at ${credentialsPath()} could not be parsed (${err instanceof Error ? err.message : String(err)}). Removing it.\n`);
|
|
30
|
-
|
|
32
|
+
deleteAllCredentials();
|
|
31
33
|
stdout.write('Logged out (credentials file removed).\n');
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
|
-
if (
|
|
36
|
+
if (entries.length === 0) {
|
|
35
37
|
stdout.write('Not logged in.\n');
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
40
|
+
if (opts.all) {
|
|
41
|
+
for (const entry of entries)
|
|
42
|
+
await revoke(entry.creds, stderr);
|
|
43
|
+
deleteAllCredentials();
|
|
44
|
+
resetManagementClient();
|
|
45
|
+
stdout.write(`Logged out of ${entries.length} app(s).\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let target;
|
|
49
|
+
if (opts.profile) {
|
|
50
|
+
try {
|
|
51
|
+
target = findCredential(opts.profile);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err instanceof CredentialSelectorError)
|
|
55
|
+
throw new Error(err.message);
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const active = readCredentials();
|
|
61
|
+
if (!active) {
|
|
62
|
+
// Several stored, none active. Removing one by guess is exactly the
|
|
63
|
+
// silent choice this ticket forbids.
|
|
64
|
+
throw new Error('No active credential. Name one with `--profile <label|app id>`, or use `--all`. ' +
|
|
65
|
+
`Stored: ${entries.map((e) => e.creds.label ?? e.creds.app.name).join(', ')}.`);
|
|
66
|
+
}
|
|
67
|
+
target = entries.find((e) => e.key === active.app.id);
|
|
68
|
+
}
|
|
69
|
+
await revoke(target.creds, stderr);
|
|
70
|
+
const remaining = removeCredential(target.key);
|
|
71
|
+
resetManagementClient();
|
|
72
|
+
const name = target.creds.label ?? target.creds.app.name;
|
|
73
|
+
stdout.write(`Logged out ${target.creds.user.email} from ${name}.\n`);
|
|
74
|
+
if (remaining > 0) {
|
|
75
|
+
const rest = listCredentials();
|
|
76
|
+
const nowActive = rest.find((e) => e.isActive);
|
|
77
|
+
stdout.write(` ${remaining} credential(s) still stored` +
|
|
78
|
+
(nowActive ? `; now using ${nowActive.creds.label ?? nowActive.creds.app.name}` : '') +
|
|
79
|
+
'.\n');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Best-effort server-side revoke. Never fatal — local removal is the point. */
|
|
83
|
+
async function revoke(creds, stderr) {
|
|
38
84
|
const api = createCliApiClient({ baseUrl: creds.baseUrl });
|
|
39
85
|
try {
|
|
40
86
|
await api.revokeToken(creds.apiKey);
|
|
41
87
|
}
|
|
42
88
|
catch (err) {
|
|
43
|
-
// Network failure — warn but still proceed with local deletion.
|
|
44
89
|
if (err instanceof CliAuthApiError && err.code === 'network_error') {
|
|
45
|
-
stderr.write(`Warning: could not reach bridge-api to revoke server-side (${err.message}). Removing local credentials anyway.\n`);
|
|
90
|
+
stderr.write(`Warning: could not reach bridge-api to revoke ${creds.app.name} server-side (${err.message}). Removing local credentials anyway.\n`);
|
|
46
91
|
}
|
|
47
92
|
else {
|
|
48
|
-
|
|
49
|
-
// surface it so the user knows the server-side revoke didn't happen.
|
|
50
|
-
stderr.write(`Warning: server-side revoke returned an error (${err instanceof Error ? err.message : String(err)}). Removing local credentials anyway.\n`);
|
|
93
|
+
stderr.write(`Warning: server-side revoke for ${creds.app.name} returned an error (${err instanceof Error ? err.message : String(err)}). Removing local credentials anyway.\n`);
|
|
51
94
|
}
|
|
52
95
|
}
|
|
53
|
-
deleteCredentials();
|
|
54
|
-
resetManagementClient();
|
|
55
|
-
stdout.write(`Logged out ${creds.user.email}.\n`);
|
|
56
96
|
}
|
|
57
97
|
//# sourceMappingURL=logout.command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logout.command.js","sourceRoot":"","sources":["../../../src/commands/auth/logout.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logout.command.js","sourceRoot":"","sources":["../../../src/commands/auth/logout.command.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* optimize for readability over machine-parseability.
|
|
9
9
|
*
|
|
10
10
|
* If the user wants JSON, they can run `cat ~/.config/bridge/credentials.json`.
|
|
11
|
+
*
|
|
12
|
+
* TBP-628 — the file can hold several apps, so this lists ALL of them and marks
|
|
13
|
+
* the active one. Knowing which app is in force is the whole reason somebody
|
|
14
|
+
* runs this command, and it was already the only place that told them.
|
|
11
15
|
*/
|
|
12
16
|
import type { Command } from 'commander';
|
|
13
17
|
export declare function registerAuthStatusCommand(auth: Command): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/status.command.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"status.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/status.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAc7D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { credentialsPath, isExpired,
|
|
1
|
+
import { credentialsPath, isExpired, listCredentials, readCredentialsFile, } from '../../credentials.js';
|
|
2
2
|
import { formatRelativeTime } from '../../auth/relative-time.js';
|
|
3
3
|
export function registerAuthStatusCommand(auth) {
|
|
4
4
|
auth
|
|
@@ -18,18 +18,20 @@ export function registerAuthStatusCommand(auth) {
|
|
|
18
18
|
}
|
|
19
19
|
function runStatus() {
|
|
20
20
|
const stdout = process.stdout;
|
|
21
|
-
//
|
|
22
|
-
//
|
|
21
|
+
// Report on `BRIDGE_API_KEY` separately if set, so a CI user setting the env
|
|
22
|
+
// var sees something useful.
|
|
23
23
|
const envApiKey = process.env.BRIDGE_API_KEY?.trim();
|
|
24
|
-
|
|
24
|
+
const envProfile = process.env.BRIDGE_PROFILE?.trim();
|
|
25
|
+
let entries;
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
+
readCredentialsFile();
|
|
28
|
+
entries = listCredentials();
|
|
27
29
|
}
|
|
28
30
|
catch (err) {
|
|
29
31
|
stdout.write(`Credentials file at ${credentialsPath()} is unreadable: ${err instanceof Error ? err.message : String(err)}\n` + 'Run `bridge auth login` to refresh, or delete the file manually.\n');
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
|
-
if (
|
|
34
|
+
if (entries.length === 0) {
|
|
33
35
|
if (envApiKey && envApiKey.length > 0) {
|
|
34
36
|
stdout.write('Using BRIDGE_API_KEY from environment (service-account / CI path).\n' +
|
|
35
37
|
'Run `bridge auth login` to switch to interactive credentials.\n');
|
|
@@ -38,22 +40,51 @@ function runStatus() {
|
|
|
38
40
|
stdout.write('Not logged in. Run `bridge auth login`.\n');
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
const active = entries.find((e) => e.isActive);
|
|
44
|
+
if (active) {
|
|
45
|
+
stdout.write(`Logged in as ${active.creds.user.email}\n`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// A pointer at a credential that is gone. Say so rather than picking one:
|
|
49
|
+
// an inferred default is exactly what this ticket is about.
|
|
50
|
+
stdout.write('No active credential selected — run `bridge auth use <profile>` to pick one.\n');
|
|
51
|
+
}
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
writeEntry(entry, entries.length > 1);
|
|
54
|
+
}
|
|
51
55
|
stdout.write(` credentials=${credentialsPath()}\n`);
|
|
56
|
+
if (entries.length > 1) {
|
|
57
|
+
stdout.write('\nSwitch the default with `bridge auth use <label|app id>`, ' +
|
|
58
|
+
'or target one command with `--profile <label|app id>`.\n');
|
|
59
|
+
}
|
|
60
|
+
if (envProfile) {
|
|
61
|
+
stdout.write(`\nBRIDGE_PROFILE=${envProfile} is set — it overrides the active credential above.\n`);
|
|
62
|
+
}
|
|
52
63
|
if (envApiKey && envApiKey.length > 0) {
|
|
53
|
-
|
|
64
|
+
// This used to claim the opposite. The credentials file has always won;
|
|
65
|
+
// saying otherwise is how somebody comes to believe they retargeted the
|
|
66
|
+
// CLI when they did not (TBP-628).
|
|
67
|
+
stdout.write('\nNote: BRIDGE_API_KEY is set in your environment but is IGNORED while a ' +
|
|
68
|
+
'credentials file exists.\nTo use it, run `bridge auth logout --all`. ' +
|
|
69
|
+
'To target another stored app, use `--profile`.\n');
|
|
54
70
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
71
|
+
if (process.env.BRIDGE_APP_ID?.trim()) {
|
|
72
|
+
stdout.write('\nNote: BRIDGE_APP_ID has no effect. The app is carried by the credential ' +
|
|
73
|
+
'itself; use `--profile <label|app id>` to target another one.\n');
|
|
57
74
|
}
|
|
58
75
|
}
|
|
76
|
+
function writeEntry(entry, showMarker) {
|
|
77
|
+
const stdout = process.stdout;
|
|
78
|
+
const { creds } = entry;
|
|
79
|
+
const expiryDelta = new Date(creds.expiresAt).getTime() - Date.now();
|
|
80
|
+
const expiryRel = formatRelativeTime(expiryDelta);
|
|
81
|
+
const expiredLabel = isExpired(creds) ? ' (EXPIRED)' : '';
|
|
82
|
+
const marker = showMarker ? (entry.isActive ? '* ' : ' ') : '';
|
|
83
|
+
stdout.write(` ${marker}app=${creds.app.name} (${creds.app.id})\n`);
|
|
84
|
+
if (creds.label)
|
|
85
|
+
stdout.write(` ${marker} label=${creds.label}\n`);
|
|
86
|
+
stdout.write(` ${marker} user=${creds.user.email}\n`);
|
|
87
|
+
stdout.write(` ${marker} expires=${expiryRel} — ${creds.expiresAt}${expiredLabel}\n`);
|
|
88
|
+
stdout.write(` ${marker} baseUrl=${creds.baseUrl}\n`);
|
|
89
|
+
}
|
|
59
90
|
//# sourceMappingURL=status.command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.command.js","sourceRoot":"","sources":["../../../src/commands/auth/status.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"status.command.js","sourceRoot":"","sources":["../../../src/commands/auth/status.command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bridge auth use <profile>` — move the persisted default to another stored
|
|
3
|
+
* credential, without a browser round-trip (TBP-628).
|
|
4
|
+
*
|
|
5
|
+
* The credential is already on disk with days left on it; switching is a
|
|
6
|
+
* pointer move. Before this existed the only way to change apps was
|
|
7
|
+
* `auth logout` + `auth login`, which discarded a perfectly valid token and
|
|
8
|
+
* cost a browser trip, so people did not switch — they ran commands against
|
|
9
|
+
* whichever app happened to be active and read the answers as if they came
|
|
10
|
+
* from the one they meant.
|
|
11
|
+
*
|
|
12
|
+
* For a single command, prefer `--profile` / `BRIDGE_PROFILE`: this command
|
|
13
|
+
* mutates state that other processes read.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command } from 'commander';
|
|
16
|
+
export declare function registerAuthUseCommand(auth: Command): void;
|
|
17
|
+
//# sourceMappingURL=use.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use.command.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/use.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAc1D"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CredentialSelectorError, findCredential, isExpired, listCredentials, setActiveCredential, } from '../../credentials.js';
|
|
2
|
+
import { resetManagementClient } from '../../config.js';
|
|
3
|
+
export function registerAuthUseCommand(auth) {
|
|
4
|
+
auth
|
|
5
|
+
.command('use <profile>')
|
|
6
|
+
.description('Switch the default app to another stored credential (no re-login)')
|
|
7
|
+
.action((profile) => {
|
|
8
|
+
try {
|
|
9
|
+
runUse(profile);
|
|
10
|
+
process.exitCode = 0;
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14
|
+
process.stderr.write(`bridge auth use: ${msg}\n`);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function runUse(profile) {
|
|
20
|
+
const stdout = process.stdout;
|
|
21
|
+
let entry;
|
|
22
|
+
try {
|
|
23
|
+
entry = findCredential(profile);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err instanceof CredentialSelectorError)
|
|
27
|
+
throw new Error(err.message);
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
setActiveCredential(entry.key);
|
|
31
|
+
resetManagementClient();
|
|
32
|
+
const name = entry.creds.label ?? entry.creds.app.name;
|
|
33
|
+
stdout.write(`Now using ${name} — ${entry.creds.app.name} (${entry.creds.app.id})\n`);
|
|
34
|
+
stdout.write(` baseUrl=${entry.creds.baseUrl}\n`);
|
|
35
|
+
// Switching TO an expired credential is allowed — it is the user's stated
|
|
36
|
+
// intent, and refusing would leave them unable to make it the default before
|
|
37
|
+
// re-authenticating it. But it must not be a surprise at the next command.
|
|
38
|
+
if (isExpired(entry.creds)) {
|
|
39
|
+
stdout.write(` WARNING: this credential expired ${entry.creds.expiresAt}. ` +
|
|
40
|
+
'Run `bridge auth login` to refresh it.\n');
|
|
41
|
+
}
|
|
42
|
+
const others = listCredentials().filter((e) => e.key !== entry.key);
|
|
43
|
+
if (others.length > 0) {
|
|
44
|
+
stdout.write(` also stored: ${others.map((e) => e.creds.label ?? e.creds.app.name).join(', ')}\n`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=use.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use.command.js","sourceRoot":"","sources":["../../../src/commands/auth/use.command.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,eAAe,EACf,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,UAAU,sBAAsB,CAAC,IAAa;IAClD,IAAI;SACD,OAAO,CAAC,eAAe,CAAC;SACxB,WAAW,CAAC,mEAAmE,CAAC;SAChF,MAAM,CAAC,CAAC,OAAe,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,MAAM,CAAC,OAAO,CAAC,CAAC;YAChB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;YAClD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,MAAM,CAAC,OAAe;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAE9B,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,uBAAuB;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzE,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,qBAAqB,EAAE,CAAC;IAExB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;IACvD,MAAM,CAAC,KAAK,CAAC,aAAa,IAAI,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,aAAa,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IAEnD,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,CAAC,KAAK,CACV,sCAAsC,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI;YAC7D,0CAA0C,CAC7C,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC;IACpE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,KAAK,CACV,kBAAkB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CACtF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - `login` (TBP-113): browser-based PKCE auth.
|
|
6
6
|
* - `logout` (TBP-113): revoke + delete credentials.
|
|
7
7
|
* - `status` (TBP-113): show current login state.
|
|
8
|
+
* - `use` (TBP-628): switch the default app, offline.
|
|
8
9
|
* - `config`/`mfa`/`password-policy`: pre-existing app auth-config commands.
|
|
9
10
|
*/
|
|
10
11
|
import type { Command } from 'commander';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.command.d.ts","sourceRoot":"","sources":["../../src/commands/auth.command.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.command.d.ts","sourceRoot":"","sources":["../../src/commands/auth.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAe3D"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerAuthLoginCommand } from './auth/login.command.js';
|
|
2
2
|
import { registerAuthLogoutCommand } from './auth/logout.command.js';
|
|
3
3
|
import { registerAuthStatusCommand } from './auth/status.command.js';
|
|
4
|
+
import { registerAuthUseCommand } from './auth/use.command.js';
|
|
4
5
|
import { registerAuthConfigCommands } from './auth/config.command.js';
|
|
5
6
|
export function registerAuthCommands(program) {
|
|
6
7
|
const auth = program
|
|
@@ -10,6 +11,8 @@ export function registerAuthCommands(program) {
|
|
|
10
11
|
registerAuthLoginCommand(auth);
|
|
11
12
|
registerAuthLogoutCommand(auth);
|
|
12
13
|
registerAuthStatusCommand(auth);
|
|
14
|
+
// TBP-628 — several apps side by side; switch without a browser round-trip.
|
|
15
|
+
registerAuthUseCommand(auth);
|
|
13
16
|
// Pre-existing — app-level auth configuration.
|
|
14
17
|
registerAuthConfigCommands(auth);
|
|
15
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.command.js","sourceRoot":"","sources":["../../src/commands/auth.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.command.js","sourceRoot":"","sources":["../../src/commands/auth.command.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAEtE,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,MAAM,IAAI,GAAG,OAAO;SACjB,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,4DAA4D,CAAC,CAAC;IAE7E,qCAAqC;IACrC,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/B,yBAAyB,CAAC,IAAI,CAAC,CAAC;IAChC,yBAAyB,CAAC,IAAI,CAAC,CAAC;IAEhC,4EAA4E;IAC5E,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAE7B,+CAA+C;IAC/C,0BAA0B,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { BridgeManagement } from '@nebulr-group/bridge-auth-core';
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://api.thebridge.dev";
|
|
3
|
+
/** Called once from the CLI entrypoint, before any action runs. */
|
|
4
|
+
export declare function setProfileOverride(profile: string | null | undefined): void;
|
|
3
5
|
/**
|
|
4
6
|
* Resolution order:
|
|
5
|
-
* 1.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 2.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* 1. `--profile <label|id|name>` / `BRIDGE_PROFILE` — a stored credential,
|
|
8
|
+
* named for this one invocation. Never falls back: naming a profile and
|
|
9
|
+
* silently getting a different app is the exact failure TBP-628 exists to
|
|
10
|
+
* stop, so an unknown or expired profile is an error.
|
|
11
|
+
* 2. The ACTIVE credential in `~/.config/bridge/credentials.json` (or
|
|
12
|
+
* `$XDG_CONFIG_HOME/...`), written by `bridge auth login` and moved by
|
|
13
|
+
* `bridge auth use`.
|
|
14
|
+
* 3. `BRIDGE_API_KEY` env var (CI / service-account fallback). Used when no
|
|
15
|
+
* usable credentials file exists — the typical CI runner shape.
|
|
16
|
+
* 4. Throw `ConfigError("Not logged in. Run `bridge auth login`.")`.
|
|
12
17
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
18
|
+
* The credentials file still wins over `BRIDGE_API_KEY`, unchanged, so
|
|
19
|
+
* `bridge auth login` takes effect immediately and CI is unaffected. What HAS
|
|
20
|
+
* changed is that being ignored is now said out loud: `BRIDGE_API_KEY` and
|
|
21
|
+
* `BRIDGE_APP_ID` look like a way to retarget the CLI and are not, and reading
|
|
22
|
+
* that in the output beats discovering it from a write that went to the wrong
|
|
23
|
+
* app (TBP-628).
|
|
16
24
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* friendly error pointing the user back to `bridge auth login`.
|
|
25
|
+
* Every resolution also prints one line to stderr naming the app that is about
|
|
26
|
+
* to answer. See `writeContextBanner`.
|
|
20
27
|
*/
|
|
21
28
|
export declare function getManagementClient(): BridgeManagement;
|
|
22
29
|
/**
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AASlE,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAa5D,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAE3E;AAQD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,IAAI,gBAAgB,CA0EtD;AAoDD;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAQnE;AAED,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B"}
|
package/dist/config.js
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
import { BridgeManagement } from '@nebulr-group/bridge-auth-core';
|
|
2
|
-
import { isExpired, readCredentials } from './credentials.js';
|
|
2
|
+
import { CredentialSelectorError, findCredential, isExpired, readCredentials, } from './credentials.js';
|
|
3
3
|
export const DEFAULT_BASE_URL = 'https://api.thebridge.dev';
|
|
4
4
|
let _client = null;
|
|
5
|
+
/**
|
|
6
|
+
* The `--profile` value for this invocation, set by the CLI's preAction hook.
|
|
7
|
+
*
|
|
8
|
+
* A per-invocation override rather than a persisted switch, because that is
|
|
9
|
+
* what scripts and long-running agent sessions need: target one app for one
|
|
10
|
+
* command without mutating state another process is reading (TBP-628).
|
|
11
|
+
*/
|
|
12
|
+
let _profileOverride = null;
|
|
13
|
+
/** Called once from the CLI entrypoint, before any action runs. */
|
|
14
|
+
export function setProfileOverride(profile) {
|
|
15
|
+
_profileOverride = profile?.trim() || null;
|
|
16
|
+
}
|
|
17
|
+
/** The profile in force: `--profile` beats `BRIDGE_PROFILE`. */
|
|
18
|
+
function activeProfileSelector() {
|
|
19
|
+
if (_profileOverride)
|
|
20
|
+
return _profileOverride;
|
|
21
|
+
return process.env.BRIDGE_PROFILE?.trim() || null;
|
|
22
|
+
}
|
|
5
23
|
/**
|
|
6
24
|
* Resolution order:
|
|
7
|
-
* 1.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 2.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
25
|
+
* 1. `--profile <label|id|name>` / `BRIDGE_PROFILE` — a stored credential,
|
|
26
|
+
* named for this one invocation. Never falls back: naming a profile and
|
|
27
|
+
* silently getting a different app is the exact failure TBP-628 exists to
|
|
28
|
+
* stop, so an unknown or expired profile is an error.
|
|
29
|
+
* 2. The ACTIVE credential in `~/.config/bridge/credentials.json` (or
|
|
30
|
+
* `$XDG_CONFIG_HOME/...`), written by `bridge auth login` and moved by
|
|
31
|
+
* `bridge auth use`.
|
|
32
|
+
* 3. `BRIDGE_API_KEY` env var (CI / service-account fallback). Used when no
|
|
33
|
+
* usable credentials file exists — the typical CI runner shape.
|
|
34
|
+
* 4. Throw `ConfigError("Not logged in. Run `bridge auth login`.")`.
|
|
14
35
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
36
|
+
* The credentials file still wins over `BRIDGE_API_KEY`, unchanged, so
|
|
37
|
+
* `bridge auth login` takes effect immediately and CI is unaffected. What HAS
|
|
38
|
+
* changed is that being ignored is now said out loud: `BRIDGE_API_KEY` and
|
|
39
|
+
* `BRIDGE_APP_ID` look like a way to retarget the CLI and are not, and reading
|
|
40
|
+
* that in the output beats discovering it from a write that went to the wrong
|
|
41
|
+
* app (TBP-628).
|
|
18
42
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* friendly error pointing the user back to `bridge auth login`.
|
|
43
|
+
* Every resolution also prints one line to stderr naming the app that is about
|
|
44
|
+
* to answer. See `writeContextBanner`.
|
|
22
45
|
*/
|
|
23
46
|
export function getManagementClient() {
|
|
24
47
|
if (_client)
|
|
@@ -29,29 +52,56 @@ export function getManagementClient() {
|
|
|
29
52
|
let apiKey;
|
|
30
53
|
let baseUrl;
|
|
31
54
|
let source;
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
55
|
+
let creds = null;
|
|
56
|
+
const selector = activeProfileSelector();
|
|
57
|
+
if (selector) {
|
|
58
|
+
// 1. Explicit profile. Resolve it or fail — no fallback of any kind.
|
|
59
|
+
let entry;
|
|
60
|
+
try {
|
|
61
|
+
entry = findCredential(selector);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err instanceof CredentialSelectorError)
|
|
65
|
+
throw new ConfigError(err.message);
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
if (isExpired(entry.creds)) {
|
|
69
|
+
throw new ConfigError(`Credential for "${selector}" (${entry.creds.app.name}) expired ` +
|
|
70
|
+
`${entry.creds.expiresAt}. Run \`bridge auth login\` to re-authenticate it. ` +
|
|
71
|
+
'Refusing to fall back to another app.');
|
|
72
|
+
}
|
|
73
|
+
creds = entry.creds;
|
|
35
74
|
apiKey = creds.apiKey;
|
|
36
|
-
// Env override still wins for baseUrl (useful for hitting a local bridge-api
|
|
37
|
-
// with a token issued by prod, or vice versa during dev).
|
|
38
75
|
baseUrl = envBaseUrl && envBaseUrl.length > 0 ? envBaseUrl : creds.baseUrl;
|
|
39
|
-
source = '
|
|
40
|
-
}
|
|
41
|
-
else if (envApiKey && envApiKey.length > 0) {
|
|
42
|
-
// 2. BRIDGE_API_KEY env var (CI / service-account fallback).
|
|
43
|
-
apiKey = envApiKey;
|
|
44
|
-
baseUrl = envBaseUrl && envBaseUrl.length > 0 ? envBaseUrl : DEFAULT_BASE_URL;
|
|
45
|
-
source = 'env';
|
|
46
|
-
}
|
|
47
|
-
else if (creds && isExpired(creds)) {
|
|
48
|
-
// 3a. Found a credentials file but token expired; no env fallback either.
|
|
49
|
-
throw new ConfigError('Token expired. Run `bridge auth login` to re-authenticate.');
|
|
76
|
+
source = 'profile';
|
|
50
77
|
}
|
|
51
78
|
else {
|
|
52
|
-
//
|
|
53
|
-
|
|
79
|
+
// 2. Active credential from `bridge auth login` / `bridge auth use`.
|
|
80
|
+
const active = safeReadCredentials();
|
|
81
|
+
if (active && !isExpired(active)) {
|
|
82
|
+
creds = active;
|
|
83
|
+
apiKey = active.apiKey;
|
|
84
|
+
// Env override still wins for baseUrl (useful for hitting a local
|
|
85
|
+
// bridge-api with a token issued by prod, or vice versa during dev).
|
|
86
|
+
baseUrl = envBaseUrl && envBaseUrl.length > 0 ? envBaseUrl : active.baseUrl;
|
|
87
|
+
source = 'credentials-file';
|
|
88
|
+
}
|
|
89
|
+
else if (envApiKey && envApiKey.length > 0) {
|
|
90
|
+
// 3. BRIDGE_API_KEY env var (CI / service-account fallback).
|
|
91
|
+
apiKey = envApiKey;
|
|
92
|
+
baseUrl = envBaseUrl && envBaseUrl.length > 0 ? envBaseUrl : DEFAULT_BASE_URL;
|
|
93
|
+
source = 'env';
|
|
94
|
+
}
|
|
95
|
+
else if (active && isExpired(active)) {
|
|
96
|
+
// 4a. Found a credentials file but token expired; no env fallback either.
|
|
97
|
+
throw new ConfigError('Token expired. Run `bridge auth login` to re-authenticate.');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// 4b. No credentials at all.
|
|
101
|
+
throw new ConfigError('Not logged in. Run `bridge auth login`.');
|
|
102
|
+
}
|
|
54
103
|
}
|
|
104
|
+
writeContextBanner({ creds, source, baseUrl, selector });
|
|
55
105
|
if (debug) {
|
|
56
106
|
const baseSource = envBaseUrl && envBaseUrl.length > 0
|
|
57
107
|
? 'env'
|
|
@@ -63,6 +113,44 @@ export function getManagementClient() {
|
|
|
63
113
|
_client = new BridgeManagement({ apiKey, baseUrl, debug });
|
|
64
114
|
return _client;
|
|
65
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* One line on stderr naming the app that is about to answer.
|
|
118
|
+
*
|
|
119
|
+
* On EVERY command, not only writes. The incident behind TBP-628 started with a
|
|
120
|
+
* read: `bridge role list` was believed to describe the local app and described
|
|
121
|
+
* production, and nothing in the output said otherwise. A banner that only
|
|
122
|
+
* appears on writes would not have prevented it, because the wrong belief was
|
|
123
|
+
* already formed by then.
|
|
124
|
+
*
|
|
125
|
+
* stderr, so stdout stays pure JSON for the agents and scripts that parse it.
|
|
126
|
+
* Suppress with `BRIDGE_NO_BANNER=true` — for the one caller who is already
|
|
127
|
+
* certain and is merging the two streams.
|
|
128
|
+
*/
|
|
129
|
+
function writeContextBanner(ctx) {
|
|
130
|
+
if (process.env.BRIDGE_NO_BANNER === 'true')
|
|
131
|
+
return;
|
|
132
|
+
const { creds, source, baseUrl, selector } = ctx;
|
|
133
|
+
const who = creds
|
|
134
|
+
? `${creds.label ?? creds.app.name} (${creds.app.id})`
|
|
135
|
+
: 'BRIDGE_API_KEY (app unknown — the key carries it)';
|
|
136
|
+
const via = source === 'profile'
|
|
137
|
+
? `--profile ${selector}`
|
|
138
|
+
: source === 'env'
|
|
139
|
+
? 'BRIDGE_API_KEY'
|
|
140
|
+
: 'saved default';
|
|
141
|
+
process.stderr.write(`bridge: ${who} · ${baseUrl} · via ${via}\n`);
|
|
142
|
+
// The two env vars that look like a way to retarget the CLI and are not.
|
|
143
|
+
// Saying so here, at the moment they are being ignored, is the whole point:
|
|
144
|
+
// the reported incident was a session that set them and believed them.
|
|
145
|
+
if (source !== 'env' && process.env.BRIDGE_API_KEY?.trim()) {
|
|
146
|
+
process.stderr.write('bridge: BRIDGE_API_KEY is set but IGNORED — the credentials file wins. ' +
|
|
147
|
+
'Use `--profile <label>` to pick a stored app, or `bridge auth logout` to use the key.\n');
|
|
148
|
+
}
|
|
149
|
+
if (process.env.BRIDGE_APP_ID?.trim()) {
|
|
150
|
+
process.stderr.write('bridge: BRIDGE_APP_ID has no effect — the app is carried by the credential itself. ' +
|
|
151
|
+
'Use `--profile <label|app id>` or `BRIDGE_PROFILE` to target another app.\n');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
66
154
|
/**
|
|
67
155
|
* For tests and for `bridge auth logout` / `bridge auth status` — they need
|
|
68
156
|
* to reset the cached client between operations.
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/credentials.d.ts
CHANGED
|
@@ -20,6 +20,21 @@ export interface StoredCredentials {
|
|
|
20
20
|
/** Optional human-friendly label set via `bridge auth login --label`. */
|
|
21
21
|
label?: string;
|
|
22
22
|
}
|
|
23
|
+
/** Current on-disk shape. See the module docstring for why it is a map. */
|
|
24
|
+
export interface CredentialsFile {
|
|
25
|
+
version: 2;
|
|
26
|
+
/** App id of the credential commands use by default. `null` when empty. */
|
|
27
|
+
active: string | null;
|
|
28
|
+
/** All stored credentials, keyed by `app.id`. */
|
|
29
|
+
credentials: Record<string, StoredCredentials>;
|
|
30
|
+
}
|
|
31
|
+
/** One entry, as `bridge auth status` and the selector logic see it. */
|
|
32
|
+
export interface CredentialEntry {
|
|
33
|
+
/** The map key — always `creds.app.id`. */
|
|
34
|
+
key: string;
|
|
35
|
+
creds: StoredCredentials;
|
|
36
|
+
isActive: boolean;
|
|
37
|
+
}
|
|
23
38
|
/**
|
|
24
39
|
* Returns the absolute path to the credentials file based on the current
|
|
25
40
|
* environment. Does not create directories or files — pure path resolution.
|
|
@@ -37,17 +52,65 @@ export interface StoredCredentials {
|
|
|
37
52
|
*/
|
|
38
53
|
export declare function credentialsPath(): string;
|
|
39
54
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
55
|
+
* Read the whole store, migrating a v1 file in memory. `null` if no file.
|
|
56
|
+
*
|
|
57
|
+
* Throws if the file exists but is unreadable or malformed — a corrupt store is
|
|
58
|
+
* not the same as an absent one, and silently treating it as absent would make
|
|
59
|
+
* `bridge auth status` report "not logged in" to somebody who very much is.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readCredentialsFile(): CredentialsFile | null;
|
|
62
|
+
/**
|
|
63
|
+
* The credential commands act on by default: the active one.
|
|
64
|
+
*
|
|
65
|
+
* Kept with its original name and signature because every existing caller wants
|
|
66
|
+
* exactly this, and because "which credential is in force" is a question with
|
|
67
|
+
* one answer whether the file holds one app or ten.
|
|
42
68
|
*/
|
|
43
69
|
export declare function readCredentials(): StoredCredentials | null;
|
|
70
|
+
/** Every stored credential, active one first, then by app name. */
|
|
71
|
+
export declare function listCredentials(): CredentialEntry[];
|
|
72
|
+
/** Raised when a selector matches nothing, or matches more than one entry. */
|
|
73
|
+
export declare class CredentialSelectorError extends Error {
|
|
74
|
+
constructor(message: string);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Find one stored credential by label, app id, or app name.
|
|
78
|
+
*
|
|
79
|
+
* Label first: users think in environments, not in 24-character object ids, and
|
|
80
|
+
* `--label` exists precisely so they can name one. App id is exact and always
|
|
81
|
+
* works. App name is last because two apps may share one.
|
|
82
|
+
*
|
|
83
|
+
* An ambiguous selector is an ERROR, never a first match. Picking one of two
|
|
84
|
+
* candidate apps silently is the whole failure mode this ticket is about.
|
|
85
|
+
*/
|
|
86
|
+
export declare function findCredential(selector: string): CredentialEntry;
|
|
44
87
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
88
|
+
* Upsert a credential by app id and make it active.
|
|
89
|
+
*
|
|
90
|
+
* Same name and signature as before: `bridge auth login` still ends with one
|
|
91
|
+
* call, and still leaves the app it just authorised in force.
|
|
47
92
|
*/
|
|
48
93
|
export declare function writeCredentials(creds: StoredCredentials): void;
|
|
94
|
+
/** Point the persisted default at an already-stored credential. Offline. */
|
|
95
|
+
export declare function setActiveCredential(key: string): void;
|
|
96
|
+
/** Persist the store atomically with mode 0600, dir 0700. */
|
|
97
|
+
export declare function writeCredentialsFile(file: CredentialsFile): void;
|
|
49
98
|
/**
|
|
50
|
-
* Deletes the
|
|
99
|
+
* Remove ONE credential. Deletes the file entirely when it was the last.
|
|
100
|
+
*
|
|
101
|
+
* Returns the remaining count so a caller can tell the user what is left.
|
|
102
|
+
* Removing the active one moves `active` to another entry rather than leaving a
|
|
103
|
+
* dangling pointer — with credentials still stored, "logged out of everything"
|
|
104
|
+
* would be a lie.
|
|
105
|
+
*/
|
|
106
|
+
export declare function removeCredential(key: string): number;
|
|
107
|
+
/** Delete the whole credentials file. No-op if it does not exist. */
|
|
108
|
+
export declare function deleteAllCredentials(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Back-compat alias for the single-app era: remove the active credential.
|
|
111
|
+
*
|
|
112
|
+
* @deprecated Prefer `removeCredential(key)` or `deleteAllCredentials()`, which
|
|
113
|
+
* say which of the two things they do.
|
|
51
114
|
*/
|
|
52
115
|
export declare function deleteCredentials(): void;
|
|
53
116
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AA6CA,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,qCAAqC;IACrC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAChD;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAQxC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CAyB5D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAU1D;AAED,mEAAmE;AACnE,wBAAgB,eAAe,IAAI,eAAe,EAAE,CASnD;AAED,8EAA8E;AAC9E,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAmChE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAK/D;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAOrD;AAED,6DAA6D;AAC7D,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CA4BhE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWpD;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,IAAI,IAAI,CAQ3C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAOxC;AAsBD;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAInF"}
|
package/dist/credentials.js
CHANGED
|
@@ -10,6 +10,34 @@
|
|
|
10
10
|
* 2. `~/.config/bridge/credentials.json` otherwise
|
|
11
11
|
*
|
|
12
12
|
* File mode: 0600 (owner read/write only). Containing dir created with 0700.
|
|
13
|
+
*
|
|
14
|
+
* ## The file holds MANY apps (TBP-628)
|
|
15
|
+
*
|
|
16
|
+
* It used to hold exactly one. Anyone running the same product as several apps
|
|
17
|
+
* — one appId per environment — had to `logout` + `login` through the browser
|
|
18
|
+
* to move between them, even though the token they already held had days left
|
|
19
|
+
* on it. Worse, nothing in a read command's output said which app answered, so
|
|
20
|
+
* a session holding a prod token could run `role list` believing it described
|
|
21
|
+
* local, and the next step would have rewritten production roles.
|
|
22
|
+
*
|
|
23
|
+
* So the on-disk shape is now a map keyed by app id, plus a pointer at the
|
|
24
|
+
* active one:
|
|
25
|
+
*
|
|
26
|
+
* ```json
|
|
27
|
+
* {
|
|
28
|
+
* "version": 2,
|
|
29
|
+
* "active": "606b4416fabdc800087d09ec",
|
|
30
|
+
* "credentials": { "606b4416fabdc800087d09ec": { …StoredCredentials } }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Keyed by app id, not by label: the label is optional and user-chosen, so it
|
|
35
|
+
* is a fine way to SELECT a credential and a bad way to key one.
|
|
36
|
+
*
|
|
37
|
+
* A v1 file (a bare `StoredCredentials` object) is migrated on read, in memory
|
|
38
|
+
* only. Reading never rewrites the file — a read command must not mutate state
|
|
39
|
+
* another process is reading. The v2 shape is persisted the next time
|
|
40
|
+
* something writes.
|
|
13
41
|
*/
|
|
14
42
|
import * as fs from 'node:fs';
|
|
15
43
|
import * as path from 'node:path';
|
|
@@ -39,10 +67,13 @@ export function credentialsPath() {
|
|
|
39
67
|
return path.join(base, '.config', 'bridge', 'credentials.json');
|
|
40
68
|
}
|
|
41
69
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
70
|
+
* Read the whole store, migrating a v1 file in memory. `null` if no file.
|
|
71
|
+
*
|
|
72
|
+
* Throws if the file exists but is unreadable or malformed — a corrupt store is
|
|
73
|
+
* not the same as an absent one, and silently treating it as absent would make
|
|
74
|
+
* `bridge auth status` report "not logged in" to somebody who very much is.
|
|
44
75
|
*/
|
|
45
|
-
export function
|
|
76
|
+
export function readCredentialsFile() {
|
|
46
77
|
const filePath = credentialsPath();
|
|
47
78
|
if (!fs.existsSync(filePath))
|
|
48
79
|
return null;
|
|
@@ -54,16 +85,120 @@ export function readCredentials() {
|
|
|
54
85
|
catch {
|
|
55
86
|
throw new Error(`Credentials file at ${filePath} is not valid JSON.`);
|
|
56
87
|
}
|
|
57
|
-
|
|
58
|
-
|
|
88
|
+
// v2 — the current shape.
|
|
89
|
+
if (isCredentialsFile(parsed))
|
|
90
|
+
return parsed;
|
|
91
|
+
// v1 — a bare StoredCredentials object. Migrate in memory; do NOT write.
|
|
92
|
+
if (isStoredCredentials(parsed)) {
|
|
93
|
+
return {
|
|
94
|
+
version: 2,
|
|
95
|
+
active: parsed.app.id,
|
|
96
|
+
credentials: { [parsed.app.id]: parsed },
|
|
97
|
+
};
|
|
59
98
|
}
|
|
60
|
-
|
|
99
|
+
throw new Error(`Credentials file at ${filePath} is missing required fields.`);
|
|
61
100
|
}
|
|
62
101
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
102
|
+
* The credential commands act on by default: the active one.
|
|
103
|
+
*
|
|
104
|
+
* Kept with its original name and signature because every existing caller wants
|
|
105
|
+
* exactly this, and because "which credential is in force" is a question with
|
|
106
|
+
* one answer whether the file holds one app or ten.
|
|
107
|
+
*/
|
|
108
|
+
export function readCredentials() {
|
|
109
|
+
const file = readCredentialsFile();
|
|
110
|
+
if (!file)
|
|
111
|
+
return null;
|
|
112
|
+
if (file.active && file.credentials[file.active])
|
|
113
|
+
return file.credentials[file.active];
|
|
114
|
+
// A pointer at a credential that is gone. Fall back to the only entry if
|
|
115
|
+
// there is exactly one — otherwise there is no defensible default, and
|
|
116
|
+
// guessing would pick an app on the user's behalf, which is the failure this
|
|
117
|
+
// ticket exists to stop.
|
|
118
|
+
const keys = Object.keys(file.credentials);
|
|
119
|
+
return keys.length === 1 ? file.credentials[keys[0]] : null;
|
|
120
|
+
}
|
|
121
|
+
/** Every stored credential, active one first, then by app name. */
|
|
122
|
+
export function listCredentials() {
|
|
123
|
+
const file = readCredentialsFile();
|
|
124
|
+
if (!file)
|
|
125
|
+
return [];
|
|
126
|
+
return Object.entries(file.credentials)
|
|
127
|
+
.map(([key, creds]) => ({ key, creds, isActive: key === file.active }))
|
|
128
|
+
.sort((a, b) => {
|
|
129
|
+
if (a.isActive !== b.isActive)
|
|
130
|
+
return a.isActive ? -1 : 1;
|
|
131
|
+
return a.creds.app.name.localeCompare(b.creds.app.name);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** Raised when a selector matches nothing, or matches more than one entry. */
|
|
135
|
+
export class CredentialSelectorError extends Error {
|
|
136
|
+
constructor(message) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = 'CredentialSelectorError';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Find one stored credential by label, app id, or app name.
|
|
143
|
+
*
|
|
144
|
+
* Label first: users think in environments, not in 24-character object ids, and
|
|
145
|
+
* `--label` exists precisely so they can name one. App id is exact and always
|
|
146
|
+
* works. App name is last because two apps may share one.
|
|
147
|
+
*
|
|
148
|
+
* An ambiguous selector is an ERROR, never a first match. Picking one of two
|
|
149
|
+
* candidate apps silently is the whole failure mode this ticket is about.
|
|
150
|
+
*/
|
|
151
|
+
export function findCredential(selector) {
|
|
152
|
+
const needle = selector.trim();
|
|
153
|
+
if (!needle)
|
|
154
|
+
throw new CredentialSelectorError('Empty profile selector.');
|
|
155
|
+
const entries = listCredentials();
|
|
156
|
+
if (entries.length === 0) {
|
|
157
|
+
throw new CredentialSelectorError('No stored credentials. Run `bridge auth login` first.');
|
|
158
|
+
}
|
|
159
|
+
const lower = needle.toLowerCase();
|
|
160
|
+
const byLabel = entries.filter((e) => e.creds.label?.toLowerCase() === lower);
|
|
161
|
+
const byId = entries.filter((e) => e.key === needle);
|
|
162
|
+
const byName = entries.filter((e) => e.creds.app.name.toLowerCase() === lower);
|
|
163
|
+
for (const [what, matches] of [
|
|
164
|
+
['label', byLabel],
|
|
165
|
+
['app id', byId],
|
|
166
|
+
['app name', byName],
|
|
167
|
+
]) {
|
|
168
|
+
if (matches.length === 1)
|
|
169
|
+
return matches[0];
|
|
170
|
+
if (matches.length > 1) {
|
|
171
|
+
throw new CredentialSelectorError(`"${needle}" matches ${matches.length} stored credentials by ${what} ` +
|
|
172
|
+
`(${matches.map((m) => m.key).join(', ')}). Select by app id instead.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
throw new CredentialSelectorError(`No stored credential matches "${needle}". Known: ` +
|
|
176
|
+
entries.map((e) => e.creds.label ?? e.creds.app.name).join(', ') +
|
|
177
|
+
'. Run `bridge auth status` to see them all.');
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Upsert a credential by app id and make it active.
|
|
181
|
+
*
|
|
182
|
+
* Same name and signature as before: `bridge auth login` still ends with one
|
|
183
|
+
* call, and still leaves the app it just authorised in force.
|
|
65
184
|
*/
|
|
66
185
|
export function writeCredentials(creds) {
|
|
186
|
+
const file = readCredentialsFileOrEmpty();
|
|
187
|
+
file.credentials[creds.app.id] = creds;
|
|
188
|
+
file.active = creds.app.id;
|
|
189
|
+
writeCredentialsFile(file);
|
|
190
|
+
}
|
|
191
|
+
/** Point the persisted default at an already-stored credential. Offline. */
|
|
192
|
+
export function setActiveCredential(key) {
|
|
193
|
+
const file = readCredentialsFileOrEmpty();
|
|
194
|
+
if (!file.credentials[key]) {
|
|
195
|
+
throw new CredentialSelectorError(`No stored credential with app id ${key}.`);
|
|
196
|
+
}
|
|
197
|
+
file.active = key;
|
|
198
|
+
writeCredentialsFile(file);
|
|
199
|
+
}
|
|
200
|
+
/** Persist the store atomically with mode 0600, dir 0700. */
|
|
201
|
+
export function writeCredentialsFile(file) {
|
|
67
202
|
const filePath = credentialsPath();
|
|
68
203
|
const dir = path.dirname(filePath);
|
|
69
204
|
// Create dir with restrictive perms. mkdirSync `mode` is masked by umask, so
|
|
@@ -80,7 +215,7 @@ export function writeCredentials(creds) {
|
|
|
80
215
|
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
81
216
|
const fd = fs.openSync(tmpPath, 'w', 0o600);
|
|
82
217
|
try {
|
|
83
|
-
fs.writeFileSync(fd, JSON.stringify(
|
|
218
|
+
fs.writeFileSync(fd, JSON.stringify(file, null, 2) + '\n');
|
|
84
219
|
}
|
|
85
220
|
finally {
|
|
86
221
|
fs.closeSync(fd);
|
|
@@ -94,9 +229,28 @@ export function writeCredentials(creds) {
|
|
|
94
229
|
fs.renameSync(tmpPath, filePath);
|
|
95
230
|
}
|
|
96
231
|
/**
|
|
97
|
-
* Deletes the
|
|
232
|
+
* Remove ONE credential. Deletes the file entirely when it was the last.
|
|
233
|
+
*
|
|
234
|
+
* Returns the remaining count so a caller can tell the user what is left.
|
|
235
|
+
* Removing the active one moves `active` to another entry rather than leaving a
|
|
236
|
+
* dangling pointer — with credentials still stored, "logged out of everything"
|
|
237
|
+
* would be a lie.
|
|
98
238
|
*/
|
|
99
|
-
export function
|
|
239
|
+
export function removeCredential(key) {
|
|
240
|
+
const file = readCredentialsFileOrEmpty();
|
|
241
|
+
delete file.credentials[key];
|
|
242
|
+
const remaining = Object.keys(file.credentials);
|
|
243
|
+
if (remaining.length === 0) {
|
|
244
|
+
deleteAllCredentials();
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
if (file.active === key)
|
|
248
|
+
file.active = remaining[0];
|
|
249
|
+
writeCredentialsFile(file);
|
|
250
|
+
return remaining.length;
|
|
251
|
+
}
|
|
252
|
+
/** Delete the whole credentials file. No-op if it does not exist. */
|
|
253
|
+
export function deleteAllCredentials() {
|
|
100
254
|
const filePath = credentialsPath();
|
|
101
255
|
try {
|
|
102
256
|
fs.unlinkSync(filePath);
|
|
@@ -107,6 +261,43 @@ export function deleteCredentials() {
|
|
|
107
261
|
throw err;
|
|
108
262
|
}
|
|
109
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Back-compat alias for the single-app era: remove the active credential.
|
|
266
|
+
*
|
|
267
|
+
* @deprecated Prefer `removeCredential(key)` or `deleteAllCredentials()`, which
|
|
268
|
+
* say which of the two things they do.
|
|
269
|
+
*/
|
|
270
|
+
export function deleteCredentials() {
|
|
271
|
+
const file = readCredentialsFileOrEmpty();
|
|
272
|
+
if (file.active) {
|
|
273
|
+
removeCredential(file.active);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
deleteAllCredentials();
|
|
277
|
+
}
|
|
278
|
+
/** The store, or an empty one — for the write paths, which create on demand. */
|
|
279
|
+
function readCredentialsFileOrEmpty() {
|
|
280
|
+
try {
|
|
281
|
+
return readCredentialsFile() ?? { version: 2, active: null, credentials: {} };
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// A corrupt file must not wedge `login` — overwriting it is how the user
|
|
285
|
+
// gets unstuck, and the token in it is unusable anyway.
|
|
286
|
+
return { version: 2, active: null, credentials: {} };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function isCredentialsFile(value) {
|
|
290
|
+
if (!value || typeof value !== 'object')
|
|
291
|
+
return false;
|
|
292
|
+
const v = value;
|
|
293
|
+
if (v.version !== 2)
|
|
294
|
+
return false;
|
|
295
|
+
if (v.active !== null && typeof v.active !== 'string')
|
|
296
|
+
return false;
|
|
297
|
+
if (!v.credentials || typeof v.credentials !== 'object')
|
|
298
|
+
return false;
|
|
299
|
+
return Object.values(v.credentials).every(isStoredCredentials);
|
|
300
|
+
}
|
|
110
301
|
/**
|
|
111
302
|
* True if the credentials' `expiresAt` is in the past relative to `now`.
|
|
112
303
|
*/
|
package/dist/credentials.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|