hub-session-refresh.mjs
132 lines 4.9 KB
Raw
sha256:4215cecbbabf5591b1ff69053cc938b4b6c800f0d487be40618c1d4254d8b67b security: npm audit fix pre-bridge 2026-09-05 Human 2 days ago
1 #!/usr/bin/env node
2 /**
3 * Refresh hosted Hub human session JWT (operator laptop only).
4 *
5 * One-time bootstrap — copy hub_token from Application → Local Storage, then:
6 * node scripts/hub-session-refresh.mjs --save-access-token '<jwt>'
7 * Exchanges the access JWT for a durable refresh token via POST /api/v1/auth/establish-refresh
8 * (requires gateway deploy with establish-refresh route).
9 *
10 * Routine use (auto-rotates refresh token, caches access JWT):
11 * node scripts/hub-session-refresh.mjs
12 *
13 * Optional env:
14 * KNOWTATION_HUB_API=https://api.knowtation.store
15 * KNOWTATION_HUB_REFRESH_TOKEN_FILE=~/.config/knowtation/hub_refresh
16 * KNOWTATION_HUB_TOKEN_FILE=~/.config/knowtation/hub_session
17 *
18 * Does not print JWTs unless --print-token (local operator only).
19 */
20
21 import path from 'node:path';
22 import { fileURLToPath } from 'node:url';
23 import dotenv from 'dotenv';
24
25 import {
26 DEFAULT_REFRESH_FILE,
27 DEFAULT_SESSION_FILE,
28 decodeAccessClaims,
29 ensureHostedSessionAccessToken,
30 establishHostedRefreshFromAccess,
31 expandHome,
32 refreshHostedSessionAccessToken,
33 writeSecretFile,
34 } from './lib/hub-session-auth.mjs';
35
36 const __dirname = path.dirname(fileURLToPath(import.meta.url));
37 dotenv.config({ path: path.join(__dirname, '..', '.env') });
38
39 function usage() {
40 console.log(`Usage:
41 node scripts/hub-session-refresh.mjs [--force]
42 node scripts/hub-session-refresh.mjs --save-refresh '<ktn_refresh value>'
43 node scripts/hub-session-refresh.mjs --save-access-token '<hub_token JWT from Local Storage>'
44 node scripts/hub-session-refresh.mjs --status
45 node scripts/hub-session-refresh.mjs --print-token # local only; never commit output
46
47 Stores:
48 refresh → ${DEFAULT_REFRESH_FILE}
49 access → ${DEFAULT_SESSION_FILE}
50 `);
51 }
52
53 const args = process.argv.slice(2);
54 if (args.includes('-h') || args.includes('--help')) {
55 usage();
56 process.exit(0);
57 }
58
59 if (args[0] === '--save-refresh') {
60 const token = (args[1] || '').trim();
61 if (!token || token.includes(' ')) {
62 console.error('Provide the ktn_refresh cookie value: --save-refresh \'<id>.<secret>\'');
63 process.exit(2);
64 }
65 const refreshFile = expandHome(process.env.KNOWTATION_HUB_REFRESH_TOKEN_FILE || DEFAULT_REFRESH_FILE);
66 writeSecretFile(refreshFile, token);
67 console.log(`saved refresh token → ${refreshFile}`);
68 const result = await refreshHostedSessionAccessToken();
69 if (!result.ok) {
70 console.error(`refresh failed: ${result.code} — ${result.detail}`);
71 process.exit(1);
72 }
73 console.log(`access token cached (${result.claims.type ?? 'session'}, exp in ${result.claims.expiresInSec}s)`);
74 process.exit(0);
75 }
76
77 if (args[0] === '--save-access-token') {
78 const access = (args[1] || '').trim();
79 if (!access || access.split('.').length !== 3) {
80 console.error('Paste hub_token from DevTools → Application → Local Storage → hub_token');
81 process.exit(2);
82 }
83 const claims = decodeAccessClaims(access);
84 if (claims.expired) {
85 console.error('hub_token is expired — sign in again and copy a fresh token');
86 process.exit(2);
87 }
88 const established = await establishHostedRefreshFromAccess(access);
89 if (!established.ok) {
90 console.error(`establish-refresh failed: ${established.code} — ${established.detail}`);
91 process.exit(1);
92 }
93 const refreshFile = expandHome(process.env.KNOWTATION_HUB_REFRESH_TOKEN_FILE || DEFAULT_REFRESH_FILE);
94 const sessionFile = expandHome(process.env.KNOWTATION_HUB_TOKEN_FILE || DEFAULT_SESSION_FILE);
95 console.log(`saved refresh token → ${refreshFile}`);
96 console.log(`saved access JWT → ${sessionFile} (type=${claims.type ?? 'session'}, exp_in_s=${claims.expiresInSec})`);
97 console.log('auto-refresh enabled — run: node scripts/hub-session-refresh.mjs');
98 process.exit(0);
99 }
100
101 if (args.includes('--status')) {
102 const fs = await import('node:fs');
103 const sessionFile = expandHome(process.env.KNOWTATION_HUB_TOKEN_FILE || DEFAULT_SESSION_FILE);
104 const refreshFile = expandHome(process.env.KNOWTATION_HUB_REFRESH_TOKEN_FILE || DEFAULT_REFRESH_FILE);
105 const envTok = (process.env.KNOWTATION_HUB_TOKEN || '').trim();
106 console.log(JSON.stringify({
107 refresh_file: refreshFile,
108 refresh_present: fs.existsSync(refreshFile),
109 session_file: sessionFile,
110 session_present: fs.existsSync(sessionFile),
111 env_token_present: Boolean(envTok),
112 env_token: envTok ? decodeAccessClaims(envTok) : null,
113 session_file_claims: fs.existsSync(sessionFile)
114 ? decodeAccessClaims(fs.readFileSync(sessionFile, 'utf8').trim())
115 : null,
116 }, null, 2));
117 process.exit(0);
118 }
119
120 const force = args.includes('--force');
121 const result = await ensureHostedSessionAccessToken({ forceRefresh: force });
122 if (!result.ok) {
123 console.error(`FAIL ${result.code}: ${result.detail}`);
124 process.exit(1);
125 }
126
127 const claims = decodeAccessClaims(result.accessToken);
128 console.log(`OK source=${result.source} refreshed=${result.refreshed} type=${claims.type ?? 'session'} exp_in_s=${claims.expiresInSec}`);
129
130 if (args.includes('--print-token')) {
131 console.log(result.accessToken);
132 }
File History 1 commit
sha256:4215cecbbabf5591b1ff69053cc938b4b6c800f0d487be40618c1d4254d8b67b security: npm audit fix pre-bridge 2026-09-05 Human 2 days ago