@sequenceholdings/studio-cli 0.1.9 → 0.1.11
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 +45 -26
- package/dist/artifact/delegate.js +119 -34
- package/dist/auth-cmds/commands.d.ts +1 -1
- package/dist/auth-cmds/commands.js +28 -17
- package/dist/auth.d.ts +49 -0
- package/dist/auth.js +59 -20
- package/dist/cli-errors.js +1 -1
- package/dist/config.d.ts +27 -11
- package/dist/config.js +72 -22
- package/dist/env-catalog.d.ts +63 -0
- package/dist/env-catalog.js +111 -0
- package/dist/envs/commands.d.ts +1 -0
- package/dist/envs/commands.js +74 -0
- package/dist/functions/commands.d.ts +1 -1
- package/dist/functions/commands.js +7 -7
- package/dist/login.d.ts +12 -0
- package/dist/login.js +233 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.js +29 -2
- package/dist/orm/delegate.js +8 -8
- package/dist/pat-hints.d.ts +3 -5
- package/dist/pat-hints.js +9 -10
- package/dist/process/commands.js +11 -12
- package/dist/repos/commands.d.ts +1 -1
- package/dist/repos/commands.js +14 -14
- package/dist/secrets/commands.d.ts +1 -1
- package/dist/secrets/commands.js +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -6,9 +6,10 @@ any repo against the platform over HTTP — no monorepo checkout required.
|
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
seq-studio process lint
|
|
9
|
-
seq-studio process plan -e
|
|
10
|
-
seq-studio process apply -e
|
|
11
|
-
seq-studio artifact deploy -e
|
|
9
|
+
seq-studio process plan -e <env>
|
|
10
|
+
seq-studio process apply -e <env>
|
|
11
|
+
seq-studio artifact deploy -e <env>
|
|
12
|
+
seq-studio envs list
|
|
12
13
|
seq-studio doctor
|
|
13
14
|
```
|
|
14
15
|
|
|
@@ -43,16 +44,17 @@ releases install immediately.)
|
|
|
43
44
|
|
|
44
45
|
## Authenticate
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`seq-studio artifact` all share that file.
|
|
47
|
+
Run the built-in browser login once. `seq-studio` and `seqapi` share the
|
|
48
|
+
resulting tokens at `~/.config/sequence-api/tokens.json`, so logging in with
|
|
49
|
+
either CLI authenticates both.
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
|
|
52
|
+
seq-studio login
|
|
53
53
|
seq-studio doctor # confirms config + auth + authorization
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
Use `seq-studio logout` to remove the shared cached tokens.
|
|
57
|
+
|
|
56
58
|
### Headless auth (CI) — M2M
|
|
57
59
|
|
|
58
60
|
When there's no interactive login (CI, automation), set the service-account
|
|
@@ -61,7 +63,7 @@ secret and `seq-studio` mints a token via the Auth0 client-credentials grant
|
|
|
61
63
|
|
|
62
64
|
```bash
|
|
63
65
|
export AUTH0_M2M_CLIENT_SECRET=... # provided by your platform administrator
|
|
64
|
-
seq-studio artifact deploy -e
|
|
66
|
+
seq-studio artifact deploy -e <env>
|
|
65
67
|
```
|
|
66
68
|
|
|
67
69
|
The secret is read at runtime — never commit it. M2M carries app scopes but
|
|
@@ -71,17 +73,35 @@ user-scoped/private resources.
|
|
|
71
73
|
**Manual escape hatch:** any `artifact` command also accepts an explicit
|
|
72
74
|
`--token <jwt>`, which wins over both the M2M and cached-user paths.
|
|
73
75
|
|
|
74
|
-
## Environments
|
|
76
|
+
## Environments
|
|
77
|
+
|
|
78
|
+
The CLI ships with a single built-in environment, `local`
|
|
79
|
+
(`http://localhost:5001`). The other environments your identity may target
|
|
80
|
+
are **discovered** after you authenticate: the CLI calls the platform's
|
|
81
|
+
environment-discovery endpoint and caches the result at
|
|
82
|
+
`~/.config/lattice/environments.json`.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
seq-studio login # or: export AUTH0_M2M_CLIENT_SECRET=...
|
|
86
|
+
seq-studio envs refresh # fetch the environments visible to your identity
|
|
87
|
+
seq-studio envs list # show them (name, URL, source)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Discovery also happens lazily: the first time you pass an `-e <env>` that
|
|
91
|
+
isn't cached yet, the CLI refreshes the catalog before failing. What you can
|
|
92
|
+
see depends on who you are — unauthenticated installs get `local` only, and
|
|
93
|
+
authenticated identities get the deployments they're entitled to. Visibility
|
|
94
|
+
is not access control: every request is still authorized server-side.
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
You can always add or override environments yourself in
|
|
97
|
+
`~/.config/lattice/config.toml` (user entries win over discovered ones):
|
|
78
98
|
|
|
79
99
|
```toml
|
|
80
100
|
[env.local]
|
|
81
101
|
url = "http://localhost:5001"
|
|
82
102
|
|
|
83
|
-
[env.
|
|
84
|
-
url = "https://
|
|
103
|
+
[env.my-atlas]
|
|
104
|
+
url = "https://atlas.example.com"
|
|
85
105
|
|
|
86
106
|
default_env = "local"
|
|
87
107
|
```
|
|
@@ -190,34 +210,33 @@ any username, PAT as password). **You do not need `seqapi`.**
|
|
|
190
210
|
|
|
191
211
|
### Everyone (recommended) — Atlas UI
|
|
192
212
|
|
|
193
|
-
1. Open **Settings → Tokens** in Atlas for your environment
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
- BankSouth: https://banksouth.seqholdings.com/settings/tokens
|
|
213
|
+
1. Open **Settings → Tokens** in Atlas for your environment
|
|
214
|
+
(`<your-atlas-url>/settings/tokens` — run `seq-studio envs list` for the
|
|
215
|
+
URLs visible to your identity)
|
|
197
216
|
2. **New token** → scopes `repo:read` (add `repo:write` for push) → copy once
|
|
198
217
|
3. Export and clone:
|
|
199
218
|
|
|
200
219
|
```bash
|
|
201
220
|
export ATLAS_GIT_PAT=atlas_git_…
|
|
202
|
-
seq-studio repos clone
|
|
203
|
-
# or: git clone https://git:$ATLAS_GIT_PAT
|
|
221
|
+
seq-studio repos clone <namespace>/<repo> -e <env>
|
|
222
|
+
# or: git clone https://git:$ATLAS_GIT_PAT@<your-atlas-host>/api/git-service/repos/<id>/git
|
|
204
223
|
```
|
|
205
224
|
|
|
206
225
|
You can also open **Repositories → Access tokens** / the clone popover’s
|
|
207
226
|
**Manage tokens** link.
|
|
208
227
|
|
|
209
|
-
###
|
|
228
|
+
### CLI mint (optional)
|
|
210
229
|
|
|
211
|
-
Requires
|
|
230
|
+
Requires Auth0 login. Same identity as the UI:
|
|
212
231
|
|
|
213
232
|
```bash
|
|
214
|
-
|
|
215
|
-
seq-studio auth pat create --name laptop --scopes repo:read,repo:write -e
|
|
233
|
+
seq-studio login
|
|
234
|
+
seq-studio auth pat create --name laptop --scopes repo:read,repo:write -e <env>
|
|
216
235
|
# optional: --expires 7d|30d|90d|1y|never (default 30d)
|
|
217
236
|
# optional: --store-credentials # git credential approve for the env host
|
|
218
237
|
|
|
219
|
-
seq-studio auth pat list -e
|
|
220
|
-
seq-studio auth pat revoke <id> -e
|
|
238
|
+
seq-studio auth pat list -e <env>
|
|
239
|
+
seq-studio auth pat revoke <id> -e <env> --yes
|
|
221
240
|
```
|
|
222
241
|
|
|
223
242
|
The raw token is printed **once** on create.
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* is shared with the rest of `seq-studio`.
|
|
12
12
|
* 4. Forward all remaining argv to `runCli`.
|
|
13
13
|
*/
|
|
14
|
-
import { getAccessToken, tryGetAccessToken } from '../auth.js';
|
|
15
|
-
import { readConfig,
|
|
14
|
+
import { getAccessToken, M2mTokenError, tryGetAccessToken } from '../auth.js';
|
|
15
|
+
import { readConfig, resolveEnvWithDiscovery } from '../config.js';
|
|
16
|
+
import { fetchCatalog, readCachedCatalog } from '../env-catalog.js';
|
|
16
17
|
import { normalizeShortEnvFlag, readEnvFromArgv } from '../env-flags.js';
|
|
17
18
|
import { PREVIEW_DOMAIN, PREVIEW_PROJECT, resolvePreviewByPr } from '../preview.js';
|
|
18
19
|
const ARTIFACT_USAGE = `usage:
|
|
@@ -31,7 +32,8 @@ const ARTIFACT_USAGE = `usage:
|
|
|
31
32
|
seq-studio artifact whoami -e <env>
|
|
32
33
|
seq-studio artifact env list
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Environments: see \`seq-studio envs list\` (built-in: local; more are
|
|
36
|
+
discovered after you authenticate).
|
|
35
37
|
|
|
36
38
|
Source for build/plan/deploy: a local [dir] (default), a platform git-service
|
|
37
39
|
repo (--repo <ns>/<name>), or any git URL (--git-url <url>). --ref selects a
|
|
@@ -48,13 +50,42 @@ const ARTIFACT_USAGE = `usage:
|
|
|
48
50
|
Preview deploys sit behind a Cloudflare WAF gate. Off the company network/VPN,
|
|
49
51
|
set PREVIEW_ACCESS_HEADER=<secret> and the CLI sends it as x-preview-access.
|
|
50
52
|
|
|
51
|
-
Authenticate with:
|
|
53
|
+
Authenticate with: seq-studio login
|
|
52
54
|
`;
|
|
53
55
|
export async function runArtifactCommand(sub, rest) {
|
|
56
|
+
// Older artifact-studio versions exposed nested login/logout commands backed
|
|
57
|
+
// by a separate token file. Keep those commands working, but route them to
|
|
58
|
+
// seq-studio's shared token so every namespace uses the same identity.
|
|
59
|
+
if (sub === 'login' || sub === 'logout') {
|
|
60
|
+
if (rest.length > 0) {
|
|
61
|
+
if (sub === 'login' && rest.includes('--token')) {
|
|
62
|
+
// The legacy `artifact login --token <jwt>` persisted a bearer to
|
|
63
|
+
// artifact-studio's own token file — a store this unification retires.
|
|
64
|
+
console.error('seq-studio artifact login no longer stores a bearer token.\n' +
|
|
65
|
+
'Scripted/headless options:\n' +
|
|
66
|
+
' - pass --token <jwt> directly to the artifact command (deploy/plan/whoami/...)\n' +
|
|
67
|
+
' - export ARTIFACT_STUDIO_TOKEN=<jwt> for the session\n' +
|
|
68
|
+
' - set AUTH0_M2M_CLIENT_SECRET for service-account (M2M) auth in CI');
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error(`seq-studio artifact ${sub} does not accept arguments.`);
|
|
72
|
+
}
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
const auth = await import('../login.js');
|
|
76
|
+
await auth[sub]();
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
54
79
|
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
55
80
|
console.log(ARTIFACT_USAGE);
|
|
56
81
|
return sub ? 0 : 1;
|
|
57
82
|
}
|
|
83
|
+
// `artifact env list` renders the tier-aware discovered catalog instead of
|
|
84
|
+
// artifact-studio's built-in (local-only) list.
|
|
85
|
+
if (sub === 'env' && rest[0] === 'list') {
|
|
86
|
+
const { runEnvsCommand } = await import('../envs/commands.js');
|
|
87
|
+
return runEnvsCommand('list', []);
|
|
88
|
+
}
|
|
58
89
|
// Normalize `-e <env>` / `-e=<env>` to `--env <env>` because
|
|
59
90
|
// artifact-studio's argv parser (`shared/services/artifact-studio/src/cli.ts`)
|
|
60
91
|
// only recognizes long flags. Without this rewrite `seq-studio
|
|
@@ -70,6 +101,7 @@ export async function runArtifactCommand(sub, rest) {
|
|
|
70
101
|
let resolved;
|
|
71
102
|
let argvForCli = forwardRest;
|
|
72
103
|
if (envUrl !== undefined) {
|
|
104
|
+
await requireSequenceTier('--env-url');
|
|
73
105
|
const validated = validatePreviewEnvUrl(envUrl);
|
|
74
106
|
// Explicit override wins over everything. Keep the user's --env name if
|
|
75
107
|
// they gave one, else label it `preview`.
|
|
@@ -77,13 +109,13 @@ export async function runArtifactCommand(sub, rest) {
|
|
|
77
109
|
argvForCli = ensureEnvFlag(forwardRest, resolved.name);
|
|
78
110
|
}
|
|
79
111
|
else if (prNumber !== undefined) {
|
|
112
|
+
await requireSequenceTier('--pr');
|
|
80
113
|
const preview = await resolvePreviewByPr({ pr: prNumber });
|
|
81
114
|
resolved = { name: `preview:${preview.slug}`, url: preview.url };
|
|
82
115
|
argvForCli = ensureEnvFlag(forwardRest, resolved.name);
|
|
83
116
|
}
|
|
84
117
|
else if (requested) {
|
|
85
|
-
|
|
86
|
-
resolved = resolveEnv({ config, requested });
|
|
118
|
+
resolved = await resolveEnvWithDiscovery({ requested });
|
|
87
119
|
// For `preview:<slug>` normalize the forwarded --env to the canonical name.
|
|
88
120
|
if (resolved.name !== requested)
|
|
89
121
|
argvForCli = ensureEnvFlag(forwardRest, resolved.name);
|
|
@@ -96,41 +128,82 @@ export async function runArtifactCommand(sub, rest) {
|
|
|
96
128
|
if (isPreviewUrl(resolved.url))
|
|
97
129
|
applyPreviewAccessHeader();
|
|
98
130
|
}
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
// Always hand artifact-studio the env map visible to this identity
|
|
132
|
+
// (discovered catalog + config.toml). Commands that resolve an env *name*
|
|
133
|
+
// without an explicit --env flag — the stored `.artifact-studio/config.json`
|
|
134
|
+
// defaultEnv, `artifact env set <name>` — would otherwise fall back to
|
|
135
|
+
// artifact-studio's built-in local-only map and fail on deployed envs.
|
|
136
|
+
const { envs } = await readConfig();
|
|
137
|
+
process.env['ARTIFACT_STUDIO_ENV_URLS'] = JSON.stringify(Object.fromEntries(Object.entries(envs).map(([name, { url }]) => [name, url])));
|
|
138
|
+
// An explicit `--token <jwt>` is the manual escape hatch and must win over
|
|
139
|
+
// everything, including a configured-but-failing M2M credential (which
|
|
140
|
+
// `tryGetAccessToken({ failClosedForM2m: true })` would otherwise turn into
|
|
141
|
+
// an abort before argv ever reaches artifact-studio). artifact-studio's
|
|
142
|
+
// `getOptionalToken` checks `flags.token` first, so when it's present we
|
|
143
|
+
// skip shared-token resolution entirely. Only the two-token `--token <jwt>`
|
|
144
|
+
// form counts: artifact-studio's parser does not split `--token=<jwt>`.
|
|
145
|
+
const hasExplicitToken = hasTokenFlag(argvForCli);
|
|
111
146
|
// Lazy import so `process` / `doctor` commands don't pull in
|
|
112
147
|
// artifact-studio's vite/react/tailwind dependency graph.
|
|
113
148
|
const { runCli: runArtifactStudio, setTokenProvider } = await import('@sequenceholdings/artifact-studio/cli');
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
149
|
+
if (!hasExplicitToken) {
|
|
150
|
+
// Share the seqapi token. `tryGetAccessToken` resolves an M2M
|
|
151
|
+
// service-account token when AUTH0_M2M_CLIENT_SECRET is set (headless /
|
|
152
|
+
// CI / cloud-agent path) and otherwise the cached interactive user
|
|
153
|
+
// token (see ../auth.ts). If neither is available, artifact-studio
|
|
154
|
+
// commands that need a token surface their own error — we don't force
|
|
155
|
+
// `seq-studio login` here because some commands (init, validate, build)
|
|
156
|
+
// work offline.
|
|
157
|
+
const token = await tryGetAccessToken({ failClosedForM2m: true });
|
|
158
|
+
if (token) {
|
|
159
|
+
process.env['ARTIFACT_STUDIO_TOKEN'] = token;
|
|
125
160
|
}
|
|
161
|
+
// The ARTIFACT_STUDIO_TOKEN env var above is captured once and never
|
|
162
|
+
// refreshes, so long-running commands (notably `artifact dev`) would start
|
|
163
|
+
// failing with "Authentication failed" once the initial token's TTL
|
|
164
|
+
// elapses. Hand artifact-studio a refreshing source — getAccessToken()
|
|
165
|
+
// mints a fresh access token via the Auth0 refresh grant when the cached
|
|
166
|
+
// one is near expiry — so a watch session survives indefinitely.
|
|
167
|
+
setTokenProvider(async () => {
|
|
168
|
+
if (process.env.AUTH0_M2M_CLIENT_SECRET?.trim()) {
|
|
169
|
+
// Fail closed for configured M2M failures so headless deploys never
|
|
170
|
+
// silently fall back to another cached identity.
|
|
171
|
+
return await getAccessToken();
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
return await getAccessToken();
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return runArtifactStudio([sub, ...argvForCli]);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Preview targeting (`preview:<slug>`, `--pr`, `--env-url`) is a
|
|
185
|
+
* Sequence-staff surface. The tier comes from the cached discovery catalog;
|
|
186
|
+
* refresh it lazily so a just-logged-in engineer isn't blocked on a stale
|
|
187
|
+
* anonymous cache.
|
|
188
|
+
*/
|
|
189
|
+
async function requireSequenceTier(flag) {
|
|
190
|
+
let { tier } = await readConfig();
|
|
191
|
+
if (tier !== 'sequence') {
|
|
126
192
|
try {
|
|
127
|
-
|
|
193
|
+
tier = (await fetchCatalog())?.tier ?? 'anonymous';
|
|
128
194
|
}
|
|
129
|
-
catch {
|
|
130
|
-
|
|
195
|
+
catch (err) {
|
|
196
|
+
// Broken CI credentials must surface as the Auth0 error, not as a
|
|
197
|
+
// misleading "Sequence-staff surface" rejection.
|
|
198
|
+
if (err instanceof M2mTokenError)
|
|
199
|
+
throw err;
|
|
200
|
+
tier = (await readCachedCatalog())?.tier ?? 'anonymous';
|
|
131
201
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
202
|
+
}
|
|
203
|
+
if (tier !== 'sequence') {
|
|
204
|
+
throw new Error(`${flag} targets per-PR preview environments, a Sequence-staff surface. ` +
|
|
205
|
+
'Authenticate with `seq-studio login` and run `seq-studio envs refresh`.');
|
|
206
|
+
}
|
|
134
207
|
}
|
|
135
208
|
/**
|
|
136
209
|
* Strip the seq-studio-only `--pr <number>` and `--env-url <url>` flags from
|
|
@@ -167,6 +240,18 @@ export function extractPreviewFlags(argv) {
|
|
|
167
240
|
}
|
|
168
241
|
return { rest, prNumber, envUrl };
|
|
169
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* True when argv carries an explicit two-token `--token <jwt>` that
|
|
245
|
+
* artifact-studio's parser will bind to `flags.token`. `--token=<jwt>` is
|
|
246
|
+
* excluded because that parser stores it as a stray flag and never binds it.
|
|
247
|
+
*/
|
|
248
|
+
function hasTokenFlag(argv) {
|
|
249
|
+
const index = argv.indexOf('--token');
|
|
250
|
+
if (index === -1)
|
|
251
|
+
return false;
|
|
252
|
+
const value = argv[index + 1];
|
|
253
|
+
return value !== undefined && !value.startsWith('--');
|
|
254
|
+
}
|
|
170
255
|
function splitInlineValue(arg) {
|
|
171
256
|
const eq = arg.indexOf('=');
|
|
172
257
|
if (arg.startsWith('--') && eq !== -1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ParsedArgs } from '../process/commands.js';
|
|
2
2
|
declare const PAT_SCOPES: readonly ["repo:read", "repo:write", "repo:admin"];
|
|
3
3
|
type PatScope = (typeof PAT_SCOPES)[number];
|
|
4
|
-
export declare const AUTH_USAGE = "usage:\n seq-studio auth pat create --name <n> [--scopes repo:read,repo:write] [-e env]\n [--expires 7d|30d|90d|1y|never] [--store-credentials]\n seq-studio auth pat list [-e env]\n seq-studio auth pat revoke <id> [-e env] [--yes]\n\n Issue a personal access token for git clone / git push against the platform\n git service.\n\n
|
|
4
|
+
export declare const AUTH_USAGE = "usage:\n seq-studio auth pat create --name <n> [--scopes repo:read,repo:write] [-e env]\n [--expires 7d|30d|90d|1y|never] [--store-credentials]\n seq-studio auth pat list [-e env]\n seq-studio auth pat revoke <id> [-e env] [--yes]\n\n Issue a personal access token for git clone / git push against the platform\n git service.\n\n Authenticate with `seq-studio login`, then run `auth pat create`.\n Alternatively, open Atlas \u2192 Settings \u2192 Tokens:\n https://<atlas-host>/settings/tokens\n Sign in, create a token (repo:read / repo:write), copy once, then:\n export ATLAS_GIT_PAT=<token>\n\n On create the raw token is printed ONCE \u2014 store it; Atlas cannot re-show it.\n Git Basic auth: any username (e.g. git), PAT as the password.\n\n Flags: -e/--env <env> (see: seq-studio envs list)\n";
|
|
5
5
|
export declare function parsePatScopes(raw: string | undefined): PatScope[];
|
|
6
6
|
/**
|
|
7
7
|
* Map UI-style duration choices to an absolute ISO-8601 expiresAt, or undefined
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `seq-studio auth <sub>` — PAT management for the platform git service.
|
|
3
3
|
*
|
|
4
|
-
* Issues / lists / revokes Personal Access Tokens via Auth0 bearer auth
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* help text and error hints lead with that path.
|
|
4
|
+
* Issues / lists / revokes Personal Access Tokens via Auth0 bearer auth.
|
|
5
|
+
* Users authenticate with `seq-studio login`; the Atlas UI at
|
|
6
|
+
* `/settings/tokens` remains an alternative.
|
|
8
7
|
*
|
|
9
8
|
* The raw token is printed exactly once on create; use it as the Basic-auth
|
|
10
9
|
* password for `git clone` / `git push` (username is ignored).
|
|
@@ -12,9 +11,23 @@
|
|
|
12
11
|
import { deleteNoContent, getJson, postJson } from '../atlas-client.js';
|
|
13
12
|
import { printCliError } from '../cli-errors.js';
|
|
14
13
|
import { buildContext, clientOptions, flagBool, LOG, } from '../functions/commands.js';
|
|
14
|
+
import { readConfig } from '../config.js';
|
|
15
15
|
import { confirmYes } from '../prompt.js';
|
|
16
16
|
import { storeGitCredentials } from '../repos/git-clone.js';
|
|
17
17
|
import { formatPatSetupHint, settingsTokensUrl } from '../pat-hints.js';
|
|
18
|
+
/**
|
|
19
|
+
* Tokens-page URLs rendered from the environments visible to this identity
|
|
20
|
+
* (discovered catalog + config.toml). Anonymous users get a generic pointer
|
|
21
|
+
* instead of a hardcoded list of internal hosts.
|
|
22
|
+
*/
|
|
23
|
+
async function settingsTokensLines() {
|
|
24
|
+
const config = await readConfig().catch(() => null);
|
|
25
|
+
const remote = Object.entries(config?.envs ?? {}).filter(([name]) => name !== 'local');
|
|
26
|
+
if (remote.length === 0) {
|
|
27
|
+
return ['Tokens page: <your Atlas deployment URL>/settings/tokens'];
|
|
28
|
+
}
|
|
29
|
+
return remote.map(([name, { url }]) => `${name}: ${settingsTokensUrl(url)}`);
|
|
30
|
+
}
|
|
18
31
|
const PAT_SCOPES = ['repo:read', 'repo:write', 'repo:admin'];
|
|
19
32
|
function stringFlag(flags, key) {
|
|
20
33
|
const value = flags[key];
|
|
@@ -33,37 +46,35 @@ export const AUTH_USAGE = `usage:
|
|
|
33
46
|
Issue a personal access token for git clone / git push against the platform
|
|
34
47
|
git service.
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
Authenticate with \`seq-studio login\`, then run \`auth pat create\`.
|
|
50
|
+
Alternatively, open Atlas → Settings → Tokens:
|
|
37
51
|
https://<atlas-host>/settings/tokens
|
|
38
52
|
Sign in, create a token (repo:read / repo:write), copy once, then:
|
|
39
53
|
export ATLAS_GIT_PAT=<token>
|
|
40
54
|
|
|
41
|
-
Sequence staff: \`seqapi login\` then \`auth pat create\` (same Auth0 session
|
|
42
|
-
as the Atlas UI). The CLI cannot mint a PAT without that login.
|
|
43
|
-
|
|
44
55
|
On create the raw token is printed ONCE — store it; Atlas cannot re-show it.
|
|
45
56
|
Git Basic auth: any username (e.g. git), PAT as the password.
|
|
46
57
|
|
|
47
|
-
Flags: -e/--env <
|
|
58
|
+
Flags: -e/--env <env> (see: seq-studio envs list)
|
|
48
59
|
`;
|
|
49
60
|
async function authContext(args) {
|
|
50
61
|
if (args.flags.env === true || args.flags.e === true) {
|
|
51
|
-
throw new Error('-e/--env requires a value
|
|
62
|
+
throw new Error('-e/--env requires a value — see: seq-studio envs list.');
|
|
52
63
|
}
|
|
53
64
|
try {
|
|
54
65
|
return await buildContext(args);
|
|
55
66
|
}
|
|
56
67
|
catch (err) {
|
|
57
68
|
const message = err instanceof Error ? err.message : String(err);
|
|
58
|
-
//
|
|
59
|
-
//
|
|
69
|
+
// buildContext failed before we know the env URL; offer CLI login and
|
|
70
|
+
// render the tokens-page URLs from the environments visible to this
|
|
71
|
+
// identity (third-party / OpCo developers only see their own hosts).
|
|
72
|
+
const tokenUrls = await settingsTokensLines();
|
|
60
73
|
throw new Error(`${message}\n` +
|
|
61
|
-
`
|
|
74
|
+
` Run \`seq-studio login\`, then retry \`auth pat create\`.\n` +
|
|
75
|
+
` Or mint a PAT in the Atlas UI (Settings → Tokens), then:\n` +
|
|
62
76
|
` export ATLAS_GIT_PAT=<token>\n` +
|
|
63
|
-
`
|
|
64
|
-
` Production: https://atlas.seqholdings.com/settings/tokens\n` +
|
|
65
|
-
` BankSouth: https://banksouth.seqholdings.com/settings/tokens\n` +
|
|
66
|
-
` Sequence staff: run \`seqapi login\`, then retry \`auth pat create\`.`);
|
|
77
|
+
tokenUrls.map((line) => ` ${line}`).join('\n'));
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
export function parsePatScopes(raw) {
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* seq-studio and seqapi share one token file and one Auth0 application:
|
|
3
|
+
* same tenant, client id, audience, and token shape. Either CLI can log in,
|
|
4
|
+
* and the resulting access token validates against every Atlas /api/* route.
|
|
5
|
+
*
|
|
6
|
+
* Two token sources, in the SAME precedence order as seqapi's
|
|
7
|
+
* `get_access_token` (`shared/seqapi/seqapi/auth.py`):
|
|
8
|
+
* 1. M2M service account — Auth0 client-credentials grant, used when
|
|
9
|
+
* `AUTH0_M2M_CLIENT_SECRET` is set. This is the headless path: CI /
|
|
10
|
+
* cloud agents with no interactive login can still push.
|
|
11
|
+
* (M2M carries app scopes but NO user identity / workspace membership
|
|
12
|
+
* — see the `atlas-test-access` rule.)
|
|
13
|
+
* 2. Cached user token — read from the seqapi token file and refreshed
|
|
14
|
+
* via the Auth0 refresh-token grant when near expiry.
|
|
15
|
+
*
|
|
16
|
+
* Login and refresh both write the shared file. This mirrors
|
|
17
|
+
* `seqapi._save_tokens` exactly:
|
|
18
|
+
* same fields, same shape, same 0o600 permissions, atomic write via
|
|
19
|
+
* tmpfile + rename. The M2M token is in-memory only (never persisted).
|
|
20
|
+
*/
|
|
21
|
+
export declare const AUTH0_DOMAIN = "dev-n1t8ts403fp8oyxp.us.auth0.com";
|
|
22
|
+
export declare const AUTH0_CLIENT_ID = "GD9riCDWocfc66odpWBjwBiX43qqAX8r";
|
|
23
|
+
export declare const AUTH0_AUDIENCE = "https://api.studio.com";
|
|
24
|
+
export interface SeqapiTokens {
|
|
25
|
+
refresh_token?: string;
|
|
26
|
+
access_token?: string;
|
|
27
|
+
expires_at?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A configured M2M credential failed to mint a token. Typed so callers that
|
|
31
|
+
* normally swallow discovery errors (lazy catalog refresh) can still surface
|
|
32
|
+
* broken CI credentials instead of a misleading "unknown env" message.
|
|
33
|
+
*/
|
|
34
|
+
export declare class M2mTokenError extends Error {
|
|
35
|
+
readonly name = "M2mTokenError";
|
|
36
|
+
}
|
|
1
37
|
export declare function seqapiTokenDir(): string;
|
|
2
38
|
export declare function seqapiTokenPath(): string;
|
|
3
39
|
export declare class NotLoggedInError extends Error {
|
|
@@ -24,3 +60,16 @@ export declare function tryGetAccessToken(options?: {
|
|
|
24
60
|
*/
|
|
25
61
|
failClosedForM2m?: boolean;
|
|
26
62
|
}): Promise<string | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Decode the `sub` claim from a JWT without verification. Verification is the
|
|
65
|
+
* server's job — this is only used to compare identities locally (e.g. to
|
|
66
|
+
* bind the cached environment catalog to the identity that fetched it).
|
|
67
|
+
*/
|
|
68
|
+
export declare function decodeJwtSub(token: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* The Auth0 subject the CLI would authenticate as right now, without any
|
|
71
|
+
* network call: the fixed M2M client subject when the secret is configured,
|
|
72
|
+
* else the `sub` of the cached user token, else null (anonymous).
|
|
73
|
+
*/
|
|
74
|
+
export declare function currentIdentitySubject(): Promise<string | null>;
|
|
75
|
+
export declare function saveTokens(tokens: SeqapiTokens): Promise<void>;
|