indraq_cli 2.2.1 → 2.2.2

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.
Files changed (41) hide show
  1. package/CHANGELOG_v2.1.0.md +27 -0
  2. package/CHANGELOG_v2.2.0.md +60 -0
  3. package/CHANGELOG_v2.2.1.md +19 -0
  4. package/CHANGELOG_v2.2.2.md +18 -0
  5. package/README.md +27 -14
  6. package/RELEASE_CHECKS_v2.2.0.md +27 -0
  7. package/RELEASE_CHECKS_v2.2.2.md +26 -0
  8. package/TEST_RESULTS_v2.2.1.txt +11 -0
  9. package/TEST_RESULTS_v2.2.2.txt +39 -0
  10. package/UPGRADE_v2.0_to_v2.1.md +68 -0
  11. package/UPGRADE_v2.1_to_v2.2.md +62 -0
  12. package/UPGRADE_v2.2.1_to_v2.2.2.md +15 -0
  13. package/cloud-api/README.md +101 -93
  14. package/cloud-api/dist/index.js +62 -7
  15. package/cloud-api/node_modules/.package-lock.json +1 -1
  16. package/cloud-api/package-lock.json +2 -2
  17. package/cloud-api/package.json +1 -1
  18. package/cloud-api/sql/010_persistent_auth_sessions.sql +12 -0
  19. package/cloud-api/src/index.ts +71 -9
  20. package/dist/cli/help-content.d.ts.map +1 -1
  21. package/dist/cli/help-content.js +27 -22
  22. package/dist/cli/help-content.js.map +1 -1
  23. package/dist/modules/cloud/commands/cloud.command.d.ts +1 -1
  24. package/dist/modules/cloud/commands/cloud.command.d.ts.map +1 -1
  25. package/dist/modules/cloud/commands/cloud.command.js +8 -4
  26. package/dist/modules/cloud/commands/cloud.command.js.map +1 -1
  27. package/dist/modules/cloud/config/cloud-config.d.ts +1 -0
  28. package/dist/modules/cloud/config/cloud-config.d.ts.map +1 -1
  29. package/dist/modules/cloud/config/cloud-config.js +14 -13
  30. package/dist/modules/cloud/config/cloud-config.js.map +1 -1
  31. package/dist/modules/cloud/services/cloud-api.service.d.ts +6 -0
  32. package/dist/modules/cloud/services/cloud-api.service.d.ts.map +1 -1
  33. package/dist/modules/cloud/services/cloud-api.service.js +56 -5
  34. package/dist/modules/cloud/services/cloud-api.service.js.map +1 -1
  35. package/dist/modules/deploy/index.d.ts.map +1 -1
  36. package/dist/modules/deploy/index.js +44 -7
  37. package/dist/modules/deploy/index.js.map +1 -1
  38. package/dist/modules/scopes/commands/scope.command.d.ts.map +1 -1
  39. package/dist/modules/scopes/commands/scope.command.js +8 -4
  40. package/dist/modules/scopes/commands/scope.command.js.map +1 -1
  41. package/package.json +14 -5
@@ -1,93 +1,101 @@
1
- # IndraQ Cloud API
2
-
3
- Control plane for IndraQ CLI v2.1. The public CLI endpoint is `https://cli.indraq.com`.
4
-
5
- ## Responsibilities
6
-
7
- - authentication and organization RBAC (`user`, `manager`, `admin`)
8
- - provider scopes for AWS/NPM/Jenkins/GHCR
9
- - scope USE/MANAGE membership and audited delegation
10
- - encrypted provider scope secrets
11
- - per-user scope defaults
12
- - per-project/environment scope bindings
13
- - scope-native AWS/NPM/Jenkins identity mappings
14
- - self-service AWS key rotation and password lifecycle
15
- - projects, environments and project resource grants
16
- - audit logging
17
-
18
- ## Requirements
19
-
20
- - Node.js 22+
21
- - PostgreSQL
22
-
23
- ## Configure
24
-
25
- ```bash
26
- npm install
27
- cp .env.example .env
28
- ```
29
-
30
- Required values include:
31
-
32
- ```env
33
- PORT=4010
34
- DATABASE_URL=postgresql://indraq:change-me@127.0.0.1:5432/indraq_cloud
35
- JWT_SECRET=replace-with-at-least-32-random-characters
36
- MASTER_KEY=replace-with-exactly-64-hex-characters
37
- BOOTSTRAP_ADMIN_EMAIL=admin@example.com
38
- BOOTSTRAP_ADMIN_PASSWORD=use-a-strong-password
39
- ```
40
-
41
- `MASTER_KEY` must remain stable because provider scope secrets use AES-256-GCM encryption.
42
-
43
- ## Build / run
44
-
45
- ```bash
46
- npm run build
47
- npm start
48
- ```
49
-
50
- Development:
51
-
52
- ```bash
53
- npm run dev
54
- ```
55
-
56
- Numbered migrations in `sql/` are applied automatically at startup. v2.1 adds:
57
-
58
- ```text
59
- 008_provider_scopes.sql
60
- ```
61
-
62
- ## v2.1 authorization model
63
-
64
- - **Admin** — creates/deletes scopes, grants MANAGE/USE, creates managers/admins, changes organization roles.
65
- - **Manager** — can add/remove normal users only inside scopes where the manager has MANAGE.
66
- - **User** — can use only assigned scopes and rotate only their own managed provider credentials.
67
-
68
- Scope membership operations and secret resolution require an audit reason and are written to `audit_logs`.
69
-
70
- ## Provider scopes
71
-
72
- `provider_scopes` stores named organization accounts/controllers. `provider_scope_members` controls visibility and use. A user cannot discover a scope simply because it exists.
73
-
74
- Related v2.1 tables:
75
-
76
- - `provider_scopes`
77
- - `provider_scope_members`
78
- - `user_scope_defaults`
79
- - `project_environment_scopes`
80
- - `scope_external_accounts`
81
- - `scope_aws_credentials`
82
-
83
- ## Projects
84
-
85
- Projects and infrastructure are durable. Deleting a user removes scope memberships/native identity mappings and the Cloud identity but retains shared project/environment/resource definitions. Ownership is reassigned before hard deletion where required.
86
-
87
- ## Security notes
88
-
89
- - Passwords are bcrypt-hashed.
90
- - Provider secrets are encrypted with AES-256-GCM.
91
- - Secret-resolution responses are `Cache-Control: no-store` and audited.
92
- - Role/account state is re-read from PostgreSQL on protected requests so demotion/disable takes effect without waiting for JWT expiry.
93
- - Keep bootstrap credentials and `MASTER_KEY` out of source control.
1
+ # IndraQ Cloud API
2
+
3
+ Control plane for IndraQ CLI v2.2.2. The public CLI endpoint is `https://cli.indraq.com`.
4
+
5
+ ## Responsibilities
6
+
7
+ - persistent CLI authentication sessions and organization RBAC (`user`, `manager`, `admin`)
8
+ - provider scopes for AWS/NPM/Jenkins/GHCR
9
+ - scope USE/MANAGE membership and audited delegation
10
+ - encrypted provider scope secrets
11
+ - per-user scope defaults
12
+ - per-project/environment scope bindings
13
+ - scope-native AWS/NPM/Jenkins identity mappings
14
+ - self-service AWS key rotation and password lifecycle
15
+ - projects, environments and project resource grants
16
+ - audit logging
17
+
18
+ ## Requirements
19
+
20
+ - Node.js 22+
21
+ - PostgreSQL
22
+
23
+ ## Configure
24
+
25
+ ```bash
26
+ npm install
27
+ cp .env.example .env
28
+ ```
29
+
30
+ Required values include:
31
+
32
+ ```env
33
+ PORT=4010
34
+ DATABASE_URL=postgresql://indraq:change-me@127.0.0.1:5432/indraq_cloud
35
+ JWT_SECRET=replace-with-at-least-32-random-characters
36
+ MASTER_KEY=replace-with-exactly-64-hex-characters
37
+ BOOTSTRAP_ADMIN_EMAIL=admin@example.com
38
+ BOOTSTRAP_ADMIN_PASSWORD=use-a-strong-password
39
+ ```
40
+
41
+ `MASTER_KEY` must remain stable because provider scope secrets use AES-256-GCM encryption.
42
+
43
+ ## Build / run
44
+
45
+ ```bash
46
+ npm run build
47
+ npm start
48
+ ```
49
+
50
+ Development:
51
+
52
+ ```bash
53
+ npm run dev
54
+ ```
55
+
56
+ Numbered migrations in `sql/` are applied automatically at startup. Current scope/session migrations include:
57
+
58
+ ```text
59
+ 008_provider_scopes.sql
60
+ 009_scope_user_secrets_and_project_lifecycle.sql
61
+ 010_persistent_auth_sessions.sql
62
+ ```
63
+
64
+ ## Authorization model
65
+
66
+ - **Admin** — creates/deletes scopes, grants MANAGE/USE, creates managers/admins, changes organization roles.
67
+ - **Manager** — can add/remove normal users only inside scopes where the manager has MANAGE.
68
+ - **User** can use only assigned scopes and rotate only their own managed provider credentials.
69
+
70
+ Scope membership operations and secret resolution require an audit reason and are written to `audit_logs`.
71
+
72
+ ## Provider scopes
73
+
74
+ `provider_scopes` stores named organization accounts/controllers. `provider_scope_members` controls visibility and use. A user cannot discover a scope simply because it exists.
75
+
76
+ Related scope tables:
77
+
78
+ - `provider_scopes`
79
+ - `provider_scope_members`
80
+ - `user_scope_defaults`
81
+ - `project_environment_scopes`
82
+ - `scope_external_accounts`
83
+ - `scope_aws_credentials`
84
+
85
+ ## Projects
86
+
87
+ Projects and infrastructure are durable. Deleting a user removes scope memberships/native identity mappings and the Cloud identity but retains shared project/environment/resource definitions. Ownership is reassigned before hard deletion where required.
88
+
89
+ ## Persistent CLI sessions
90
+
91
+ A successful `/auth/login` creates a server-side `auth_sessions` row and returns an access JWT plus an opaque refresh token. The database stores only a SHA-256 hash of the refresh token. The CLI automatically calls `/auth/refresh` after access-token expiry and retries the original request, so normal users remain signed in until they explicitly logout or a security event revokes the session.
92
+
93
+ `/auth/logout` revokes the session. Session-backed access JWTs include a session ID and the auth middleware checks that the corresponding session has not been revoked on every protected request. Still-valid legacy v2.2.1 JWTs may use `/auth/session` once to upgrade to the persistent model without another password prompt.
94
+
95
+ ## Security notes
96
+
97
+ - Passwords are bcrypt-hashed.
98
+ - Provider secrets are encrypted with AES-256-GCM.
99
+ - Secret-resolution responses are `Cache-Control: no-store` and audited.
100
+ - Role/account state is re-read from PostgreSQL on protected requests so demotion/disable takes effect without waiting for JWT expiry.
101
+ - Keep bootstrap credentials and `MASTER_KEY` out of source control.
@@ -5,7 +5,7 @@ import jwt from 'jsonwebtoken';
5
5
  import pg from 'pg';
6
6
  import nodemailer from 'nodemailer';
7
7
  import { IAMClient, CreateAccessKeyCommand, DeleteAccessKeyCommand, ListAccessKeysCommand } from '@aws-sdk/client-iam';
8
- import { createCipheriv, createDecipheriv, randomBytes, randomInt } from 'node:crypto';
8
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, randomInt } from 'node:crypto';
9
9
  import { isIP } from 'node:net';
10
10
  import { readdir, readFile } from 'node:fs/promises';
11
11
  import { dirname, resolve } from 'node:path';
@@ -21,6 +21,13 @@ const masterHex = process.env.MASTER_KEY || '';
21
21
  if (!/^[a-fA-F0-9]{64}$/.test(masterHex))
22
22
  throw new Error('MASTER_KEY must be exactly 64 hex characters (32 bytes).');
23
23
  const masterKey = Buffer.from(masterHex, 'hex');
24
+ const accessTokenFor = (user, sessionId) => jwt.sign({ id: user.id, email: user.email, role: user.role, sid: sessionId }, jwtSecret, { expiresIn: '1h' });
25
+ const refreshHash = (token) => createHash('sha256').update(token).digest('hex');
26
+ const createAuthSession = async (userId) => {
27
+ const refreshToken = randomBytes(48).toString('base64url');
28
+ const result = await pool.query('INSERT INTO auth_sessions(user_id,refresh_token_hash) VALUES($1,$2) RETURNING id', [userId, refreshHash(refreshToken)]);
29
+ return { sessionId: result.rows[0].id, refreshToken };
30
+ };
24
31
  const encrypt = (text) => {
25
32
  const iv = randomBytes(12);
26
33
  const cipher = createCipheriv('aes-256-gcm', masterKey, iv);
@@ -49,15 +56,21 @@ const auth = async (req, res, next) => {
49
56
  return;
50
57
  }
51
58
  // Roles and account state are resolved from the database on every request.
52
- // This makes admin/manager demotion and account disabling effective
53
- // immediately instead of trusting a stale role embedded in a 12-hour JWT.
54
- const current = await pool.query('SELECT id,email,name,role,is_active,must_change_password FROM users WHERE id=$1', [token.id]);
59
+ // Session-backed tokens also verify server-side revocation on every request,
60
+ // so `indraq logout` takes effect immediately. Legacy tokens without sid are
61
+ // temporarily accepted so a v2.2.1 CLI session can upgrade without re-login.
62
+ const current = token.sid
63
+ ? await pool.query(`SELECT u.id,u.email,u.name,u.role,u.is_active,u.must_change_password
64
+ FROM users u
65
+ JOIN auth_sessions s ON s.id=$2 AND s.user_id=u.id AND s.revoked_at IS NULL
66
+ WHERE u.id=$1`, [token.id, token.sid])
67
+ : await pool.query('SELECT id,email,name,role,is_active,must_change_password FROM users WHERE id=$1', [token.id]);
55
68
  const user = current.rows[0];
56
69
  if (!user || !user.is_active || !['user', 'manager', 'admin'].includes(user.role)) {
57
70
  res.status(401).json({ error: 'Account is disabled or no longer available' });
58
71
  return;
59
72
  }
60
- req.auth = { id: user.id, email: user.email, name: user.name, role: user.role, mustChangePassword: !!user.must_change_password };
73
+ req.auth = { id: user.id, email: user.email, name: user.name, role: user.role, mustChangePassword: !!user.must_change_password, ...(token.sid ? { sessionId: token.sid } : {}) };
61
74
  if (user.must_change_password && !['/auth/me', '/auth/first-login-password'].includes(req.path)) {
62
75
  res.status(428).json({ error: 'Temporary password must be changed before using IndraQ. Run `indraq login` and complete the first-login password change.' });
63
76
  return;
@@ -467,8 +480,46 @@ app.post('/auth/login', async (req, res) => {
467
480
  return;
468
481
  }
469
482
  await pool.query('UPDATE users SET last_login_at=now() WHERE id=$1', [user.id]);
470
- const token = jwt.sign({ id: user.id, email: user.email, role: user.role }, jwtSecret, { expiresIn: '12h' });
471
- res.json({ token, user: { id: user.id, email: user.email, name: user.name, role: user.role, mustChangePassword: !!user.must_change_password } });
483
+ const session = await createAuthSession(user.id);
484
+ const token = accessTokenFor({ id: user.id, email: user.email, role: user.role }, session.sessionId);
485
+ res.setHeader('Cache-Control', 'no-store');
486
+ res.json({ token, refreshToken: session.refreshToken, user: { id: user.id, email: user.email, name: user.name, role: user.role, mustChangePassword: !!user.must_change_password } });
487
+ });
488
+ app.post('/auth/session', auth, async (req, res) => {
489
+ if (req.auth.sessionId) {
490
+ res.status(409).json({ error: 'This access token already belongs to a persistent session.' });
491
+ return;
492
+ }
493
+ const session = await createAuthSession(req.auth.id);
494
+ const token = accessTokenFor({ id: req.auth.id, email: req.auth.email, role: req.auth.role }, session.sessionId);
495
+ res.setHeader('Cache-Control', 'no-store');
496
+ res.json({ token, refreshToken: session.refreshToken });
497
+ });
498
+ app.post('/auth/refresh', async (req, res) => {
499
+ const refreshToken = String(req.body?.refreshToken || '');
500
+ if (!refreshToken) {
501
+ res.status(401).json({ error: 'Refresh token required' });
502
+ return;
503
+ }
504
+ const result = await pool.query(`SELECT s.id AS session_id,u.id,u.email,u.role,u.is_active
505
+ FROM auth_sessions s
506
+ JOIN users u ON u.id=s.user_id
507
+ WHERE s.refresh_token_hash=$1 AND s.revoked_at IS NULL`, [refreshHash(refreshToken)]);
508
+ const row = result.rows[0];
509
+ if (!row || !row.is_active) {
510
+ res.status(401).json({ error: 'Session is no longer valid' });
511
+ return;
512
+ }
513
+ await pool.query('UPDATE auth_sessions SET last_used_at=now() WHERE id=$1', [row.session_id]);
514
+ const token = accessTokenFor({ id: row.id, email: row.email, role: row.role }, row.session_id);
515
+ res.setHeader('Cache-Control', 'no-store');
516
+ res.json({ token, refreshToken });
517
+ });
518
+ app.post('/auth/logout', async (req, res) => {
519
+ const refreshToken = String(req.body?.refreshToken || '');
520
+ if (refreshToken)
521
+ await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE refresh_token_hash=$1 AND revoked_at IS NULL', [refreshHash(refreshToken)]);
522
+ res.status(204).end();
472
523
  });
473
524
  app.get('/auth/me', auth, (req, res) => res.json(req.auth));
474
525
  app.post('/auth/first-login-password', auth, async (req, res) => {
@@ -488,6 +539,8 @@ app.post('/auth/first-login-password', auth, async (req, res) => {
488
539
  return;
489
540
  }
490
541
  await pool.query('UPDATE users SET password_hash=$1,must_change_password=false,password_changed_at=now(),updated_at=now() WHERE id=$2', [await bcrypt.hash(newPassword, 12), req.auth.id]);
542
+ if (req.auth.sessionId)
543
+ await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE user_id=$1 AND id<>$2 AND revoked_at IS NULL', [req.auth.id, req.auth.sessionId]);
491
544
  await audit(req.auth.id, 'user.password.first-login', 'user', req.auth.id, 'Required first-login password change', { providers: external.map((item) => item.provider) });
492
545
  res.json({ ok: true, results: [{ provider: 'cloud', ok: true }, ...external] });
493
546
  });
@@ -553,6 +606,7 @@ app.post('/auth/password-reset/verify', async (req, res) => {
553
606
  results.push(...await resetManagedExternalPasswords(user.id, newPassword, targets));
554
607
  if (targets.includes('cloud')) {
555
608
  await pool.query('UPDATE users SET password_hash=$1,must_change_password=false,password_changed_at=now(),updated_at=now() WHERE id=$2', [await bcrypt.hash(newPassword, 12), user.id]);
609
+ await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE user_id=$1 AND revoked_at IS NULL', [user.id]);
556
610
  results.unshift({ provider: 'cloud', ok: true });
557
611
  }
558
612
  await pool.query('UPDATE password_reset_otps SET used_at=now() WHERE id=$1', [row.id]);
@@ -606,6 +660,7 @@ app.patch('/users/:id/password', ...allow('manager', 'admin'), async (req, res)
606
660
  return;
607
661
  }
608
662
  await pool.query('UPDATE users SET password_hash=$1,must_change_password=true,updated_at=now() WHERE id=$2', [await bcrypt.hash(password, 12), id]);
663
+ await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE user_id=$1 AND revoked_at IS NULL', [id]);
609
664
  await audit(req.auth.id, 'user.password.update', 'user', id, reason);
610
665
  res.status(204).end();
611
666
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indraq/cloud-api",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@indraq/cloud-api",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@indraq/cloud-api",
9
- "version": "0.7.0",
9
+ "version": "0.8.0",
10
10
  "dependencies": {
11
11
  "@aws-sdk/client-iam": "^3.1123.0",
12
12
  "bcryptjs": "^3.0.2",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@indraq/cloud-api",
3
3
  "private": true,
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "tsx watch src/index.ts",
@@ -0,0 +1,12 @@
1
+ CREATE TABLE IF NOT EXISTS auth_sessions (
2
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
3
+ user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
4
+ refresh_token_hash text NOT NULL UNIQUE,
5
+ created_at timestamptz NOT NULL DEFAULT now(),
6
+ last_used_at timestamptz NOT NULL DEFAULT now(),
7
+ revoked_at timestamptz
8
+ );
9
+
10
+ CREATE INDEX IF NOT EXISTS auth_sessions_user_active_idx
11
+ ON auth_sessions(user_id)
12
+ WHERE revoked_at IS NULL;
@@ -5,7 +5,7 @@ import jwt from 'jsonwebtoken';
5
5
  import pg from 'pg';
6
6
  import nodemailer from 'nodemailer';
7
7
  import { IAMClient, CreateAccessKeyCommand, DeleteAccessKeyCommand, ListAccessKeysCommand } from '@aws-sdk/client-iam';
8
- import { createCipheriv, createDecipheriv, randomBytes, randomInt } from 'node:crypto';
8
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, randomInt } from 'node:crypto';
9
9
  import { isIP } from 'node:net';
10
10
  import { readdir, readFile } from 'node:fs/promises';
11
11
  import { dirname, resolve } from 'node:path';
@@ -23,7 +23,22 @@ if (!/^[a-fA-F0-9]{64}$/.test(masterHex)) throw new Error('MASTER_KEY must be ex
23
23
  const masterKey = Buffer.from(masterHex, 'hex');
24
24
 
25
25
  type Role = 'user' | 'manager' | 'admin';
26
- type Auth = { id: string; email: string; name: string; role: Role; mustChangePassword: boolean };
26
+ type Auth = { id: string; email: string; name: string; role: Role; mustChangePassword: boolean; sessionId?: string };
27
+ type AccessClaims = { id?: string; sid?: string };
28
+
29
+ const accessTokenFor = (user: { id: string; email: string; role: Role }, sessionId: string): string =>
30
+ jwt.sign({ id: user.id, email: user.email, role: user.role, sid: sessionId }, jwtSecret, { expiresIn: '1h' });
31
+
32
+ const refreshHash = (token: string): string => createHash('sha256').update(token).digest('hex');
33
+
34
+ const createAuthSession = async (userId: string): Promise<{ sessionId: string; refreshToken: string }> => {
35
+ const refreshToken = randomBytes(48).toString('base64url');
36
+ const result = await pool.query(
37
+ 'INSERT INTO auth_sessions(user_id,refresh_token_hash) VALUES($1,$2) RETURNING id',
38
+ [userId, refreshHash(refreshToken)]
39
+ );
40
+ return { sessionId: result.rows[0].id, refreshToken };
41
+ };
27
42
  interface AuthedRequest extends Request { auth?: Auth }
28
43
 
29
44
  const encrypt = (text: string): string => {
@@ -46,17 +61,26 @@ const auth = async (req: AuthedRequest, res: Response, next: NextFunction): Prom
46
61
  const header = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
47
62
  if (!header?.startsWith('Bearer ')) { res.status(401).json({ error: 'Authentication required' }); return; }
48
63
  try {
49
- const token = jwt.verify(header.slice(7), jwtSecret) as Partial<Auth>;
64
+ const token = jwt.verify(header.slice(7), jwtSecret) as AccessClaims;
50
65
  if (!token.id) { res.status(401).json({ error: 'Invalid or expired token' }); return; }
51
66
  // Roles and account state are resolved from the database on every request.
52
- // This makes admin/manager demotion and account disabling effective
53
- // immediately instead of trusting a stale role embedded in a 12-hour JWT.
54
- const current = await pool.query('SELECT id,email,name,role,is_active,must_change_password FROM users WHERE id=$1', [token.id]);
67
+ // Session-backed tokens also verify server-side revocation on every request,
68
+ // so `indraq logout` takes effect immediately. Legacy tokens without sid are
69
+ // temporarily accepted so a v2.2.1 CLI session can upgrade without re-login.
70
+ const current = token.sid
71
+ ? await pool.query(
72
+ `SELECT u.id,u.email,u.name,u.role,u.is_active,u.must_change_password
73
+ FROM users u
74
+ JOIN auth_sessions s ON s.id=$2 AND s.user_id=u.id AND s.revoked_at IS NULL
75
+ WHERE u.id=$1`,
76
+ [token.id, token.sid]
77
+ )
78
+ : await pool.query('SELECT id,email,name,role,is_active,must_change_password FROM users WHERE id=$1', [token.id]);
55
79
  const user = current.rows[0];
56
80
  if (!user || !user.is_active || !['user','manager','admin'].includes(user.role)) {
57
81
  res.status(401).json({ error: 'Account is disabled or no longer available' }); return;
58
82
  }
59
- req.auth = { id: user.id, email: user.email, name: user.name, role: user.role as Role, mustChangePassword: !!user.must_change_password };
83
+ req.auth = { id: user.id, email: user.email, name: user.name, role: user.role as Role, mustChangePassword: !!user.must_change_password, ...(token.sid ? { sessionId: token.sid } : {}) };
60
84
  if (user.must_change_password && !['/auth/me','/auth/first-login-password'].includes(req.path)) {
61
85
  res.status(428).json({ error: 'Temporary password must be changed before using IndraQ. Run `indraq login` and complete the first-login password change.' }); return;
62
86
  }
@@ -515,9 +539,44 @@ app.post('/auth/login', async (req, res) => {
515
539
  res.status(401).json({ error: 'Invalid credentials' }); return;
516
540
  }
517
541
  await pool.query('UPDATE users SET last_login_at=now() WHERE id=$1', [user.id]);
518
- const token = jwt.sign({ id: user.id, email: user.email, role: user.role }, jwtSecret, { expiresIn: '12h' });
519
- res.json({ token, user: { id: user.id, email: user.email, name: user.name, role: user.role, mustChangePassword: !!user.must_change_password } });
542
+ const session = await createAuthSession(user.id);
543
+ const token = accessTokenFor({ id: user.id, email: user.email, role: user.role as Role }, session.sessionId);
544
+ res.setHeader('Cache-Control', 'no-store');
545
+ res.json({ token, refreshToken: session.refreshToken, user: { id: user.id, email: user.email, name: user.name, role: user.role, mustChangePassword: !!user.must_change_password } });
546
+ });
547
+
548
+ app.post('/auth/session', auth, async (req: AuthedRequest, res) => {
549
+ if (req.auth!.sessionId) { res.status(409).json({ error: 'This access token already belongs to a persistent session.' }); return; }
550
+ const session = await createAuthSession(req.auth!.id);
551
+ const token = accessTokenFor({ id: req.auth!.id, email: req.auth!.email, role: req.auth!.role }, session.sessionId);
552
+ res.setHeader('Cache-Control', 'no-store');
553
+ res.json({ token, refreshToken: session.refreshToken });
520
554
  });
555
+
556
+ app.post('/auth/refresh', async (req, res) => {
557
+ const refreshToken = String(req.body?.refreshToken || '');
558
+ if (!refreshToken) { res.status(401).json({ error: 'Refresh token required' }); return; }
559
+ const result = await pool.query(
560
+ `SELECT s.id AS session_id,u.id,u.email,u.role,u.is_active
561
+ FROM auth_sessions s
562
+ JOIN users u ON u.id=s.user_id
563
+ WHERE s.refresh_token_hash=$1 AND s.revoked_at IS NULL`,
564
+ [refreshHash(refreshToken)]
565
+ );
566
+ const row = result.rows[0];
567
+ if (!row || !row.is_active) { res.status(401).json({ error: 'Session is no longer valid' }); return; }
568
+ await pool.query('UPDATE auth_sessions SET last_used_at=now() WHERE id=$1', [row.session_id]);
569
+ const token = accessTokenFor({ id: row.id, email: row.email, role: row.role as Role }, row.session_id);
570
+ res.setHeader('Cache-Control', 'no-store');
571
+ res.json({ token, refreshToken });
572
+ });
573
+
574
+ app.post('/auth/logout', async (req, res) => {
575
+ const refreshToken = String(req.body?.refreshToken || '');
576
+ if (refreshToken) await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE refresh_token_hash=$1 AND revoked_at IS NULL', [refreshHash(refreshToken)]);
577
+ res.status(204).end();
578
+ });
579
+
521
580
  app.get('/auth/me', auth, (req: AuthedRequest, res) => res.json(req.auth));
522
581
 
523
582
  app.post('/auth/first-login-password', auth, async (req: AuthedRequest, res) => {
@@ -528,6 +587,7 @@ app.post('/auth/first-login-password', auth, async (req: AuthedRequest, res) =>
528
587
  const failed = external.filter((item) => !item.ok);
529
588
  if (failed.length) { res.status(502).json({ error: 'Could not rotate the temporary password on every managed provider. Retry or ask an admin.', results: external }); return; }
530
589
  await pool.query('UPDATE users SET password_hash=$1,must_change_password=false,password_changed_at=now(),updated_at=now() WHERE id=$2', [await bcrypt.hash(newPassword, 12), req.auth!.id]);
590
+ if (req.auth!.sessionId) await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE user_id=$1 AND id<>$2 AND revoked_at IS NULL', [req.auth!.id, req.auth!.sessionId]);
531
591
  await audit(req.auth!.id, 'user.password.first-login', 'user', req.auth!.id, 'Required first-login password change', { providers: external.map((item) => item.provider) });
532
592
  res.json({ ok: true, results: [{ provider: 'cloud', ok: true }, ...external] });
533
593
  });
@@ -569,6 +629,7 @@ app.post('/auth/password-reset/verify', async (req, res) => {
569
629
  if (targets.includes('npm') || targets.includes('jenkins')) results.push(...await resetManagedExternalPasswords(user.id, newPassword, targets));
570
630
  if (targets.includes('cloud')) {
571
631
  await pool.query('UPDATE users SET password_hash=$1,must_change_password=false,password_changed_at=now(),updated_at=now() WHERE id=$2', [await bcrypt.hash(newPassword, 12), user.id]);
632
+ await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE user_id=$1 AND revoked_at IS NULL', [user.id]);
572
633
  results.unshift({ provider: 'cloud', ok: true });
573
634
  }
574
635
  await pool.query('UPDATE password_reset_otps SET used_at=now() WHERE id=$1', [row.id]);
@@ -602,6 +663,7 @@ app.patch('/users/:id/password', ...allow('manager', 'admin'), async (req: Authe
602
663
  const password = String(req.body.password || '');
603
664
  if (password.length < 10) { res.status(400).json({ error: 'Password must be at least 10 characters.' }); return; }
604
665
  await pool.query('UPDATE users SET password_hash=$1,must_change_password=true,updated_at=now() WHERE id=$2', [await bcrypt.hash(password, 12), id]);
666
+ await pool.query('UPDATE auth_sessions SET revoked_at=now() WHERE user_id=$1 AND revoked_at IS NULL', [id]);
605
667
  await audit(req.auth!.id, 'user.password.update', 'user', id, reason);
606
668
  res.status(204).end();
607
669
  });
@@ -1 +1 @@
1
- {"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../../src/cli/help-content.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;AAMpH,eAAO,MAAM,oBAAoB,QAAO,MAqGvC,CAAC;AA6IF,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,KAAG,MAS/C,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,SAAS,GAAG,SAa3D,CAAC"}
1
+ {"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../../src/cli/help-content.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;AAMpH,eAAO,MAAM,oBAAoB,QAAO,MAuGvC,CAAC;AAgJF,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,KAAG,MAS/C,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,SAAS,GAAG,SAa3D,CAAC"}
@@ -9,16 +9,18 @@ const heading = (value) => chalk_1.default.bold(value);
9
9
  const command = (value) => chalk_1.default.cyan(value);
10
10
  const note = (value) => chalk_1.default.gray(value);
11
11
  const fullCommandReference = () => `
12
- ${heading('IndraQ CLI v2.2 — scope-first infrastructure and deployment')}
12
+ ${heading('IndraQ CLI v2.2.2 — scope-first infrastructure and deployment')}
13
13
 
14
14
  ${heading('Start here')}
15
- ${command('indraq login')} Sign in to https://cli.indraq.com
15
+ ${command('indraq login')} Sign in once; the session refreshes automatically until logout
16
+ ${command('indraq logout')} Revoke the persistent session and clear local session credentials
17
+ ${note('Login persists across builds and time; access tokens refresh automatically until you explicitly logout or a security event revokes the session.')}
16
18
  ${command('indraq scope list')} Show only provider scopes assigned to you
17
19
  ${command('indraq scope describe [nameOrId]')} Identify a scope safely without revealing secrets
18
20
  ${command('indraq project status')} Review the current linked project
19
- ${command('indraq project scopes --env dev')} Choose project provider defaults for an environment
20
- ${command('indraq prebuild --env dev')} Reconcile required/optional project infrastructure
21
- ${command('indraq deploy:dev')} Build, push and deploy the dev environment
21
+ ${command('indraq project scopes --env <environment>')} Choose project provider defaults for a shared environment
22
+ ${command('indraq prebuild')} Choose a shared environment and reconcile infrastructure
23
+ ${command('indraq deploy')} Choose an available shared environment and deploy
22
24
 
23
25
  ${heading('Scopes — AWS / NPM / Jenkins / GHCR')}
24
26
  ${command('indraq scope list')} List scopes visible to your account
@@ -58,13 +60,13 @@ ${heading('Projects and project access')}
58
60
  ${command('indraq access delete user')} Revoke selected project resources
59
61
 
60
62
  ${heading('Prebuild and deploy')}
61
- ${command('indraq prebuild --env dev')} Jenkins + GHCR required; DNS + NPM optional
62
- ${command('indraq deploy:dev')} Deploy dev
63
- ${command('indraq deploy:prod')} Deploy prod
64
- ${command('indraq deploy --env staging')} Deploy a named IndraQ project/resource environment
65
- ${command('indraq deploy --env dev --select-scopes')} Explicitly review/change saved scope bindings before deploy
66
- ${command('indraq deployment create --env dev')} Reconcile deployment infrastructure
67
- ${command('indraq deployment run --env dev')} Run deployment
63
+ ${command('indraq prebuild')} Choose an available shared environment and reconcile infrastructure
64
+ ${command('indraq prebuild --env <environment>')} Reconcile one named shared environment directly
65
+ ${command('indraq deploy')} Choose an available shared environment and deploy
66
+ ${command('indraq deploy --env <environment>')} Deploy one named shared environment directly
67
+ ${command('indraq deploy --env <environment> --select-scopes')} Explicitly review/change saved scope bindings before deploy
68
+ ${command('indraq deployment create --env <environment>')} Reconcile deployment infrastructure
69
+ ${command('indraq deployment run --env <environment>')} Run deployment
68
70
 
69
71
  ${note('--env selects an IndraQ deployment/resource environment. It does NOT load .env, .env.dev, or .env.prod files.')}
70
72
 
@@ -176,27 +178,30 @@ ${command('indraq password reset')}
176
178
  const deployHelp = () => `
177
179
  ${heading('Project scopes, prebuild and deployment')}
178
180
 
179
- ${command('indraq project scopes --env dev')}
180
- Select project/provider defaults from scopes visible to you. Jenkins and GHCR are
181
- required for deployment. AWS and NPM scope links are optional.
181
+ ${command('indraq project scopes --env <environment>')}
182
+ Shows the scopes visible to you, then lets you explicitly choose which scope is linked
183
+ to that shared environment. Jenkins and GHCR are required; AWS and NPM are optional.
182
184
 
183
- ${command('indraq prebuild --env dev')}
185
+ ${command('indraq prebuild')}
184
186
  Required: Jenkins + GHCR
185
187
  Optional Route53: Skip | Create/reconcile | Link existing
186
188
  Optional NPM: Skip | Create/reconcile | Link existing
187
189
 
188
- ${command('indraq deploy --env dev')}
189
- Builds the Docker image, pushes it to the GHCR scope linked/defaulted for dev, then
190
- runs the Jenkins deployment associated with the dev project environment. Normal
191
- deployments reuse the saved project/environment scope bindings without prompting.
190
+ ${command('indraq deploy')}
191
+ Shows the available shared environments and lets you choose one. It then builds the
192
+ Docker image, pushes it to the GHCR scope linked to that exact environment, and runs
193
+ the matching Jenkins deployment. Existing project/environment scope bindings are reused.
192
194
 
193
- ${command('indraq deploy --env dev --select-scopes')}
195
+ ${command('indraq deploy --env indraq-test-airtel')}
196
+ Skip the environment picker and deploy that exact shared environment.
197
+
198
+ ${command('indraq deploy --env <environment> --select-scopes')}
194
199
  Explicitly review or change the saved scope bindings before this deployment.
195
200
 
196
201
  ${note('--env is an IndraQ environment selector. Application environment files are part of your own Docker/build process and are not selected by this flag.')}
197
202
  `;
198
203
  const configureHelp = () => `
199
- ${heading('Configuration in v2.2')}
204
+ ${heading('Configuration in v2.2.2')}
200
205
 
201
206
  Organization provider credentials are configured once as named scopes by an ADMIN.
202
207
  Managers and users do not create organization AWS/NPM/Jenkins/GHCR credentials.
@@ -1 +1 @@
1
- {"version":3,"file":"help-content.js","sourceRoot":"","sources":["../../src/cli/help-content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"help-content.js","sourceRoot":"","sources":["../../src/cli/help-content.ts"],"names":[],"mappings":"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"}