@digilogiclabs/saas-factory-auth 0.2.0 → 0.3.2

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.
@@ -0,0 +1,595 @@
1
+ import React, { createContext, useContext, useState, useEffect } from 'react';
2
+ import { StyleSheet, Alert, View, Text, TextInput, TouchableOpacity, Image } from 'react-native';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+
22
+ // src/core/IAuthProvider.ts
23
+ var AuthError = class _AuthError extends Error {
24
+ constructor(type, message, originalError, context) {
25
+ super(message);
26
+ this.type = type;
27
+ this.originalError = originalError;
28
+ this.context = context;
29
+ this.name = "AuthError";
30
+ this.timestamp = /* @__PURE__ */ new Date();
31
+ this.code = type;
32
+ Object.setPrototypeOf(this, _AuthError.prototype);
33
+ if (Error.captureStackTrace) {
34
+ Error.captureStackTrace(this, _AuthError);
35
+ }
36
+ }
37
+ /**
38
+ * Convert error to JSON for logging/debugging
39
+ */
40
+ toJSON() {
41
+ return {
42
+ name: this.name,
43
+ type: this.type,
44
+ code: this.code,
45
+ message: this.message,
46
+ timestamp: this.timestamp.toISOString(),
47
+ context: this.context,
48
+ originalError: this.originalError ? {
49
+ name: this.originalError.name,
50
+ message: this.originalError.message,
51
+ code: this.originalError.code
52
+ } : undefined
53
+ };
54
+ }
55
+ /**
56
+ * Check if error is of a specific type
57
+ */
58
+ isType(type) {
59
+ return this.type === type;
60
+ }
61
+ /**
62
+ * Check if error is retryable
63
+ */
64
+ isRetryable() {
65
+ return [
66
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
67
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
68
+ ].includes(this.type);
69
+ }
70
+ };
71
+ var AuthContext = createContext(null);
72
+
73
+ // src/hooks/useAuthForm.ts
74
+ var useAuthForm = (options = {}) => {
75
+ const auth = useContext(AuthContext);
76
+ const [email, setEmail] = useState("");
77
+ const [password, setPassword] = useState("");
78
+ const [confirmPassword, setConfirmPassword] = useState("");
79
+ const [loading, setLoading] = useState(false);
80
+ const [error, setError] = useState(null);
81
+ const [message, setMessage] = useState(null);
82
+ if (!auth) {
83
+ throw new Error("useAuthForm must be used within AuthProvider");
84
+ }
85
+ const clearError = () => setError(null);
86
+ const clearMessage = () => setMessage(null);
87
+ const handleSignIn = async () => {
88
+ var _a, _b;
89
+ setLoading(true);
90
+ setError(null);
91
+ setMessage(null);
92
+ try {
93
+ await auth.signIn(email, password);
94
+ (_a = options.onSuccess) == null ? void 0 : _a.call(options);
95
+ } catch (err) {
96
+ const authError = err instanceof AuthError ? err : new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, err.message);
97
+ setError(authError.message);
98
+ (_b = options.onError) == null ? undefined : _b.call(options, authError);
99
+ } finally {
100
+ setLoading(false);
101
+ }
102
+ };
103
+ const handleSignUp = async () => {
104
+ var _a, _b;
105
+ if (password !== confirmPassword) {
106
+ setError("Passwords do not match");
107
+ return;
108
+ }
109
+ setLoading(true);
110
+ setError(null);
111
+ setMessage(null);
112
+ try {
113
+ await auth.signUp(email, password);
114
+ setMessage("Check your email to confirm your account");
115
+ (_a = options.onSuccess) == null ? void 0 : _a.call(options);
116
+ } catch (err) {
117
+ const authError = err instanceof AuthError ? err : new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, err.message);
118
+ setError(authError.message);
119
+ (_b = options.onError) == null ? undefined : _b.call(options, authError);
120
+ } finally {
121
+ setLoading(false);
122
+ }
123
+ };
124
+ const handleOAuthSignIn = async (provider) => {
125
+ var _a, _b;
126
+ setLoading(true);
127
+ setError(null);
128
+ try {
129
+ await auth.signInWithOAuth(provider, options.redirectUrl);
130
+ (_a = options.onSuccess) == null ? void 0 : _a.call(options);
131
+ } catch (err) {
132
+ const authError = err instanceof AuthError ? err : new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, err.message);
133
+ setError(authError.message);
134
+ (_b = options.onError) == null ? undefined : _b.call(options, authError);
135
+ } finally {
136
+ setLoading(false);
137
+ }
138
+ };
139
+ const handlePasswordReset = async () => {
140
+ var _a;
141
+ if (!email) {
142
+ setError("Please enter your email address");
143
+ return;
144
+ }
145
+ setLoading(true);
146
+ setError(null);
147
+ setMessage(null);
148
+ try {
149
+ await auth.resetPassword(email);
150
+ setMessage("Password reset email sent");
151
+ } catch (err) {
152
+ const authError = err instanceof AuthError ? err : new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, err.message);
153
+ setError(authError.message);
154
+ (_a = options.onError) == null ? undefined : _a.call(options, authError);
155
+ } finally {
156
+ setLoading(false);
157
+ }
158
+ };
159
+ const handleMagicLink = async () => {
160
+ var _a;
161
+ if (!email) {
162
+ setError("Please enter your email address");
163
+ return;
164
+ }
165
+ setLoading(true);
166
+ setError(null);
167
+ setMessage(null);
168
+ try {
169
+ await auth.signInWithMagicLink(email, options.redirectUrl);
170
+ setMessage("Magic link sent to your email");
171
+ } catch (err) {
172
+ const authError = err instanceof AuthError ? err : new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, err.message);
173
+ setError(authError.message);
174
+ (_a = options.onError) == null ? undefined : _a.call(options, authError);
175
+ } finally {
176
+ setLoading(false);
177
+ }
178
+ };
179
+ return {
180
+ // State
181
+ email,
182
+ password,
183
+ confirmPassword,
184
+ loading,
185
+ error,
186
+ message,
187
+ // Actions
188
+ setEmail,
189
+ setPassword,
190
+ setConfirmPassword,
191
+ handleSignIn,
192
+ handleSignUp,
193
+ handleOAuthSignIn,
194
+ handlePasswordReset,
195
+ handleMagicLink,
196
+ clearError,
197
+ clearMessage
198
+ };
199
+ };
200
+ var AuthForm = ({
201
+ mode = "signin",
202
+ onSuccess,
203
+ onModeChange,
204
+ redirectUrl,
205
+ showOAuth = true,
206
+ oauthProviders = ["google", "github"],
207
+ style
208
+ }) => {
209
+ const {
210
+ email,
211
+ password,
212
+ confirmPassword,
213
+ loading,
214
+ error,
215
+ message,
216
+ setEmail,
217
+ setPassword,
218
+ setConfirmPassword,
219
+ handleSignIn,
220
+ handleSignUp,
221
+ handleOAuthSignIn,
222
+ handlePasswordReset,
223
+ handleMagicLink,
224
+ clearError,
225
+ clearMessage
226
+ } = useAuthForm({ onSuccess, redirectUrl });
227
+ const handleSubmit = async () => {
228
+ if (mode === "signin") {
229
+ await handleSignIn();
230
+ } else if (mode === "signup") {
231
+ await handleSignUp();
232
+ } else if (mode === "reset") {
233
+ await handlePasswordReset();
234
+ }
235
+ };
236
+ const handleOAuthPress = async (provider) => {
237
+ try {
238
+ await handleOAuthSignIn(provider);
239
+ } catch (err) {
240
+ Alert.alert("OAuth Error", "OAuth sign-in is not fully supported in React Native yet. Please use email/password authentication.");
241
+ }
242
+ };
243
+ const handleMagicLinkPress = async () => {
244
+ await handleMagicLink();
245
+ };
246
+ React.useEffect(() => {
247
+ if (error) {
248
+ Alert.alert("Error", error, [{ text: "OK", onPress: clearError }]);
249
+ }
250
+ }, [error, clearError]);
251
+ React.useEffect(() => {
252
+ if (message) {
253
+ Alert.alert("Success", message, [{ text: "OK", onPress: clearMessage }]);
254
+ }
255
+ }, [message, clearMessage]);
256
+ return /* @__PURE__ */ jsxs(View, { style: [styles.container, style], children: [
257
+ /* @__PURE__ */ jsx(Text, { style: styles.title, children: mode === "signin" ? "Sign In" : mode === "signup" ? "Sign Up" : "Reset Password" }),
258
+ /* @__PURE__ */ jsx(
259
+ TextInput,
260
+ {
261
+ style: styles.input,
262
+ placeholder: "Email",
263
+ value: email,
264
+ onChangeText: setEmail,
265
+ keyboardType: "email-address",
266
+ autoCapitalize: "none",
267
+ autoCorrect: false
268
+ }
269
+ ),
270
+ mode !== "reset" && /* @__PURE__ */ jsx(
271
+ TextInput,
272
+ {
273
+ style: styles.input,
274
+ placeholder: "Password",
275
+ value: password,
276
+ onChangeText: setPassword,
277
+ secureTextEntry: true,
278
+ autoCapitalize: "none",
279
+ autoCorrect: false
280
+ }
281
+ ),
282
+ mode === "signup" && /* @__PURE__ */ jsx(
283
+ TextInput,
284
+ {
285
+ style: styles.input,
286
+ placeholder: "Confirm Password",
287
+ value: confirmPassword,
288
+ onChangeText: setConfirmPassword,
289
+ secureTextEntry: true,
290
+ autoCapitalize: "none",
291
+ autoCorrect: false
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsx(
295
+ TouchableOpacity,
296
+ {
297
+ style: [styles.button, loading && styles.buttonDisabled],
298
+ onPress: handleSubmit,
299
+ disabled: loading,
300
+ children: /* @__PURE__ */ jsx(Text, { style: styles.buttonText, children: loading ? "Loading..." : mode === "signin" ? "Sign In" : mode === "signup" ? "Sign Up" : "Send Reset Email" })
301
+ }
302
+ ),
303
+ mode === "signin" && /* @__PURE__ */ jsx(
304
+ TouchableOpacity,
305
+ {
306
+ style: styles.linkButton,
307
+ onPress: handleMagicLinkPress,
308
+ disabled: loading,
309
+ children: /* @__PURE__ */ jsx(Text, { style: styles.linkText, children: "Send Magic Link" })
310
+ }
311
+ ),
312
+ showOAuth && mode !== "reset" && /* @__PURE__ */ jsxs(View, { style: styles.oauthContainer, children: [
313
+ /* @__PURE__ */ jsx(Text, { style: styles.oauthTitle, children: "Or continue with:" }),
314
+ oauthProviders.map((provider) => /* @__PURE__ */ jsx(
315
+ TouchableOpacity,
316
+ {
317
+ style: styles.oauthButton,
318
+ onPress: () => handleOAuthPress(provider),
319
+ disabled: loading,
320
+ children: /* @__PURE__ */ jsx(Text, { style: styles.oauthButtonText, children: provider.charAt(0).toUpperCase() + provider.slice(1) })
321
+ },
322
+ provider
323
+ ))
324
+ ] }),
325
+ /* @__PURE__ */ jsxs(View, { style: styles.footer, children: [
326
+ mode === "signin" && /* @__PURE__ */ jsxs(Fragment, { children: [
327
+ /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => onModeChange == null ? undefined : onModeChange("signup"), children: /* @__PURE__ */ jsx(Text, { style: styles.linkText, children: "Don't have an account? Sign up" }) }),
328
+ /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => onModeChange == null ? undefined : onModeChange("reset"), children: /* @__PURE__ */ jsx(Text, { style: styles.linkText, children: "Forgot password?" }) })
329
+ ] }),
330
+ mode === "signup" && /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => onModeChange == null ? undefined : onModeChange("signin"), children: /* @__PURE__ */ jsx(Text, { style: styles.linkText, children: "Already have an account? Sign in" }) }),
331
+ mode === "reset" && /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => onModeChange == null ? undefined : onModeChange("signin"), children: /* @__PURE__ */ jsx(Text, { style: styles.linkText, children: "Back to sign in" }) })
332
+ ] })
333
+ ] });
334
+ };
335
+ var styles = StyleSheet.create({
336
+ container: {
337
+ padding: 20,
338
+ backgroundColor: "#fff"
339
+ },
340
+ title: {
341
+ fontSize: 24,
342
+ fontWeight: "bold",
343
+ textAlign: "center",
344
+ marginBottom: 30,
345
+ color: "#333"
346
+ },
347
+ input: {
348
+ borderWidth: 1,
349
+ borderColor: "#ddd",
350
+ borderRadius: 8,
351
+ padding: 15,
352
+ marginBottom: 15,
353
+ fontSize: 16,
354
+ backgroundColor: "#f9f9f9"
355
+ },
356
+ button: {
357
+ backgroundColor: "#007AFF",
358
+ borderRadius: 8,
359
+ padding: 15,
360
+ alignItems: "center",
361
+ marginBottom: 15
362
+ },
363
+ buttonDisabled: {
364
+ backgroundColor: "#ccc"
365
+ },
366
+ buttonText: {
367
+ color: "#fff",
368
+ fontSize: 16,
369
+ fontWeight: "600"
370
+ },
371
+ linkButton: {
372
+ alignItems: "center",
373
+ marginBottom: 20
374
+ },
375
+ linkText: {
376
+ color: "#007AFF",
377
+ fontSize: 14,
378
+ textAlign: "center",
379
+ marginVertical: 5
380
+ },
381
+ oauthContainer: {
382
+ marginTop: 20,
383
+ paddingTop: 20,
384
+ borderTopWidth: 1,
385
+ borderTopColor: "#eee"
386
+ },
387
+ oauthTitle: {
388
+ textAlign: "center",
389
+ color: "#666",
390
+ marginBottom: 15,
391
+ fontSize: 14
392
+ },
393
+ oauthButton: {
394
+ borderWidth: 1,
395
+ borderColor: "#ddd",
396
+ borderRadius: 8,
397
+ padding: 15,
398
+ alignItems: "center",
399
+ marginBottom: 10,
400
+ backgroundColor: "#f9f9f9"
401
+ },
402
+ oauthButtonText: {
403
+ color: "#333",
404
+ fontSize: 16,
405
+ fontWeight: "500"
406
+ },
407
+ footer: {
408
+ marginTop: 20,
409
+ alignItems: "center"
410
+ }
411
+ });
412
+ var useAuth = () => {
413
+ const store = useContext(AuthContext);
414
+ if (!store) {
415
+ throw new Error("useAuth must be used within an AuthProvider");
416
+ }
417
+ useEffect(() => {
418
+ return () => {
419
+ };
420
+ }, [store]);
421
+ return __spreadValues({}, store);
422
+ };
423
+ var AuthStatus = ({
424
+ showSignOut = true,
425
+ showUserInfo = true,
426
+ onSignOut,
427
+ style
428
+ }) => {
429
+ const { user, loading, signOut } = useAuth();
430
+ const handleSignOut = async () => {
431
+ try {
432
+ await signOut();
433
+ onSignOut == null ? void 0 : onSignOut();
434
+ } catch (error) {
435
+ console.error("Sign out error:", error);
436
+ }
437
+ };
438
+ if (loading) {
439
+ return /* @__PURE__ */ jsx(View, { style: [styles2.container, style], children: /* @__PURE__ */ jsx(Text, { style: styles2.loadingText, children: "Loading..." }) });
440
+ }
441
+ if (!user) {
442
+ return /* @__PURE__ */ jsx(View, { style: [styles2.container, style], children: /* @__PURE__ */ jsx(Text, { style: styles2.statusText, children: "Not signed in" }) });
443
+ }
444
+ return /* @__PURE__ */ jsxs(View, { style: [styles2.container, style], children: [
445
+ showUserInfo && /* @__PURE__ */ jsxs(View, { style: styles2.userInfo, children: [
446
+ user.photoURL && /* @__PURE__ */ jsx(Image, { source: { uri: user.photoURL }, style: styles2.avatar }),
447
+ /* @__PURE__ */ jsxs(View, { style: styles2.userDetails, children: [
448
+ user.displayName && /* @__PURE__ */ jsx(Text, { style: styles2.displayName, children: user.displayName }),
449
+ /* @__PURE__ */ jsx(Text, { style: styles2.email, children: user.email })
450
+ ] })
451
+ ] }),
452
+ showSignOut && /* @__PURE__ */ jsx(TouchableOpacity, { style: styles2.signOutButton, onPress: handleSignOut, children: /* @__PURE__ */ jsx(Text, { style: styles2.signOutText, children: "Sign Out" }) })
453
+ ] });
454
+ };
455
+ var styles2 = StyleSheet.create({
456
+ container: {
457
+ padding: 15,
458
+ backgroundColor: "#f9f9f9",
459
+ borderRadius: 8,
460
+ margin: 10
461
+ },
462
+ loadingText: {
463
+ textAlign: "center",
464
+ color: "#666",
465
+ fontSize: 16
466
+ },
467
+ statusText: {
468
+ textAlign: "center",
469
+ color: "#666",
470
+ fontSize: 16
471
+ },
472
+ userInfo: {
473
+ flexDirection: "row",
474
+ alignItems: "center",
475
+ marginBottom: 15
476
+ },
477
+ avatar: {
478
+ width: 50,
479
+ height: 50,
480
+ borderRadius: 25,
481
+ marginRight: 15
482
+ },
483
+ userDetails: {
484
+ flex: 1
485
+ },
486
+ displayName: {
487
+ fontSize: 18,
488
+ fontWeight: "600",
489
+ color: "#333",
490
+ marginBottom: 2
491
+ },
492
+ email: {
493
+ fontSize: 14,
494
+ color: "#666"
495
+ },
496
+ signOutButton: {
497
+ backgroundColor: "#FF3B30",
498
+ borderRadius: 6,
499
+ padding: 12,
500
+ alignItems: "center"
501
+ },
502
+ signOutText: {
503
+ color: "#fff",
504
+ fontSize: 16,
505
+ fontWeight: "600"
506
+ }
507
+ });
508
+ var useProtectedRoute = (options = {}) => {
509
+ const auth = useContext(AuthContext);
510
+ useEffect(() => {
511
+ if (!auth || auth.loading) return;
512
+ const checkAuth = async () => {
513
+ var _a, _b;
514
+ const user = await auth.getUser();
515
+ if (!user) {
516
+ (_a = options.onUnauthenticated) == null ? undefined : _a.call(options);
517
+ return;
518
+ }
519
+ if (options.requiredRoles && options.requiredRoles.length > 0) {
520
+ const userRoles = user.roles || [];
521
+ const hasRequiredRole = options.requiredRoles.some((role) => userRoles.includes(role));
522
+ if (!hasRequiredRole) {
523
+ (_b = options.onUnauthorized) == null ? undefined : _b.call(options);
524
+ return;
525
+ }
526
+ }
527
+ };
528
+ checkAuth();
529
+ const unsubscribe = auth.onAuthStateChange((event, session) => {
530
+ var _a;
531
+ if (!(session == null ? undefined : session.user)) {
532
+ (_a = options.onUnauthenticated) == null ? undefined : _a.call(options);
533
+ }
534
+ });
535
+ return () => {
536
+ var _a, _b;
537
+ if (typeof unsubscribe === "function") {
538
+ unsubscribe();
539
+ } else if (unsubscribe && "data" in unsubscribe && ((_b = (_a = unsubscribe.data) == null ? undefined : _a.subscription) == null ? undefined : _b.unsubscribe)) {
540
+ unsubscribe.data.subscription.unsubscribe();
541
+ }
542
+ };
543
+ }, [auth, options]);
544
+ return {
545
+ user: auth == null ? undefined : auth.user,
546
+ loading: auth == null ? undefined : auth.loading,
547
+ isAuthenticated: !!(auth == null ? undefined : auth.user)
548
+ };
549
+ };
550
+ var ProtectedRoute = ({
551
+ children,
552
+ requiredRoles,
553
+ fallback,
554
+ loadingComponent,
555
+ onUnauthenticated,
556
+ onUnauthorized
557
+ }) => {
558
+ const { user, loading, isAuthenticated } = useProtectedRoute({
559
+ requiredRoles,
560
+ onUnauthenticated,
561
+ onUnauthorized
562
+ });
563
+ if (loading) {
564
+ return loadingComponent || /* @__PURE__ */ jsx(View, { style: styles3.container, children: /* @__PURE__ */ jsx(Text, { style: styles3.text, children: "Loading..." }) });
565
+ }
566
+ if (!isAuthenticated) {
567
+ return fallback || /* @__PURE__ */ jsx(View, { style: styles3.container, children: /* @__PURE__ */ jsx(Text, { style: styles3.text, children: "Please sign in to access this content" }) });
568
+ }
569
+ if (requiredRoles && requiredRoles.length > 0) {
570
+ const userRoles = (user == null ? undefined : user.roles) || [];
571
+ const hasRequiredRole = requiredRoles.some((role) => userRoles.includes(role));
572
+ if (!hasRequiredRole) {
573
+ return fallback || /* @__PURE__ */ jsx(View, { style: styles3.container, children: /* @__PURE__ */ jsx(Text, { style: styles3.text, children: "You don't have permission to access this content" }) });
574
+ }
575
+ }
576
+ return /* @__PURE__ */ jsx(Fragment, { children });
577
+ };
578
+ var styles3 = StyleSheet.create({
579
+ container: {
580
+ flex: 1,
581
+ justifyContent: "center",
582
+ alignItems: "center",
583
+ padding: 20,
584
+ backgroundColor: "#f9f9f9"
585
+ },
586
+ text: {
587
+ fontSize: 16,
588
+ color: "#666",
589
+ textAlign: "center"
590
+ }
591
+ });
592
+
593
+ export { AuthForm, AuthStatus, ProtectedRoute };
594
+ //# sourceMappingURL=index.mjs.map
595
+ //# sourceMappingURL=index.mjs.map