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.
@@ -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, MessagePayload } from "firebase/messaging";
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 FirebaseUnifiedService implements LoginService {
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
- constructor(client: P2PMarketplaceAPIClient, config?: FirebaseLoginServiceConfig) {
130
- this.client = client;
131
- this.actionCodeSettings = config?.actionCodeSettings ?? {
132
- url: 'http://localhost:3000/auth/verify-email',
133
- handleCodeInApp: true,
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
- this.initializeFirebase();
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
- if (!this.auth) {
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(this.auth, email, password);
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 authResult = this._createSuccessAuthResult(userCredential.user, backendUser, AuthProvider.EMAIL, idToken);
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: backendUser.username || '',
364
+ username: syncedBackendUser.username || '',
227
365
  email: userCredential.user.email || '',
228
- idToken: idToken,
229
- isAdmin: backendUser.admin || false
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
- if (!this.auth) {
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(this.auth, email, password);
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
- if (!this.auth) {
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(this.auth, provider);
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, user.displayName || '');
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, backendUser, AuthProvider.GOOGLE, idToken);
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
- if (!this.auth) {
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(this.auth, provider);
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, user.displayName || '');
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, backendUser, AuthProvider.FACEBOOK, idToken);
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
- if (!this.auth) {
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(this.auth, provider);
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, user.displayName || '');
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, backendUser, AuthProvider.TWITTER, idToken);
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
- if (!this.auth) {
494
+ const auth = await this.ensureAuthInstance();
495
+ if (!auth) {
349
496
  return false;
350
497
  }
351
498
 
352
- const user = this.auth.currentUser;
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
- if (!this.auth) {
513
+ const auth = await this.ensureAuthInstance();
514
+ if (!auth) {
367
515
  return false;
368
516
  }
369
517
 
370
- await sendPasswordResetEmail(this.auth, email);
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
- if (!this.auth) {
528
+ const auth = await this.ensureAuthInstance();
529
+ if (!auth) {
530
+ userContext.clearUser();
381
531
  return;
382
532
  }
383
533
 
384
- await signOut(this.auth);
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
- if (!this.auth) {
551
+ const auth = await this.ensureAuthInstance();
552
+ if (!auth) {
402
553
  return false;
403
554
  }
404
555
 
405
- await sendSignInLinkToEmail(this.auth, email, this.actionCodeSettings);
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
- if (!this.auth) {
575
+ const auth = await this.ensureAuthInstance();
576
+ if (!auth) {
425
577
  return AuthResult.failure("Firebase Auth not initialized");
426
578
  }
427
579
 
428
- if (!this.isSignInWithEmailLink()) {
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(this.auth, email, window.location.href);
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, backendUser, AuthProvider.EMAIL, idToken);
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, loginId: string): Promise<KioscoinUser | null> {
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 ${loginId}`,
642
+ Authorization: `Bearer ${authToken}`,
480
643
  });
481
644
  return response.users[0];
482
645
  } catch (error) {
@@ -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
- const { FirebaseUnifiedService } = require('../FirebaseLoginService');
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
  }
@@ -5,6 +5,7 @@ export interface User {
5
5
  email: string;
6
6
  idToken: string;
7
7
  isAdmin: boolean;
8
+ isEmailVerified: boolean;
8
9
  }
9
10
 
10
11
  // Define the UserContext interface
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 { FirebaseUnifiedService } from './auth/FirebaseLoginService';
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;