svelte-firekit 0.0.12 → 0.0.13
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/firebase/auth/auth-manager.svelte.d.ts +24 -10
- package/dist/firebase/auth/auth-manager.svelte.js +256 -112
- package/package.json +1 -1
- package/dist/firebase/auth/auth-guard.svelte.d.ts +0 -1
- package/dist/firebase/auth/auth-guard.svelte.js +0 -119
- package/dist/firebase/auth/user.svelte.d.ts +0 -1
- package/dist/firebase/auth/user.svelte.js +0 -177
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type User } from "firebase/auth";
|
|
2
1
|
import { type DocumentData } from "firebase/firestore";
|
|
3
2
|
type UserClaims = Record<string, any>;
|
|
4
3
|
interface UserData extends DocumentData {
|
|
@@ -22,15 +21,17 @@ interface GuardConfig {
|
|
|
22
21
|
}
|
|
23
22
|
export declare class FirekitAuthManager {
|
|
24
23
|
private static instance;
|
|
24
|
+
private readonly SESSION_TIMEOUT;
|
|
25
|
+
private activityInterval;
|
|
26
|
+
private _initPromise;
|
|
25
27
|
private _user;
|
|
26
28
|
private _userData;
|
|
27
29
|
private _claims;
|
|
28
30
|
private _initialized;
|
|
29
|
-
private _initPromise;
|
|
30
31
|
private _loading;
|
|
31
32
|
private _error;
|
|
33
|
+
private _currentSession;
|
|
32
34
|
private _lastValidationTime;
|
|
33
|
-
private readonly VALIDATION_THROTTLE;
|
|
34
35
|
readonly isLoggedIn: boolean;
|
|
35
36
|
readonly uid: string | undefined;
|
|
36
37
|
readonly email: string | null | undefined;
|
|
@@ -40,13 +41,28 @@ export declare class FirekitAuthManager {
|
|
|
40
41
|
readonly claims: UserClaims;
|
|
41
42
|
readonly data: UserData | null;
|
|
42
43
|
readonly initialized: boolean;
|
|
44
|
+
readonly loading: boolean;
|
|
45
|
+
readonly error: Error | null;
|
|
46
|
+
readonly sessionActive: boolean;
|
|
43
47
|
private constructor();
|
|
44
48
|
static getInstance(): FirekitAuthManager;
|
|
45
49
|
private initialize;
|
|
46
50
|
waitForInit(): Promise<void>;
|
|
51
|
+
private handleAuthStateChange;
|
|
47
52
|
private loadUserData;
|
|
48
53
|
private loadUserClaims;
|
|
49
|
-
|
|
54
|
+
private setupActivityTracking;
|
|
55
|
+
private initializeSession;
|
|
56
|
+
private updateLastActivity;
|
|
57
|
+
private checkSession;
|
|
58
|
+
private handleSessionTimeout;
|
|
59
|
+
private clearSession;
|
|
60
|
+
private handleError;
|
|
61
|
+
private generateDeviceId;
|
|
62
|
+
private getPlatformInfo;
|
|
63
|
+
private validateClaims;
|
|
64
|
+
private handleRedirect;
|
|
65
|
+
validateAuth({ authRequired, redirectTo, requiredClaims, requiredData, allowIf, redirectParams, }?: GuardConfig): Promise<boolean>;
|
|
50
66
|
updateEmailUser(email: string): Promise<void>;
|
|
51
67
|
updatePassword(password: string): Promise<void>;
|
|
52
68
|
updateProfileInfo({ displayName, photoURL, }: {
|
|
@@ -55,19 +71,17 @@ export declare class FirekitAuthManager {
|
|
|
55
71
|
}): Promise<void>;
|
|
56
72
|
updateUserData(data: Partial<UserData>): Promise<void>;
|
|
57
73
|
saveUserData(data: UserData): Promise<void>;
|
|
74
|
+
sendVerificationEmail(): Promise<void>;
|
|
75
|
+
refreshUserData(): Promise<void>;
|
|
58
76
|
hasRequiredClaims(requiredClaims: string[]): boolean;
|
|
59
77
|
isAdmin(): boolean;
|
|
60
78
|
isPremium(): boolean;
|
|
61
|
-
private shouldThrottleValidation;
|
|
62
|
-
private handleRedirect;
|
|
63
|
-
private validateClaims;
|
|
64
|
-
validateAuth({ authRequired, redirectTo, requiredClaims, requiredData, allowIf, redirectParams, }?: GuardConfig): Promise<boolean>;
|
|
65
79
|
requireAuth(redirectTo?: string, redirectParams?: Record<string, string>): Promise<boolean>;
|
|
66
80
|
requireNoAuth(redirectTo?: string, redirectParams?: Record<string, string>): Promise<boolean>;
|
|
67
81
|
requireClaims(claims: string[], redirectTo?: string, redirectParams?: Record<string, string>): Promise<boolean>;
|
|
68
82
|
requireData(validator: (data: DocumentData | null) => boolean, redirectTo?: string, redirectParams?: Record<string, string>): Promise<boolean>;
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
logOut(): Promise<void>;
|
|
84
|
+
destroy(): void;
|
|
71
85
|
}
|
|
72
86
|
export declare const firekitAuthManager: FirekitAuthManager;
|
|
73
87
|
export {};
|
|
@@ -2,23 +2,33 @@ import { browser } from "$app/environment";
|
|
|
2
2
|
import { goto } from "$app/navigation";
|
|
3
3
|
import { firebaseService } from "../firebase.js";
|
|
4
4
|
import { toast } from "svelte-sonner";
|
|
5
|
-
import { onAuthStateChanged,
|
|
5
|
+
import { onAuthStateChanged, updateEmail, updatePassword, updateProfile, sendEmailVerification, } from "firebase/auth";
|
|
6
6
|
import { doc, getDoc, setDoc, updateDoc, } from "firebase/firestore";
|
|
7
|
-
|
|
7
|
+
class AuthError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
originalError;
|
|
10
|
+
constructor(message, code = 'unknown', originalError) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.originalError = originalError;
|
|
14
|
+
this.name = 'AuthError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
8
17
|
export class FirekitAuthManager {
|
|
9
18
|
static instance;
|
|
10
|
-
//
|
|
19
|
+
SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
|
|
20
|
+
activityInterval = null;
|
|
21
|
+
_initPromise = null;
|
|
22
|
+
// State using Svelte 5 runes
|
|
11
23
|
_user = $state();
|
|
12
24
|
_userData = $state(null);
|
|
13
25
|
_claims = $state({});
|
|
14
26
|
_initialized = $state(false);
|
|
15
|
-
_initPromise = null;
|
|
16
|
-
// Auth-Guard related states
|
|
17
27
|
_loading = $state(true);
|
|
18
28
|
_error = $state(null);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Derived
|
|
29
|
+
_currentSession = $state(null);
|
|
30
|
+
_lastValidationTime = $state(0);
|
|
31
|
+
// Derived state
|
|
22
32
|
isLoggedIn = $derived(Boolean(this._user));
|
|
23
33
|
uid = $derived(this._user?.uid);
|
|
24
34
|
email = $derived(this._user?.email);
|
|
@@ -28,9 +38,13 @@ export class FirekitAuthManager {
|
|
|
28
38
|
claims = $derived(this._claims);
|
|
29
39
|
data = $derived(this._userData);
|
|
30
40
|
initialized = $derived(this._initialized);
|
|
41
|
+
loading = $derived(this._loading);
|
|
42
|
+
error = $derived(this._error);
|
|
43
|
+
sessionActive = $derived(Boolean(this._currentSession));
|
|
31
44
|
constructor() {
|
|
32
45
|
if (browser) {
|
|
33
46
|
this.initialize();
|
|
47
|
+
this.setupActivityTracking();
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
static getInstance() {
|
|
@@ -43,16 +57,11 @@ export class FirekitAuthManager {
|
|
|
43
57
|
if (this._initialized)
|
|
44
58
|
return;
|
|
45
59
|
this._initPromise = new Promise((resolve) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
else {
|
|
52
|
-
this._userData = null;
|
|
53
|
-
this._claims = {};
|
|
54
|
-
}
|
|
55
|
-
this._initialized = true;
|
|
60
|
+
const auth = firebaseService.getAuthInstance();
|
|
61
|
+
if (!auth)
|
|
62
|
+
throw new AuthError('Firebase Auth not initialized', 'init_failed');
|
|
63
|
+
onAuthStateChanged(auth, async (user) => {
|
|
64
|
+
await this.handleAuthStateChange(user);
|
|
56
65
|
resolve();
|
|
57
66
|
});
|
|
58
67
|
});
|
|
@@ -62,19 +71,44 @@ export class FirekitAuthManager {
|
|
|
62
71
|
return;
|
|
63
72
|
return this._initPromise || Promise.resolve();
|
|
64
73
|
}
|
|
65
|
-
async
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
async handleAuthStateChange(user) {
|
|
75
|
+
this._loading = true;
|
|
76
|
+
try {
|
|
77
|
+
if (user) {
|
|
78
|
+
this._user = user;
|
|
79
|
+
await Promise.all([
|
|
80
|
+
this.loadUserData(user.uid),
|
|
81
|
+
this.loadUserClaims(user)
|
|
82
|
+
]);
|
|
83
|
+
this.initializeSession();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this._user = null;
|
|
87
|
+
this._userData = null;
|
|
88
|
+
this._claims = {};
|
|
89
|
+
this.clearSession();
|
|
90
|
+
}
|
|
91
|
+
this._initialized = true;
|
|
92
|
+
this._error = null;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
this.handleError(error);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
this._loading = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async loadUserData(uid) {
|
|
102
|
+
const docRef = doc(firebaseService.getDb(), "users", uid);
|
|
69
103
|
const docSnap = await getDoc(docRef);
|
|
70
104
|
if (docSnap.exists()) {
|
|
71
105
|
this._userData = docSnap.data();
|
|
72
106
|
}
|
|
73
107
|
else {
|
|
74
108
|
const initialData = {
|
|
75
|
-
displayName: this._user
|
|
76
|
-
email: this._user
|
|
77
|
-
photoURL: this._user
|
|
109
|
+
displayName: this._user?.displayName || "",
|
|
110
|
+
email: this._user?.email || "",
|
|
111
|
+
photoURL: this._user?.photoURL || "",
|
|
78
112
|
createdAt: new Date(),
|
|
79
113
|
updatedAt: new Date(),
|
|
80
114
|
isProfileComplete: false,
|
|
@@ -82,83 +116,70 @@ export class FirekitAuthManager {
|
|
|
82
116
|
await this.saveUserData(initialData);
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
|
-
async loadUserClaims() {
|
|
86
|
-
|
|
87
|
-
return;
|
|
88
|
-
const tokenResult = await this._user.getIdTokenResult();
|
|
119
|
+
async loadUserClaims(user) {
|
|
120
|
+
const tokenResult = await user.getIdTokenResult();
|
|
89
121
|
this._claims = tokenResult.claims;
|
|
90
122
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!this._user)
|
|
96
|
-
throw new Error("No authenticated user");
|
|
97
|
-
try {
|
|
98
|
-
await updateEmail(this._user, email);
|
|
99
|
-
await this.updateUserData({ email });
|
|
100
|
-
toast.success("Email updated successfully!", {
|
|
101
|
-
description: "Please note that you will be logged out, and you will need to log in again using your new email address.",
|
|
123
|
+
setupActivityTracking() {
|
|
124
|
+
if (browser) {
|
|
125
|
+
['mousedown', 'keydown', 'scroll', 'touchstart'].forEach(eventName => {
|
|
126
|
+
window.addEventListener(eventName, () => this.updateLastActivity());
|
|
102
127
|
});
|
|
103
|
-
|
|
104
|
-
await firekitAuth.logOut();
|
|
105
|
-
}, 4500);
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
toast.error(error.message);
|
|
128
|
+
this.activityInterval = setInterval(() => this.checkSession(), 60000);
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
initializeSession() {
|
|
132
|
+
this._currentSession = {
|
|
133
|
+
lastActivity: Date.now(),
|
|
134
|
+
deviceId: this.generateDeviceId(),
|
|
135
|
+
platform: this.getPlatformInfo()
|
|
136
|
+
};
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
updateLastActivity() {
|
|
139
|
+
if (this._currentSession) {
|
|
140
|
+
this._currentSession.lastActivity = Date.now();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async checkSession() {
|
|
144
|
+
if (!this._currentSession)
|
|
120
145
|
return;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
const inactiveTime = Date.now() - this._currentSession.lastActivity;
|
|
147
|
+
if (inactiveTime > this.SESSION_TIMEOUT) {
|
|
148
|
+
await this.handleSessionTimeout();
|
|
149
|
+
}
|
|
125
150
|
}
|
|
126
|
-
async
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await
|
|
132
|
-
await this.loadUserData();
|
|
151
|
+
async handleSessionTimeout() {
|
|
152
|
+
toast.warning('Session expired due to inactivity', {
|
|
153
|
+
description: 'Please log in again to continue.',
|
|
154
|
+
duration: 5000
|
|
155
|
+
});
|
|
156
|
+
await this.logOut();
|
|
133
157
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
createdAt: data.createdAt || new Date(),
|
|
141
|
-
updatedAt: new Date(),
|
|
142
|
-
};
|
|
143
|
-
await setDoc(docRef, saveData);
|
|
144
|
-
await this.loadUserData();
|
|
158
|
+
clearSession() {
|
|
159
|
+
this._currentSession = null;
|
|
160
|
+
if (this.activityInterval) {
|
|
161
|
+
clearInterval(this.activityInterval);
|
|
162
|
+
this.activityInterval = null;
|
|
163
|
+
}
|
|
145
164
|
}
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
handleError(error) {
|
|
166
|
+
const authError = error instanceof AuthError
|
|
167
|
+
? error
|
|
168
|
+
: new AuthError(error instanceof Error ? error.message : 'An unknown error occurred', 'unknown', error instanceof Error ? error : undefined);
|
|
169
|
+
this._error = authError;
|
|
170
|
+
this._loading = false;
|
|
171
|
+
console.error('[FirekitAuthManager]', authError);
|
|
148
172
|
}
|
|
149
|
-
|
|
150
|
-
return
|
|
173
|
+
generateDeviceId() {
|
|
174
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
151
175
|
}
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
getPlatformInfo() {
|
|
177
|
+
if (!browser)
|
|
178
|
+
return 'server';
|
|
179
|
+
return `${navigator.platform} - ${navigator.userAgent}`;
|
|
154
180
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (now - this._lastValidationTime < this.VALIDATION_THROTTLE) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
this._lastValidationTime = now;
|
|
161
|
-
return false;
|
|
181
|
+
validateClaims(requiredClaims, userClaims) {
|
|
182
|
+
return requiredClaims.every(claim => userClaims[claim]);
|
|
162
183
|
}
|
|
163
184
|
async handleRedirect(redirectTo, params) {
|
|
164
185
|
const url = new URL(redirectTo, window.location.origin);
|
|
@@ -169,42 +190,41 @@ export class FirekitAuthManager {
|
|
|
169
190
|
}
|
|
170
191
|
await goto(url.toString());
|
|
171
192
|
}
|
|
172
|
-
|
|
173
|
-
if (!requiredClaims.length)
|
|
174
|
-
return true;
|
|
175
|
-
if (!userClaims)
|
|
176
|
-
return false;
|
|
177
|
-
return requiredClaims.every((claim) => userClaims[claim]);
|
|
178
|
-
}
|
|
193
|
+
// Public Methods
|
|
179
194
|
async validateAuth({ authRequired = true, redirectTo = "/login", requiredClaims = [], requiredData, allowIf, redirectParams, } = {}) {
|
|
180
195
|
if (!browser)
|
|
181
196
|
return true;
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
if (now - this._lastValidationTime < 1000)
|
|
199
|
+
return false;
|
|
200
|
+
this._lastValidationTime = now;
|
|
184
201
|
try {
|
|
185
202
|
this._loading = true;
|
|
186
203
|
this._error = null;
|
|
187
204
|
await this.waitForInit();
|
|
188
|
-
|
|
189
|
-
if (authRequired && !
|
|
205
|
+
// Basic auth validation
|
|
206
|
+
if (authRequired && !this._user) {
|
|
190
207
|
await this.handleRedirect(redirectTo, {
|
|
191
208
|
...redirectParams,
|
|
192
209
|
returnTo: window.location.pathname,
|
|
193
210
|
});
|
|
194
211
|
return false;
|
|
195
212
|
}
|
|
213
|
+
if (!authRequired && this._user) {
|
|
214
|
+
await this.handleRedirect(redirectTo, redirectParams);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
// Custom conditions
|
|
196
218
|
if (allowIf && !allowIf(this)) {
|
|
197
219
|
await this.handleRedirect(redirectTo, redirectParams);
|
|
198
220
|
return false;
|
|
199
221
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
await this.handleRedirect(redirectTo, redirectParams);
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
222
|
+
// Claims validation
|
|
223
|
+
if (requiredClaims.length > 0 && !this.validateClaims(requiredClaims, this._claims)) {
|
|
224
|
+
await this.handleRedirect(redirectTo, redirectParams);
|
|
225
|
+
return false;
|
|
207
226
|
}
|
|
227
|
+
// Data validation
|
|
208
228
|
if (requiredData && !requiredData(this._userData)) {
|
|
209
229
|
await this.handleRedirect(redirectTo, redirectParams);
|
|
210
230
|
return false;
|
|
@@ -212,13 +232,128 @@ export class FirekitAuthManager {
|
|
|
212
232
|
return true;
|
|
213
233
|
}
|
|
214
234
|
catch (error) {
|
|
215
|
-
this.
|
|
235
|
+
this.handleError(error);
|
|
216
236
|
return false;
|
|
217
237
|
}
|
|
218
238
|
finally {
|
|
219
239
|
this._loading = false;
|
|
220
240
|
}
|
|
221
241
|
}
|
|
242
|
+
async updateEmailUser(email) {
|
|
243
|
+
if (!this._user)
|
|
244
|
+
throw new AuthError("No authenticated user", "no_user");
|
|
245
|
+
try {
|
|
246
|
+
await updateEmail(this._user, email);
|
|
247
|
+
await this.updateUserData({ email });
|
|
248
|
+
toast.success("Email updated successfully!", {
|
|
249
|
+
description: "Please note that you will be logged out, and you will need to log in again using your new email address.",
|
|
250
|
+
});
|
|
251
|
+
setTimeout(async () => {
|
|
252
|
+
await this.logOut();
|
|
253
|
+
}, 4500);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
this.handleError(error);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async updatePassword(password) {
|
|
261
|
+
if (!this._user)
|
|
262
|
+
throw new AuthError("No authenticated user", "no_user");
|
|
263
|
+
try {
|
|
264
|
+
await updatePassword(this._user, password);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
this.handleError(error);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async updateProfileInfo({ displayName, photoURL, }) {
|
|
272
|
+
if (!this._user)
|
|
273
|
+
throw new AuthError("No authenticated user", "no_user");
|
|
274
|
+
if (!displayName && !photoURL)
|
|
275
|
+
return;
|
|
276
|
+
try {
|
|
277
|
+
if (displayName)
|
|
278
|
+
await updateProfile(this._user, { displayName });
|
|
279
|
+
if (photoURL)
|
|
280
|
+
await updateProfile(this._user, { photoURL });
|
|
281
|
+
await this.updateUserData({ displayName, photoURL });
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
this.handleError(error);
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async updateUserData(data) {
|
|
289
|
+
if (!this._user?.uid)
|
|
290
|
+
throw new AuthError("No authenticated user", "no_user");
|
|
291
|
+
try {
|
|
292
|
+
const docRef = doc(firebaseService.getDb(), "users", this._user.uid);
|
|
293
|
+
const updateData = { ...data, updatedAt: new Date() };
|
|
294
|
+
await updateDoc(docRef, updateData);
|
|
295
|
+
await this.loadUserData(this._user.uid);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
this.handleError(error);
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async saveUserData(data) {
|
|
303
|
+
if (!this._user?.uid)
|
|
304
|
+
throw new AuthError("No authenticated user", "no_user");
|
|
305
|
+
try {
|
|
306
|
+
const docRef = doc(firebaseService.getDb(), "users", this._user.uid);
|
|
307
|
+
const saveData = {
|
|
308
|
+
...data,
|
|
309
|
+
createdAt: data.createdAt || new Date(),
|
|
310
|
+
updatedAt: new Date(),
|
|
311
|
+
};
|
|
312
|
+
await setDoc(docRef, saveData);
|
|
313
|
+
await this.loadUserData(this._user.uid);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
this.handleError(error);
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async sendVerificationEmail() {
|
|
321
|
+
if (!this._user)
|
|
322
|
+
throw new AuthError('No authenticated user', 'no_user');
|
|
323
|
+
try {
|
|
324
|
+
await sendEmailVerification(this._user);
|
|
325
|
+
toast.success('Verification email sent!');
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
this.handleError(error);
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async refreshUserData() {
|
|
333
|
+
if (!this._user)
|
|
334
|
+
return;
|
|
335
|
+
try {
|
|
336
|
+
await Promise.all([
|
|
337
|
+
this.loadUserData(this._user.uid),
|
|
338
|
+
this.loadUserClaims(this._user)
|
|
339
|
+
]);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
this.handleError(error);
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Helper methods
|
|
347
|
+
hasRequiredClaims(requiredClaims) {
|
|
348
|
+
return requiredClaims.every((claim) => this._claims[claim]);
|
|
349
|
+
}
|
|
350
|
+
isAdmin() {
|
|
351
|
+
return Boolean(this._claims.admin);
|
|
352
|
+
}
|
|
353
|
+
isPremium() {
|
|
354
|
+
return Boolean(this._claims.premium);
|
|
355
|
+
}
|
|
356
|
+
// Convenience methods for route guards
|
|
222
357
|
async requireAuth(redirectTo = "/login", redirectParams) {
|
|
223
358
|
return this.validateAuth({
|
|
224
359
|
authRequired: true,
|
|
@@ -247,11 +382,20 @@ export class FirekitAuthManager {
|
|
|
247
382
|
redirectParams,
|
|
248
383
|
});
|
|
249
384
|
}
|
|
250
|
-
|
|
251
|
-
|
|
385
|
+
async logOut() {
|
|
386
|
+
try {
|
|
387
|
+
await firebaseService.getAuthInstance().signOut();
|
|
388
|
+
this.clearSession();
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
this.handleError(error);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
252
394
|
}
|
|
253
|
-
|
|
254
|
-
|
|
395
|
+
// Cleanup
|
|
396
|
+
destroy() {
|
|
397
|
+
this.clearSession();
|
|
398
|
+
// Additional cleanup if needed
|
|
255
399
|
}
|
|
256
400
|
}
|
|
257
401
|
export const firekitAuthManager = FirekitAuthManager.getInstance();
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import { browser } from "$app/environment";
|
|
3
|
-
// import { goto } from "$app/navigation";
|
|
4
|
-
// import type { DocumentData } from "firebase/firestore";
|
|
5
|
-
// import { firekitUser } from "./user.svelte.js";
|
|
6
|
-
// type UserClaims = Record<string, any>;
|
|
7
|
-
// interface GuardConfig {
|
|
8
|
-
// authRequired?: boolean;
|
|
9
|
-
// redirectTo?: string;
|
|
10
|
-
// requiredClaims?: string[];
|
|
11
|
-
// requiredData?: (data: DocumentData | null) => boolean;
|
|
12
|
-
// allowIf?: (user: typeof firekitUser) => boolean;
|
|
13
|
-
// redirectParams?: Record<string, string>;
|
|
14
|
-
// }
|
|
15
|
-
// export class FirekitAuthGuard {
|
|
16
|
-
// private static instance: FirekitAuthGuard;
|
|
17
|
-
// private _loading = $state(true);
|
|
18
|
-
// private _error = $state<Error | null>(null);
|
|
19
|
-
// private _lastValidationTime = 0;
|
|
20
|
-
// private readonly VALIDATION_THROTTLE = 1000; // 1 second
|
|
21
|
-
// private constructor() { }
|
|
22
|
-
// static getInstance(): FirekitAuthGuard {
|
|
23
|
-
// if (!FirekitAuthGuard.instance) {
|
|
24
|
-
// FirekitAuthGuard.instance = new FirekitAuthGuard();
|
|
25
|
-
// }
|
|
26
|
-
// return FirekitAuthGuard.instance;
|
|
27
|
-
// }
|
|
28
|
-
// private shouldThrottleValidation(): boolean {
|
|
29
|
-
// const now = Date.now();
|
|
30
|
-
// if (now - this._lastValidationTime < this.VALIDATION_THROTTLE) {
|
|
31
|
-
// return true;
|
|
32
|
-
// }
|
|
33
|
-
// this._lastValidationTime = now;
|
|
34
|
-
// return false;
|
|
35
|
-
// }
|
|
36
|
-
// private async handleRedirect(redirectTo: string, params?: Record<string, string>): Promise<void> {
|
|
37
|
-
// const url = new URL(redirectTo, window.location.origin);
|
|
38
|
-
// if (params) {
|
|
39
|
-
// Object.entries(params).forEach(([key, value]) => {
|
|
40
|
-
// url.searchParams.append(key, value);
|
|
41
|
-
// });
|
|
42
|
-
// }
|
|
43
|
-
// await goto(url.toString());
|
|
44
|
-
// }
|
|
45
|
-
// private async validateClaims(requiredClaims: string[], userClaims?: UserClaims): Promise<boolean> {
|
|
46
|
-
// if (!requiredClaims.length) return true;
|
|
47
|
-
// if (!userClaims) return false;
|
|
48
|
-
// return requiredClaims.every(claim => userClaims[claim]);
|
|
49
|
-
// }
|
|
50
|
-
// async validateAuth({
|
|
51
|
-
// authRequired = true,
|
|
52
|
-
// redirectTo = '/login',
|
|
53
|
-
// requiredClaims = [],
|
|
54
|
-
// requiredData,
|
|
55
|
-
// allowIf,
|
|
56
|
-
// redirectParams
|
|
57
|
-
// }: GuardConfig = {}): Promise<boolean> {
|
|
58
|
-
// if (!browser) return true;
|
|
59
|
-
// if (this.shouldThrottleValidation()) return true;
|
|
60
|
-
// try {
|
|
61
|
-
// this._loading = true;
|
|
62
|
-
// this._error = null;
|
|
63
|
-
// await firekitUser.waitForInit();
|
|
64
|
-
// const isAuthenticated = firekitUser.isLoggedIn;
|
|
65
|
-
// if (authRequired && !isAuthenticated) {
|
|
66
|
-
// await this.handleRedirect(redirectTo, {
|
|
67
|
-
// ...redirectParams,
|
|
68
|
-
// returnTo: window.location.pathname
|
|
69
|
-
// });
|
|
70
|
-
// return false;
|
|
71
|
-
// }
|
|
72
|
-
// if (allowIf && !allowIf(firekitUser)) {
|
|
73
|
-
// await this.handleRedirect(redirectTo, redirectParams);
|
|
74
|
-
// return false;
|
|
75
|
-
// }
|
|
76
|
-
// if (requiredClaims.length > 0) {
|
|
77
|
-
// const userClaims = await firekitUser.user?.getIdTokenResult();
|
|
78
|
-
// const hasClaims = await this.validateClaims(requiredClaims, userClaims?.claims);
|
|
79
|
-
// if (!hasClaims) {
|
|
80
|
-
// await this.handleRedirect(redirectTo, redirectParams);
|
|
81
|
-
// return false;
|
|
82
|
-
// }
|
|
83
|
-
// }
|
|
84
|
-
// if (requiredData && !requiredData(firekitUser.data)) {
|
|
85
|
-
// await this.handleRedirect(redirectTo, redirectParams);
|
|
86
|
-
// return false;
|
|
87
|
-
// }
|
|
88
|
-
// return true;
|
|
89
|
-
// } catch (error) {
|
|
90
|
-
// this._error = error instanceof Error ? error : new Error(String(error));
|
|
91
|
-
// return false;
|
|
92
|
-
// } finally {
|
|
93
|
-
// this._loading = false;
|
|
94
|
-
// }
|
|
95
|
-
// }
|
|
96
|
-
// async requireAuth(redirectTo = '/login', redirectParams?: Record<string, string>) {
|
|
97
|
-
// return this.validateAuth({ authRequired: true, redirectTo, redirectParams });
|
|
98
|
-
// }
|
|
99
|
-
// async requireNoAuth(redirectTo = '/dashboard', redirectParams?: Record<string, string>) {
|
|
100
|
-
// return this.validateAuth({ authRequired: false, redirectTo, redirectParams });
|
|
101
|
-
// }
|
|
102
|
-
// async requireClaims(claims: string[], redirectTo = '/login', redirectParams?: Record<string, string>) {
|
|
103
|
-
// return this.validateAuth({ requiredClaims: claims, redirectTo, redirectParams });
|
|
104
|
-
// }
|
|
105
|
-
// async requireData(
|
|
106
|
-
// validator: (data: DocumentData | null) => boolean,
|
|
107
|
-
// redirectTo = '/login',
|
|
108
|
-
// redirectParams?: Record<string, string>
|
|
109
|
-
// ) {
|
|
110
|
-
// return this.validateAuth({ requiredData: validator, redirectTo, redirectParams });
|
|
111
|
-
// }
|
|
112
|
-
// get loading() {
|
|
113
|
-
// return this._loading;
|
|
114
|
-
// }
|
|
115
|
-
// get error() {
|
|
116
|
-
// return this._error;
|
|
117
|
-
// }
|
|
118
|
-
// }
|
|
119
|
-
// export const firekitAuthGuard = FirekitAuthGuard.getInstance();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import { browser } from "$app/environment";
|
|
3
|
-
// import { firebaseService } from "../firebase.js";
|
|
4
|
-
// import {
|
|
5
|
-
// onAuthStateChanged,
|
|
6
|
-
// updateCurrentUser,
|
|
7
|
-
// updateEmail,
|
|
8
|
-
// updatePassword,
|
|
9
|
-
// updatePhoneNumber,
|
|
10
|
-
// updateProfile,
|
|
11
|
-
// type User,
|
|
12
|
-
// } from "firebase/auth";
|
|
13
|
-
// import {
|
|
14
|
-
// doc,
|
|
15
|
-
// getDoc,
|
|
16
|
-
// setDoc,
|
|
17
|
-
// updateDoc,
|
|
18
|
-
// type DocumentData,
|
|
19
|
-
// } from "firebase/firestore";
|
|
20
|
-
// import { toast } from "svelte-sonner";
|
|
21
|
-
// import { firekitAuth } from "./auth.js";
|
|
22
|
-
// interface UserClaims {
|
|
23
|
-
// [key: string]: any;
|
|
24
|
-
// admin?: boolean;
|
|
25
|
-
// premium?: boolean;
|
|
26
|
-
// }
|
|
27
|
-
// interface UserData extends DocumentData {
|
|
28
|
-
// displayName?: string;
|
|
29
|
-
// email?: string;
|
|
30
|
-
// photoURL?: string;
|
|
31
|
-
// createdAt?: Date;
|
|
32
|
-
// updatedAt?: Date;
|
|
33
|
-
// isProfileComplete?: boolean;
|
|
34
|
-
// role?: string;
|
|
35
|
-
// settings?: Record<string, any>;
|
|
36
|
-
// [key: string]: any;
|
|
37
|
-
// }
|
|
38
|
-
// export class FirekitUser {
|
|
39
|
-
// private static instance: FirekitUser;
|
|
40
|
-
// private _user: User | null | undefined = $state();
|
|
41
|
-
// private _userData: UserData | null = $state(null);
|
|
42
|
-
// private _claims: UserClaims = $state({});
|
|
43
|
-
// private _initialized = $state(false);
|
|
44
|
-
// readonly isLoggedIn = $derived(Boolean(this._user));
|
|
45
|
-
// readonly uid = $derived(this._user?.uid);
|
|
46
|
-
// readonly email = $derived(this._user?.email);
|
|
47
|
-
// readonly displayName = $derived(this._user?.displayName);
|
|
48
|
-
// readonly photoURL = $derived(this._user?.photoURL);
|
|
49
|
-
// readonly emailVerified = $derived(this._user?.emailVerified);
|
|
50
|
-
// readonly claims = $derived(this._claims);
|
|
51
|
-
// readonly data = $derived(this._userData);
|
|
52
|
-
// readonly initialized = $derived(this._initialized);
|
|
53
|
-
// private _initPromise: Promise<void> | null = null;
|
|
54
|
-
// constructor() {
|
|
55
|
-
// if (browser) {
|
|
56
|
-
// // Add this check
|
|
57
|
-
// this.initialize();
|
|
58
|
-
// }
|
|
59
|
-
// }
|
|
60
|
-
// private async initialize(): Promise<void> {
|
|
61
|
-
// if (this._initialized) return;
|
|
62
|
-
// return new Promise((resolve) => {
|
|
63
|
-
// onAuthStateChanged(firebaseService.getAuthInstance(), async (user) => {
|
|
64
|
-
// this._user = user;
|
|
65
|
-
// if (user) {
|
|
66
|
-
// await Promise.all([this.loadUserData(), this.loadUserClaims()]);
|
|
67
|
-
// } else {
|
|
68
|
-
// this._userData = null;
|
|
69
|
-
// this._claims = {};
|
|
70
|
-
// }
|
|
71
|
-
// this._initialized = true;
|
|
72
|
-
// resolve();
|
|
73
|
-
// });
|
|
74
|
-
// });
|
|
75
|
-
// }
|
|
76
|
-
// async waitForInit(): Promise<void> {
|
|
77
|
-
// if (this._initialized) return;
|
|
78
|
-
// return this._initPromise || Promise.resolve();
|
|
79
|
-
// }
|
|
80
|
-
// static getInstance(): FirekitUser {
|
|
81
|
-
// if (!FirekitUser.instance) {
|
|
82
|
-
// FirekitUser.instance = new FirekitUser();
|
|
83
|
-
// }
|
|
84
|
-
// return FirekitUser.instance;
|
|
85
|
-
// }
|
|
86
|
-
// private async loadUserData() {
|
|
87
|
-
// if (!this._user?.uid) return;
|
|
88
|
-
// const docRef = doc(firebaseService.getDb(), "users", this._user.uid);
|
|
89
|
-
// const docSnap = await getDoc(docRef);
|
|
90
|
-
// if (docSnap.exists()) {
|
|
91
|
-
// this._userData = docSnap.data() as UserData;
|
|
92
|
-
// } else {
|
|
93
|
-
// // Initialize user document if it doesn't exist
|
|
94
|
-
// const initialData: UserData = {
|
|
95
|
-
// displayName: this._user.displayName || "",
|
|
96
|
-
// email: this._user.email || "",
|
|
97
|
-
// photoURL: this._user.photoURL || "",
|
|
98
|
-
// createdAt: new Date(),
|
|
99
|
-
// updatedAt: new Date(),
|
|
100
|
-
// isProfileComplete: false,
|
|
101
|
-
// };
|
|
102
|
-
// await this.saveUserData(initialData);
|
|
103
|
-
// }
|
|
104
|
-
// }
|
|
105
|
-
// private async loadUserClaims() {
|
|
106
|
-
// if (!this._user) return;
|
|
107
|
-
// const tokenResult = await this._user.getIdTokenResult();
|
|
108
|
-
// this._claims = tokenResult.claims as UserClaims;
|
|
109
|
-
// }
|
|
110
|
-
// get user(): User | null | undefined {
|
|
111
|
-
// return this._user;
|
|
112
|
-
// }
|
|
113
|
-
// async updateEmailUser(email: string) {
|
|
114
|
-
// let message: string = "";
|
|
115
|
-
// if (!this._user) throw new Error("No authenticated user");
|
|
116
|
-
// try {
|
|
117
|
-
// await updateEmail(this._user, email);
|
|
118
|
-
// await this.updateUserData({ email });
|
|
119
|
-
// toast.success("Email updated successfully!", {
|
|
120
|
-
// description:
|
|
121
|
-
// "Please note that you will be logged out, and you will need to log in again using your new email address.",
|
|
122
|
-
// });
|
|
123
|
-
// setTimeout(async () => {
|
|
124
|
-
// await firekitAuth.logOut();
|
|
125
|
-
// }, 4500);
|
|
126
|
-
// } catch (error) {
|
|
127
|
-
// toast.error(error.message);
|
|
128
|
-
// }
|
|
129
|
-
// }
|
|
130
|
-
// async updatePassword(password: string) {
|
|
131
|
-
// if (!this._user) throw new Error("No authenticated user");
|
|
132
|
-
// await updatePassword(this._user, password);
|
|
133
|
-
// }
|
|
134
|
-
// async updateProfileInfo({
|
|
135
|
-
// displayName,
|
|
136
|
-
// photoURL,
|
|
137
|
-
// }: {
|
|
138
|
-
// displayName?: string;
|
|
139
|
-
// photoURL?: string;
|
|
140
|
-
// }) {
|
|
141
|
-
// if (!this._user) throw new Error("No authenticated user");
|
|
142
|
-
// if (!displayName && !photoURL) return;
|
|
143
|
-
// if (displayName) await updateProfile(this._user, { displayName });
|
|
144
|
-
// if (photoURL) await updateProfile(this._user, { photoURL });
|
|
145
|
-
// }
|
|
146
|
-
// async updateUserData(data: Partial<UserData>) {
|
|
147
|
-
// if (!this._user?.uid) throw new Error("No authenticated user");
|
|
148
|
-
// const docRef = doc(firebaseService.getDb(), "users", this._user.uid);
|
|
149
|
-
// const updateData = {
|
|
150
|
-
// ...data,
|
|
151
|
-
// updatedAt: new Date(),
|
|
152
|
-
// };
|
|
153
|
-
// await updateDoc(docRef, updateData);
|
|
154
|
-
// await this.loadUserData(); // Reload user data
|
|
155
|
-
// }
|
|
156
|
-
// async saveUserData(data: UserData) {
|
|
157
|
-
// if (!this._user?.uid) throw new Error("No authenticated user");
|
|
158
|
-
// const docRef = doc(firebaseService.getDb(), "users", this._user.uid);
|
|
159
|
-
// const saveData = {
|
|
160
|
-
// ...data,
|
|
161
|
-
// createdAt: data.createdAt || new Date(),
|
|
162
|
-
// updatedAt: new Date(),
|
|
163
|
-
// };
|
|
164
|
-
// await setDoc(docRef, saveData);
|
|
165
|
-
// await this.loadUserData(); // Reload user data
|
|
166
|
-
// }
|
|
167
|
-
// hasRequiredClaims(requiredClaims: string[]): boolean {
|
|
168
|
-
// return requiredClaims.every((claim) => this._claims[claim]);
|
|
169
|
-
// }
|
|
170
|
-
// isAdmin(): boolean {
|
|
171
|
-
// return Boolean(this._claims.admin);
|
|
172
|
-
// }
|
|
173
|
-
// isPremium(): boolean {
|
|
174
|
-
// return Boolean(this._claims.premium);
|
|
175
|
-
// }
|
|
176
|
-
// }
|
|
177
|
-
// export const firekitUser = FirekitUser.getInstance();
|