Framework-agnostic OAuth integration for AT Protocol (Bluesky) applications.
1/**
2 * Types for AT Protocol OAuth integration
3 * Framework-agnostic - works with standard Request/Response APIs
4 */
5
6import type { OAuthStorage } from "@tijs/atproto-storage";
7
8/**
9 * Logger interface for custom logging implementations.
10 * Compatible with oauth-client-deno's Logger interface.
11 */
12export interface Logger {
13 debug(...args: unknown[]): void;
14 info(...args: unknown[]): void;
15 warn(...args: unknown[]): void;
16 error(...args: unknown[]): void;
17}
18
19/**
20 * No-op logger for production use
21 */
22export const noopLogger: Logger = {
23 debug: () => {},
24 info: () => {},
25 warn: () => {},
26 error: () => {},
27};
28
29/**
30 * Generic OAuth session interface
31 *
32 * Compatible with @tijs/oauth-client-deno Session class and similar implementations.
33 * For AT Protocol applications, makeRequest() provides automatic DPoP authentication.
34 */
35export interface SessionInterface {
36 /** User's DID */
37 did: string;
38
39 /** Access token for API calls */
40 accessToken: string;
41
42 /** Refresh token (optional) */
43 refreshToken?: string;
44
45 /** Handle/username (optional) */
46 handle?: string;
47
48 /** User's PDS URL */
49 pdsUrl: string;
50
51 /** Time until token expires in milliseconds (optional) */
52 timeUntilExpiry?: number;
53
54 /**
55 * Make authenticated request with automatic DPoP handling.
56 */
57 makeRequest(
58 method: string,
59 url: string,
60 options?: RequestInit,
61 ): Promise<Response>;
62
63 /**
64 * Refresh tokens (optional)
65 */
66 refresh?(): Promise<SessionInterface>;
67
68 /**
69 * Serialize session data for storage
70 */
71 toJSON(): unknown;
72}
73
74/**
75 * Generic OAuth client interface - bring your own client!
76 * Compatible with @tijs/oauth-client-deno v1.0.0+
77 */
78export interface OAuthClientInterface {
79 /**
80 * Start OAuth authorization flow
81 * @returns URL object for authorization redirect
82 */
83 authorize(
84 handle: string,
85 options?: { state?: string; scope?: string; prompt?: string },
86 ): Promise<URL>;
87
88 /**
89 * Handle OAuth callback and exchange code for tokens
90 * @param params URLSearchParams from OAuth callback
91 */
92 callback(params: URLSearchParams): Promise<{
93 session: SessionInterface;
94 state?: string | null;
95 }>;
96
97 /**
98 * Restore a session from storage by session ID.
99 * The OAuth client should handle automatic token refresh during restore if needed.
100 * @param sessionId - Session identifier to restore
101 * @returns Promise resolving to restored session, or null if not found
102 */
103 restore(sessionId: string): Promise<SessionInterface | null>;
104}
105
106/**
107 * Configuration options for ATProto OAuth integration.
108 */
109export interface ATProtoOAuthConfig {
110 /** Base URL of your application (e.g. "https://myapp.example.com") */
111 baseUrl: string;
112
113 /** Display name for OAuth consent screen */
114 appName: string;
115
116 /** URL to app logo for OAuth consent screen */
117 logoUri?: string;
118
119 /** URL to privacy policy */
120 policyUri?: string;
121
122 /** Cookie signing secret (required, at least 32 characters) */
123 cookieSecret: string;
124
125 /** OAuth scope (default: "atproto transition:generic") */
126 scope?: string;
127
128 /**
129 * Session TTL in seconds (default: 7 days).
130 * For AT Protocol OAuth public clients, max is 14 days per spec.
131 */
132 sessionTtl?: number;
133
134 /** Storage implementation for OAuth sessions */
135 storage: OAuthStorage;
136
137 /**
138 * Optional logger for debugging and monitoring OAuth flows.
139 * Defaults to a no-op logger (no console output).
140 * Pass console for standard logging.
141 */
142 logger?: Logger;
143
144 /**
145 * URL scheme for mobile app OAuth callback.
146 * When mobile=true is passed to /login, the callback will redirect to this
147 * scheme with session_token and did as query params.
148 * Example: "myapp://auth-callback" or "anchor-app://auth-callback"
149 */
150 mobileScheme?: string;
151}
152
153/**
154 * ATProto OAuth client metadata for /.well-known/oauth-client
155 */
156export interface ClientMetadata {
157 client_name: string;
158 client_id: string;
159 client_uri: string;
160 redirect_uris: string[];
161 scope: string;
162 grant_types: string[];
163 response_types: string[];
164 application_type: string;
165 token_endpoint_auth_method: string;
166 dpop_bound_access_tokens: boolean;
167 logo_uri?: string;
168 policy_uri?: string;
169}
170
171/**
172 * Session validation result.
173 */
174export interface SessionValidationResult {
175 valid: boolean;
176 did?: string;
177 handle?: string;
178}
179
180/**
181 * Stored OAuth session data
182 */
183export interface StoredOAuthSession {
184 did: string;
185 accessToken: string;
186 refreshToken?: string;
187 handle?: string;
188 pdsUrl: string;
189 expiresAt?: number;
190 createdAt: number;
191 updatedAt: number;
192}
193
194/**
195 * Iron Session data stored in encrypted cookie
196 */
197export interface SessionData {
198 did: string;
199 createdAt: number;
200 lastAccessed: number;
201}
202
203/**
204 * OAuth sessions manager interface
205 */
206export interface OAuthSessionsInterface {
207 /**
208 * Get an OAuth session for a specific DID
209 * @param did - User's DID
210 * @returns OAuth session or null if not found
211 */
212 getOAuthSession(did: string): Promise<SessionInterface | null>;
213
214 /**
215 * Save OAuth session to storage
216 * @param session - Session to save
217 */
218 saveOAuthSession(session: SessionInterface): Promise<void>;
219
220 /**
221 * Delete OAuth session from storage
222 * @param did - User's DID
223 */
224 deleteOAuthSession(did: string): Promise<void>;
225}
226
227/**
228 * Result from getOAuthSessionFromRequest()
229 */
230export interface OAuthSessionFromRequestResult {
231 /** The OAuth session, or null if not found/invalid */
232 session: SessionInterface | null;
233
234 /** Set-Cookie header to refresh the session (set when session is valid) */
235 setCookieHeader?: string;
236
237 /** Error information if session retrieval failed */
238 error?: {
239 type:
240 | "NO_COOKIE"
241 | "INVALID_COOKIE"
242 | "SESSION_EXPIRED"
243 | "OAUTH_ERROR"
244 | "UNKNOWN";
245 message: string;
246 details?: unknown;
247 };
248}
249
250/**
251 * ATProto OAuth instance returned by createATProtoOAuth().
252 */
253export interface ATProtoOAuthInstance {
254 /**
255 * Handle /login route - start OAuth flow
256 * @param request - HTTP request with ?handle= query param
257 * @returns Response (redirect to OAuth provider)
258 */
259 handleLogin(request: Request): Promise<Response>;
260
261 /**
262 * Handle /oauth/callback route - complete OAuth flow
263 * @param request - HTTP request from OAuth callback
264 * @returns Response (redirect to app)
265 */
266 handleCallback(request: Request): Promise<Response>;
267
268 /**
269 * Handle /oauth-client-metadata.json route
270 * @returns Response with client metadata JSON
271 */
272 handleClientMetadata(): Response;
273
274 /**
275 * Handle /api/auth/logout route
276 * @param request - HTTP request
277 * @returns Response
278 */
279 handleLogout(request: Request): Promise<Response>;
280
281 /**
282 * Get OAuth session from request (cookie or Bearer token)
283 * @param request - HTTP request
284 * @returns Session result with optional Set-Cookie header
285 */
286 getSessionFromRequest(
287 request: Request,
288 ): Promise<OAuthSessionFromRequestResult>;
289
290 /**
291 * Generate client metadata
292 */
293 getClientMetadata(): ClientMetadata;
294
295 /**
296 * Get a Set-Cookie header to clear the session cookie.
297 * Useful for custom logout flows or error handling scenarios.
298 * @returns Set-Cookie header string
299 */
300 getClearCookieHeader(): string;
301
302 /** Direct access to sessions interface for advanced usage */
303 sessions: OAuthSessionsInterface;
304}
305
306/**
307 * OAuth state stored during authorization flow
308 */
309export interface OAuthState {
310 handle: string;
311 timestamp: number;
312 /** Redirect path after successful web OAuth */
313 redirectPath?: string;
314 /** Flag for mobile OAuth flow - redirects to mobileScheme instead of web */
315 mobile?: boolean;
316 /** Flag for PWA OAuth flow - returns HTML page with postMessage instead of redirect */
317 pwa?: boolean;
318}
319
320// Re-export OAuthStorage from atproto-storage for convenience
321export type { OAuthStorage };