@blackcode_sa/metaestetics-api 1.14.18 → 1.14.26

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.
@@ -483,6 +483,34 @@ export class PractitionerService extends BaseService {
483
483
  throw new Error("Practitioner is not associated with this clinic");
484
484
  }
485
485
 
486
+ // Security check: Verify that the clinic belongs to the clinic group of the user creating the token
487
+ // createdBy can be either clinicGroupId or clinicId
488
+ let expectedClinicGroupId: string | null = null;
489
+
490
+ // First, check if createdBy matches the clinic's clinicGroupId directly
491
+ if (clinic.clinicGroupId === createdBy) {
492
+ // createdBy is the clinicGroupId, which matches - this is valid
493
+ expectedClinicGroupId = createdBy;
494
+ } else {
495
+ // createdBy might be a clinicId, check if that clinic belongs to the same group
496
+ try {
497
+ const creatorClinic = await this.getClinicService().getClinic(createdBy);
498
+ if (creatorClinic && creatorClinic.clinicGroupId === clinic.clinicGroupId) {
499
+ // Both clinics belong to the same group - valid
500
+ expectedClinicGroupId = clinic.clinicGroupId;
501
+ } else {
502
+ throw new Error("Clinic does not belong to your clinic group");
503
+ }
504
+ } catch (error: any) {
505
+ // If createdBy is not a valid clinicId, or clinics don't match, reject
506
+ if (error.message === "Clinic does not belong to your clinic group") {
507
+ throw error;
508
+ }
509
+ // If getClinic fails, createdBy might be a clinicGroupId that doesn't match
510
+ throw new Error("Clinic does not belong to your clinic group");
511
+ }
512
+ }
513
+
486
514
  // Default expiration is 7 days from now if not specified
487
515
  const expiration =
488
516
  validatedData.expiresAt ||
@@ -522,21 +550,29 @@ export class PractitionerService extends BaseService {
522
550
  /**
523
551
  * Gets active tokens for a practitioner
524
552
  * @param practitionerId ID of the practitioner
553
+ * @param clinicId Optional clinic ID to filter tokens by. If provided, only returns tokens for this clinic.
525
554
  * @returns Array of active tokens
526
555
  */
527
556
  async getPractitionerActiveTokens(
528
- practitionerId: string
557
+ practitionerId: string,
558
+ clinicId?: string
529
559
  ): Promise<PractitionerToken[]> {
530
560
  const tokensRef = collection(
531
561
  this.db,
532
562
  `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
533
563
  );
534
564
 
535
- const q = query(
536
- tokensRef,
565
+ const conditions = [
537
566
  where("status", "==", PractitionerTokenStatus.ACTIVE),
538
567
  where("expiresAt", ">", Timestamp.now())
539
- );
568
+ ];
569
+
570
+ // Filter by clinic if provided
571
+ if (clinicId) {
572
+ conditions.push(where("clinicId", "==", clinicId));
573
+ }
574
+
575
+ const q = query(tokensRef, ...conditions);
540
576
 
541
577
  const querySnapshot = await getDocs(q);
542
578
  return querySnapshot.docs.map((doc) => doc.data() as PractitionerToken);
@@ -628,6 +664,45 @@ export class PractitionerService extends BaseService {
628
664
  });
629
665
  }
630
666
 
667
+ /**
668
+ * Revokes a token by setting its status to REVOKED
669
+ * @param tokenId ID of the token
670
+ * @param practitionerId ID of the practitioner
671
+ * @param clinicId ID of the clinic that owns the token. Used to verify ownership before revoking.
672
+ * @throws Error if token doesn't exist or doesn't belong to the specified clinic
673
+ */
674
+ async revokeToken(
675
+ tokenId: string,
676
+ practitionerId: string,
677
+ clinicId: string
678
+ ): Promise<void> {
679
+ const tokenRef = doc(
680
+ this.db,
681
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
682
+ );
683
+
684
+ // First, verify the token exists and belongs to the clinic
685
+ const tokenDoc = await getDoc(tokenRef);
686
+ if (!tokenDoc.exists()) {
687
+ throw new Error("Token not found");
688
+ }
689
+
690
+ const tokenData = tokenDoc.data() as PractitionerToken;
691
+ if (tokenData.clinicId !== clinicId) {
692
+ throw new Error("Token does not belong to the specified clinic");
693
+ }
694
+
695
+ // Only revoke if token is still active
696
+ if (tokenData.status !== PractitionerTokenStatus.ACTIVE) {
697
+ throw new Error("Token is not active and cannot be revoked");
698
+ }
699
+
700
+ await updateDoc(tokenRef, {
701
+ status: PractitionerTokenStatus.REVOKED,
702
+ updatedAt: serverTimestamp(),
703
+ });
704
+ }
705
+
631
706
  /**
632
707
  * Dohvata zdravstvenog radnika po ID-u
633
708
  */
@@ -45,18 +45,6 @@ export class UserService extends BaseService {
45
45
  ) {
46
46
  super(db, auth, app);
47
47
 
48
- // DEBUG: Tag the auth instance
49
- // @ts-ignore
50
- if (!this.auth.__userServiceId) {
51
- // @ts-ignore
52
- this.auth.__userServiceId = 'user-service-' + Date.now();
53
- }
54
- // @ts-ignore
55
- console.log('[USER_SERVICE] Constructor - auth ID:', this.auth.__userServiceId);
56
- // @ts-ignore
57
- console.log('[USER_SERVICE] Constructor - auth.__authServiceId:', this.auth.__authServiceId || 'NOT SET');
58
-
59
- // Kreiramo servise samo ako nisu prosleđeni
60
48
  if (!patientService) {
61
49
  patientService = new PatientService(db, auth, app);
62
50
  }
@@ -100,27 +88,6 @@ export class UserService extends BaseService {
100
88
  skipProfileCreation?: boolean;
101
89
  },
102
90
  ): Promise<User> {
103
- // DEBUG LOGGING - Check auth state before creating user document
104
- console.log('[USER_SERVICE] ====== CREATE USER DEBUG ======');
105
- // @ts-ignore - Debug: Check auth instance ID
106
- console.log(this.auth)
107
- console.log('[USER_SERVICE] Auth instance ID:', (this.auth as any)?.__debugId || 'no-id');
108
- console.log('[USER_SERVICE] Current auth state:', {
109
- currentUser: this.auth?.currentUser?.uid || 'NULL',
110
- currentUserEmail: this.auth?.currentUser?.email || 'NULL',
111
- currentUserProvider: this.auth?.currentUser?.providerId || 'NULL',
112
- });
113
- console.log('[USER_SERVICE] Firebase user passed to createUser:', {
114
- uid: firebaseUser?.uid || 'NULL',
115
- email: firebaseUser?.email || 'NULL',
116
- providerId: firebaseUser?.providerId || 'NULL',
117
- isAnonymous: firebaseUser?.isAnonymous,
118
- });
119
- console.log('[USER_SERVICE] Auth instances match:', this.auth?.currentUser?.uid === firebaseUser?.uid);
120
- console.log('[USER_SERVICE] Document path:', `${USERS_COLLECTION}/${firebaseUser?.uid}`);
121
- console.log('[USER_SERVICE] Roles:', roles);
122
- console.log('[USER_SERVICE] ================================');
123
-
124
91
  const userData: CreateUserData = {
125
92
  uid: firebaseUser.uid,
126
93
  email: firebaseUser.email,
@@ -131,24 +98,7 @@ export class UserService extends BaseService {
131
98
  lastLoginAt: serverTimestamp(),
132
99
  };
133
100
 
134
- console.log('[USER_SERVICE] Attempting setDoc with userData:', {
135
- uid: userData.uid,
136
- email: userData.email,
137
- roles: userData.roles,
138
- });
139
-
140
- // Kreiramo osnovnog korisnika
141
- try {
142
- await setDoc(doc(this.db, USERS_COLLECTION, userData.uid), userData);
143
- console.log('[USER_SERVICE] ✅ setDoc SUCCEEDED for:', userData.uid);
144
- } catch (error: any) {
145
- console.error('[USER_SERVICE] ❌ setDoc FAILED:', {
146
- errorCode: error?.code,
147
- errorMessage: error?.message,
148
- uid: userData.uid,
149
- });
150
- throw error;
151
- }
101
+ await setDoc(doc(this.db, USERS_COLLECTION, userData.uid), userData);
152
102
 
153
103
  // Kreiramo odgovarajuće profile na osnovu rola
154
104
  if (options?.skipProfileCreation) {