@levr-one/auth 1.0.3 → 1.1.1

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.
@@ -1,20 +1,216 @@
1
1
  /**
2
- * Disk-backed workspace state at ~/.tq/workspace.json
3
- * Follows the same atomic-write pattern as token-store.ts
2
+ * Disk-backed workspace/identity state at ~/.tq/workspace.json
3
+ *
4
+ * Stored as a map keyed by backend URL — the SAME key space as
5
+ * ~/.tq/oauth-tokens.json (see token-store.ts):
6
+ * {
7
+ * "https://api.levr.now": { workspace_id, user_id, user_email, ... },
8
+ * "http://localhost:8180": { ... }
9
+ * }
10
+ *
11
+ * internal: this file used to hold a SINGLE global record with no note of
12
+ * which backend it came from. A login or `select_workspace` against a local
13
+ * backend (including a worktree's own backend on its assigned port)
14
+ * overwrote it machine-globally, and the next `yarn setup:mcp` stamped that
15
+ * local workspace id into the `Workspace-Id` header of a STAGING levr URL.
16
+ * Keying by backend URL makes that structurally impossible: a local id lives
17
+ * under a different key than the staging one, so it cannot be read back for
18
+ * a staging endpoint.
19
+ *
20
+ * Backward-compatible: a legacy flat file (`{ workspace_id, ... }` at the top
21
+ * level) is attributed to the currently-configured backend and migrated
22
+ * in-place on first read — the same treatment `readTokenMap()` gives legacy
23
+ * token files. Attribution is a best guess by construction; that is
24
+ * acceptable because this file is a hint-only cache (see `IdentityCache`) and
25
+ * a wrong guess costs one workspace re-selection, not a security decision.
26
+ *
27
+ * Follows the same atomic-write pattern as token-store.ts.
4
28
  */
5
29
  import * as fs from 'fs';
6
30
  import * as path from 'path';
7
- import { getTqDir } from './token-store.js';
31
+ import { getTqDir, normalizeBackendUrl } from './token-store.js';
32
+ import { loadConfig } from './config.js';
8
33
  const WORKSPACE_FILE = 'workspace.json';
9
34
  function getWorkspacePath() {
10
35
  return path.join(getTqDir(), WORKSPACE_FILE);
11
36
  }
37
+ /** The only fields an identity record — or the mirror — may carry. */
38
+ const IDENTITY_FIELDS = [
39
+ 'workspace_id',
40
+ 'user_id',
41
+ 'user_email',
42
+ 'user_name',
43
+ 'role',
44
+ ];
45
+ function isIdentityKey(key) {
46
+ return /^https?:\/\//i.test(key);
47
+ }
12
48
  /**
13
- * Atomically write `payload` as JSON to `~/.tq/workspace.json`.
49
+ * Strip trailing slashes and reject anything that is not an http(s) origin.
50
+ *
51
+ * This is the SINGLE gate both sides use: `resolveKey` will not write under a
52
+ * key that `readIdentityMap` would later discard. Without that agreement a
53
+ * scheme-less `apiBaseUrl` (`localhost:8080`) wrote an entry to disk that
54
+ * every subsequent read dropped — a silent, permanent loss of the pin with no
55
+ * error anywhere.
56
+ */
57
+ function normalizeKey(url) {
58
+ // Canonical form comes from token-store, so the two files cannot disagree
59
+ // about what key a given backend has (internal). The http(s) gate on top is
60
+ // this store's own: it will not write a key that readIdentityMap discards.
61
+ const trimmed = normalizeBackendUrl(url);
62
+ if (!trimmed || !isIdentityKey(trimmed))
63
+ return undefined;
64
+ return trimmed;
65
+ }
66
+ /**
67
+ * The backend the UNKEYED callers actually talk to.
68
+ *
69
+ * This MUST be `loadConfig()`, not `getCurrentApiUrl()`. The two disagree:
70
+ * `getCurrentApiUrl()` has two tiers (`TQ_BACKEND_URL` > `~/.tq/config.json`)
71
+ * while `loadConfig()` has four, and its third is the STORED TOKEN's
72
+ * `apiBaseUrl`. With no `config.json` but a token for a local backend, the
73
+ * two-tier version returns undefined and the old fallback guessed `staging` —
74
+ * filing a LOCAL workspace id under the STAGING key, which `setup-mcp` then
75
+ * stamped into the staging `Workspace-Id` header. That is internal reappearing
76
+ * through its own fix.
77
+ *
78
+ * Using the same resolver the callers use makes the key correct by
79
+ * construction: whatever backend they are about to talk to is the backend the
80
+ * record is filed under, including when that resolution itself ends at the
81
+ * staging default.
82
+ */
83
+ function configuredBackend() {
84
+ return normalizeKey(loadConfig().apiUrl) ?? 'https://api.levr.now';
85
+ }
86
+ /**
87
+ * Resolve which backend URL an identity read/write belongs to.
88
+ *
89
+ * An explicit argument (the caller knows its backend) wins; otherwise the
90
+ * configured backend. Callers that know their backend SHOULD pass it.
91
+ * `setup-mcp.ts` in particular must pass the URL it is about to write into
92
+ * `.mcp.json`, which is not necessarily the configured one — the levr MCP
93
+ * host differs from the API host (`https://ai.levr.now/api/v1/mcp` is served
94
+ * by the backend at `https://api.levr.now`), and stdio MCP servers resolve
95
+ * their own backend independently of whatever the levr HTTP entry points at.
96
+ *
97
+ * An explicit-but-malformed key is ignored rather than written, with a log —
98
+ * silently retargeting it at another backend would be the very leak this
99
+ * module exists to prevent.
100
+ */
101
+ function resolveKey(apiBaseUrl) {
102
+ if (apiBaseUrl !== undefined) {
103
+ const explicit = normalizeKey(apiBaseUrl);
104
+ if (explicit)
105
+ return explicit;
106
+ console.error(`[levr-auth] Ignoring malformed backend url "${apiBaseUrl}" — ` +
107
+ 'expected an http(s) origin. Falling back to the configured backend.');
108
+ }
109
+ return configuredBackend();
110
+ }
111
+ /**
112
+ * Read the raw identity map from disk.
113
+ * Handles migration from the legacy single-record format.
114
+ */
115
+ function readIdentityMap() {
116
+ try {
117
+ const filePath = getWorkspacePath();
118
+ if (!fs.existsSync(filePath))
119
+ return {};
120
+ const content = fs.readFileSync(filePath, 'utf-8');
121
+ const parsed = JSON.parse(content);
122
+ // `typeof [] === 'object'` — explicitly reject arrays so property access
123
+ // on a parsed JSON array (`[0]`, `[1]`...) cannot accidentally satisfy
124
+ // the per-field checks in loadIdentityCache().
125
+ if (parsed === null ||
126
+ typeof parsed !== 'object' ||
127
+ Array.isArray(parsed)) {
128
+ return {};
129
+ }
130
+ // Keep only URL-shaped keys. This also strips the back-compat mirror
131
+ // fields (see writeIdentityMap) so they can never be mistaken for a
132
+ // backend entry.
133
+ const map = {};
134
+ for (const [key, value] of Object.entries(parsed)) {
135
+ if (!isIdentityKey(key))
136
+ continue;
137
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
138
+ continue;
139
+ }
140
+ // Normalize on read, mirroring readTokenMap: a `.../` entry and a `...`
141
+ // entry are the same backend, and only the canonical form is ever
142
+ // looked up, so an un-normalized key would be permanently unreachable.
143
+ map[normalizeKey(key) ?? key] = value;
144
+ }
145
+ // Legacy flat shape — `{ workspace_id, ... }` and NOT ONE url key. The
146
+ // test is the ABSENCE of url keys, never the presence of `workspace_id`:
147
+ // a current file carries BOTH (the mirror), and treating that as legacy
148
+ // would collapse every backend's entry into one on the next read.
149
+ if (Object.keys(map).length === 0 && 'workspace_id' in parsed) {
150
+ const key = configuredBackend();
151
+ const migrated = { [key]: parsed };
152
+ writeIdentityMap(migrated);
153
+ return migrated;
154
+ }
155
+ return map;
156
+ }
157
+ catch (error) {
158
+ console.error('[levr-auth] Failed to load workspace:', error);
159
+ return {};
160
+ }
161
+ }
162
+ /**
163
+ * Atomically write the identity map to `~/.tq/workspace.json`.
14
164
  * Ensures the ~/.tq directory exists with 0o700, writes to `*.tmp` with
15
165
  * 0o600, then renames over the target. Errors are logged, never thrown.
166
+ *
167
+ * ## TRANSITIONAL: the top-level mirror
168
+ *
169
+ * Alongside the url-keyed entries we also write the CONFIGURED backend's
170
+ * record at the top level, in the pre-internal flat shape:
171
+ *
172
+ * {
173
+ * "https://api.levr.now": { workspace_id: "A", ... },
174
+ * "http://localhost:8180": { workspace_id: "B", ... },
175
+ * "workspace_id": "A", "user_id": ..., ... <-- mirror of the configured one
176
+ * }
177
+ *
178
+ * ~/.tq is machine-global but every checkout runs its OWN
179
+ * `plugins/<name>/dist/index.js` (the `.mcp.json` entries use relative
180
+ * paths, so each worktree loads its own build), and
181
+ * a worktree on a feature branch does not get this code until that branch
182
+ * merges main — days or weeks. Without the mirror, an old stdio server in
183
+ * such a worktree reads `.workspace_id`, gets undefined, falls through to
184
+ * `ensureWorkspace()`, and its old `saveWorkspace()` REWRITES THE WHOLE FILE
185
+ * FLAT — wiping every other backend's entry. Main then re-migrates it, and
186
+ * the two ping-pong for as long as any stale checkout is in use.
187
+ *
188
+ * The mirror keeps those old readers resolving, so they never reach the
189
+ * read-fallthrough that ends in a flattening write.
190
+ *
191
+ * It does NOT stop an old WRITER, and that is not only the deliberate case
192
+ * (`tq:workspace` in a stale checkout). Observed live: `yarn tq:login`
193
+ * rewrites oauth-tokens.json, the running qinetic-mcp server's token watcher
194
+ * fires, and `qinetic-mcp/src/auth.ts` calls `saveIdentityCache()`
195
+ * unconditionally after re-auth — flattening the file two seconds after the
196
+ * login wrote it, with no user action at all.
197
+ *
198
+ * Note what decides that: a long-lived process holds whatever `@levr-one/auth`
199
+ * it imported AT STARTUP, so a freshly rebuilt `dist/` on disk changes
200
+ * nothing until the server is restarted (`/mcp` reconnect). Checkout
201
+ * freshness is necessary but not sufficient.
202
+ *
203
+ * This is survivable rather than fixed: the flat file still carries the
204
+ * correct id for the configured backend, and the next read by new code
205
+ * re-migrates it. What is lost each time is OTHER backends' entries — local
206
+ * worktree pins, which re-derive on their own since those backends
207
+ * auto-select their single seeded workspace. Nothing new code writes can
208
+ * prevent an old writer from overwriting the file.
209
+ *
210
+ * REMOVE once every checkout has rebuilt against this version — the mirror is
211
+ * only meaningful for one backend and is pure carrying cost after that.
16
212
  */
17
- function writeWorkspaceJsonAtomic(payload) {
213
+ function writeIdentityMap(map) {
18
214
  try {
19
215
  const tqDir = getTqDir();
20
216
  if (!fs.existsSync(tqDir)) {
@@ -22,6 +218,19 @@ function writeWorkspaceJsonAtomic(payload) {
22
218
  }
23
219
  const filePath = getWorkspacePath();
24
220
  const tempPath = filePath + '.tmp';
221
+ // Mirror ONLY the five IdentityCache fields. Spreading the entry wholesale
222
+ // promoted any url-shaped field INSIDE it to a top-level entry, where the
223
+ // next read would serve it as that backend's record — a hand-edited or
224
+ // corrupted file could put an arbitrary workspace id under an arbitrary
225
+ // backend.
226
+ const mirrored = map[configuredBackend()];
227
+ const payload = { ...map };
228
+ if (mirrored) {
229
+ for (const field of IDENTITY_FIELDS) {
230
+ if (typeof mirrored[field] === 'string')
231
+ payload[field] = mirrored[field];
232
+ }
233
+ }
25
234
  fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2), {
26
235
  mode: 0o600,
27
236
  });
@@ -32,112 +241,127 @@ function writeWorkspaceJsonAtomic(payload) {
32
241
  }
33
242
  }
34
243
  /**
35
- * Load persisted workspace ID from disk
36
- * @returns workspace_id or null if missing/corrupt
244
+ * Load the persisted workspace ID for a backend.
245
+ *
246
+ * @param apiBaseUrl Backend the id must belong to. Defaults to the currently
247
+ * configured backend — correct for stdio MCP servers and the CLI, which
248
+ * talk to whatever `~/.tq/config.json` points at.
249
+ * @returns workspace_id, or null when this backend has no entry
37
250
  */
38
- export function loadWorkspace() {
39
- try {
40
- const filePath = getWorkspacePath();
41
- if (fs.existsSync(filePath)) {
42
- const content = fs.readFileSync(filePath, 'utf-8');
43
- const data = JSON.parse(content);
44
- if (data.workspace_id && typeof data.workspace_id === 'string') {
45
- return data.workspace_id;
46
- }
47
- }
48
- }
49
- catch (error) {
50
- console.error('[levr-auth] Failed to load workspace:', error);
51
- }
52
- return null;
251
+ export function loadWorkspace(apiBaseUrl) {
252
+ const entry = readIdentityMap()[resolveKey(apiBaseUrl)];
253
+ const id = entry?.workspace_id;
254
+ return typeof id === 'string' && id ? id : null;
53
255
  }
54
256
  /**
55
- * Save workspace ID to disk with atomic write
257
+ * Save a workspace ID against a backend, leaving every other backend's entry
258
+ * intact. Preserves any identity fields already cached for that backend.
56
259
  */
57
- export function saveWorkspace(workspaceId) {
58
- writeWorkspaceJsonAtomic({ workspace_id: workspaceId });
260
+ export function saveWorkspace(workspaceId, apiBaseUrl) {
261
+ const map = readIdentityMap();
262
+ const key = resolveKey(apiBaseUrl);
263
+ map[key] = { ...map[key], workspace_id: workspaceId };
264
+ writeIdentityMap(map);
59
265
  }
60
266
  /**
61
- * Load the full identity cache from disk.
267
+ * Load the full identity cache for a backend.
62
268
  *
63
- * Reads ~/.tq/workspace.json and returns a typed `IdentityCache` only when
64
- * all five required fields are present and are strings. Returns `null` for:
65
- * - missing file
66
- * - malformed JSON
67
- * - legacy single-field shape (`{ workspace_id }` only)
68
- * - any field present but not a string
269
+ * Returns a typed `IdentityCache` only when all five fields are present and
270
+ * are strings. Returns `null` for a missing file, malformed JSON, a backend
271
+ * with no entry, a workspace-id-only entry, or any field that is not a
272
+ * string.
69
273
  *
70
274
  * Never throws — graceful degradation is mandatory because callers wire this
71
275
  * into startup paths and watcher callbacks.
72
276
  */
73
- export function loadIdentityCache() {
74
- try {
75
- const filePath = getWorkspacePath();
76
- if (!fs.existsSync(filePath))
77
- return null;
78
- const content = fs.readFileSync(filePath, 'utf-8');
79
- const data = JSON.parse(content);
80
- // `typeof [] === 'object'` — explicitly reject arrays so property
81
- // access on a parsed JSON array (`[0]`, `[1]`...) cannot accidentally
82
- // satisfy the per-field checks below.
83
- if (data === null || typeof data !== 'object' || Array.isArray(data)) {
84
- return null;
85
- }
86
- const keys = [
87
- 'workspace_id',
88
- 'user_id',
89
- 'user_email',
90
- 'user_name',
91
- 'role',
92
- ];
93
- for (const key of keys) {
94
- const value = data[key];
95
- if (typeof value !== 'string')
96
- return null;
97
- }
98
- return {
99
- workspace_id: data.workspace_id,
100
- user_id: data.user_id,
101
- user_email: data.user_email,
102
- user_name: data.user_name,
103
- role: data.role,
104
- };
105
- }
106
- catch {
277
+ export function loadIdentityCache(apiBaseUrl) {
278
+ const entry = readIdentityMap()[resolveKey(apiBaseUrl)];
279
+ if (!entry)
107
280
  return null;
281
+ const keys = [
282
+ 'workspace_id',
283
+ 'user_id',
284
+ 'user_email',
285
+ 'user_name',
286
+ 'role',
287
+ ];
288
+ for (const key of keys) {
289
+ if (typeof entry[key] !== 'string')
290
+ return null;
108
291
  }
292
+ return {
293
+ workspace_id: entry.workspace_id,
294
+ user_id: entry.user_id,
295
+ user_email: entry.user_email,
296
+ user_name: entry.user_name,
297
+ role: entry.role,
298
+ };
109
299
  }
110
300
  /**
111
- * Save the full identity cache to disk with atomic write.
112
- *
113
- * Single writer pattern: `saveWorkspace` writes only `{ workspace_id }`;
114
- * `saveIdentityCache` is the only path that writes the five-field shape.
115
- * After the first identity-aware save, the file always contains all five
116
- * fields. Same `.tmp` + `renameSync` pattern as `saveWorkspace`.
301
+ * Save the full identity cache for a backend with an atomic write, leaving
302
+ * every other backend's entry intact.
117
303
  */
118
- export function saveIdentityCache(data) {
119
- writeWorkspaceJsonAtomic({
304
+ export function saveIdentityCache(data, apiBaseUrl) {
305
+ const map = readIdentityMap();
306
+ map[resolveKey(apiBaseUrl)] = {
120
307
  workspace_id: data.workspace_id,
121
308
  user_id: data.user_id,
122
309
  user_email: data.user_email,
123
310
  user_name: data.user_name,
124
311
  role: data.role,
125
- });
312
+ };
313
+ writeIdentityMap(map);
126
314
  }
127
315
  /**
128
- * Clear persisted workspace (e.g. on env switch or login)
316
+ * Clear the persisted identity for ONE backend (e.g. on logout).
317
+ *
318
+ * Mirrors `clearTokens()`: only the current backend's entry is removed, and
319
+ * the file is deleted outright once the last entry is gone. An env switch
320
+ * must NOT call this — under URL keying, switching environments already
321
+ * selects a different entry, and clearing would throw away a still-valid
322
+ * workspace pin the user would have to re-select.
129
323
  */
130
- export function clearWorkspace() {
324
+ export function clearWorkspace(apiBaseUrl) {
131
325
  try {
132
326
  const filePath = getWorkspacePath();
133
- if (fs.existsSync(filePath)) {
327
+ if (!fs.existsSync(filePath))
328
+ return;
329
+ const map = readIdentityMap();
330
+ const key = resolveKey(apiBaseUrl);
331
+ if (!(key in map))
332
+ return;
333
+ delete map[key];
334
+ if (Object.keys(map).length === 0) {
134
335
  fs.unlinkSync(filePath);
135
336
  }
337
+ else {
338
+ writeIdentityMap(map);
339
+ }
136
340
  }
137
341
  catch (error) {
138
342
  console.error('[levr-auth] Failed to clear workspace:', error);
139
343
  }
140
344
  }
345
+ /**
346
+ * Re-point the transitional top-level mirror at the CURRENTLY configured
347
+ * backend, without touching any keyed entry. Call this after changing which
348
+ * backend is configured (`tq:env`).
349
+ *
350
+ * The mirror is only recomputed on write, and an env switch writes nothing —
351
+ * so without this the mirror keeps pointing at the PREVIOUS backend and an
352
+ * old (pre-internal) client reads a pin for a backend it is no longer talking
353
+ * to. That is strictly worse than what it replaced: before the file was
354
+ * keyed, `tq:env` deleted the whole file, so an old client got nothing and
355
+ * fell back to unpinned. Failing to unpinned is safe; failing to another
356
+ * backend's workspace is not.
357
+ *
358
+ * No-op when the file does not exist. Remove alongside the mirror itself.
359
+ */
360
+ export function refreshMirror() {
361
+ if (!fs.existsSync(getWorkspacePath()))
362
+ return;
363
+ writeIdentityMap(readIdentityMap());
364
+ }
141
365
  /**
142
366
  * Get workspace file path (for debugging)
143
367
  */
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-store.js","sourceRoot":"","sources":["../src/workspace-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-store.js","sourceRoot":"","sources":["../src/workspace-store.ts"],"names":[],"mappings":"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"}