@douvery/auth 0.1.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/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # @douvery/auth
2
+
3
+ OAuth 2.0/OIDC client library for Douvery authentication.
4
+
5
+ ## Packages
6
+
7
+ | Package | Description |
8
+ |---------|-------------|
9
+ | [@douvery/auth](./packages/core) | Core client library (vanilla TypeScript) |
10
+ | [@douvery/auth-react](./packages/react) | React hooks and components |
11
+ | [@douvery/auth-qwik](./packages/qwik) | Qwik hooks and components |
12
+
13
+ ## Installation
14
+
15
+ ### React
16
+
17
+ ```bash
18
+ npm install @douvery/auth-react
19
+ # or
20
+ pnpm add @douvery/auth-react
21
+ ```
22
+
23
+ ### Qwik
24
+
25
+ ```bash
26
+ npm install @douvery/auth-qwik
27
+ # or
28
+ pnpm add @douvery/auth-qwik
29
+ ```
30
+
31
+ ### Vanilla TypeScript
32
+
33
+ ```bash
34
+ npm install @douvery/auth
35
+ # or
36
+ pnpm add @douvery/auth
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### React
42
+
43
+ ```tsx
44
+ import { DouveryAuthProvider, useDouveryAuth } from '@douvery/auth-react';
45
+
46
+ // Wrap your app with the provider
47
+ function App() {
48
+ return (
49
+ <DouveryAuthProvider
50
+ config={{
51
+ clientId: 'your-client-id',
52
+ redirectUri: 'http://localhost:3000/callback',
53
+ issuer: 'https://auth.douvery.com',
54
+ scopes: ['openid', 'profile', 'email'],
55
+ }}
56
+ >
57
+ <YourApp />
58
+ </DouveryAuthProvider>
59
+ );
60
+ }
61
+
62
+ // Use the hook in your components
63
+ function LoginButton() {
64
+ const { login, logout, isAuthenticated, user } = useDouveryAuth();
65
+
66
+ if (isAuthenticated) {
67
+ return (
68
+ <div>
69
+ <p>Welcome, {user?.name}!</p>
70
+ <button onClick={() => logout()}>Logout</button>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ return <button onClick={() => login()}>Login</button>;
76
+ }
77
+ ```
78
+
79
+ ### Qwik
80
+
81
+ ```tsx
82
+ import { DouveryAuthProvider, useDouveryAuth } from '@douvery/auth-qwik';
83
+ import { component$ } from '@builder.io/qwik';
84
+
85
+ // Wrap your app with the provider
86
+ export default component$(() => {
87
+ return (
88
+ <DouveryAuthProvider
89
+ config={{
90
+ clientId: 'your-client-id',
91
+ redirectUri: 'http://localhost:5173/callback',
92
+ issuer: 'https://auth.douvery.com',
93
+ scopes: ['openid', 'profile', 'email'],
94
+ }}
95
+ >
96
+ <Slot />
97
+ </DouveryAuthProvider>
98
+ );
99
+ });
100
+
101
+ // Use the hook in your components
102
+ export const LoginButton = component$(() => {
103
+ const { login, logout, isAuthenticated, user } = useDouveryAuth();
104
+
105
+ return (
106
+ <>
107
+ {isAuthenticated.value ? (
108
+ <div>
109
+ <p>Welcome, {user.value?.name}!</p>
110
+ <button onClick$={() => logout()}>Logout</button>
111
+ </div>
112
+ ) : (
113
+ <button onClick$={() => login()}>Login</button>
114
+ )}
115
+ </>
116
+ );
117
+ });
118
+ ```
119
+
120
+ ### Vanilla TypeScript
121
+
122
+ ```typescript
123
+ import { createDouveryAuth } from '@douvery/auth';
124
+
125
+ const auth = createDouveryAuth({
126
+ clientId: 'your-client-id',
127
+ redirectUri: 'http://localhost:3000/callback',
128
+ issuer: 'https://auth.douvery.com',
129
+ scopes: ['openid', 'profile', 'email'],
130
+ });
131
+
132
+ // Initialize (checks for existing session or handles callback)
133
+ await auth.initialize();
134
+
135
+ // Login
136
+ await auth.login();
137
+
138
+ // After callback, get user
139
+ const user = auth.getUser();
140
+ console.log(user);
141
+
142
+ // Get access token (auto-refreshes if needed)
143
+ const token = await auth.getAccessToken();
144
+
145
+ // Logout
146
+ await auth.logout();
147
+ ```
148
+
149
+ ## Configuration Options
150
+
151
+ ```typescript
152
+ interface DouveryAuthConfig {
153
+ /** OAuth Client ID */
154
+ clientId: string;
155
+
156
+ /** Authorization server base URL (default: "https://auth.douvery.com") */
157
+ issuer?: string;
158
+
159
+ /** Redirect URI after authentication */
160
+ redirectUri: string;
161
+
162
+ /** Post-logout redirect URI */
163
+ postLogoutRedirectUri?: string;
164
+
165
+ /** OAuth scopes to request (default: ["openid", "profile", "email"]) */
166
+ scopes?: string[];
167
+
168
+ /** Token storage strategy (default: "localStorage") */
169
+ storage?: "localStorage" | "sessionStorage" | "memory" | "cookie";
170
+
171
+ /** Auto-refresh tokens before expiry (default: true) */
172
+ autoRefresh?: boolean;
173
+
174
+ /** Seconds before expiry to trigger refresh (default: 60) */
175
+ refreshThreshold?: number;
176
+
177
+ /** Enable debug logging (default: false) */
178
+ debug?: boolean;
179
+ }
180
+ ```
181
+
182
+ ## Login Options
183
+
184
+ ```typescript
185
+ await auth.login({
186
+ // URL to return to after login
187
+ returnTo: '/dashboard',
188
+
189
+ // Force re-authentication
190
+ prompt: 'login',
191
+
192
+ // Pre-fill email
193
+ loginHint: 'user@example.com',
194
+
195
+ // UI locale
196
+ uiLocales: 'es',
197
+ });
198
+ ```
199
+
200
+ ## Features
201
+
202
+ - ✅ **PKCE Support** - Secure authorization code flow with PKCE
203
+ - ✅ **Auto Token Refresh** - Automatic token refresh before expiry
204
+ - ✅ **Multiple Storage Options** - localStorage, sessionStorage, memory, or cookies
205
+ - ✅ **TypeScript First** - Full TypeScript support with comprehensive types
206
+ - ✅ **Framework Adapters** - React and Qwik adapters with hooks
207
+ - ✅ **Event System** - Subscribe to auth events (login, logout, token refresh, etc.)
208
+ - ✅ **SSR Compatible** - Works with server-side rendering
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ # Install dependencies
214
+ pnpm install
215
+
216
+ # Build all packages
217
+ pnpm build
218
+
219
+ # Run in development mode
220
+ pnpm dev
221
+
222
+ # Type check
223
+ pnpm typecheck
224
+
225
+ # Clean build outputs
226
+ pnpm clean
227
+ ```
228
+
229
+ ## License
230
+
231
+ MIT
@@ -0,0 +1,352 @@
1
+ /**
2
+ * @douvery/auth - Core Types
3
+ * OAuth 2.0/OIDC type definitions
4
+ */
5
+ interface DouveryAuthConfig {
6
+ /** OAuth Client ID */
7
+ clientId: string;
8
+ /** Authorization server base URL @default "https://auth.douvery.com" */
9
+ issuer?: string;
10
+ /** Redirect URI after authentication */
11
+ redirectUri: string;
12
+ /** Post-logout redirect URI */
13
+ postLogoutRedirectUri?: string;
14
+ /** OAuth scopes to request @default ["openid", "profile", "email"] */
15
+ scopes?: string[];
16
+ /** Token storage strategy @default "localStorage" */
17
+ storage?: "localStorage" | "sessionStorage" | "memory" | "cookie";
18
+ /** Custom storage implementation */
19
+ customStorage?: TokenStorage;
20
+ /** Auto-refresh tokens before expiry @default true */
21
+ autoRefresh?: boolean;
22
+ /** Seconds before expiry to trigger refresh @default 60 */
23
+ refreshThreshold?: number;
24
+ /** Enable debug logging @default false */
25
+ debug?: boolean;
26
+ }
27
+ interface TokenSet {
28
+ access_token: string;
29
+ token_type: string;
30
+ expires_in: number;
31
+ refresh_token?: string;
32
+ id_token?: string;
33
+ scope?: string;
34
+ }
35
+ interface TokenInfo {
36
+ accessToken: string;
37
+ refreshToken?: string;
38
+ idToken?: string;
39
+ expiresAt: number;
40
+ tokenType: string;
41
+ scope: string[];
42
+ }
43
+ interface DecodedIdToken {
44
+ iss: string;
45
+ sub: string;
46
+ aud: string;
47
+ exp: number;
48
+ iat: number;
49
+ auth_time?: number;
50
+ nonce?: string;
51
+ acr?: string;
52
+ amr?: string[];
53
+ azp?: string;
54
+ at_hash?: string;
55
+ c_hash?: string;
56
+ name?: string;
57
+ given_name?: string;
58
+ family_name?: string;
59
+ middle_name?: string;
60
+ nickname?: string;
61
+ preferred_username?: string;
62
+ profile?: string;
63
+ picture?: string;
64
+ website?: string;
65
+ email?: string;
66
+ email_verified?: boolean;
67
+ gender?: string;
68
+ birthdate?: string;
69
+ zoneinfo?: string;
70
+ locale?: string;
71
+ phone_number?: string;
72
+ phone_number_verified?: boolean;
73
+ address?: {
74
+ formatted?: string;
75
+ street_address?: string;
76
+ locality?: string;
77
+ region?: string;
78
+ postal_code?: string;
79
+ country?: string;
80
+ };
81
+ updated_at?: number;
82
+ [key: string]: unknown;
83
+ }
84
+ interface User {
85
+ id: string;
86
+ email?: string;
87
+ emailVerified?: boolean;
88
+ name?: string;
89
+ firstName?: string;
90
+ lastName?: string;
91
+ picture?: string;
92
+ phoneNumber?: string;
93
+ phoneNumberVerified?: boolean;
94
+ locale?: string;
95
+ [key: string]: unknown;
96
+ }
97
+ type AuthStatus = "loading" | "authenticated" | "unauthenticated";
98
+ interface AuthState {
99
+ status: AuthStatus;
100
+ user: User | null;
101
+ tokens: TokenInfo | null;
102
+ error: AuthError | null;
103
+ }
104
+ interface PKCEPair {
105
+ codeVerifier: string;
106
+ codeChallenge: string;
107
+ codeChallengeMethod: "S256";
108
+ }
109
+ interface TokenStorage {
110
+ get(key: string): string | null | Promise<string | null>;
111
+ set(key: string, value: string): void | Promise<void>;
112
+ remove(key: string): void | Promise<void>;
113
+ clear(): void | Promise<void>;
114
+ }
115
+ interface StorageKeys {
116
+ accessToken: string;
117
+ refreshToken: string;
118
+ idToken: string;
119
+ expiresAt: string;
120
+ state: string;
121
+ nonce: string;
122
+ codeVerifier: string;
123
+ returnTo: string;
124
+ }
125
+ type AuthEvent = {
126
+ type: "INITIALIZED";
127
+ } | {
128
+ type: "LOGIN_STARTED";
129
+ } | {
130
+ type: "LOGIN_SUCCESS";
131
+ user: User;
132
+ tokens: TokenInfo;
133
+ } | {
134
+ type: "LOGIN_ERROR";
135
+ error: AuthError;
136
+ } | {
137
+ type: "LOGOUT_STARTED";
138
+ } | {
139
+ type: "LOGOUT_SUCCESS";
140
+ } | {
141
+ type: "LOGOUT_ERROR";
142
+ error: AuthError;
143
+ } | {
144
+ type: "TOKEN_REFRESHED";
145
+ tokens: TokenInfo;
146
+ } | {
147
+ type: "TOKEN_REFRESH_ERROR";
148
+ error: AuthError;
149
+ } | {
150
+ type: "SESSION_EXPIRED";
151
+ };
152
+ type AuthEventHandler = (event: AuthEvent) => void;
153
+ declare class AuthError extends Error {
154
+ code: AuthErrorCode;
155
+ cause?: Error | undefined;
156
+ constructor(code: AuthErrorCode, message: string, cause?: Error | undefined);
157
+ }
158
+ type AuthErrorCode = "invalid_request" | "invalid_client" | "invalid_grant" | "unauthorized_client" | "unsupported_grant_type" | "invalid_scope" | "access_denied" | "server_error" | "temporarily_unavailable" | "login_required" | "consent_required" | "interaction_required" | "invalid_token" | "insufficient_scope" | "token_expired" | "token_refresh_failed" | "pkce_error" | "state_mismatch" | "nonce_mismatch" | "network_error" | "configuration_error" | "unknown_error";
159
+ interface OIDCDiscovery {
160
+ issuer: string;
161
+ authorization_endpoint: string;
162
+ token_endpoint: string;
163
+ userinfo_endpoint: string;
164
+ jwks_uri: string;
165
+ revocation_endpoint?: string;
166
+ introspection_endpoint?: string;
167
+ end_session_endpoint?: string;
168
+ registration_endpoint?: string;
169
+ scopes_supported: string[];
170
+ response_types_supported: string[];
171
+ response_modes_supported?: string[];
172
+ grant_types_supported: string[];
173
+ token_endpoint_auth_methods_supported?: string[];
174
+ subject_types_supported: string[];
175
+ id_token_signing_alg_values_supported: string[];
176
+ claims_supported?: string[];
177
+ code_challenge_methods_supported?: string[];
178
+ }
179
+ interface CallbackResult {
180
+ success: boolean;
181
+ user?: User;
182
+ tokens?: TokenInfo;
183
+ error?: AuthError;
184
+ returnTo?: string;
185
+ }
186
+ interface LoginOptions {
187
+ /** URL to return to after login */
188
+ returnTo?: string;
189
+ /** Additional authorization parameters */
190
+ authorizationParams?: Record<string, string>;
191
+ /** Prompt parameter (none, login, consent, select_account) */
192
+ prompt?: "none" | "login" | "consent" | "select_account";
193
+ /** Login hint (email or identifier) */
194
+ loginHint?: string;
195
+ /** UI locales preference */
196
+ uiLocales?: string;
197
+ /** Maximum authentication age in seconds */
198
+ maxAge?: number;
199
+ /** ACR values requested */
200
+ acrValues?: string;
201
+ }
202
+ interface LogoutOptions {
203
+ /** URL to return to after logout */
204
+ returnTo?: string;
205
+ /** Whether to federate logout (end session at IdP) @default true */
206
+ federated?: boolean;
207
+ /** Only clear local session, don't redirect @default false */
208
+ localOnly?: boolean;
209
+ }
210
+
211
+ /**
212
+ * @douvery/auth - Auth Client
213
+ * Main OAuth 2.0/OIDC client implementation
214
+ */
215
+
216
+ declare class DouveryAuthClient {
217
+ private config;
218
+ private tokenManager;
219
+ private discovery;
220
+ private eventHandlers;
221
+ private refreshTimer;
222
+ private state;
223
+ constructor(config: DouveryAuthConfig);
224
+ /** Initialize the auth client */
225
+ initialize(): Promise<AuthState>;
226
+ /** Start the login flow */
227
+ login(options?: LoginOptions): Promise<void>;
228
+ /** Logout the user */
229
+ logout(options?: LogoutOptions): Promise<void>;
230
+ /** Check if current URL is an OAuth callback */
231
+ isCallback(): boolean;
232
+ /** Handle the OAuth callback */
233
+ handleCallback(): Promise<CallbackResult>;
234
+ private exchangeCode;
235
+ /** Refresh the access token */
236
+ refreshTokens(): Promise<TokenInfo>;
237
+ /** Get current access token (auto-refreshes if needed) */
238
+ getAccessToken(): Promise<string | null>;
239
+ private tokenSetToInfo;
240
+ private fetchUser;
241
+ private extractUserFromIdToken;
242
+ private normalizeUser;
243
+ private getDiscovery;
244
+ private setupAutoRefresh;
245
+ private clearAutoRefresh;
246
+ getState(): AuthState;
247
+ isAuthenticated(): boolean;
248
+ getUser(): User | null;
249
+ subscribe(handler: AuthEventHandler): () => void;
250
+ private updateState;
251
+ private emit;
252
+ private log;
253
+ }
254
+ /** Create a new DouveryAuthClient instance */
255
+ declare function createDouveryAuth(config: DouveryAuthConfig): DouveryAuthClient;
256
+
257
+ /**
258
+ * @douvery/auth - PKCE Utilities
259
+ * RFC 7636 - Proof Key for Code Exchange
260
+ */
261
+
262
+ /** Generate a cryptographically random string for use as code_verifier */
263
+ declare function generateCodeVerifier(length?: number): string;
264
+ /** Generate a random state parameter for CSRF protection */
265
+ declare function generateState(): string;
266
+ /** Generate a random nonce for replay attack protection */
267
+ declare function generateNonce(): string;
268
+ /** Create SHA-256 hash and encode as base64url */
269
+ declare function generateCodeChallenge(verifier: string): Promise<string>;
270
+ /** Encode ArrayBuffer as base64url (RFC 4648 Section 5) */
271
+ declare function base64UrlEncode(buffer: ArrayBuffer): string;
272
+ /** Decode base64url string to ArrayBuffer */
273
+ declare function base64UrlDecode(input: string): ArrayBuffer;
274
+ /** Generate a complete PKCE pair (verifier + challenge) */
275
+ declare function generatePKCEPair(): Promise<PKCEPair>;
276
+ /** Verify a code_verifier against a code_challenge */
277
+ declare function verifyCodeChallenge(verifier: string, challenge: string, method?: "S256" | "plain"): Promise<boolean>;
278
+ /** Parse and decode a JWT token (without verification) */
279
+ declare function decodeJWT<T = Record<string, unknown>>(token: string): T;
280
+ /** Check if a JWT token is expired */
281
+ declare function isTokenExpired(token: string, clockSkew?: number): boolean;
282
+ /** Get token expiration timestamp */
283
+ declare function getTokenExpiration(token: string): number | null;
284
+
285
+ /**
286
+ * @douvery/auth - Token Storage
287
+ * Abstraction for token persistence
288
+ */
289
+
290
+ declare const STORAGE_KEYS: StorageKeys;
291
+ /** In-memory storage implementation */
292
+ declare class MemoryStorage implements TokenStorage {
293
+ private store;
294
+ get(key: string): string | null;
295
+ set(key: string, value: string): void;
296
+ remove(key: string): void;
297
+ clear(): void;
298
+ }
299
+ /** LocalStorage implementation */
300
+ declare class LocalStorage implements TokenStorage {
301
+ get(key: string): string | null;
302
+ set(key: string, value: string): void;
303
+ remove(key: string): void;
304
+ clear(): void;
305
+ }
306
+ /** SessionStorage implementation */
307
+ declare class SessionStorage implements TokenStorage {
308
+ get(key: string): string | null;
309
+ set(key: string, value: string): void;
310
+ remove(key: string): void;
311
+ clear(): void;
312
+ }
313
+ /** Cookie storage implementation (for SSR compatibility) */
314
+ declare class CookieStorage implements TokenStorage {
315
+ private options;
316
+ constructor(options?: {
317
+ path?: string;
318
+ domain?: string;
319
+ secure?: boolean;
320
+ sameSite?: "Strict" | "Lax" | "None";
321
+ maxAge?: number;
322
+ });
323
+ get(key: string): string | null;
324
+ set(key: string, value: string): void;
325
+ remove(key: string): void;
326
+ clear(): void;
327
+ }
328
+ /** Create storage instance based on type */
329
+ declare function createStorage(type: "localStorage" | "sessionStorage" | "memory" | "cookie"): TokenStorage;
330
+ /** Token manager for handling token persistence */
331
+ declare class TokenManager {
332
+ private storage;
333
+ constructor(storage: TokenStorage);
334
+ getTokens(): Promise<TokenInfo | null>;
335
+ setTokens(tokens: TokenInfo): Promise<void>;
336
+ clearTokens(): Promise<void>;
337
+ saveState(state: string): Promise<void>;
338
+ getState(): Promise<string | null>;
339
+ clearState(): Promise<void>;
340
+ saveNonce(nonce: string): Promise<void>;
341
+ getNonce(): Promise<string | null>;
342
+ clearNonce(): Promise<void>;
343
+ saveCodeVerifier(verifier: string): Promise<void>;
344
+ getCodeVerifier(): Promise<string | null>;
345
+ clearCodeVerifier(): Promise<void>;
346
+ saveReturnTo(url: string): Promise<void>;
347
+ getReturnTo(): Promise<string | null>;
348
+ clearReturnTo(): Promise<void>;
349
+ clearAll(): Promise<void>;
350
+ }
351
+
352
+ export { AuthError, type AuthErrorCode, type AuthEvent, type AuthEventHandler, type AuthState, type AuthStatus, type CallbackResult, CookieStorage, type DecodedIdToken, DouveryAuthClient, type DouveryAuthConfig, LocalStorage, type LoginOptions, type LogoutOptions, MemoryStorage, type OIDCDiscovery, type PKCEPair, STORAGE_KEYS, SessionStorage, type StorageKeys, type TokenInfo, TokenManager, type TokenSet, type TokenStorage, type User, base64UrlDecode, base64UrlEncode, createDouveryAuth, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };