b5-api-client 0.0.26 → 0.0.28
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/P2PMarketplaceAPIClient.d.ts +11 -3
- package/dist/P2PMarketplaceAPIClient.js +79 -59
- package/dist/auth/AuthTokenProvider.d.ts +5 -0
- package/dist/auth/AuthTokenProvider.js +2 -0
- package/dist/auth/FirebaseLoginService.d.ts +11 -10
- package/dist/auth/FirebaseLoginService.js +192 -67
- package/dist/auth/LoginService.d.ts +2 -0
- package/dist/auth/LoginService.js +2 -2
- package/dist/auth/UserContext.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -2
- package/dist/types.d.ts +5 -1
- package/package.json +1 -1
- package/src/P2PMarketplaceAPIClient.ts +88 -58
- package/src/auth/AuthTokenProvider.ts +5 -0
- package/src/auth/FirebaseLoginService.ts +233 -70
- package/src/auth/LoginService.ts +4 -3
- package/src/auth/UserContext.ts +1 -0
- package/src/index.ts +1 -1
- package/src/types.ts +8 -1
|
@@ -14,15 +14,41 @@ import {
|
|
|
14
14
|
isSignInWithEmailLink,
|
|
15
15
|
sendSignInLinkToEmail,
|
|
16
16
|
signInWithEmailLink,
|
|
17
|
-
Auth
|
|
17
|
+
Auth,
|
|
18
|
+
applyActionCode
|
|
18
19
|
} from "firebase/auth";
|
|
19
|
-
import { getMessaging, getToken, onMessage
|
|
20
|
-
import { CreateUserRequest, KioscoinUser } from "../types";
|
|
20
|
+
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
|
21
|
+
import { CreateUserRequest, KioscoinUser, UpdateUserSettingsRequest } from "../types";
|
|
21
22
|
import P2PMarketplaceAPIClient from "../P2PMarketplaceAPIClient";
|
|
23
|
+
import { AuthTokenProvider } from "./AuthTokenProvider";
|
|
22
24
|
import { FirebaseLoginServiceConfig, LoginService } from "./LoginService";
|
|
23
25
|
import { userContext } from './UserContext';
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
const DEFAULT_EMAIL_VERIFICATION_PAGE =
|
|
29
|
+
process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_URL ??
|
|
30
|
+
"http://localhost:3000/auth/email-verified";
|
|
31
|
+
|
|
32
|
+
const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, "");
|
|
33
|
+
|
|
34
|
+
const appendQueryParams = (
|
|
35
|
+
base: string,
|
|
36
|
+
params: Record<string, string | undefined>
|
|
37
|
+
): string => {
|
|
38
|
+
const query = Object.entries(params)
|
|
39
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
40
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`)
|
|
41
|
+
.join("&");
|
|
42
|
+
|
|
43
|
+
if (!query) {
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const separator = base.includes("?") ? "&" : "?";
|
|
48
|
+
return `${base}${separator}${query}`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
26
52
|
// Auth provider type for better type safety
|
|
27
53
|
export enum AuthProvider {
|
|
28
54
|
EMAIL = 'EMAIL',
|
|
@@ -31,24 +57,6 @@ export enum AuthProvider {
|
|
|
31
57
|
TWITTER = 'TWITTER'
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
// Immutable auth state class
|
|
35
|
-
export class AuthState {
|
|
36
|
-
private constructor(
|
|
37
|
-
public readonly isAuthenticated: boolean,
|
|
38
|
-
public readonly user: UserData | null,
|
|
39
|
-
public readonly provider: AuthProvider | null
|
|
40
|
-
) { }
|
|
41
|
-
|
|
42
|
-
static authenticated(user: UserData, provider: AuthProvider): AuthState {
|
|
43
|
-
return new AuthState(true, user, provider);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static unauthenticated(): AuthState {
|
|
47
|
-
return new AuthState(false, null, null);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Enhanced UserData class with additional safety
|
|
52
60
|
export class UserData {
|
|
53
61
|
private constructor(
|
|
54
62
|
public readonly id: string,
|
|
@@ -119,23 +127,44 @@ export interface FirebaseConfig {
|
|
|
119
127
|
measurementId?: string;
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
export class
|
|
130
|
+
export class FirebaseService implements LoginService {
|
|
123
131
|
private app: ReturnType<typeof initializeApp> | null = null;
|
|
124
132
|
private messaging: ReturnType<typeof getMessaging> | null = null;
|
|
125
133
|
private auth: Auth | null = null;
|
|
126
134
|
private readonly actionCodeSettings: { url: string; handleCodeInApp: boolean };
|
|
135
|
+
private readonly emailVerificationUrl: string;
|
|
136
|
+
private readonly emailVerificationContinueUrl?: string;
|
|
127
137
|
private client: P2PMarketplaceAPIClient;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
private readonly initializationPromise: Promise<void>;
|
|
139
|
+
|
|
140
|
+
constructor(backendClient: P2PMarketplaceAPIClient, firebaseLoginConfig?: FirebaseLoginServiceConfig) {
|
|
141
|
+
this.client = backendClient;
|
|
142
|
+
const fallbackVerificationPage = firebaseLoginConfig?.emailVerificationUrl ?? DEFAULT_EMAIL_VERIFICATION_PAGE;
|
|
143
|
+
this.emailVerificationUrl = trimTrailingSlash(fallbackVerificationPage);
|
|
144
|
+
this.emailVerificationContinueUrl = firebaseLoginConfig?.emailVerificationContinueUrl;
|
|
145
|
+
const configuredActionSettings = firebaseLoginConfig?.actionCodeSettings;
|
|
146
|
+
const defaultActionUrl =
|
|
147
|
+
configuredActionSettings?.url ??
|
|
148
|
+
process.env.REACT_APP_EMAIL_LINK_URL ??
|
|
149
|
+
process.env.NEXT_PUBLIC_EMAIL_LINK_URL
|
|
150
|
+
|
|
151
|
+
this.actionCodeSettings = {
|
|
152
|
+
url: defaultActionUrl ?? "http://localhost:3000/auth/verify-email",
|
|
153
|
+
handleCodeInApp: configuredActionSettings?.handleCodeInApp ?? true,
|
|
134
154
|
};
|
|
135
|
-
|
|
155
|
+
|
|
156
|
+
// Set the auth token provider first (before initialization)
|
|
157
|
+
// It will gracefully return null until auth is ready
|
|
158
|
+
this.client.setAuthTokenProvider(this.buildAuthTokenProvider());
|
|
159
|
+
|
|
160
|
+
// Start initialization (this will fetch config, which needs to work without auth)
|
|
161
|
+
this.initializationPromise = this.initializeFirebase();
|
|
136
162
|
}
|
|
137
163
|
|
|
138
164
|
private async initializeFirebase() {
|
|
165
|
+
if (this.app) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
139
168
|
try {
|
|
140
169
|
const firebaseConfig = await this.fetchFirebaseConfig();
|
|
141
170
|
this.app = initializeApp(firebaseConfig);
|
|
@@ -145,6 +174,47 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
145
174
|
}
|
|
146
175
|
}
|
|
147
176
|
|
|
177
|
+
private async ensureAuthInstance(): Promise<Auth | null> {
|
|
178
|
+
try {
|
|
179
|
+
await this.initializationPromise;
|
|
180
|
+
} catch (_error) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return this.auth;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private buildAuthTokenProvider(): AuthTokenProvider {
|
|
188
|
+
return {
|
|
189
|
+
getToken: async () => {
|
|
190
|
+
// Check if auth is available without waiting for initialization
|
|
191
|
+
// This prevents circular dependency during initial config fetch
|
|
192
|
+
if (!this.auth) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const currentUser = this.auth.currentUser;
|
|
196
|
+
if (!currentUser) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return currentUser.getIdToken();
|
|
200
|
+
},
|
|
201
|
+
refreshToken: async () => {
|
|
202
|
+
// For token refresh, we need to ensure auth is initialized
|
|
203
|
+
const auth = await this.ensureAuthInstance();
|
|
204
|
+
const currentUser = auth?.currentUser;
|
|
205
|
+
if (!currentUser) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const refreshedToken = await currentUser.getIdToken(true);
|
|
209
|
+
userContext.updateUser({ idToken: refreshedToken });
|
|
210
|
+
return refreshedToken;
|
|
211
|
+
},
|
|
212
|
+
onRefreshFailure: (error) => {
|
|
213
|
+
console.error("Failed to refresh Firebase ID token:", error);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
148
218
|
private async fetchFirebaseConfig(): Promise<FirebaseConfig> {
|
|
149
219
|
try {
|
|
150
220
|
const response = await this.client.getConfig<{ firebase: FirebaseConfig }>();
|
|
@@ -155,6 +225,63 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
155
225
|
}
|
|
156
226
|
}
|
|
157
227
|
|
|
228
|
+
private buildEmailVerificationSettings(loginId: string) {
|
|
229
|
+
return {
|
|
230
|
+
url: this.buildEmailVerificationUrl(loginId),
|
|
231
|
+
handleCodeInApp: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private buildEmailVerificationUrl(loginId: string): string {
|
|
236
|
+
return appendQueryParams(this.emailVerificationUrl, {
|
|
237
|
+
loginId,
|
|
238
|
+
continueUrl: this.emailVerificationContinueUrl,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async ensureBackendVerification(
|
|
243
|
+
firebaseUser: User,
|
|
244
|
+
backendUser: KioscoinUser,
|
|
245
|
+
idToken: string
|
|
246
|
+
): Promise<KioscoinUser> {
|
|
247
|
+
const firebaseVerified = firebaseUser.emailVerified;
|
|
248
|
+
const backendVerified = backendUser.settings?.hasVerified ?? false;
|
|
249
|
+
|
|
250
|
+
if (!firebaseVerified || backendVerified) {
|
|
251
|
+
return backendUser;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!backendUser.id) {
|
|
255
|
+
console.warn("Missing backend user id when attempting to mark verification status.");
|
|
256
|
+
return backendUser;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const settingsUpdate: UpdateUserSettingsRequest["settings"] = {
|
|
260
|
+
paymentMethods: (backendUser.settings?.paymentMethods ?? []).map((method) => ({ ...method })),
|
|
261
|
+
hasVerified: true,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const preferredToken = backendUser.settings?.preferredTokenCode;
|
|
265
|
+
if (preferredToken !== undefined && preferredToken !== null) {
|
|
266
|
+
settingsUpdate.preferredTokenCode = preferredToken;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const request: UpdateUserSettingsRequest = {
|
|
270
|
+
userId: backendUser.id,
|
|
271
|
+
settings: settingsUpdate,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const response = await this.client.updateUserSettings(request, {
|
|
276
|
+
Authorization: `Bearer ${idToken}`,
|
|
277
|
+
});
|
|
278
|
+
return response.users?.[0] ?? backendUser;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error("Failed to update backend verification status:", error);
|
|
281
|
+
return backendUser;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
158
285
|
// Messaging methods
|
|
159
286
|
public async requestNotificationPermission(): Promise<string | null> {
|
|
160
287
|
if (!this.app) {
|
|
@@ -210,23 +337,35 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
210
337
|
// Authentication methods
|
|
211
338
|
async signInWithEmail(email: string, password: string): Promise<AuthResult> {
|
|
212
339
|
try {
|
|
213
|
-
|
|
340
|
+
const auth = await this.ensureAuthInstance();
|
|
341
|
+
if (!auth) {
|
|
214
342
|
console.error("Firebase Auth not initialized");
|
|
215
343
|
return AuthResult.failure("Firebase Auth not initialized");
|
|
216
344
|
}
|
|
217
|
-
const userCredential = await signInWithEmailAndPassword(
|
|
345
|
+
const userCredential = await signInWithEmailAndPassword(auth, email, password);
|
|
218
346
|
const idToken = await userCredential.user.getIdToken();
|
|
219
347
|
const backendUser = await this.getUserFromBackend(userCredential.user.uid, idToken);
|
|
220
348
|
|
|
221
349
|
if (backendUser) {
|
|
222
|
-
const
|
|
350
|
+
const syncedBackendUser = await this.ensureBackendVerification(
|
|
351
|
+
userCredential.user,
|
|
352
|
+
backendUser,
|
|
353
|
+
idToken
|
|
354
|
+
);
|
|
355
|
+
const authResult = this._createSuccessAuthResult(
|
|
356
|
+
userCredential.user,
|
|
357
|
+
syncedBackendUser,
|
|
358
|
+
AuthProvider.EMAIL,
|
|
359
|
+
idToken
|
|
360
|
+
);
|
|
223
361
|
// Update UserContext
|
|
224
362
|
userContext.setUser({
|
|
225
363
|
id: userCredential.user.uid,
|
|
226
|
-
username:
|
|
364
|
+
username: syncedBackendUser.username || '',
|
|
227
365
|
email: userCredential.user.email || '',
|
|
228
|
-
idToken
|
|
229
|
-
isAdmin:
|
|
366
|
+
idToken,
|
|
367
|
+
isAdmin: syncedBackendUser.admin || false,
|
|
368
|
+
isEmailVerified: userCredential.user.emailVerified
|
|
230
369
|
});
|
|
231
370
|
return authResult;
|
|
232
371
|
} else {
|
|
@@ -244,16 +383,17 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
244
383
|
|
|
245
384
|
async createUserWithEmail(email: string, password: string, username: string): Promise<AuthResult> {
|
|
246
385
|
try {
|
|
247
|
-
|
|
386
|
+
const auth = await this.ensureAuthInstance();
|
|
387
|
+
if (!auth) {
|
|
248
388
|
return AuthResult.failure("Firebase Auth not initialized");
|
|
249
389
|
}
|
|
250
390
|
|
|
251
391
|
// First create the user in Firebase
|
|
252
|
-
const userCredential = await createUserWithEmailAndPassword(
|
|
392
|
+
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
|
|
253
393
|
const user = userCredential.user;
|
|
254
394
|
const idToken = await user.getIdToken();
|
|
255
395
|
// Send email verification
|
|
256
|
-
await sendEmailVerification(user);
|
|
396
|
+
await sendEmailVerification(user, this.buildEmailVerificationSettings(user.uid));
|
|
257
397
|
|
|
258
398
|
const backendUser = await this.createUserInBackend(user.uid, username, idToken);
|
|
259
399
|
|
|
@@ -270,20 +410,22 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
270
410
|
|
|
271
411
|
async signInWithGoogle(): Promise<AuthResult> {
|
|
272
412
|
try {
|
|
273
|
-
|
|
413
|
+
const auth = await this.ensureAuthInstance();
|
|
414
|
+
if (!auth) {
|
|
274
415
|
return AuthResult.failure("Firebase Auth not initialized");
|
|
275
416
|
}
|
|
276
417
|
|
|
277
418
|
const provider = new GoogleAuthProvider();
|
|
278
|
-
const userCredential = await signInWithPopup(
|
|
419
|
+
const userCredential = await signInWithPopup(auth, provider);
|
|
279
420
|
const user = userCredential.user;
|
|
421
|
+
const idToken = await user.getIdToken();
|
|
280
422
|
|
|
281
423
|
// Get or create user in your Kotlin backend
|
|
282
|
-
const backendUser = await this.getUserFromBackend(user.uid,
|
|
283
|
-
const idToken = await user.getIdToken();
|
|
424
|
+
const backendUser = await this.getUserFromBackend(user.uid, idToken);
|
|
284
425
|
if (backendUser) {
|
|
426
|
+
const syncedBackendUser = await this.ensureBackendVerification(user, backendUser, idToken);
|
|
285
427
|
// Use the new helper method
|
|
286
|
-
return this._createSuccessAuthResult(user,
|
|
428
|
+
return this._createSuccessAuthResult(user, syncedBackendUser, AuthProvider.GOOGLE, idToken);
|
|
287
429
|
} else {
|
|
288
430
|
return AuthResult.failure('Failed to get or create user in backend');
|
|
289
431
|
}
|
|
@@ -295,20 +437,22 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
295
437
|
|
|
296
438
|
async signInWithFacebook(): Promise<AuthResult> {
|
|
297
439
|
try {
|
|
298
|
-
|
|
440
|
+
const auth = await this.ensureAuthInstance();
|
|
441
|
+
if (!auth) {
|
|
299
442
|
return AuthResult.failure("Firebase Auth not initialized");
|
|
300
443
|
}
|
|
301
444
|
|
|
302
445
|
const provider = new FacebookAuthProvider();
|
|
303
|
-
const userCredential = await signInWithPopup(
|
|
446
|
+
const userCredential = await signInWithPopup(auth, provider);
|
|
304
447
|
const user = userCredential.user;
|
|
448
|
+
const idToken = await user.getIdToken();
|
|
305
449
|
|
|
306
450
|
// Get or create user in your Kotlin backend
|
|
307
|
-
const backendUser = await this.getUserFromBackend(user.uid,
|
|
308
|
-
const idToken = await user.getIdToken();
|
|
451
|
+
const backendUser = await this.getUserFromBackend(user.uid, idToken);
|
|
309
452
|
if (backendUser) {
|
|
453
|
+
const syncedBackendUser = await this.ensureBackendVerification(user, backendUser, idToken);
|
|
310
454
|
// Use the new helper method
|
|
311
|
-
return this._createSuccessAuthResult(user,
|
|
455
|
+
return this._createSuccessAuthResult(user, syncedBackendUser, AuthProvider.FACEBOOK, idToken);
|
|
312
456
|
} else {
|
|
313
457
|
return AuthResult.failure('Failed to get or create user in backend');
|
|
314
458
|
}
|
|
@@ -320,20 +464,22 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
320
464
|
|
|
321
465
|
async signInWithTwitter(): Promise<AuthResult> {
|
|
322
466
|
try {
|
|
323
|
-
|
|
467
|
+
const auth = await this.ensureAuthInstance();
|
|
468
|
+
if (!auth) {
|
|
324
469
|
return AuthResult.failure("Firebase Auth not initialized");
|
|
325
470
|
}
|
|
326
471
|
|
|
327
472
|
const provider = new TwitterAuthProvider();
|
|
328
|
-
const userCredential = await signInWithPopup(
|
|
473
|
+
const userCredential = await signInWithPopup(auth, provider);
|
|
329
474
|
const user = userCredential.user;
|
|
475
|
+
const idToken = await user.getIdToken();
|
|
330
476
|
|
|
331
477
|
// Get or create user in your Kotlin backend
|
|
332
|
-
const backendUser = await this.getUserFromBackend(user.uid,
|
|
333
|
-
const idToken = await user.getIdToken();
|
|
478
|
+
const backendUser = await this.getUserFromBackend(user.uid, idToken);
|
|
334
479
|
if (backendUser) {
|
|
480
|
+
const syncedBackendUser = await this.ensureBackendVerification(user, backendUser, idToken);
|
|
335
481
|
// Use the new helper method
|
|
336
|
-
return this._createSuccessAuthResult(user,
|
|
482
|
+
return this._createSuccessAuthResult(user, syncedBackendUser, AuthProvider.TWITTER, idToken);
|
|
337
483
|
} else {
|
|
338
484
|
return AuthResult.failure('Failed to get or create user in backend');
|
|
339
485
|
}
|
|
@@ -345,13 +491,14 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
345
491
|
|
|
346
492
|
async sendEmailVerification(): Promise<boolean> {
|
|
347
493
|
try {
|
|
348
|
-
|
|
494
|
+
const auth = await this.ensureAuthInstance();
|
|
495
|
+
if (!auth) {
|
|
349
496
|
return false;
|
|
350
497
|
}
|
|
351
498
|
|
|
352
|
-
const user =
|
|
499
|
+
const user = auth.currentUser;
|
|
353
500
|
if (user) {
|
|
354
|
-
await sendEmailVerification(user);
|
|
501
|
+
await sendEmailVerification(user, this.buildEmailVerificationSettings(user.uid));
|
|
355
502
|
return true;
|
|
356
503
|
}
|
|
357
504
|
return false;
|
|
@@ -363,11 +510,12 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
363
510
|
|
|
364
511
|
async sendPasswordResetEmail(email: string): Promise<boolean> {
|
|
365
512
|
try {
|
|
366
|
-
|
|
513
|
+
const auth = await this.ensureAuthInstance();
|
|
514
|
+
if (!auth) {
|
|
367
515
|
return false;
|
|
368
516
|
}
|
|
369
517
|
|
|
370
|
-
await sendPasswordResetEmail(
|
|
518
|
+
await sendPasswordResetEmail(auth, email);
|
|
371
519
|
return true;
|
|
372
520
|
} catch (error) {
|
|
373
521
|
console.error('Error sending password reset email:', error);
|
|
@@ -377,11 +525,13 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
377
525
|
|
|
378
526
|
async signOut(): Promise<void> {
|
|
379
527
|
try {
|
|
380
|
-
|
|
528
|
+
const auth = await this.ensureAuthInstance();
|
|
529
|
+
if (!auth) {
|
|
530
|
+
userContext.clearUser();
|
|
381
531
|
return;
|
|
382
532
|
}
|
|
383
533
|
|
|
384
|
-
await signOut(
|
|
534
|
+
await signOut(auth);
|
|
385
535
|
// Clear UserContext
|
|
386
536
|
userContext.clearUser();
|
|
387
537
|
} catch (error) {
|
|
@@ -398,11 +548,12 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
398
548
|
|
|
399
549
|
async sendSignInLinkToEmail(email: string): Promise<boolean> {
|
|
400
550
|
try {
|
|
401
|
-
|
|
551
|
+
const auth = await this.ensureAuthInstance();
|
|
552
|
+
if (!auth) {
|
|
402
553
|
return false;
|
|
403
554
|
}
|
|
404
555
|
|
|
405
|
-
await sendSignInLinkToEmail(
|
|
556
|
+
await sendSignInLinkToEmail(auth, email, this.actionCodeSettings);
|
|
406
557
|
// Save the email locally to use it later when the user clicks the link
|
|
407
558
|
window.localStorage.setItem('emailForSignIn', email);
|
|
408
559
|
return true;
|
|
@@ -421,26 +572,29 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
421
572
|
|
|
422
573
|
async signInWithEmailLink(email: string): Promise<AuthResult> {
|
|
423
574
|
try {
|
|
424
|
-
|
|
575
|
+
const auth = await this.ensureAuthInstance();
|
|
576
|
+
if (!auth) {
|
|
425
577
|
return AuthResult.failure("Firebase Auth not initialized");
|
|
426
578
|
}
|
|
427
579
|
|
|
428
|
-
if (!
|
|
580
|
+
if (!isSignInWithEmailLink(auth, window.location.href)) {
|
|
429
581
|
return AuthResult.failure("Invalid sign-in link");
|
|
430
582
|
}
|
|
431
583
|
|
|
432
|
-
const userCredential = await signInWithEmailLink(
|
|
584
|
+
const userCredential = await signInWithEmailLink(auth, email, window.location.href);
|
|
433
585
|
const user = userCredential.user;
|
|
434
586
|
|
|
435
587
|
// Clear the email from localStorage
|
|
436
588
|
window.localStorage.removeItem('emailForSignIn');
|
|
437
589
|
|
|
438
|
-
// Get or create user in your Kotlin backend
|
|
439
|
-
const backendUser = await this.getUserFromBackend(user.uid, user.displayName || '');
|
|
440
590
|
const idToken = await user.getIdToken();
|
|
591
|
+
|
|
592
|
+
// Get or create user in your Kotlin backend
|
|
593
|
+
const backendUser = await this.getUserFromBackend(user.uid, idToken);
|
|
441
594
|
if (backendUser) {
|
|
595
|
+
const syncedBackendUser = await this.ensureBackendVerification(user, backendUser, idToken);
|
|
442
596
|
// Use the new helper method
|
|
443
|
-
return this._createSuccessAuthResult(user,
|
|
597
|
+
return this._createSuccessAuthResult(user, syncedBackendUser, AuthProvider.EMAIL, idToken);
|
|
444
598
|
} else {
|
|
445
599
|
return AuthResult.failure('Failed to get or create user in backend');
|
|
446
600
|
}
|
|
@@ -454,6 +608,15 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
454
608
|
return window.localStorage.getItem('emailForSignIn');
|
|
455
609
|
}
|
|
456
610
|
|
|
611
|
+
async applyEmailVerificationCode(oobCode: string): Promise<void> {
|
|
612
|
+
const auth = await this.ensureAuthInstance();
|
|
613
|
+
if (!auth) {
|
|
614
|
+
throw new Error("Firebase Auth not initialized");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
await applyActionCode(auth, oobCode);
|
|
618
|
+
}
|
|
619
|
+
|
|
457
620
|
// Helper method to create success AuthResult
|
|
458
621
|
private _createSuccessAuthResult(
|
|
459
622
|
user: User,
|
|
@@ -473,10 +636,10 @@ export class FirebaseUnifiedService implements LoginService {
|
|
|
473
636
|
}
|
|
474
637
|
|
|
475
638
|
// Backend integration methods
|
|
476
|
-
private async getUserFromBackend(userId: string,
|
|
639
|
+
private async getUserFromBackend(userId: string, authToken: string): Promise<KioscoinUser | null> {
|
|
477
640
|
try {
|
|
478
641
|
const response = await this.client.getUser(userId, {
|
|
479
|
-
Authorization: `Bearer ${
|
|
642
|
+
Authorization: `Bearer ${authToken}`,
|
|
480
643
|
});
|
|
481
644
|
return response.users[0];
|
|
482
645
|
} catch (error) {
|
package/src/auth/LoginService.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import P2PMarketplaceAPIClient from "../P2PMarketplaceAPIClient";
|
|
2
|
-
import { AuthResult } from "./FirebaseLoginService";
|
|
2
|
+
import { AuthResult, FirebaseService } from "./FirebaseLoginService";
|
|
3
3
|
|
|
4
4
|
export type LoginProvider = 'firebase' | 'custom';
|
|
5
5
|
|
|
@@ -21,13 +21,14 @@ export interface FirebaseLoginServiceConfig {
|
|
|
21
21
|
url: string;
|
|
22
22
|
handleCodeInApp: boolean;
|
|
23
23
|
};
|
|
24
|
+
emailVerificationUrl?: string;
|
|
25
|
+
emailVerificationContinueUrl?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export function createLoginService(config: LoginServiceConfig): LoginService {
|
|
27
29
|
const { provider, apiClient } = config;
|
|
28
30
|
if (provider === 'firebase') {
|
|
29
|
-
|
|
30
|
-
return new FirebaseUnifiedService(apiClient, config.firebase);
|
|
31
|
+
return new FirebaseService(apiClient, config.firebase);
|
|
31
32
|
}
|
|
32
33
|
throw new Error(`Unsupported login provider: ${provider}`);
|
|
33
34
|
}
|
package/src/auth/UserContext.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as P2PMarketplaceAPIClient } from './P2PMarketplaceAPIClient';
|
|
2
2
|
export { generateSecretAndHash } from './cryptoUtils'
|
|
3
3
|
export * from './types';
|
|
4
|
-
export {
|
|
4
|
+
export { FirebaseService, AuthProvider, UserData, AuthResult } from './auth/FirebaseLoginService';
|
|
5
5
|
export { createLoginService } from './auth/LoginService';
|
|
6
6
|
export { userContext } from './auth/UserContext';
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AxiosRequestConfig } from "axios";
|
|
2
|
+
|
|
1
3
|
export type OrderType = 'SELL' | 'BUY';
|
|
2
4
|
|
|
3
5
|
export type OrderStatus =
|
|
@@ -76,8 +78,9 @@ export interface UsersResponse {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
export interface UserSettings {
|
|
79
|
-
preferredTokenCode?: string;
|
|
81
|
+
preferredTokenCode?: string | null;
|
|
80
82
|
paymentMethods: PaymentMethod[];
|
|
83
|
+
hasVerified?: boolean;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
export interface UpdateUserSettingsRequest {
|
|
@@ -85,6 +88,10 @@ export interface UpdateUserSettingsRequest {
|
|
|
85
88
|
settings: UserSettings;
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
export interface VerifyEmailRequest {
|
|
92
|
+
loginId: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
88
95
|
export interface PaymentMethod {
|
|
89
96
|
type: string;
|
|
90
97
|
alias?: string;
|