@levr-one/auth 1.0.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.
Files changed (70) hide show
  1. package/README.md +37 -0
  2. package/dist/config-watcher.d.ts +65 -0
  3. package/dist/config-watcher.d.ts.map +1 -0
  4. package/dist/config-watcher.js +140 -0
  5. package/dist/config-watcher.js.map +1 -0
  6. package/dist/config-watcher.test.d.ts +2 -0
  7. package/dist/config-watcher.test.d.ts.map +1 -0
  8. package/dist/config-watcher.test.js +116 -0
  9. package/dist/config-watcher.test.js.map +1 -0
  10. package/dist/config.d.ts +58 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +234 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/config.test.d.ts +2 -0
  15. package/dist/config.test.d.ts.map +1 -0
  16. package/dist/config.test.js +221 -0
  17. package/dist/config.test.js.map +1 -0
  18. package/dist/file-lock.d.ts +41 -0
  19. package/dist/file-lock.d.ts.map +1 -0
  20. package/dist/file-lock.js +110 -0
  21. package/dist/file-lock.js.map +1 -0
  22. package/dist/index.d.ts +43 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +43 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/oauth-client.d.ts +156 -0
  27. package/dist/oauth-client.d.ts.map +1 -0
  28. package/dist/oauth-client.js +572 -0
  29. package/dist/oauth-client.js.map +1 -0
  30. package/dist/oauth-client.test.d.ts +2 -0
  31. package/dist/oauth-client.test.d.ts.map +1 -0
  32. package/dist/oauth-client.test.js +290 -0
  33. package/dist/oauth-client.test.js.map +1 -0
  34. package/dist/test-utils.d.ts +10 -0
  35. package/dist/test-utils.d.ts.map +1 -0
  36. package/dist/test-utils.js +45 -0
  37. package/dist/test-utils.js.map +1 -0
  38. package/dist/token-store.d.ts +78 -0
  39. package/dist/token-store.d.ts.map +1 -0
  40. package/dist/token-store.js +299 -0
  41. package/dist/token-store.js.map +1 -0
  42. package/dist/token-store.test.d.ts +2 -0
  43. package/dist/token-store.test.d.ts.map +1 -0
  44. package/dist/token-store.test.js +239 -0
  45. package/dist/token-store.test.js.map +1 -0
  46. package/dist/token-watcher.d.ts +82 -0
  47. package/dist/token-watcher.d.ts.map +1 -0
  48. package/dist/token-watcher.js +185 -0
  49. package/dist/token-watcher.js.map +1 -0
  50. package/dist/types.d.ts +70 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +5 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/workspace-store.d.ts +60 -0
  55. package/dist/workspace-store.d.ts.map +1 -0
  56. package/dist/workspace-store.js +147 -0
  57. package/dist/workspace-store.js.map +1 -0
  58. package/dist/workspace-store.test.d.ts +2 -0
  59. package/dist/workspace-store.test.d.ts.map +1 -0
  60. package/dist/workspace-store.test.js +169 -0
  61. package/dist/workspace-store.test.js.map +1 -0
  62. package/dist/workspaces.d.ts +60 -0
  63. package/dist/workspaces.d.ts.map +1 -0
  64. package/dist/workspaces.js +80 -0
  65. package/dist/workspaces.js.map +1 -0
  66. package/dist/workspaces.test.d.ts +2 -0
  67. package/dist/workspaces.test.d.ts.map +1 -0
  68. package/dist/workspaces.test.js +111 -0
  69. package/dist/workspaces.test.js.map +1 -0
  70. package/package.json +58 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @levr-one/auth - Shared OAuth 2.1 client with self-healing token management
3
+ *
4
+ * This package provides:
5
+ * - OAuthClient: OAuth 2.1 with PKCE for authorization flow
6
+ * - TokenWatcher: Self-healing token file watching
7
+ * - Token storage utilities with atomic writes
8
+ *
9
+ * Usage in MCP servers:
10
+ *
11
+ * ```typescript
12
+ * import { OAuthClient, TokenWatcher } from '@levr-one/auth';
13
+ *
14
+ * // Initialize OAuth client
15
+ * const oauth = new OAuthClient({ clientId: 'my-client' });
16
+ *
17
+ * // Start token watcher for self-healing
18
+ * const watcher = new TokenWatcher();
19
+ * watcher.onTokenChange((tokens) => {
20
+ * if (tokens) {
21
+ * oauth.setTokensFromStorage(tokens);
22
+ * }
23
+ * });
24
+ * watcher.start();
25
+ * ```
26
+ */
27
+ export type { TokenResponse, StoredTokens, OAuthConfig, AuthStatus, TokenChangeCallback, DeviceAuthorizationResponse, } from './types.js';
28
+ export { loadTokens, loadTokensForEnv, loadTokensForUrl, saveTokens, saveTokensForEnv, clearTokens, restoreTokensFromBackup, hasStoredTokens, getTokenFilePath, getTqDir, resolveEnvFromUrl, resolveUrlFromEnv, } from './token-store.js';
29
+ export type { EnvName } from './token-store.js';
30
+ export { loadWorkspace, saveWorkspace, clearWorkspace, getWorkspaceFilePath, loadIdentityCache, saveIdentityCache, } from './workspace-store.js';
31
+ export type { IdentityCache } from './workspace-store.js';
32
+ export { loadConfig, writeConfig, getConfigFilePath, resolveFromApiUrl, PRESETS, } from './config.js';
33
+ export type { TqConfig } from './config.js';
34
+ export { OAuthClient } from './oauth-client.js';
35
+ export { listWorkspaces, WorkspaceFetchError } from './workspaces.js';
36
+ export type { WorkspaceSite, WorkspaceFetchErrorCode } from './workspaces.js';
37
+ export { acquireFileLock, getLockPath } from './file-lock.js';
38
+ export type { FileLockHandle } from './file-lock.js';
39
+ export { TokenWatcher } from './token-watcher.js';
40
+ export type { TokenWatcherOptions } from './token-watcher.js';
41
+ export { ConfigWatcher } from './config-watcher.js';
42
+ export type { ConfigWatcherOptions, ConfigChangeCallback, } from './config-watcher.js';
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EACL,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtE,YAAY,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG9E,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EACV,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @levr-one/auth - Shared OAuth 2.1 client with self-healing token management
3
+ *
4
+ * This package provides:
5
+ * - OAuthClient: OAuth 2.1 with PKCE for authorization flow
6
+ * - TokenWatcher: Self-healing token file watching
7
+ * - Token storage utilities with atomic writes
8
+ *
9
+ * Usage in MCP servers:
10
+ *
11
+ * ```typescript
12
+ * import { OAuthClient, TokenWatcher } from '@levr-one/auth';
13
+ *
14
+ * // Initialize OAuth client
15
+ * const oauth = new OAuthClient({ clientId: 'my-client' });
16
+ *
17
+ * // Start token watcher for self-healing
18
+ * const watcher = new TokenWatcher();
19
+ * watcher.onTokenChange((tokens) => {
20
+ * if (tokens) {
21
+ * oauth.setTokensFromStorage(tokens);
22
+ * }
23
+ * });
24
+ * watcher.start();
25
+ * ```
26
+ */
27
+ // Token storage (with atomic writes, map keyed by backend URL)
28
+ export { loadTokens, loadTokensForEnv, loadTokensForUrl, saveTokens, saveTokensForEnv, clearTokens, restoreTokensFromBackup, hasStoredTokens, getTokenFilePath, getTqDir, resolveEnvFromUrl, resolveUrlFromEnv, } from './token-store.js';
29
+ // Workspace storage
30
+ export { loadWorkspace, saveWorkspace, clearWorkspace, getWorkspaceFilePath, loadIdentityCache, saveIdentityCache, } from './workspace-store.js';
31
+ // Unified environment config
32
+ export { loadConfig, writeConfig, getConfigFilePath, resolveFromApiUrl, PRESETS, } from './config.js';
33
+ // OAuth client
34
+ export { OAuthClient } from './oauth-client.js';
35
+ // Workspace listing (public library surface)
36
+ export { listWorkspaces, WorkspaceFetchError } from './workspaces.js';
37
+ // File lock (cross-process refresh coordination)
38
+ export { acquireFileLock, getLockPath } from './file-lock.js';
39
+ // Token watcher (self-healing core)
40
+ export { TokenWatcher } from './token-watcher.js';
41
+ // Config watcher (environment change detection)
42
+ export { ConfigWatcher } from './config-watcher.js';
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAYH,+DAA+D;AAC/D,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,oBAAoB;AACpB,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAG9B,6BAA6B;AAC7B,OAAO,EACL,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,OAAO,GACR,MAAM,aAAa,CAAC;AAGrB,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,6CAA6C;AAC7C,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGtE,iDAAiD;AACjD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG9D,oCAAoC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * OAuth 2.0 client for TQ MCP servers
3
+ * Implements authorization code flow with PKCE (RFC 7636)
4
+ */
5
+ import type { OAuthConfig, StoredTokens } from './types.js';
6
+ export declare class OAuthClient {
7
+ private config;
8
+ private accessToken;
9
+ private refreshToken;
10
+ private expiresAt;
11
+ private consecutiveRefreshFailures;
12
+ /**
13
+ * Set when refresh is definitively rejected (400/401).
14
+ * Prevents further network refresh attempts until re-authorization
15
+ * or fresh tokens are loaded via setTokensFromStorage/authorize.
16
+ */
17
+ private refreshRejected;
18
+ /**
19
+ * Deduplicates concurrent refresh calls. When multiple callers invoke
20
+ * refresh() simultaneously (e.g., parallel MCP tool requests), they
21
+ * all await the same in-flight promise instead of each firing a
22
+ * separate token rotation request — which would cause the second
23
+ * request to fail with invalid_grant (token already revoked by first).
24
+ */
25
+ private refreshPromise;
26
+ constructor(config: Partial<OAuthConfig> & {
27
+ clientId: string;
28
+ });
29
+ /**
30
+ * Load tokens from persistent storage
31
+ */
32
+ private loadStoredTokens;
33
+ /**
34
+ * Set tokens from external storage (used during initialization)
35
+ */
36
+ setTokensFromStorage(tokens: StoredTokens): void;
37
+ /**
38
+ * Generate PKCE code verifier and challenge
39
+ * @see https://datatracker.ietf.org/doc/html/rfc7636
40
+ */
41
+ private generatePKCE;
42
+ /**
43
+ * Start local HTTP server to receive OAuth callback
44
+ */
45
+ private waitForCallback;
46
+ /**
47
+ * Perform full OAuth authorization flow
48
+ * Opens browser for user to authorize, waits for callback
49
+ */
50
+ authorize(): Promise<void>;
51
+ /**
52
+ * Perform OAuth 2.0 Device Authorization Grant (RFC 8628).
53
+ * Works in headless/remote terminals — no browser redirect needed.
54
+ * Displays a URL and code for the user to authorize on any device.
55
+ */
56
+ authorizeDevice(): Promise<void>;
57
+ /**
58
+ * Exchange authorization code for access and refresh tokens
59
+ */
60
+ private exchangeCode;
61
+ /**
62
+ * Refresh access token using refresh token.
63
+ *
64
+ * Two layers of coordination:
65
+ * 1. In-process: `refreshPromise` dedups concurrent callers within
66
+ * this OAuthClient instance.
67
+ * 2. Cross-process: a file lock keyed on apiBaseUrl serializes
68
+ * refreshes across MCP server processes. On lock acquire, we
69
+ * re-read the on-disk token — if another process already
70
+ * refreshed while we waited, adopt their tokens and skip the
71
+ * network call. This avoids the "N processes all POST with the
72
+ * same refresh_token, all but one get invalid_grant" race that
73
+ * would otherwise exhaust consecutiveRefreshFailures and trigger
74
+ * clearTokens().
75
+ */
76
+ refresh(): Promise<void>;
77
+ /**
78
+ * Acquire the cross-process refresh lock, re-check disk state, and
79
+ * either adopt freshly-rotated tokens from disk or call the refresh
80
+ * endpoint. Always releases the lock.
81
+ */
82
+ private refreshWithLock;
83
+ /**
84
+ * Internal refresh implementation — called only once per dedup window.
85
+ * All concurrent callers share the same promise via refresh().
86
+ */
87
+ private refreshInternal;
88
+ /**
89
+ * Store tokens in memory and persist to disk.
90
+ *
91
+ * Non-refresh grants (authorization_code, device_code, password) carry
92
+ * `tokens.user.workspace_id` — the workspace the user just logged into.
93
+ * Refresh grants omit it. When present we persist it to ~/.tq/workspace.json
94
+ * so MCP clients pick up the selected workspace without a separate round-trip.
95
+ */
96
+ private setTokens;
97
+ /**
98
+ * Get a valid access token, refreshing if needed
99
+ * Note: Does NOT auto-authorize - throws if no tokens available
100
+ */
101
+ getAccessToken(): Promise<string>;
102
+ /**
103
+ * Get access token without auto-refresh (for checking current state)
104
+ */
105
+ getAccessTokenSync(): string | null;
106
+ /**
107
+ * Check if client has valid tokens
108
+ */
109
+ isAuthorized(): boolean;
110
+ /**
111
+ * Check if tokens exist (may be expired)
112
+ */
113
+ hasTokens(): boolean;
114
+ /**
115
+ * Attempt a proactive (background) refresh without accumulating toward
116
+ * the nuclear clearTokens() threshold. If refresh fails, the failure
117
+ * counter is reset so background timer ticks can never delete the
118
+ * token file. Only critical-path refreshes (from getAccessToken /
119
+ * ensureAuth) should count toward the threshold.
120
+ *
121
+ * @returns true if refresh succeeded, false otherwise
122
+ */
123
+ refreshProactive(): Promise<boolean>;
124
+ /**
125
+ * Clear all tokens (logout). Also clears the persisted workspace selection
126
+ * so the next login starts from a clean state on shared machines.
127
+ */
128
+ clearTokens(): void;
129
+ /**
130
+ * Get token expiration time
131
+ */
132
+ getExpiresAt(): number;
133
+ /**
134
+ * Get current refresh token (for storage)
135
+ */
136
+ getRefreshToken(): string | null;
137
+ /**
138
+ * Get time until expiration in milliseconds
139
+ */
140
+ getTimeUntilExpiry(): number;
141
+ /**
142
+ * Check if token is expiring soon (within given minutes)
143
+ */
144
+ isExpiringSoon(withinMinutes?: number): boolean;
145
+ /**
146
+ * Check if refresh was definitively rejected (token revoked/invalid).
147
+ * When true, no further refresh attempts will be made until
148
+ * re-authorization or fresh tokens are loaded.
149
+ */
150
+ isRefreshRejected(): boolean;
151
+ /**
152
+ * Reload tokens from storage (useful after external refresh)
153
+ */
154
+ reloadTokens(): void;
155
+ }
156
+ //# sourceMappingURL=oauth-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client.d.ts","sourceRoot":"","sources":["../src/oauth-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH,OAAO,KAAK,EAEV,WAAW,EACX,YAAY,EAEb,MAAM,YAAY,CAAC;AAYpB,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,0BAA0B,CAAa;IAC/C;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAkB;IACzC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,CAA8B;gBAExC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE;IAkB/D;;OAEG;IACH,OAAO,CAAC,gBAAgB;IASxB;;OAEG;IACH,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAQhD;;;OAGG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;YACW,eAAe;IAkF7B;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAsChC;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAkFtC;;OAEG;YACW,YAAY;IAgC1B;;;;;;;;;;;;;;OAcG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC9B;;;;OAIG;YACW,eAAe;IA+C7B;;;OAGG;YACW,eAAe;IAyE7B;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;IAuBjB;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAoBvC;;OAEG;IACH,kBAAkB,IAAI,MAAM,GAAG,IAAI;IAInC;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;;;;;;OAQG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAY1C;;;OAGG;IACH,WAAW,IAAI,IAAI;IAQnB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;OAEG;IACH,cAAc,CAAC,aAAa,GAAE,MAAU,GAAG,OAAO;IAMlD;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;OAEG;IACH,YAAY,IAAI,IAAI;CAGrB"}