oauth.do 0.1.9 → 0.1.10

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/dist/node.js ADDED
@@ -0,0 +1,624 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/storage.ts
12
+ var storage_exports = {};
13
+ __export(storage_exports, {
14
+ CompositeTokenStorage: () => CompositeTokenStorage,
15
+ FileTokenStorage: () => FileTokenStorage,
16
+ KeychainTokenStorage: () => KeychainTokenStorage,
17
+ LocalStorageTokenStorage: () => LocalStorageTokenStorage,
18
+ MemoryTokenStorage: () => MemoryTokenStorage,
19
+ SecureFileTokenStorage: () => SecureFileTokenStorage,
20
+ createSecureStorage: () => createSecureStorage
21
+ });
22
+ function isNode() {
23
+ return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
24
+ }
25
+ function getEnv2(key) {
26
+ if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
27
+ return void 0;
28
+ }
29
+ function createSecureStorage() {
30
+ if (isNode()) {
31
+ return new SecureFileTokenStorage();
32
+ }
33
+ if (typeof localStorage !== "undefined") {
34
+ return new LocalStorageTokenStorage();
35
+ }
36
+ return new MemoryTokenStorage();
37
+ }
38
+ var KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, KeychainTokenStorage, SecureFileTokenStorage, FileTokenStorage, MemoryTokenStorage, LocalStorageTokenStorage, CompositeTokenStorage;
39
+ var init_storage = __esm({
40
+ "src/storage.ts"() {
41
+ KEYCHAIN_SERVICE = "oauth.do";
42
+ KEYCHAIN_ACCOUNT = "access_token";
43
+ KeychainTokenStorage = class {
44
+ keytar = null;
45
+ initialized = false;
46
+ /**
47
+ * Lazily load keytar module
48
+ * Returns null if keytar is not available (e.g., missing native dependencies)
49
+ */
50
+ async getKeytar() {
51
+ if (this.initialized) {
52
+ return this.keytar;
53
+ }
54
+ this.initialized = true;
55
+ try {
56
+ const imported = await import('keytar');
57
+ const keytarModule = imported.default || imported;
58
+ this.keytar = keytarModule;
59
+ if (typeof this.keytar.getPassword !== "function") {
60
+ if (getEnv2("DEBUG")) {
61
+ console.warn("Keytar module loaded but getPassword is not a function:", Object.keys(this.keytar));
62
+ }
63
+ this.keytar = null;
64
+ return null;
65
+ }
66
+ return this.keytar;
67
+ } catch (error) {
68
+ if (getEnv2("DEBUG")) {
69
+ console.warn("Keychain storage not available:", error);
70
+ }
71
+ return null;
72
+ }
73
+ }
74
+ async getToken() {
75
+ const keytar = await this.getKeytar();
76
+ if (!keytar) {
77
+ return null;
78
+ }
79
+ try {
80
+ const token = await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
81
+ return token;
82
+ } catch (error) {
83
+ if (getEnv2("DEBUG")) {
84
+ console.warn("Failed to get token from keychain:", error);
85
+ }
86
+ return null;
87
+ }
88
+ }
89
+ async setToken(token) {
90
+ try {
91
+ const keytar = await this.getKeytar();
92
+ if (!keytar) {
93
+ throw new Error("Keychain storage not available");
94
+ }
95
+ await keytar.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, token);
96
+ } catch (error) {
97
+ if (error?.code === "MODULE_NOT_FOUND" || error?.message?.includes("Cannot find module")) {
98
+ throw new Error("Keychain storage not available: native module not built");
99
+ }
100
+ throw new Error(`Failed to save token to keychain: ${error}`);
101
+ }
102
+ }
103
+ async removeToken() {
104
+ const keytar = await this.getKeytar();
105
+ if (!keytar) {
106
+ return;
107
+ }
108
+ try {
109
+ await keytar.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
110
+ } catch {
111
+ }
112
+ }
113
+ /**
114
+ * Check if keychain storage is available on this system
115
+ */
116
+ async isAvailable() {
117
+ try {
118
+ const keytar = await this.getKeytar();
119
+ if (!keytar) {
120
+ return false;
121
+ }
122
+ await keytar.getPassword(KEYCHAIN_SERVICE, "__test__");
123
+ return true;
124
+ } catch (error) {
125
+ if (getEnv2("DEBUG")) {
126
+ console.warn("Keychain not available:", error);
127
+ }
128
+ return false;
129
+ }
130
+ }
131
+ };
132
+ SecureFileTokenStorage = class {
133
+ tokenPath = null;
134
+ configDir = null;
135
+ initialized = false;
136
+ async init() {
137
+ if (this.initialized) return this.tokenPath !== null;
138
+ this.initialized = true;
139
+ if (!isNode()) return false;
140
+ try {
141
+ const os = await import('os');
142
+ const path = await import('path');
143
+ this.configDir = path.join(os.homedir(), ".oauth.do");
144
+ this.tokenPath = path.join(this.configDir, "token");
145
+ return true;
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ async getToken() {
151
+ if (!await this.init() || !this.tokenPath) return null;
152
+ try {
153
+ const fs = await import('fs/promises');
154
+ const stats = await fs.stat(this.tokenPath);
155
+ const mode = stats.mode & 511;
156
+ if (mode !== 384 && getEnv2("DEBUG")) {
157
+ console.warn(
158
+ `Warning: Token file has insecure permissions (${mode.toString(8)}). Expected 600. Run: chmod 600 ${this.tokenPath}`
159
+ );
160
+ }
161
+ const token = await fs.readFile(this.tokenPath, "utf-8");
162
+ return token.trim();
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ async setToken(token) {
168
+ if (!await this.init() || !this.tokenPath || !this.configDir) {
169
+ throw new Error("File storage not available");
170
+ }
171
+ try {
172
+ const fs = await import('fs/promises');
173
+ await fs.mkdir(this.configDir, { recursive: true, mode: 448 });
174
+ await fs.writeFile(this.tokenPath, token, { encoding: "utf-8", mode: 384 });
175
+ await fs.chmod(this.tokenPath, 384);
176
+ } catch (error) {
177
+ console.error("Failed to save token:", error);
178
+ throw error;
179
+ }
180
+ }
181
+ async removeToken() {
182
+ if (!await this.init() || !this.tokenPath) return;
183
+ try {
184
+ const fs = await import('fs/promises');
185
+ await fs.unlink(this.tokenPath);
186
+ } catch {
187
+ }
188
+ }
189
+ /**
190
+ * Get information about the storage backend
191
+ */
192
+ async getStorageInfo() {
193
+ await this.init();
194
+ return { type: "file", secure: true, path: this.tokenPath };
195
+ }
196
+ };
197
+ FileTokenStorage = class {
198
+ tokenPath = null;
199
+ configDir = null;
200
+ initialized = false;
201
+ async init() {
202
+ if (this.initialized) return this.tokenPath !== null;
203
+ this.initialized = true;
204
+ if (!isNode()) return false;
205
+ try {
206
+ const os = await import('os');
207
+ const path = await import('path');
208
+ this.configDir = path.join(os.homedir(), ".oauth.do");
209
+ this.tokenPath = path.join(this.configDir, "token");
210
+ return true;
211
+ } catch {
212
+ return false;
213
+ }
214
+ }
215
+ async getToken() {
216
+ if (!await this.init() || !this.tokenPath) return null;
217
+ try {
218
+ const fs = await import('fs/promises');
219
+ const token = await fs.readFile(this.tokenPath, "utf-8");
220
+ return token.trim();
221
+ } catch {
222
+ return null;
223
+ }
224
+ }
225
+ async setToken(token) {
226
+ if (!await this.init() || !this.tokenPath || !this.configDir) {
227
+ throw new Error("File storage not available");
228
+ }
229
+ try {
230
+ const fs = await import('fs/promises');
231
+ await fs.mkdir(this.configDir, { recursive: true });
232
+ await fs.writeFile(this.tokenPath, token, "utf-8");
233
+ } catch (error) {
234
+ console.error("Failed to save token:", error);
235
+ throw error;
236
+ }
237
+ }
238
+ async removeToken() {
239
+ if (!await this.init() || !this.tokenPath) return;
240
+ try {
241
+ const fs = await import('fs/promises');
242
+ await fs.unlink(this.tokenPath);
243
+ } catch {
244
+ }
245
+ }
246
+ };
247
+ MemoryTokenStorage = class {
248
+ token = null;
249
+ async getToken() {
250
+ return this.token;
251
+ }
252
+ async setToken(token) {
253
+ this.token = token;
254
+ }
255
+ async removeToken() {
256
+ this.token = null;
257
+ }
258
+ };
259
+ LocalStorageTokenStorage = class {
260
+ key = "oauth.do:token";
261
+ async getToken() {
262
+ if (typeof localStorage === "undefined") {
263
+ return null;
264
+ }
265
+ return localStorage.getItem(this.key);
266
+ }
267
+ async setToken(token) {
268
+ if (typeof localStorage === "undefined") {
269
+ throw new Error("localStorage is not available");
270
+ }
271
+ localStorage.setItem(this.key, token);
272
+ }
273
+ async removeToken() {
274
+ if (typeof localStorage === "undefined") {
275
+ return;
276
+ }
277
+ localStorage.removeItem(this.key);
278
+ }
279
+ };
280
+ CompositeTokenStorage = class {
281
+ keychainStorage;
282
+ fileStorage;
283
+ preferredStorage = null;
284
+ constructor() {
285
+ this.keychainStorage = new KeychainTokenStorage();
286
+ this.fileStorage = new SecureFileTokenStorage();
287
+ }
288
+ /**
289
+ * Determine the best available storage backend
290
+ */
291
+ async getPreferredStorage() {
292
+ if (this.preferredStorage) {
293
+ return this.preferredStorage;
294
+ }
295
+ if (await this.keychainStorage.isAvailable()) {
296
+ this.preferredStorage = this.keychainStorage;
297
+ return this.preferredStorage;
298
+ }
299
+ this.preferredStorage = this.fileStorage;
300
+ return this.preferredStorage;
301
+ }
302
+ async getToken() {
303
+ const keychainToken = await this.keychainStorage.getToken();
304
+ if (keychainToken) {
305
+ return keychainToken;
306
+ }
307
+ const fileToken = await this.fileStorage.getToken();
308
+ if (fileToken) {
309
+ if (await this.keychainStorage.isAvailable()) {
310
+ try {
311
+ await this.keychainStorage.setToken(fileToken);
312
+ await this.fileStorage.removeToken();
313
+ if (getEnv2("DEBUG")) {
314
+ console.log("Migrated token from file to keychain");
315
+ }
316
+ } catch {
317
+ }
318
+ }
319
+ return fileToken;
320
+ }
321
+ return null;
322
+ }
323
+ async setToken(token) {
324
+ const storage = await this.getPreferredStorage();
325
+ await storage.setToken(token);
326
+ }
327
+ async removeToken() {
328
+ await Promise.all([this.keychainStorage.removeToken(), this.fileStorage.removeToken()]);
329
+ }
330
+ /**
331
+ * Get information about the current storage backend
332
+ */
333
+ async getStorageInfo() {
334
+ if (await this.keychainStorage.isAvailable()) {
335
+ return { type: "keychain", secure: true };
336
+ }
337
+ return { type: "file", secure: true };
338
+ }
339
+ };
340
+ }
341
+ });
342
+
343
+ // src/config.ts
344
+ function getEnv(key) {
345
+ if (globalThis[key]) return globalThis[key];
346
+ if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
347
+ return void 0;
348
+ }
349
+ var globalConfig = {
350
+ apiUrl: getEnv("OAUTH_API_URL") || getEnv("API_URL") || "https://apis.do",
351
+ clientId: getEnv("OAUTH_CLIENT_ID") || "client_01JQYTRXK9ZPD8JPJTKDCRB656",
352
+ authKitDomain: getEnv("OAUTH_AUTHKIT_DOMAIN") || "login.oauth.do",
353
+ fetch: globalThis.fetch
354
+ };
355
+ function configure(config) {
356
+ globalConfig = {
357
+ ...globalConfig,
358
+ ...config
359
+ };
360
+ }
361
+ function getConfig() {
362
+ return globalConfig;
363
+ }
364
+
365
+ // src/auth.ts
366
+ async function resolveSecret(value) {
367
+ if (!value) return null;
368
+ if (typeof value === "string") return value;
369
+ if (typeof value === "object" && typeof value.get === "function") {
370
+ return await value.get();
371
+ }
372
+ return null;
373
+ }
374
+ function getEnv3(key) {
375
+ if (globalThis[key]) return globalThis[key];
376
+ if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
377
+ return void 0;
378
+ }
379
+ async function getUser(token) {
380
+ const config = getConfig();
381
+ const authToken = token || getEnv3("DO_TOKEN") || "";
382
+ if (!authToken) {
383
+ return { user: null };
384
+ }
385
+ try {
386
+ const response = await config.fetch(`${config.apiUrl}/me`, {
387
+ method: "GET",
388
+ headers: {
389
+ "Authorization": `Bearer ${authToken}`,
390
+ "Content-Type": "application/json"
391
+ }
392
+ });
393
+ if (!response.ok) {
394
+ if (response.status === 401) {
395
+ return { user: null };
396
+ }
397
+ throw new Error(`Authentication failed: ${response.statusText}`);
398
+ }
399
+ const user = await response.json();
400
+ return { user, token: authToken };
401
+ } catch (error) {
402
+ console.error("Auth error:", error);
403
+ return { user: null };
404
+ }
405
+ }
406
+ async function login(credentials) {
407
+ const config = getConfig();
408
+ try {
409
+ const response = await config.fetch(`${config.apiUrl}/login`, {
410
+ method: "POST",
411
+ headers: {
412
+ "Content-Type": "application/json"
413
+ },
414
+ body: JSON.stringify(credentials)
415
+ });
416
+ if (!response.ok) {
417
+ throw new Error(`Login failed: ${response.statusText}`);
418
+ }
419
+ const data = await response.json();
420
+ return { user: data.user, token: data.token };
421
+ } catch (error) {
422
+ console.error("Login error:", error);
423
+ throw error;
424
+ }
425
+ }
426
+ async function logout(token) {
427
+ const config = getConfig();
428
+ const authToken = token || getEnv3("DO_TOKEN") || "";
429
+ if (!authToken) {
430
+ return;
431
+ }
432
+ try {
433
+ const response = await config.fetch(`${config.apiUrl}/logout`, {
434
+ method: "POST",
435
+ headers: {
436
+ "Authorization": `Bearer ${authToken}`,
437
+ "Content-Type": "application/json"
438
+ }
439
+ });
440
+ if (!response.ok) {
441
+ console.warn(`Logout warning: ${response.statusText}`);
442
+ }
443
+ } catch (error) {
444
+ console.error("Logout error:", error);
445
+ }
446
+ }
447
+ async function getToken() {
448
+ const adminToken = getEnv3("DO_ADMIN_TOKEN");
449
+ if (adminToken) return adminToken;
450
+ const doToken = getEnv3("DO_TOKEN");
451
+ if (doToken) return doToken;
452
+ try {
453
+ const { env } = await import('cloudflare:workers');
454
+ const cfAdminToken = await resolveSecret(env.DO_ADMIN_TOKEN);
455
+ if (cfAdminToken) return cfAdminToken;
456
+ const cfToken = await resolveSecret(env.DO_TOKEN);
457
+ if (cfToken) return cfToken;
458
+ } catch {
459
+ }
460
+ try {
461
+ const { createSecureStorage: createSecureStorage2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
462
+ const storage = createSecureStorage2();
463
+ return await storage.getToken();
464
+ } catch {
465
+ return null;
466
+ }
467
+ }
468
+ async function isAuthenticated(token) {
469
+ const result = await getUser(token);
470
+ return result.user !== null;
471
+ }
472
+ function auth() {
473
+ return getToken;
474
+ }
475
+ function buildAuthUrl(options) {
476
+ const config = getConfig();
477
+ const clientId = options.clientId || config.clientId;
478
+ const authDomain = options.authDomain || config.authKitDomain;
479
+ const params = new URLSearchParams({
480
+ client_id: clientId,
481
+ redirect_uri: options.redirectUri,
482
+ response_type: options.responseType || "code",
483
+ scope: options.scope || "openid profile email"
484
+ });
485
+ if (options.state) {
486
+ params.set("state", options.state);
487
+ }
488
+ return `https://${authDomain}/authorize?${params.toString()}`;
489
+ }
490
+
491
+ // src/device.ts
492
+ async function authorizeDevice() {
493
+ const config = getConfig();
494
+ if (!config.clientId) {
495
+ throw new Error('Client ID is required for device authorization. Set OAUTH_CLIENT_ID or configure({ clientId: "..." })');
496
+ }
497
+ try {
498
+ const url = "https://auth.apis.do/user_management/authorize/device";
499
+ const body = new URLSearchParams({
500
+ client_id: config.clientId,
501
+ scope: "openid profile email"
502
+ });
503
+ const response = await config.fetch(url, {
504
+ method: "POST",
505
+ headers: {
506
+ "Content-Type": "application/x-www-form-urlencoded"
507
+ },
508
+ body
509
+ });
510
+ if (!response.ok) {
511
+ const errorText = await response.text();
512
+ throw new Error(`Device authorization failed: ${response.statusText} - ${errorText}`);
513
+ }
514
+ const data = await response.json();
515
+ return data;
516
+ } catch (error) {
517
+ console.error("Device authorization error:", error);
518
+ throw error;
519
+ }
520
+ }
521
+ async function pollForTokens(deviceCode, interval = 5, expiresIn = 600) {
522
+ const config = getConfig();
523
+ if (!config.clientId) {
524
+ throw new Error("Client ID is required for token polling");
525
+ }
526
+ const startTime = Date.now();
527
+ const timeout = expiresIn * 1e3;
528
+ let currentInterval = interval * 1e3;
529
+ while (true) {
530
+ if (Date.now() - startTime > timeout) {
531
+ throw new Error("Device authorization expired. Please try again.");
532
+ }
533
+ await new Promise((resolve) => setTimeout(resolve, currentInterval));
534
+ try {
535
+ const response = await config.fetch("https://auth.apis.do/user_management/authenticate", {
536
+ method: "POST",
537
+ headers: {
538
+ "Content-Type": "application/x-www-form-urlencoded"
539
+ },
540
+ body: new URLSearchParams({
541
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
542
+ device_code: deviceCode,
543
+ client_id: config.clientId
544
+ })
545
+ });
546
+ if (response.ok) {
547
+ const data = await response.json();
548
+ return data;
549
+ }
550
+ const errorData = await response.json().catch(() => ({ error: "unknown" }));
551
+ const error = errorData.error || "unknown";
552
+ switch (error) {
553
+ case "authorization_pending":
554
+ continue;
555
+ case "slow_down":
556
+ currentInterval += 5e3;
557
+ continue;
558
+ case "access_denied":
559
+ throw new Error("Access denied by user");
560
+ case "expired_token":
561
+ throw new Error("Device code expired");
562
+ default:
563
+ throw new Error(`Token polling failed: ${error}`);
564
+ }
565
+ } catch (error) {
566
+ if (error instanceof Error) {
567
+ throw error;
568
+ }
569
+ continue;
570
+ }
571
+ }
572
+ }
573
+
574
+ // src/index.ts
575
+ init_storage();
576
+
577
+ // src/login.ts
578
+ init_storage();
579
+ async function ensureLoggedIn(options = {}) {
580
+ const { openBrowser = true, print = console.log, storage = createSecureStorage() } = options;
581
+ const existingToken = await storage.getToken();
582
+ if (existingToken) {
583
+ return { token: existingToken, isNewLogin: false };
584
+ }
585
+ print("\nLogging in...\n");
586
+ const authResponse = await authorizeDevice();
587
+ print(`To complete login:`);
588
+ print(` 1. Visit: ${authResponse.verification_uri}`);
589
+ print(` 2. Enter code: ${authResponse.user_code}`);
590
+ print(`
591
+ Or open: ${authResponse.verification_uri_complete}
592
+ `);
593
+ if (openBrowser) {
594
+ try {
595
+ const open = await import('open');
596
+ await open.default(authResponse.verification_uri_complete);
597
+ print("Browser opened automatically\n");
598
+ } catch {
599
+ }
600
+ }
601
+ print("Waiting for authorization...\n");
602
+ const tokenResponse = await pollForTokens(
603
+ authResponse.device_code,
604
+ authResponse.interval,
605
+ authResponse.expires_in
606
+ );
607
+ await storage.setToken(tokenResponse.access_token);
608
+ print("Login successful!\n");
609
+ return { token: tokenResponse.access_token, isNewLogin: true };
610
+ }
611
+ async function forceLogin(options = {}) {
612
+ const { storage = createSecureStorage() } = options;
613
+ await storage.removeToken();
614
+ return ensureLoggedIn(options);
615
+ }
616
+ async function ensureLoggedOut(options = {}) {
617
+ const { print = console.log, storage = createSecureStorage() } = options;
618
+ await storage.removeToken();
619
+ print("Logged out successfully\n");
620
+ }
621
+
622
+ export { CompositeTokenStorage, FileTokenStorage, KeychainTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, SecureFileTokenStorage, auth, authorizeDevice, buildAuthUrl, configure, createSecureStorage, ensureLoggedIn, ensureLoggedOut, forceLogin, getConfig, getToken, getUser, isAuthenticated, login, logout, pollForTokens };
623
+ //# sourceMappingURL=node.js.map
624
+ //# sourceMappingURL=node.js.map