@umituz/web-firebase 3.0.1 → 3.2.1
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 +166 -25
- package/package.json +3 -1
- package/src/domain/config/auth.config.ts +200 -0
- package/src/domain/index.ts +3 -0
- package/src/domains/auth/services/auth.service.ts +75 -154
- package/src/infrastructure/firebase/auth.adapter.ts +204 -14
- package/src/infrastructure/firebase/client.ts +7 -7
- package/src/presentation/hooks/useAuth.ts +288 -47
|
@@ -1,162 +1,117 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Service
|
|
3
|
-
* @description Firebase Auth
|
|
3
|
+
* @description Firebase Auth service with Google & Apple OAuth
|
|
4
|
+
* Facade pattern using AuthAdapter
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
signInWithEmailAndPassword,
|
|
8
|
-
signInWithPopup,
|
|
9
|
-
createUserWithEmailAndPassword,
|
|
10
|
-
signOut as firebaseSignOut,
|
|
11
|
-
sendPasswordResetEmail,
|
|
12
|
-
sendEmailVerification,
|
|
13
|
-
updateProfile as updateAuthProfile,
|
|
14
|
-
updateEmail as updateAuthEmail,
|
|
15
|
-
updatePassword as updateAuthPassword,
|
|
16
|
-
reauthenticateWithCredential,
|
|
17
|
-
EmailAuthProvider,
|
|
18
|
-
UserCredential,
|
|
19
|
-
} from 'firebase/auth'
|
|
20
|
-
import { GoogleAuthProvider } from 'firebase/auth'
|
|
21
|
-
import { getFirebaseAuth } from '../../../infrastructure/firebase/client'
|
|
22
|
-
import type { IAuthService } from '../types'
|
|
7
|
+
import type { UserCredential } from 'firebase/auth'
|
|
23
8
|
import type { AuthUser } from '../entities'
|
|
9
|
+
import type { IAuthService } from '../types'
|
|
10
|
+
import { authAdapter } from '../../../infrastructure/firebase/auth.adapter'
|
|
24
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Auth Service
|
|
14
|
+
* Implements IAuthService interface using AuthAdapter
|
|
15
|
+
*/
|
|
25
16
|
class AuthService implements IAuthService {
|
|
26
|
-
|
|
27
|
-
return getFirebaseAuth()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Authentication Methods
|
|
17
|
+
// ==================== Authentication Methods ====================
|
|
31
18
|
|
|
32
19
|
async signIn(email: string, password: string): Promise<UserCredential> {
|
|
33
|
-
|
|
34
|
-
return await signInWithEmailAndPassword(this.auth, email, password)
|
|
35
|
-
} catch (error) {
|
|
36
|
-
throw this.handleAuthError(error)
|
|
37
|
-
}
|
|
20
|
+
return await authAdapter.signIn(email, password)
|
|
38
21
|
}
|
|
39
22
|
|
|
40
23
|
async signUp(email: string, password: string, displayName: string): Promise<UserCredential> {
|
|
41
|
-
|
|
42
|
-
const result = await createUserWithEmailAndPassword(this.auth, email, password)
|
|
43
|
-
|
|
44
|
-
// Update profile
|
|
45
|
-
await updateAuthProfile(result.user, { displayName })
|
|
46
|
-
|
|
47
|
-
// Send email verification
|
|
48
|
-
await sendEmailVerification(result.user)
|
|
49
|
-
|
|
50
|
-
return result
|
|
51
|
-
} catch (error) {
|
|
52
|
-
throw this.handleAuthError(error)
|
|
53
|
-
}
|
|
24
|
+
return await authAdapter.signUp(email, password, displayName)
|
|
54
25
|
}
|
|
55
26
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Sign in with Google
|
|
29
|
+
*/
|
|
30
|
+
async signInWithGoogle(useRedirect = false): Promise<UserCredential> {
|
|
31
|
+
return await authAdapter.signInWithGoogle(useRedirect)
|
|
32
|
+
}
|
|
61
33
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Sign in with Apple
|
|
36
|
+
*/
|
|
37
|
+
async signInWithApple(useRedirect = false): Promise<UserCredential> {
|
|
38
|
+
return await authAdapter.signInWithApple(useRedirect)
|
|
66
39
|
}
|
|
67
40
|
|
|
68
41
|
async signOut(): Promise<void> {
|
|
69
|
-
|
|
70
|
-
await firebaseSignOut(this.auth)
|
|
71
|
-
} catch (error) {
|
|
72
|
-
throw new Error('Sign out failed')
|
|
73
|
-
}
|
|
42
|
+
await authAdapter.signOut()
|
|
74
43
|
}
|
|
75
44
|
|
|
76
45
|
async sendPasswordReset(email: string): Promise<void> {
|
|
77
|
-
|
|
78
|
-
await sendPasswordResetEmail(this.auth, email)
|
|
79
|
-
} catch (error) {
|
|
80
|
-
throw this.handleAuthError(error)
|
|
81
|
-
}
|
|
46
|
+
await authAdapter.sendPasswordReset(email)
|
|
82
47
|
}
|
|
83
48
|
|
|
84
49
|
async resendEmailVerification(): Promise<void> {
|
|
85
|
-
|
|
86
|
-
const user = this.auth.currentUser
|
|
87
|
-
if (!user) {
|
|
88
|
-
throw new Error('No user logged in')
|
|
89
|
-
}
|
|
90
|
-
await sendEmailVerification(user)
|
|
91
|
-
} catch (error) {
|
|
92
|
-
throw new Error('Failed to resend verification')
|
|
93
|
-
}
|
|
50
|
+
await authAdapter.resendEmailVerification()
|
|
94
51
|
}
|
|
95
52
|
|
|
96
|
-
// Profile Management
|
|
53
|
+
// ==================== Profile Management ====================
|
|
97
54
|
|
|
98
55
|
async updateProfile(updates: { displayName?: string; photoURL?: string }): Promise<void> {
|
|
99
|
-
|
|
100
|
-
const user = this.auth.currentUser
|
|
101
|
-
if (!user) {
|
|
102
|
-
throw new Error('No user logged in')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
await updateAuthProfile(user, updates)
|
|
106
|
-
} catch (error) {
|
|
107
|
-
throw new Error('Profile update failed')
|
|
108
|
-
}
|
|
56
|
+
await authAdapter.updateProfile(updates)
|
|
109
57
|
}
|
|
110
58
|
|
|
111
59
|
async updateEmail(newEmail: string, password: string): Promise<void> {
|
|
112
|
-
|
|
113
|
-
const user = this.auth.currentUser
|
|
114
|
-
if (!user || !user.email) {
|
|
115
|
-
throw new Error('No user logged in')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const credential = EmailAuthProvider.credential(user.email, password)
|
|
119
|
-
await reauthenticateWithCredential(user, credential)
|
|
120
|
-
await updateAuthEmail(user, newEmail)
|
|
121
|
-
} catch (error) {
|
|
122
|
-
throw new Error('Email update failed')
|
|
123
|
-
}
|
|
60
|
+
await authAdapter.updateEmail(newEmail, password)
|
|
124
61
|
}
|
|
125
62
|
|
|
126
63
|
async updatePassword(currentPassword: string, newPassword: string): Promise<void> {
|
|
127
|
-
|
|
128
|
-
const user = this.auth.currentUser
|
|
129
|
-
if (!user || !user.email) {
|
|
130
|
-
throw new Error('No user logged in')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const credential = EmailAuthProvider.credential(user.email, currentPassword)
|
|
134
|
-
await reauthenticateWithCredential(user, credential)
|
|
135
|
-
await updateAuthPassword(user, newPassword)
|
|
136
|
-
} catch (error) {
|
|
137
|
-
throw new Error('Password update failed')
|
|
138
|
-
}
|
|
64
|
+
await authAdapter.updatePassword(currentPassword, newPassword)
|
|
139
65
|
}
|
|
140
66
|
|
|
141
67
|
async deleteAccount(password: string): Promise<void> {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
68
|
+
await authAdapter.deleteAccount(password)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ==================== Provider Linking ====================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Link Google to current user
|
|
75
|
+
*/
|
|
76
|
+
async linkGoogle(): Promise<UserCredential> {
|
|
77
|
+
return await authAdapter.linkGoogle()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Link Apple to current user
|
|
82
|
+
*/
|
|
83
|
+
async linkApple(): Promise<UserCredential> {
|
|
84
|
+
return await authAdapter.linkApple()
|
|
154
85
|
}
|
|
155
86
|
|
|
156
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Unlink a provider from current user
|
|
89
|
+
*/
|
|
90
|
+
async unlinkProvider(providerId: string): Promise<void> {
|
|
91
|
+
await authAdapter.unlinkProvider(providerId)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ==================== Token Management ====================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get ID Token
|
|
98
|
+
*/
|
|
99
|
+
async getIdToken(forceRefresh = false): Promise<string> {
|
|
100
|
+
return await authAdapter.getIdToken(forceRefresh)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Refresh ID Token
|
|
105
|
+
*/
|
|
106
|
+
async refreshToken(): Promise<void> {
|
|
107
|
+
await authAdapter.refreshToken()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ==================== State Management ====================
|
|
157
111
|
|
|
158
112
|
getCurrentUser(): AuthUser | null {
|
|
159
|
-
const user =
|
|
113
|
+
const user = authAdapter.getCurrentUser()
|
|
114
|
+
|
|
160
115
|
if (!user) return null
|
|
161
116
|
|
|
162
117
|
return {
|
|
@@ -180,7 +135,7 @@ class AuthService implements IAuthService {
|
|
|
180
135
|
callback: (user: AuthUser | null) => void,
|
|
181
136
|
onError?: (error: Error) => void
|
|
182
137
|
): () => void {
|
|
183
|
-
return
|
|
138
|
+
return authAdapter.onAuthStateChanged(
|
|
184
139
|
(user) => {
|
|
185
140
|
callback(
|
|
186
141
|
user
|
|
@@ -202,43 +157,9 @@ class AuthService implements IAuthService {
|
|
|
202
157
|
: null
|
|
203
158
|
)
|
|
204
159
|
},
|
|
205
|
-
|
|
206
|
-
onError?.(error as Error)
|
|
207
|
-
}
|
|
160
|
+
onError
|
|
208
161
|
)
|
|
209
162
|
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Handle Firebase Auth errors
|
|
213
|
-
*/
|
|
214
|
-
private handleAuthError(error: unknown): Error {
|
|
215
|
-
if (error instanceof Error && 'code' in error) {
|
|
216
|
-
const code = (error as { code: string }).code
|
|
217
|
-
|
|
218
|
-
switch (code) {
|
|
219
|
-
case 'auth/user-not-found':
|
|
220
|
-
case 'auth/wrong-password':
|
|
221
|
-
case 'auth/invalid-credential':
|
|
222
|
-
return new Error('Invalid credentials')
|
|
223
|
-
case 'auth/email-already-in-use':
|
|
224
|
-
return new Error('Email already in use')
|
|
225
|
-
case 'auth/weak-password':
|
|
226
|
-
return new Error('Password is too weak')
|
|
227
|
-
case 'auth/invalid-email':
|
|
228
|
-
return new Error('Invalid email')
|
|
229
|
-
case 'auth/user-disabled':
|
|
230
|
-
return new Error('Account disabled')
|
|
231
|
-
case 'auth/too-many-requests':
|
|
232
|
-
return new Error('Too many requests')
|
|
233
|
-
case 'auth/popup-closed-by-user':
|
|
234
|
-
return new Error('Sign in cancelled')
|
|
235
|
-
default:
|
|
236
|
-
return new Error(`Auth error: ${code}`)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return new Error('Unknown auth error')
|
|
241
|
-
}
|
|
242
163
|
}
|
|
243
164
|
|
|
244
165
|
// Export class and singleton instance
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Adapter
|
|
3
|
-
* @description Firebase Auth implementation
|
|
4
|
-
* Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/services/auth.ts
|
|
3
|
+
* @description Firebase Auth implementation with Google & Apple OAuth support
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import {
|
|
@@ -18,21 +17,40 @@ import {
|
|
|
18
17
|
EmailAuthProvider,
|
|
19
18
|
User as FirebaseUser,
|
|
20
19
|
UserCredential,
|
|
20
|
+
GoogleAuthProvider,
|
|
21
|
+
OAuthProvider,
|
|
22
|
+
signInWithRedirect,
|
|
23
|
+
getRedirectResult,
|
|
24
|
+
linkWithPopup,
|
|
25
|
+
unlink as firebaseUnlink,
|
|
21
26
|
} from 'firebase/auth'
|
|
22
|
-
import { GoogleAuthProvider } from 'firebase/auth'
|
|
23
27
|
import { getFirebaseAuth } from './client'
|
|
24
28
|
import type { IAuthRepository } from '../../domain/interfaces/auth.repository.interface'
|
|
25
29
|
import type { User } from '../../domain/entities/user.entity'
|
|
26
30
|
import { createAuthError, AuthErrorCode } from '../../domain/errors/auth.errors'
|
|
31
|
+
import { getAuthConfig } from '../../domain/config/auth.config'
|
|
27
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Auth Adapter
|
|
35
|
+
* Implements IAuthRepository with Google & Apple OAuth support
|
|
36
|
+
*/
|
|
28
37
|
export class AuthAdapter implements IAuthRepository {
|
|
29
38
|
private get auth() {
|
|
30
39
|
return getFirebaseAuth()
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
private config = getAuthConfig()
|
|
43
|
+
|
|
44
|
+
// ==================== Authentication Methods ====================
|
|
34
45
|
|
|
35
46
|
async signIn(email: string, password: string): Promise<UserCredential> {
|
|
47
|
+
if (!this.config.isEmailPasswordEnabled()) {
|
|
48
|
+
throw createAuthError(
|
|
49
|
+
AuthErrorCode.UNKNOWN,
|
|
50
|
+
'Email/password authentication is disabled'
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
try {
|
|
37
55
|
return await signInWithEmailAndPassword(this.auth, email, password)
|
|
38
56
|
} catch (error) {
|
|
@@ -41,14 +59,23 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
async signUp(email: string, password: string, displayName: string): Promise<UserCredential> {
|
|
62
|
+
if (!this.config.isEmailPasswordEnabled()) {
|
|
63
|
+
throw createAuthError(
|
|
64
|
+
AuthErrorCode.UNKNOWN,
|
|
65
|
+
'Email/password authentication is disabled'
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
try {
|
|
45
70
|
const result = await createUserWithEmailAndPassword(this.auth, email, password)
|
|
46
71
|
|
|
47
72
|
// Update profile
|
|
48
73
|
await updateAuthProfile(result.user, { displayName })
|
|
49
74
|
|
|
50
|
-
// Send email verification
|
|
51
|
-
|
|
75
|
+
// Send email verification if required
|
|
76
|
+
if (this.config.getConfig().requireEmailVerification) {
|
|
77
|
+
await sendEmailVerification(result.user)
|
|
78
|
+
}
|
|
52
79
|
|
|
53
80
|
return result
|
|
54
81
|
} catch (error) {
|
|
@@ -56,13 +83,72 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Sign in with Google
|
|
88
|
+
* @param useRedirect - Whether to use redirect flow instead of popup (default: false)
|
|
89
|
+
*/
|
|
90
|
+
async signInWithGoogle(useRedirect = false): Promise<UserCredential> {
|
|
91
|
+
if (!this.config.isGoogleEnabled()) {
|
|
92
|
+
throw createAuthError(
|
|
93
|
+
AuthErrorCode.UNKNOWN,
|
|
94
|
+
'Google authentication is disabled'
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
60
98
|
try {
|
|
61
99
|
const provider = new GoogleAuthProvider()
|
|
62
|
-
|
|
63
|
-
|
|
100
|
+
const config = this.config.getConfig()
|
|
101
|
+
|
|
102
|
+
// Add scopes
|
|
103
|
+
if (config.googleScopes) {
|
|
104
|
+
config.googleScopes.forEach((scope) => provider.addScope(scope))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add custom parameters
|
|
108
|
+
if (config.googleCustomParameters) {
|
|
109
|
+
provider.setCustomParameters(config.googleCustomParameters)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (useRedirect) {
|
|
113
|
+
await signInWithRedirect(this.auth, provider)
|
|
114
|
+
const result = await getRedirectResult(this.auth)
|
|
115
|
+
if (!result) {
|
|
116
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'No redirect result found')
|
|
117
|
+
}
|
|
118
|
+
return result
|
|
119
|
+
} else {
|
|
120
|
+
return await signInWithPopup(this.auth, provider)
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw this.handleAuthError(error)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sign in with Apple
|
|
129
|
+
* @param useRedirect - Whether to use redirect flow instead of popup (default: false)
|
|
130
|
+
*/
|
|
131
|
+
async signInWithApple(useRedirect = false): Promise<UserCredential> {
|
|
132
|
+
if (!this.config.isAppleEnabled()) {
|
|
133
|
+
throw createAuthError(
|
|
134
|
+
AuthErrorCode.UNKNOWN,
|
|
135
|
+
'Apple authentication is disabled'
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const provider = new OAuthProvider('apple.com')
|
|
64
141
|
|
|
65
|
-
|
|
142
|
+
if (useRedirect) {
|
|
143
|
+
await signInWithRedirect(this.auth, provider)
|
|
144
|
+
const result = await getRedirectResult(this.auth)
|
|
145
|
+
if (!result) {
|
|
146
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'No redirect result found')
|
|
147
|
+
}
|
|
148
|
+
return result
|
|
149
|
+
} else {
|
|
150
|
+
return await signInWithPopup(this.auth, provider)
|
|
151
|
+
}
|
|
66
152
|
} catch (error) {
|
|
67
153
|
throw this.handleAuthError(error)
|
|
68
154
|
}
|
|
@@ -96,7 +182,7 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
96
182
|
}
|
|
97
183
|
}
|
|
98
184
|
|
|
99
|
-
// Profile Management
|
|
185
|
+
// ==================== Profile Management ====================
|
|
100
186
|
|
|
101
187
|
async updateProfile(
|
|
102
188
|
updates: Partial<Pick<User['profile'], 'displayName' | 'photoURL'>>
|
|
@@ -158,7 +244,77 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
158
244
|
}
|
|
159
245
|
}
|
|
160
246
|
|
|
161
|
-
//
|
|
247
|
+
// ==================== Provider Linking ====================
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Link Google to current user
|
|
251
|
+
*/
|
|
252
|
+
async linkGoogle(): Promise<UserCredential> {
|
|
253
|
+
if (!this.config.isGoogleEnabled()) {
|
|
254
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'Google authentication is disabled')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const user = this.auth.currentUser
|
|
259
|
+
if (!user) {
|
|
260
|
+
throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const provider = new GoogleAuthProvider()
|
|
264
|
+
const config = this.config.getConfig()
|
|
265
|
+
|
|
266
|
+
if (config.googleScopes) {
|
|
267
|
+
config.googleScopes.forEach((scope) => provider.addScope(scope))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (config.googleCustomParameters) {
|
|
271
|
+
provider.setCustomParameters(config.googleCustomParameters)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return await linkWithPopup(user, provider)
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw this.handleAuthError(error)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Link Apple to current user
|
|
282
|
+
*/
|
|
283
|
+
async linkApple(): Promise<UserCredential> {
|
|
284
|
+
if (!this.config.isAppleEnabled()) {
|
|
285
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'Apple authentication is disabled')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const user = this.auth.currentUser
|
|
290
|
+
if (!user) {
|
|
291
|
+
throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const provider = new OAuthProvider('apple.com')
|
|
295
|
+
return await linkWithPopup(user, provider)
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw this.handleAuthError(error)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Unlink a provider from current user
|
|
303
|
+
*/
|
|
304
|
+
async unlinkProvider(providerId: string): Promise<FirebaseUser> {
|
|
305
|
+
try {
|
|
306
|
+
const user = this.auth.currentUser
|
|
307
|
+
if (!user) {
|
|
308
|
+
throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return await firebaseUnlink(user, providerId)
|
|
312
|
+
} catch (error) {
|
|
313
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'Failed to unlink provider', error)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ==================== State Management ====================
|
|
162
318
|
|
|
163
319
|
getCurrentUser(): FirebaseUser | null {
|
|
164
320
|
return this.auth.currentUser
|
|
@@ -168,8 +324,37 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
168
324
|
return this.auth.onAuthStateChanged(callback)
|
|
169
325
|
}
|
|
170
326
|
|
|
171
|
-
//
|
|
172
|
-
|
|
327
|
+
// ==================== Token Management ====================
|
|
328
|
+
|
|
329
|
+
async getIdToken(forceRefresh = false): Promise<string> {
|
|
330
|
+
try {
|
|
331
|
+
const user = this.auth.currentUser
|
|
332
|
+
if (!user) {
|
|
333
|
+
throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return await user.getIdToken(forceRefresh)
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'Failed to get ID token', error)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async refreshToken(): Promise<void> {
|
|
343
|
+
try {
|
|
344
|
+
const user = this.auth.currentUser
|
|
345
|
+
if (!user) {
|
|
346
|
+
throw createAuthError(AuthErrorCode.UNAUTHENTICATED, 'No user logged in')
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
await user.getIdToken(true)
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw createAuthError(AuthErrorCode.UNKNOWN, 'Failed to refresh token', error)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ==================== Note ====================
|
|
356
|
+
// User document operations should be handled by UserAdapter
|
|
357
|
+
|
|
173
358
|
async createUserDocument(
|
|
174
359
|
_userId: string,
|
|
175
360
|
_data: Partial<Omit<User, 'profile'>> & { email: string; displayName: string }
|
|
@@ -181,6 +366,8 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
181
366
|
throw new Error('updateLastLogin should be handled by UserAdapter')
|
|
182
367
|
}
|
|
183
368
|
|
|
369
|
+
// ==================== Error Handling ====================
|
|
370
|
+
|
|
184
371
|
/**
|
|
185
372
|
* Handle Firebase Auth errors
|
|
186
373
|
*/
|
|
@@ -218,3 +405,6 @@ export class AuthAdapter implements IAuthRepository {
|
|
|
218
405
|
return createAuthError(AuthErrorCode.UNKNOWN, 'Unknown auth error', error)
|
|
219
406
|
}
|
|
220
407
|
}
|
|
408
|
+
|
|
409
|
+
// Export singleton instance
|
|
410
|
+
export const authAdapter = new AuthAdapter()
|
|
@@ -12,13 +12,13 @@ import { getAnalytics, Analytics } from 'firebase/analytics'
|
|
|
12
12
|
import { getFunctions, Functions } from 'firebase/functions'
|
|
13
13
|
|
|
14
14
|
const firebaseConfig = {
|
|
15
|
-
apiKey:
|
|
16
|
-
authDomain:
|
|
17
|
-
projectId:
|
|
18
|
-
storageBucket:
|
|
19
|
-
messagingSenderId:
|
|
20
|
-
appId:
|
|
21
|
-
measurementId:
|
|
15
|
+
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
|
|
16
|
+
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
|
|
17
|
+
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
|
|
18
|
+
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
|
|
19
|
+
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
|
|
20
|
+
appId: import.meta.env.VITE_FIREBASE_APP_ID,
|
|
21
|
+
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// Singleton instances
|