@digilogiclabs/saas-factory-auth 0.4.3 → 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.
package/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # @digilogiclabs/saas-factory-auth
2
+
3
+ A universal authentication package supporting both Next.js web applications and React Native mobile apps. Provides a unified API for multiple authentication providers with zero-configuration setup.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **Multi-Provider Support**
8
+ - Supabase Authentication
9
+ - Firebase Authentication
10
+ - Auto-detection based on environment variables
11
+ - 📱 **Cross-Platform**
12
+ - Next.js 15+ (App Router)
13
+ - React Native with Expo
14
+ - React 19+ compatible
15
+ - 🚀 **Complete Auth Flows**
16
+ - Email/Password authentication
17
+ - OAuth providers (Google, GitHub, Facebook, Twitter, Discord, Apple)
18
+ - Magic link (passwordless) authentication
19
+ - Password reset
20
+ - 🎯 **Modern Architecture**
21
+ - TypeScript-first
22
+ - Zustand state management
23
+ - Provider factory pattern
24
+ - Proper error handling with typed errors
25
+ - 💾 **Session Management**
26
+ - Automatic token refresh
27
+ - Persistent sessions
28
+ - Custom storage adapters
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @digilogiclabs/saas-factory-auth
34
+ # or
35
+ yarn add @digilogiclabs/saas-factory-auth
36
+ # or
37
+ pnpm add @digilogiclabs/saas-factory-auth
38
+ ```
39
+
40
+ ## Requirements
41
+
42
+ - Next.js 15+ or React Native with Expo
43
+ - React 19+
44
+ - Either Supabase or Firebase project credentials
45
+
46
+ ## Quick Start
47
+
48
+ ### 1. Configure Environment Variables
49
+
50
+ #### For Supabase:
51
+ ```env
52
+ NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
53
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
54
+ ```
55
+
56
+ #### For Firebase:
57
+ ```env
58
+ NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
59
+ NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-auth-domain
60
+ NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
61
+ ```
62
+
63
+ #### Optional - Explicit Provider Selection:
64
+ ```env
65
+ NEXT_PUBLIC_AUTH_PROVIDER=supabase # or 'firebase'
66
+ ```
67
+
68
+ ### 2. Wrap Your App with AuthProvider
69
+
70
+ #### Next.js App Router:
71
+ ```tsx
72
+ // app/layout.tsx
73
+ import { AuthProvider } from '@digilogiclabs/saas-factory-auth';
74
+
75
+ export default function RootLayout({
76
+ children,
77
+ }: {
78
+ children: React.ReactNode;
79
+ }) {
80
+ return (
81
+ <html lang="en">
82
+ <body>
83
+ <AuthProvider>
84
+ {children}
85
+ </AuthProvider>
86
+ </body>
87
+ </html>
88
+ );
89
+ }
90
+ ```
91
+
92
+ #### React Native:
93
+ ```tsx
94
+ // App.tsx
95
+ import { AuthProvider } from '@digilogiclabs/saas-factory-auth';
96
+
97
+ export default function App() {
98
+ return (
99
+ <AuthProvider>
100
+ {/* Your app components */}
101
+ </AuthProvider>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ### 3. Use the Auth Hook
107
+
108
+ ```tsx
109
+ 'use client'; // For Next.js App Router
110
+
111
+ import { useAuth } from '@digilogiclabs/saas-factory-auth';
112
+
113
+ export default function AuthExample() {
114
+ const {
115
+ user,
116
+ loading,
117
+ error,
118
+ signIn,
119
+ signUp,
120
+ signOut,
121
+ signInWithOAuth,
122
+ signInWithMagicLink,
123
+ resetPassword
124
+ } = useAuth();
125
+
126
+ if (loading) return <div>Loading...</div>;
127
+ if (error) return <div>Error: {error.message}</div>;
128
+
129
+ if (user) {
130
+ return (
131
+ <div>
132
+ <p>Welcome, {user.email}!</p>
133
+ <button onClick={() => signOut()}>Sign Out</button>
134
+ </div>
135
+ );
136
+ }
137
+
138
+ return (
139
+ <div>
140
+ <button onClick={() => signIn('user@example.com', 'password')}>
141
+ Sign In
142
+ </button>
143
+ <button onClick={() => signInWithOAuth('google')}>
144
+ Sign In with Google
145
+ </button>
146
+ </div>
147
+ );
148
+ }
149
+ ```
150
+
151
+ ## API Reference
152
+
153
+ ### `<AuthProvider>`
154
+
155
+ The main provider component that initializes authentication.
156
+
157
+ ```tsx
158
+ interface AuthProviderProps {
159
+ children: React.ReactNode;
160
+ onAuthChange?: (event: AuthEvent) => void;
161
+ autoSignIn?: boolean; // Default: true
162
+ }
163
+ ```
164
+
165
+ ### `useAuth()` Hook
166
+
167
+ Returns the complete authentication state and methods:
168
+
169
+ ```tsx
170
+ interface AuthStore {
171
+ // State
172
+ user: User | null;
173
+ session: AuthSession | null;
174
+ loading: boolean;
175
+ error: Error | null;
176
+
177
+ // Methods
178
+ signIn: (email: string, password: string) => Promise<void>;
179
+ signUp: (email: string, password: string) => Promise<void>;
180
+ signOut: () => Promise<void>;
181
+ signInWithOAuth: (provider: OAuthProvider, redirectTo?: string) => Promise<void>;
182
+ signInWithMagicLink: (email: string, redirectTo?: string) => Promise<void>;
183
+ resetPassword: (email: string) => Promise<void>;
184
+ getUser: () => Promise<User | null>;
185
+ getSession: () => Promise<AuthSession | null>;
186
+ }
187
+ ```
188
+
189
+ ### Types
190
+
191
+ ```tsx
192
+ // User object
193
+ interface User {
194
+ id: string;
195
+ email?: string;
196
+ name?: string | null;
197
+ avatar?: string | null;
198
+ [key: string]: any; // Additional metadata
199
+ }
200
+
201
+ // Session object
202
+ interface AuthSession {
203
+ user: User;
204
+ access_token: string;
205
+ refresh_token?: string;
206
+ expires_at?: number;
207
+ }
208
+
209
+ // OAuth providers
210
+ type OAuthProvider = 'google' | 'github' | 'facebook' | 'twitter' | 'discord' | 'apple';
211
+
212
+ // Auth events
213
+ enum AuthEvent {
214
+ SIGNED_IN = 'SIGNED_IN',
215
+ SIGNED_OUT = 'SIGNED_OUT',
216
+ TOKEN_REFRESHED = 'TOKEN_REFRESHED',
217
+ USER_UPDATED = 'USER_UPDATED',
218
+ }
219
+ ```
220
+
221
+ ### Error Handling
222
+
223
+ The package provides typed errors for better error handling:
224
+
225
+ ```tsx
226
+ enum AuthErrorType {
227
+ INVALID_CREDENTIALS = 'INVALID_CREDENTIALS',
228
+ USER_NOT_FOUND = 'USER_NOT_FOUND',
229
+ EMAIL_ALREADY_EXISTS = 'EMAIL_ALREADY_EXISTS',
230
+ WEAK_PASSWORD = 'WEAK_PASSWORD',
231
+ NETWORK_ERROR = 'NETWORK_ERROR',
232
+ PROVIDER_ERROR = 'PROVIDER_ERROR',
233
+ CONFIGURATION_ERROR = 'CONFIGURATION_ERROR',
234
+ VALIDATION_ERROR = 'VALIDATION_ERROR'
235
+ }
236
+
237
+ // Usage
238
+ const { error } = useAuth();
239
+ if (error?.type === AuthErrorType.INVALID_CREDENTIALS) {
240
+ // Handle invalid credentials
241
+ }
242
+ ```
243
+
244
+ ## Advanced Usage
245
+
246
+ ### Custom Storage (React Native)
247
+
248
+ For React Native, you can provide a custom storage implementation:
249
+
250
+ ```tsx
251
+ import AsyncStorage from '@react-native-async-storage/async-storage';
252
+ import { AuthProvider } from '@digilogiclabs/saas-factory-auth';
253
+
254
+ const customStorage = {
255
+ getItem: async (key: string) => AsyncStorage.getItem(key),
256
+ setItem: async (key: string, value: string) => AsyncStorage.setItem(key, value),
257
+ removeItem: async (key: string) => AsyncStorage.removeItem(key),
258
+ };
259
+
260
+ // Pass to provider factory if using advanced configuration
261
+ ```
262
+
263
+ ### Listen to Auth State Changes
264
+
265
+ ```tsx
266
+ <AuthProvider
267
+ onAuthChange={(event) => {
268
+ console.log('Auth event:', event);
269
+ // Handle auth state changes
270
+ }}
271
+ >
272
+ {children}
273
+ </AuthProvider>
274
+ ```
275
+
276
+ ### Disable Auto Sign-In
277
+
278
+ ```tsx
279
+ <AuthProvider autoSignIn={false}>
280
+ {children}
281
+ </AuthProvider>
282
+ ```
283
+
284
+ ## Provider-Specific Features
285
+
286
+ ### Supabase-Specific
287
+ - Row Level Security (RLS) support
288
+ - Realtime subscriptions compatibility
289
+ - Built-in email verification flows
290
+
291
+ ### Firebase-Specific
292
+ - Firebase Auth UI compatibility
293
+ - Firestore rules integration
294
+ - Firebase Admin SDK support
295
+
296
+ ## Migration Guide
297
+
298
+ ### From v0.3.x to v0.4.x
299
+
300
+ 1. **Update imports**: Components like `SignIn`, `SignUp`, `OAuthButtons` are no longer provided. Use the `useAuth` hook directly.
301
+
302
+ 2. **Update AuthProvider usage**: Remove any configuration props, configuration is now handled via environment variables.
303
+
304
+ 3. **Update auth method calls**: All methods now return promises and handle errors internally:
305
+ ```tsx
306
+ // Old
307
+ const { data, error } = await signIn(email, password);
308
+
309
+ // New
310
+ try {
311
+ await signIn(email, password);
312
+ } catch (error) {
313
+ // Handle error
314
+ }
315
+ ```
316
+
317
+ ## Troubleshooting
318
+
319
+ ### "Auth provider not initialized"
320
+ Ensure you've wrapped your app with `<AuthProvider>` at the root level.
321
+
322
+ ### "Invalid Supabase URL"
323
+ Check that your `NEXT_PUBLIC_SUPABASE_URL` includes the full URL with `https://`.
324
+
325
+ ### Network/DNS Errors
326
+ Verify your project credentials and ensure the project is not paused (for Supabase).
327
+
328
+ ## Contributing
329
+
330
+ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) for details.
331
+
332
+ ## Support
333
+
334
+ - [GitHub Issues](https://github.com/DigiLogicLabs/saas-factory-auth/issues)
335
+ - [Documentation](https://github.com/DigiLogicLabs/saas-factory-auth/wiki)
336
+
337
+ ## License
338
+
339
+ MIT © [DigiLogic Labs](https://github.com/DigiLogicLabs)
340
+
341
+ ## Changelog
342
+
343
+ ### v0.4.3 (Latest)
344
+ - Fixed React hooks usage in auth callbacks
345
+ - Improved error handling and network error detection
346
+ - Added proper auth provider initialization in store
347
+ - Enhanced TypeScript types
348
+
349
+ ### v0.4.0
350
+ - Complete architecture rewrite
351
+ - Added Firebase support alongside Supabase
352
+ - Implemented provider factory pattern
353
+ - Migrated to Zustand for state management
354
+ - React 19 and Next.js 15 compatibility
355
+
356
+ ### v0.3.x
357
+ - Initial Supabase-only implementation
358
+ - Basic authentication flows
359
+ - Component-based architecture
@@ -0,0 +1,55 @@
1
+ import * as _supabase_supabase_js from '@supabase/supabase-js';
2
+ import { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
3
+
4
+ type Database = {
5
+ public: {
6
+ Tables: {
7
+ profiles: {
8
+ Row: {
9
+ id: string;
10
+ updated_at: string;
11
+ username: string;
12
+ full_name: string;
13
+ avatar_url: string;
14
+ website: string;
15
+ };
16
+ Insert: {
17
+ id: string;
18
+ updated_at?: string;
19
+ username: string;
20
+ full_name?: string;
21
+ avatar_url?: string;
22
+ website?: string;
23
+ };
24
+ Update: {
25
+ id?: string;
26
+ updated_at?: string;
27
+ username?: string;
28
+ full_name?: string;
29
+ avatar_url?: string;
30
+ website?: string;
31
+ };
32
+ };
33
+ };
34
+ Views: {
35
+ [_ in never]: never;
36
+ };
37
+ Functions: {
38
+ [_ in never]: never;
39
+ };
40
+ Enums: {
41
+ [_ in never]: never;
42
+ };
43
+ };
44
+ };
45
+
46
+ declare const createSupabaseServerClient: (cookieStore: ReadonlyRequestCookies) => _supabase_supabase_js.SupabaseClient<Database, "public", any>;
47
+ declare function handleEmailVerification(token: string): Promise<{
48
+ success: boolean;
49
+ error?: never;
50
+ } | {
51
+ success: boolean;
52
+ error: unknown;
53
+ }>;
54
+
55
+ export { type Database as D, createSupabaseServerClient as c, handleEmailVerification as h };
@@ -0,0 +1,55 @@
1
+ import * as _supabase_supabase_js from '@supabase/supabase-js';
2
+ import { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
3
+
4
+ type Database = {
5
+ public: {
6
+ Tables: {
7
+ profiles: {
8
+ Row: {
9
+ id: string;
10
+ updated_at: string;
11
+ username: string;
12
+ full_name: string;
13
+ avatar_url: string;
14
+ website: string;
15
+ };
16
+ Insert: {
17
+ id: string;
18
+ updated_at?: string;
19
+ username: string;
20
+ full_name?: string;
21
+ avatar_url?: string;
22
+ website?: string;
23
+ };
24
+ Update: {
25
+ id?: string;
26
+ updated_at?: string;
27
+ username?: string;
28
+ full_name?: string;
29
+ avatar_url?: string;
30
+ website?: string;
31
+ };
32
+ };
33
+ };
34
+ Views: {
35
+ [_ in never]: never;
36
+ };
37
+ Functions: {
38
+ [_ in never]: never;
39
+ };
40
+ Enums: {
41
+ [_ in never]: never;
42
+ };
43
+ };
44
+ };
45
+
46
+ declare const createSupabaseServerClient: (cookieStore: ReadonlyRequestCookies) => _supabase_supabase_js.SupabaseClient<Database, "public", any>;
47
+ declare function handleEmailVerification(token: string): Promise<{
48
+ success: boolean;
49
+ error?: never;
50
+ } | {
51
+ success: boolean;
52
+ error: unknown;
53
+ }>;
54
+
55
+ export { type Database as D, createSupabaseServerClient as c, handleEmailVerification as h };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,63 @@
1
1
  import React from 'react';
2
2
  import * as _supabase_supabase_js from '@supabase/supabase-js';
3
+ import { D as Database } from './index-BeqpJTCy.mjs';
4
+ export { c as createSupabaseServerClient } from './index-BeqpJTCy.mjs';
3
5
  import { NextRequest, NextResponse } from 'next/server.js';
4
- import { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
6
+ import 'next/dist/server/web/spec-extension/adapters/request-cookies';
5
7
 
8
+ /**
9
+ * Authentication error types for better error handling
10
+ */
11
+ declare enum AuthErrorType {
12
+ INVALID_CREDENTIALS = "INVALID_CREDENTIALS",
13
+ USER_NOT_FOUND = "USER_NOT_FOUND",
14
+ EMAIL_ALREADY_EXISTS = "EMAIL_ALREADY_EXISTS",
15
+ WEAK_PASSWORD = "WEAK_PASSWORD",
16
+ NETWORK_ERROR = "NETWORK_ERROR",
17
+ PROVIDER_ERROR = "PROVIDER_ERROR",
18
+ CONFIGURATION_ERROR = "CONFIGURATION_ERROR",
19
+ VALIDATION_ERROR = "VALIDATION_ERROR",
20
+ UNKNOWN = "UNKNOWN"
21
+ }
22
+ /**
23
+ * Custom authentication error class
24
+ */
25
+ declare class AuthError extends Error {
26
+ readonly type: AuthErrorType;
27
+ readonly originalError?: unknown | undefined;
28
+ readonly context?: Record<string, unknown> | undefined;
29
+ readonly timestamp: Date;
30
+ readonly code: string;
31
+ constructor(type: AuthErrorType, message: string, originalError?: unknown | undefined, context?: Record<string, unknown> | undefined);
32
+ /**
33
+ * Convert error to JSON for logging/debugging
34
+ */
35
+ toJSON(): {
36
+ name: string;
37
+ type: AuthErrorType;
38
+ code: string;
39
+ message: string;
40
+ timestamp: string;
41
+ context: Record<string, unknown> | undefined;
42
+ originalError: {
43
+ name: string;
44
+ message: string;
45
+ stack: string | undefined;
46
+ } | {
47
+ message: string;
48
+ name?: never;
49
+ stack?: never;
50
+ } | undefined;
51
+ };
52
+ /**
53
+ * Check if error is of a specific type
54
+ */
55
+ isType(type: AuthErrorType): boolean;
56
+ /**
57
+ * Check if error is retryable
58
+ */
59
+ isRetryable(): boolean;
60
+ }
6
61
  /**
7
62
  * OAuth provider types - re-export from types for backward compatibility
8
63
  */
@@ -21,7 +76,7 @@ interface IAuthProvider {
21
76
  signIn(email: string, password: string): Promise<{
22
77
  user: User | null;
23
78
  session: AuthSession | null;
24
- error: any;
79
+ error: AuthError | null;
25
80
  }>;
26
81
  /**
27
82
  * Sign up with email and password
@@ -32,7 +87,7 @@ interface IAuthProvider {
32
87
  signUp(email: string, password: string): Promise<{
33
88
  user: User | null;
34
89
  session: AuthSession | null;
35
- error: any;
90
+ error: AuthError | null;
36
91
  }>;
37
92
  /**
38
93
  * Sign in with OAuth provider
@@ -53,7 +108,7 @@ interface IAuthProvider {
53
108
  * @returns Promise resolving to an object containing error
54
109
  */
55
110
  signOut(): Promise<{
56
- error: any;
111
+ error: AuthError | null;
57
112
  }>;
58
113
  /**
59
114
  * Get the current authenticated user
@@ -71,14 +126,16 @@ interface IAuthProvider {
71
126
  * @returns Promise resolving to an object containing error
72
127
  */
73
128
  resetPassword(email: string): Promise<{
74
- error: any;
129
+ error: AuthError | null;
75
130
  }>;
76
131
  /**
77
132
  * Listen for authentication state changes
78
133
  * @param callback Function called when auth state changes
79
134
  * @returns Object containing subscription data for cleanup
80
135
  */
81
- onAuthStateChange(callback: (event: AuthEvent, session: AuthSession | null) => void): any;
136
+ onAuthStateChange(callback: (event: AuthEvent, session: AuthSession | null) => void): {
137
+ unsubscribe: () => void;
138
+ };
82
139
  /**
83
140
  * Validate provider configuration
84
141
  * @returns Promise that resolves if configuration is valid
@@ -95,7 +152,7 @@ interface User {
95
152
  name?: string | null;
96
153
  avatar?: string | null;
97
154
  roles?: UserRole[];
98
- [key: string]: any;
155
+ [key: string]: unknown;
99
156
  }
100
157
  /**
101
158
  * Authentication session containing user and token information
@@ -188,7 +245,9 @@ interface AuthStore {
188
245
  signInWithMagicLink: (email: string, redirectTo?: string) => Promise<void>;
189
246
  getUser: () => Promise<User | null>;
190
247
  getSession: () => Promise<AuthSession | null>;
191
- onAuthStateChange: (callback: (event: AuthEvent, session: AuthSession | null) => void) => any;
248
+ onAuthStateChange: (callback: (event: AuthEvent, session: AuthSession | null) => void) => {
249
+ unsubscribe: () => void;
250
+ };
192
251
  }
193
252
 
194
253
  interface AuthProviderProps {
@@ -293,55 +352,11 @@ declare class AuthProviderFactory {
293
352
  static getInstances(): Map<AuthProviderType, IAuthProvider>;
294
353
  }
295
354
 
296
- type Database = {
297
- public: {
298
- Tables: {
299
- profiles: {
300
- Row: {
301
- id: string;
302
- updated_at: string;
303
- username: string;
304
- full_name: string;
305
- avatar_url: string;
306
- website: string;
307
- };
308
- Insert: {
309
- id: string;
310
- updated_at?: string;
311
- username: string;
312
- full_name?: string;
313
- avatar_url?: string;
314
- website?: string;
315
- };
316
- Update: {
317
- id?: string;
318
- updated_at?: string;
319
- username?: string;
320
- full_name?: string;
321
- avatar_url?: string;
322
- website?: string;
323
- };
324
- };
325
- };
326
- Views: {
327
- [_ in never]: never;
328
- };
329
- Functions: {
330
- [_ in never]: never;
331
- };
332
- Enums: {
333
- [_ in never]: never;
334
- };
335
- };
336
- };
337
-
338
- declare const getSupabaseClient: () => _supabase_supabase_js.SupabaseClient<Database, "public", any>;
355
+ declare const createClient: () => _supabase_supabase_js.SupabaseClient<Database, "public", any>;
339
356
 
340
357
  declare function withAuth(request: NextRequest): {
341
358
  response: NextResponse<unknown>;
342
359
  supabase: _supabase_supabase_js.SupabaseClient<any, "public", any>;
343
360
  };
344
361
 
345
- declare const createSupabaseServerClient: (cookieStore: ReadonlyRequestCookies) => _supabase_supabase_js.SupabaseClient<Database, "public", any>;
346
-
347
- export { type AuthConfig, AuthEvent, type AuthFormProps, type AuthMode, AuthProvider, AuthProviderFactory, type AuthSession, type AuthStore, type OAuthProvider, type PasswordConfig, type User, type UserRole, createSupabaseServerClient, getSupabaseClient, useAuth, withAuth };
362
+ export { type AuthConfig, AuthEvent, type AuthFormProps, type AuthMode, AuthProvider, AuthProviderFactory, type AuthSession, type AuthStore, type OAuthProvider, type PasswordConfig, type User, type UserRole, createClient as createSupabaseClient, useAuth, withAuth };