oauth.do 0.1.14 → 0.2.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.
package/dist/hono.js ADDED
@@ -0,0 +1,198 @@
1
+ import { getCookie } from 'hono/cookie';
2
+ import * as jose from 'jose';
3
+
4
+ // src/hono.ts
5
+ var OAUTH_DO_CONFIG = {
6
+ clientId: "client_01JQYTRXK9ZPD8JPJTKDCRB656",
7
+ jwksUri: "https://api.workos.com/sso/jwks/client_01JQYTRXK9ZPD8JPJTKDCRB656"
8
+ };
9
+ var TOKEN_CACHE_TTL = 5 * 60;
10
+ var CACHE_URL_PREFIX = "https://oauth.do/_cache/token/";
11
+ async function hashToken(token) {
12
+ const data = new TextEncoder().encode(token);
13
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
14
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
15
+ }
16
+ async function getCachedUser(token) {
17
+ try {
18
+ const cache = caches.default;
19
+ const hash = await hashToken(token);
20
+ const cacheKey = new Request(`${CACHE_URL_PREFIX}${hash}`);
21
+ const cached = await cache.match(cacheKey);
22
+ if (!cached) return null;
23
+ const data = await cached.json();
24
+ if (data.expiresAt < Date.now()) return null;
25
+ return data.user;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ async function cacheUser(token, user) {
31
+ try {
32
+ const cache = caches.default;
33
+ const hash = await hashToken(token);
34
+ const cacheKey = new Request(`${CACHE_URL_PREFIX}${hash}`);
35
+ const data = { user, expiresAt: Date.now() + TOKEN_CACHE_TTL * 1e3 };
36
+ const response = new Response(JSON.stringify(data), {
37
+ headers: { "Cache-Control": `max-age=${TOKEN_CACHE_TTL}` }
38
+ });
39
+ await cache.put(cacheKey, response);
40
+ } catch {
41
+ }
42
+ }
43
+ function extractToken(c, cookieName, headerName) {
44
+ const authHeader = c.req.header(headerName);
45
+ if (authHeader?.startsWith("Bearer ")) {
46
+ return authHeader.slice(7);
47
+ }
48
+ const cookie = getCookie(c, cookieName);
49
+ if (cookie) return cookie;
50
+ return null;
51
+ }
52
+ function payloadToUser(payload) {
53
+ return {
54
+ id: payload.sub || "",
55
+ email: payload.email,
56
+ name: payload.name,
57
+ organizationId: payload.org_id,
58
+ roles: payload.roles,
59
+ permissions: payload.permissions,
60
+ metadata: payload.metadata
61
+ };
62
+ }
63
+ var jwksCache = null;
64
+ var jwksCacheExpiry = 0;
65
+ async function getJwks(jwksUri, cacheTtl) {
66
+ const now = Date.now();
67
+ if (jwksCache && jwksCacheExpiry > now) {
68
+ return jwksCache;
69
+ }
70
+ jwksCache = jose.createRemoteJWKSet(new URL(jwksUri));
71
+ jwksCacheExpiry = now + cacheTtl * 1e3;
72
+ return jwksCache;
73
+ }
74
+ function auth(options = {}) {
75
+ const {
76
+ cookieName = "auth",
77
+ headerName = "Authorization",
78
+ clientId = OAUTH_DO_CONFIG.clientId,
79
+ jwksUri = OAUTH_DO_CONFIG.jwksUri,
80
+ skip,
81
+ jwksCacheTtl = 3600
82
+ } = options;
83
+ return async (c, next) => {
84
+ c.set("user", null);
85
+ c.set("userId", null);
86
+ c.set("isAuth", false);
87
+ c.set("token", null);
88
+ if (skip?.(c)) {
89
+ return next();
90
+ }
91
+ const token = extractToken(c, cookieName, headerName);
92
+ if (!token) {
93
+ return next();
94
+ }
95
+ c.set("token", token);
96
+ const cached = await getCachedUser(token);
97
+ if (cached) {
98
+ c.set("user", cached);
99
+ c.set("userId", cached.id);
100
+ c.set("isAuth", true);
101
+ return next();
102
+ }
103
+ try {
104
+ const jwks = await getJwks(jwksUri, jwksCacheTtl);
105
+ const { payload } = await jose.jwtVerify(token, jwks, {
106
+ audience: clientId
107
+ });
108
+ const user = payloadToUser(payload);
109
+ c.set("user", user);
110
+ c.set("userId", user.id);
111
+ c.set("isAuth", true);
112
+ await cacheUser(token, user);
113
+ } catch {
114
+ }
115
+ return next();
116
+ };
117
+ }
118
+ function requireAuth(options = {}) {
119
+ const { redirectTo, roles, permissions, ...authOptions } = options;
120
+ return async (c, next) => {
121
+ if (c.var.user === void 0) {
122
+ await auth(authOptions)(c, async () => {
123
+ });
124
+ }
125
+ if (!c.var.isAuth || !c.var.user) {
126
+ if (redirectTo) {
127
+ return c.redirect(redirectTo);
128
+ }
129
+ return c.json({ error: "Authentication required" }, 401);
130
+ }
131
+ if (roles?.length) {
132
+ const userRoles = c.var.user.roles || [];
133
+ const hasRole = roles.some((r) => userRoles.includes(r));
134
+ if (!hasRole) {
135
+ return c.json({ error: "Insufficient permissions" }, 403);
136
+ }
137
+ }
138
+ if (permissions?.length) {
139
+ const userPerms = c.var.user.permissions || [];
140
+ const hasAllPerms = permissions.every((p) => userPerms.includes(p));
141
+ if (!hasAllPerms) {
142
+ return c.json({ error: "Insufficient permissions" }, 403);
143
+ }
144
+ }
145
+ return next();
146
+ };
147
+ }
148
+ function apiKey(options) {
149
+ const { headerName = "X-API-Key", verify } = options;
150
+ return async (c, next) => {
151
+ c.set("user", null);
152
+ c.set("userId", null);
153
+ c.set("isAuth", false);
154
+ c.set("token", null);
155
+ const key = c.req.header(headerName);
156
+ if (!key) {
157
+ return c.json({ error: "API key required" }, 401);
158
+ }
159
+ const user = await verify(key, c);
160
+ if (!user) {
161
+ return c.json({ error: "Invalid API key" }, 401);
162
+ }
163
+ c.set("user", user);
164
+ c.set("userId", user.id);
165
+ c.set("isAuth", true);
166
+ c.set("token", key);
167
+ return next();
168
+ };
169
+ }
170
+ function combined(options) {
171
+ return async (c, next) => {
172
+ if (options.auth) {
173
+ await auth(options.auth)(c, async () => {
174
+ });
175
+ if (c.var.isAuth) {
176
+ return next();
177
+ }
178
+ }
179
+ if (options.apiKey) {
180
+ const key = c.req.header(options.apiKey.headerName || "X-API-Key");
181
+ if (key) {
182
+ const user = await options.apiKey.verify(key, c);
183
+ if (user) {
184
+ c.set("user", user);
185
+ c.set("userId", user.id);
186
+ c.set("isAuth", true);
187
+ c.set("token", key);
188
+ return next();
189
+ }
190
+ }
191
+ }
192
+ return c.json({ error: "Authentication required" }, 401);
193
+ };
194
+ }
195
+
196
+ export { apiKey, auth, combined, requireAuth };
197
+ //# sourceMappingURL=hono.js.map
198
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hono.ts"],"names":[],"mappings":"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","file":"hono.js","sourcesContent":["/**\n * oauth.do/hono - Hono middleware for authentication\n *\n * Lightweight authentication middleware for Cloudflare Workers.\n * Uses jose for JWT verification - no heavy WorkOS SDK dependency.\n *\n * @packageDocumentation\n */\n\nimport type { Context, MiddlewareHandler } from 'hono'\nimport { getCookie } from 'hono/cookie'\nimport type { JWTPayload } from 'jose'\nimport * as jose from 'jose'\n\n// Cloudflare Workers Cache API type\ndeclare const caches: {\n default: Cache\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Types\n// ═══════════════════════════════════════════════════════════════════════════\n\nexport interface AuthUser {\n id: string\n email?: string\n name?: string\n organizationId?: string\n roles?: string[]\n permissions?: string[]\n metadata?: Record<string, unknown>\n}\n\nexport interface AuthVariables {\n user: AuthUser | null\n userId: string | null\n isAuth: boolean\n token: string | null\n}\n\ndeclare module 'hono' {\n interface ContextVariableMap extends AuthVariables {}\n}\n\nexport interface AuthOptions {\n /** Cookie name for JWT token (default: 'auth') */\n cookieName?: string\n /** Header name for Bearer token (default: 'Authorization') */\n headerName?: string\n /** WorkOS Client ID (default: oauth.do client ID) */\n clientId?: string\n /** JWKS URI for token verification (default: WorkOS JWKS) */\n jwksUri?: string\n /** Skip auth for certain paths */\n skip?: (c: Context) => boolean\n /** Cache duration for JWKS in seconds (default: 3600) */\n jwksCacheTtl?: number\n}\n\nexport interface RequireAuthOptions extends AuthOptions {\n /** Redirect to login page instead of 401 */\n redirectTo?: string\n /** Required roles (any of) */\n roles?: string[]\n /** Required permissions (all of) */\n permissions?: string[]\n}\n\nexport interface ApiKeyOptions {\n /** Header name (default: 'X-API-Key') */\n headerName?: string\n /** Verify function - return user if valid, null if not */\n verify: (key: string, c: Context) => Promise<AuthUser | null>\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst OAUTH_DO_CONFIG = {\n clientId: 'client_01JQYTRXK9ZPD8JPJTKDCRB656',\n jwksUri: 'https://api.workos.com/sso/jwks/client_01JQYTRXK9ZPD8JPJTKDCRB656',\n}\n\nconst TOKEN_CACHE_TTL = 5 * 60 // 5 minutes\nconst CACHE_URL_PREFIX = 'https://oauth.do/_cache/token/'\n\n// ═══════════════════════════════════════════════════════════════════════════\n// JWT Utilities\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Hash a token for cache key (avoids storing raw tokens in cache)\n */\nasync function hashToken(token: string): Promise<string> {\n const data = new TextEncoder().encode(token)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n return Array.from(new Uint8Array(hashBuffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Get cached user from Cache API\n */\nasync function getCachedUser(token: string): Promise<AuthUser | null> {\n try {\n const cache = caches.default\n const hash = await hashToken(token)\n const cacheKey = new Request(`${CACHE_URL_PREFIX}${hash}`)\n const cached = await cache.match(cacheKey)\n\n if (!cached) return null\n\n const data = await cached.json() as { user: AuthUser; expiresAt: number }\n if (data.expiresAt < Date.now()) return null\n\n return data.user\n } catch {\n return null\n }\n}\n\n/**\n * Cache user in Cache API\n */\nasync function cacheUser(token: string, user: AuthUser): Promise<void> {\n try {\n const cache = caches.default\n const hash = await hashToken(token)\n const cacheKey = new Request(`${CACHE_URL_PREFIX}${hash}`)\n const data = { user, expiresAt: Date.now() + TOKEN_CACHE_TTL * 1000 }\n const response = new Response(JSON.stringify(data), {\n headers: { 'Cache-Control': `max-age=${TOKEN_CACHE_TTL}` },\n })\n await cache.put(cacheKey, response)\n } catch {\n // Cache failures are non-fatal\n }\n}\n\n/**\n * Extract JWT from request (cookie or Bearer header)\n */\nfunction extractToken(c: Context, cookieName: string, headerName: string): string | null {\n // Try Bearer header first\n const authHeader = c.req.header(headerName)\n if (authHeader?.startsWith('Bearer ')) {\n return authHeader.slice(7)\n }\n\n // Try cookie\n const cookie = getCookie(c, cookieName)\n if (cookie) return cookie\n\n return null\n}\n\n/**\n * Convert JWT payload to AuthUser\n */\nfunction payloadToUser(payload: JWTPayload): AuthUser {\n return {\n id: payload.sub || '',\n email: payload.email as string | undefined,\n name: payload.name as string | undefined,\n organizationId: payload.org_id as string | undefined,\n roles: payload.roles as string[] | undefined,\n permissions: payload.permissions as string[] | undefined,\n metadata: payload.metadata as Record<string, unknown> | undefined,\n }\n}\n\n// JWKS cache (module-level, persists across requests)\nlet jwksCache: jose.JWTVerifyGetKey | null = null\nlet jwksCacheExpiry = 0\n\n/**\n * Get JWKS verifier with caching\n */\nasync function getJwks(jwksUri: string, cacheTtl: number): Promise<jose.JWTVerifyGetKey> {\n const now = Date.now()\n if (jwksCache && jwksCacheExpiry > now) {\n return jwksCache\n }\n\n jwksCache = jose.createRemoteJWKSet(new URL(jwksUri))\n jwksCacheExpiry = now + cacheTtl * 1000\n return jwksCache\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Middleware\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Auth middleware - populates c.var.user if authenticated\n *\n * Does NOT reject unauthenticated requests. Use requireAuth() for that.\n *\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { auth } from 'oauth.do/hono'\n *\n * const app = new Hono()\n * app.use('*', auth())\n *\n * app.get('/api/me', (c) => {\n * if (!c.var.user) return c.json({ error: 'Not authenticated' }, 401)\n * return c.json(c.var.user)\n * })\n * ```\n */\nexport function auth(options: AuthOptions = {}): MiddlewareHandler {\n const {\n cookieName = 'auth',\n headerName = 'Authorization',\n clientId = OAUTH_DO_CONFIG.clientId,\n jwksUri = OAUTH_DO_CONFIG.jwksUri,\n skip,\n jwksCacheTtl = 3600,\n } = options\n\n return async (c, next) => {\n // Initialize variables\n c.set('user', null)\n c.set('userId', null)\n c.set('isAuth', false)\n c.set('token', null)\n\n // Skip if configured\n if (skip?.(c)) {\n return next()\n }\n\n const token = extractToken(c, cookieName, headerName)\n if (!token) {\n return next()\n }\n\n c.set('token', token)\n\n // Check cache first\n const cached = await getCachedUser(token)\n if (cached) {\n c.set('user', cached)\n c.set('userId', cached.id)\n c.set('isAuth', true)\n return next()\n }\n\n // Verify JWT\n try {\n const jwks = await getJwks(jwksUri, jwksCacheTtl)\n const { payload } = await jose.jwtVerify(token, jwks, {\n audience: clientId,\n })\n\n const user = payloadToUser(payload)\n c.set('user', user)\n c.set('userId', user.id)\n c.set('isAuth', true)\n\n // Cache the result\n await cacheUser(token, user)\n } catch {\n // Invalid token - leave user as null\n }\n\n return next()\n }\n}\n\n/**\n * Require auth middleware - rejects unauthenticated requests\n *\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { auth, requireAuth } from 'oauth.do/hono'\n *\n * const app = new Hono()\n * app.use('*', auth())\n * app.use('/api/*', requireAuth())\n *\n * app.get('/api/secret', (c) => {\n * return c.json({ secret: 'data', user: c.var.user })\n * })\n * ```\n */\nexport function requireAuth(options: RequireAuthOptions = {}): MiddlewareHandler {\n const { redirectTo, roles, permissions, ...authOptions } = options\n\n return async (c, next) => {\n // Run auth middleware first if not already done\n if (c.var.user === undefined) {\n await auth(authOptions)(c, async () => {})\n }\n\n if (!c.var.isAuth || !c.var.user) {\n if (redirectTo) {\n return c.redirect(redirectTo)\n }\n return c.json({ error: 'Authentication required' }, 401)\n }\n\n // Check roles (any of)\n if (roles?.length) {\n const userRoles = c.var.user.roles || []\n const hasRole = roles.some((r) => userRoles.includes(r))\n if (!hasRole) {\n return c.json({ error: 'Insufficient permissions' }, 403)\n }\n }\n\n // Check permissions (all of)\n if (permissions?.length) {\n const userPerms = c.var.user.permissions || []\n const hasAllPerms = permissions.every((p) => userPerms.includes(p))\n if (!hasAllPerms) {\n return c.json({ error: 'Insufficient permissions' }, 403)\n }\n }\n\n return next()\n }\n}\n\n/**\n * API key middleware - authenticates via API key header\n *\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { apiKey } from 'oauth.do/hono'\n *\n * const app = new Hono()\n * app.use('/api/*', apiKey({\n * verify: async (key, c) => {\n * // Verify key against your database/service\n * const user = await verifyApiKey(key)\n * return user\n * }\n * }))\n * ```\n */\nexport function apiKey(options: ApiKeyOptions): MiddlewareHandler {\n const { headerName = 'X-API-Key', verify } = options\n\n return async (c, next) => {\n c.set('user', null)\n c.set('userId', null)\n c.set('isAuth', false)\n c.set('token', null)\n\n const key = c.req.header(headerName)\n if (!key) {\n return c.json({ error: 'API key required' }, 401)\n }\n\n const user = await verify(key, c)\n if (!user) {\n return c.json({ error: 'Invalid API key' }, 401)\n }\n\n c.set('user', user)\n c.set('userId', user.id)\n c.set('isAuth', true)\n c.set('token', key)\n\n return next()\n }\n}\n\n/**\n * Combined auth middleware - tries JWT first, then API key\n *\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { combined } from 'oauth.do/hono'\n *\n * const app = new Hono()\n * app.use('/api/*', combined({\n * apiKey: {\n * verify: async (key) => verifyApiKey(key)\n * }\n * }))\n * ```\n */\nexport function combined(options: {\n auth?: AuthOptions\n apiKey?: ApiKeyOptions\n}): MiddlewareHandler {\n return async (c, next) => {\n // Try JWT auth first\n if (options.auth) {\n await auth(options.auth)(c, async () => {})\n if (c.var.isAuth) {\n return next()\n }\n }\n\n // Fall back to API key\n if (options.apiKey) {\n const key = c.req.header(options.apiKey.headerName || 'X-API-Key')\n if (key) {\n const user = await options.apiKey.verify(key, c)\n if (user) {\n c.set('user', user)\n c.set('userId', user.id)\n c.set('isAuth', true)\n c.set('token', key)\n return next()\n }\n }\n }\n\n return c.json({ error: 'Authentication required' }, 401)\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,91 +1,5 @@
1
- /**
2
- * OAuth configuration options
3
- */
4
- interface OAuthConfig {
5
- /**
6
- * Base URL for API endpoints
7
- * @default 'https://apis.do'
8
- */
9
- apiUrl?: string;
10
- /**
11
- * Client ID for OAuth flow
12
- */
13
- clientId?: string;
14
- /**
15
- * AuthKit domain for device authorization
16
- * @default 'login.oauth.do'
17
- */
18
- authKitDomain?: string;
19
- /**
20
- * Custom fetch implementation
21
- */
22
- fetch?: typeof fetch;
23
- /**
24
- * Custom path for token storage
25
- * Supports ~ for home directory (e.g., '~/.studio/tokens.json')
26
- * @default '~/.oauth.do/token'
27
- */
28
- storagePath?: string;
29
- }
30
- /**
31
- * User information returned from auth endpoints
32
- */
33
- interface User {
34
- id: string;
35
- email?: string;
36
- name?: string;
37
- [key: string]: any;
38
- }
39
- /**
40
- * Authentication result
41
- */
42
- interface AuthResult {
43
- user: User | null;
44
- token?: string;
45
- }
46
- /**
47
- * Device authorization response
48
- */
49
- interface DeviceAuthorizationResponse {
50
- device_code: string;
51
- user_code: string;
52
- verification_uri: string;
53
- verification_uri_complete: string;
54
- expires_in: number;
55
- interval: number;
56
- }
57
- /**
58
- * Token response
59
- */
60
- interface TokenResponse {
61
- access_token: string;
62
- refresh_token?: string;
63
- token_type: string;
64
- expires_in?: number;
65
- user?: User;
66
- }
67
- /**
68
- * Token polling error types
69
- */
70
- type TokenError = 'authorization_pending' | 'slow_down' | 'access_denied' | 'expired_token' | 'unknown';
71
- /**
72
- * Stored token data including refresh token and expiration
73
- */
74
- interface StoredTokenData {
75
- accessToken: string;
76
- refreshToken?: string;
77
- expiresAt?: number;
78
- }
79
- /**
80
- * Token storage interface
81
- */
82
- interface TokenStorage {
83
- getToken(): Promise<string | null>;
84
- setToken(token: string): Promise<void>;
85
- removeToken(): Promise<void>;
86
- getTokenData?(): Promise<StoredTokenData | null>;
87
- setTokenData?(data: StoredTokenData): Promise<void>;
88
- }
1
+ import { AuthResult, OAuthConfig, DeviceAuthorizationResponse, TokenResponse, TokenStorage, StoredTokenData } from './types-export.js';
2
+ export { TokenError, User } from './types-export.js';
89
3
 
90
4
  /**
91
5
  * Get current authenticated user
@@ -121,7 +35,7 @@ declare function logout(token?: string): Promise<void>;
121
35
  * 1. globalThis.DO_ADMIN_TOKEN / DO_TOKEN (Workers legacy)
122
36
  * 2. process.env.DO_ADMIN_TOKEN / DO_TOKEN (Node.js)
123
37
  * 3. cloudflare:workers env import (Workers 2025+) - supports secrets store bindings
124
- * 4. Stored token (keychain/secure file)
38
+ * 4. Stored token (keychain/secure file) - with automatic refresh if expired
125
39
  *
126
40
  * @see https://developers.cloudflare.com/changelog/2025-03-17-importable-env/
127
41
  */
@@ -460,6 +374,8 @@ interface LoginResult {
460
374
  * Get existing token or perform device flow login
461
375
  * Handles browser launch and token storage automatically
462
376
  * Automatically refreshes expired tokens if refresh_token is available
377
+ *
378
+ * Uses singleton pattern to prevent multiple concurrent login/refresh attempts
463
379
  */
464
380
  declare function ensureLoggedIn(options?: LoginOptions): Promise<LoginResult>;
465
381
  /**
@@ -471,4 +387,4 @@ declare function forceLogin(options?: LoginOptions): Promise<LoginResult>;
471
387
  */
472
388
  declare function ensureLoggedOut(options?: LoginOptions): Promise<void>;
473
389
 
474
- export { type AuthProvider, type AuthResult, CompositeTokenStorage, type DeviceAuthorizationResponse, FileTokenStorage, type GitHubDeviceAuthResponse, type GitHubDeviceFlowOptions, type GitHubTokenResponse, type GitHubUser, KeychainTokenStorage, LocalStorageTokenStorage, type LoginOptions, type LoginResult, MemoryTokenStorage, type OAuthConfig, type OAuthProvider, SecureFileTokenStorage, type TokenError, type TokenResponse, type TokenStorage, type User, ensureLoggedOut as a, auth, authorizeDevice, buildAuthUrl, configure, createSecureStorage, ensureLoggedIn as e, forceLogin as f, getConfig, getGitHubUser, getToken, getUser, isAuthenticated, login, logout, pollForTokens, pollGitHubDeviceFlow, startGitHubDeviceFlow };
390
+ export { type AuthProvider, AuthResult, CompositeTokenStorage, DeviceAuthorizationResponse, FileTokenStorage, type GitHubDeviceAuthResponse, type GitHubDeviceFlowOptions, type GitHubTokenResponse, type GitHubUser, KeychainTokenStorage, LocalStorageTokenStorage, type LoginOptions, type LoginResult, MemoryTokenStorage, OAuthConfig, type OAuthProvider, SecureFileTokenStorage, TokenResponse, TokenStorage, ensureLoggedOut as a, auth, authorizeDevice, buildAuthUrl, configure, createSecureStorage, ensureLoggedIn as e, forceLogin as f, getConfig, getGitHubUser, getToken, getUser, isAuthenticated, login, logout, pollForTokens, pollGitHubDeviceFlow, startGitHubDeviceFlow };
package/dist/index.js CHANGED
@@ -8,6 +8,17 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/utils.ts
12
+ function getEnv(key) {
13
+ if (globalThis[key]) return globalThis[key];
14
+ if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
15
+ return void 0;
16
+ }
17
+ var init_utils = __esm({
18
+ "src/utils.ts"() {
19
+ }
20
+ });
21
+
11
22
  // src/storage.ts
12
23
  var storage_exports = {};
13
24
  __export(storage_exports, {
@@ -22,10 +33,6 @@ __export(storage_exports, {
22
33
  function isNode() {
23
34
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
24
35
  }
25
- function getEnv2(key) {
26
- if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
27
- return void 0;
28
- }
29
36
  function createSecureStorage(storagePath) {
30
37
  if (isNode()) {
31
38
  return new SecureFileTokenStorage(storagePath);
@@ -38,6 +45,7 @@ function createSecureStorage(storagePath) {
38
45
  var KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, KeychainTokenStorage, SecureFileTokenStorage, FileTokenStorage, MemoryTokenStorage, LocalStorageTokenStorage, CompositeTokenStorage;
39
46
  var init_storage = __esm({
40
47
  "src/storage.ts"() {
48
+ init_utils();
41
49
  KEYCHAIN_SERVICE = "oauth.do";
42
50
  KEYCHAIN_ACCOUNT = "access_token";
43
51
  KeychainTokenStorage = class {
@@ -57,7 +65,7 @@ var init_storage = __esm({
57
65
  const keytarModule = imported.default || imported;
58
66
  this.keytar = keytarModule;
59
67
  if (typeof this.keytar.getPassword !== "function") {
60
- if (getEnv2("DEBUG")) {
68
+ if (getEnv("DEBUG")) {
61
69
  console.warn("Keytar module loaded but getPassword is not a function:", Object.keys(this.keytar));
62
70
  }
63
71
  this.keytar = null;
@@ -65,7 +73,7 @@ var init_storage = __esm({
65
73
  }
66
74
  return this.keytar;
67
75
  } catch (error) {
68
- if (getEnv2("DEBUG")) {
76
+ if (getEnv("DEBUG")) {
69
77
  console.warn("Keychain storage not available:", error);
70
78
  }
71
79
  return null;
@@ -80,7 +88,7 @@ var init_storage = __esm({
80
88
  const token = await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
81
89
  return token;
82
90
  } catch (error) {
83
- if (getEnv2("DEBUG")) {
91
+ if (getEnv("DEBUG")) {
84
92
  console.warn("Failed to get token from keychain:", error);
85
93
  }
86
94
  return null;
@@ -122,7 +130,7 @@ var init_storage = __esm({
122
130
  await keytar.getPassword(KEYCHAIN_SERVICE, "__test__");
123
131
  return true;
124
132
  } catch (error) {
125
- if (getEnv2("DEBUG")) {
133
+ if (getEnv("DEBUG")) {
126
134
  console.warn("Keychain not available:", error);
127
135
  }
128
136
  return false;
@@ -167,7 +175,7 @@ var init_storage = __esm({
167
175
  const fs = await import('fs/promises');
168
176
  const stats = await fs.stat(this.tokenPath);
169
177
  const mode = stats.mode & 511;
170
- if (mode !== 384 && getEnv2("DEBUG")) {
178
+ if (mode !== 384 && getEnv("DEBUG")) {
171
179
  console.warn(
172
180
  `Warning: Token file has insecure permissions (${mode.toString(8)}). Expected 600. Run: chmod 600 ${this.tokenPath}`
173
181
  );
@@ -346,7 +354,7 @@ var init_storage = __esm({
346
354
  try {
347
355
  await this.keychainStorage.setToken(fileToken);
348
356
  await this.fileStorage.removeToken();
349
- if (getEnv2("DEBUG")) {
357
+ if (getEnv("DEBUG")) {
350
358
  console.log("Migrated token from file to keychain");
351
359
  }
352
360
  } catch {
@@ -377,11 +385,7 @@ var init_storage = __esm({
377
385
  });
378
386
 
379
387
  // src/config.ts
380
- function getEnv(key) {
381
- if (globalThis[key]) return globalThis[key];
382
- if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
383
- return void 0;
384
- }
388
+ init_utils();
385
389
  var globalConfig = {
386
390
  apiUrl: getEnv("OAUTH_API_URL") || getEnv("API_URL") || "https://apis.do",
387
391
  clientId: getEnv("OAUTH_CLIENT_ID") || "client_01JQYTRXK9ZPD8JPJTKDCRB656",
@@ -400,6 +404,7 @@ function getConfig() {
400
404
  }
401
405
 
402
406
  // src/auth.ts
407
+ init_utils();
403
408
  async function resolveSecret(value) {
404
409
  if (!value) return null;
405
410
  if (typeof value === "string") return value;
@@ -408,14 +413,9 @@ async function resolveSecret(value) {
408
413
  }
409
414
  return null;
410
415
  }
411
- function getEnv3(key) {
412
- if (globalThis[key]) return globalThis[key];
413
- if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
414
- return void 0;
415
- }
416
416
  async function getUser(token) {
417
417
  const config = getConfig();
418
- const authToken = token || getEnv3("DO_TOKEN") || "";
418
+ const authToken = token || getEnv("DO_TOKEN") || "";
419
419
  if (!authToken) {
420
420
  return { user: null };
421
421
  }
@@ -462,7 +462,7 @@ async function login(credentials) {
462
462
  }
463
463
  async function logout(token) {
464
464
  const config = getConfig();
465
- const authToken = token || getEnv3("DO_TOKEN") || "";
465
+ const authToken = token || getEnv("DO_TOKEN") || "";
466
466
  if (!authToken) {
467
467
  return;
468
468
  }
@@ -481,10 +481,15 @@ async function logout(token) {
481
481
  console.error("Logout error:", error);
482
482
  }
483
483
  }
484
+ var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
485
+ function isTokenExpired(expiresAt) {
486
+ if (!expiresAt) return false;
487
+ return Date.now() >= expiresAt - REFRESH_BUFFER_MS;
488
+ }
484
489
  async function getToken() {
485
- const adminToken = getEnv3("DO_ADMIN_TOKEN");
490
+ const adminToken = getEnv("DO_ADMIN_TOKEN");
486
491
  if (adminToken) return adminToken;
487
- const doToken = getEnv3("DO_TOKEN");
492
+ const doToken = getEnv("DO_TOKEN");
488
493
  if (doToken) return doToken;
489
494
  try {
490
495
  const { env } = await import('cloudflare:workers');
@@ -498,6 +503,32 @@ async function getToken() {
498
503
  const { createSecureStorage: createSecureStorage2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
499
504
  const config = getConfig();
500
505
  const storage = createSecureStorage2(config.storagePath);
506
+ const tokenData = storage.getTokenData ? await storage.getTokenData() : null;
507
+ if (tokenData) {
508
+ if (!isTokenExpired(tokenData.expiresAt)) {
509
+ return tokenData.accessToken;
510
+ }
511
+ if (tokenData.refreshToken) {
512
+ try {
513
+ const newTokens = await refreshAccessToken(tokenData.refreshToken);
514
+ const expiresAt = newTokens.expires_in ? Date.now() + newTokens.expires_in * 1e3 : void 0;
515
+ const newData = {
516
+ accessToken: newTokens.access_token,
517
+ refreshToken: newTokens.refresh_token || tokenData.refreshToken,
518
+ expiresAt
519
+ };
520
+ if (storage.setTokenData) {
521
+ await storage.setTokenData(newData);
522
+ } else {
523
+ await storage.setToken(newTokens.access_token);
524
+ }
525
+ return newTokens.access_token;
526
+ } catch {
527
+ return null;
528
+ }
529
+ }
530
+ return null;
531
+ }
501
532
  return await storage.getToken();
502
533
  } catch {
503
534
  return null;
@@ -510,6 +541,28 @@ async function isAuthenticated(token) {
510
541
  function auth() {
511
542
  return getToken;
512
543
  }
544
+ async function refreshAccessToken(refreshToken) {
545
+ const config = getConfig();
546
+ if (!config.clientId) {
547
+ throw new Error("Client ID is required for token refresh");
548
+ }
549
+ const response = await config.fetch("https://auth.apis.do/user_management/authenticate", {
550
+ method: "POST",
551
+ headers: {
552
+ "Content-Type": "application/x-www-form-urlencoded"
553
+ },
554
+ body: new URLSearchParams({
555
+ grant_type: "refresh_token",
556
+ refresh_token: refreshToken,
557
+ client_id: config.clientId
558
+ }).toString()
559
+ });
560
+ if (!response.ok) {
561
+ const errorText = await response.text();
562
+ throw new Error(`Token refresh failed: ${response.status} - ${errorText}`);
563
+ }
564
+ return await response.json();
565
+ }
513
566
  function buildAuthUrl(options) {
514
567
  const config = getConfig();
515
568
  const clientId = options.clientId || config.clientId;