@progalaxyelabs/ngx-stonescriptphp-client 1.4.0 → 1.5.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.
|
@@ -166,10 +166,23 @@ class MyEnvironmentModel {
|
|
|
166
166
|
*/
|
|
167
167
|
platformCode = '';
|
|
168
168
|
/**
|
|
169
|
-
* Accounts platform URL for centralized authentication
|
|
169
|
+
* Accounts platform URL for centralized authentication (single-server mode)
|
|
170
170
|
* @example 'https://accounts.progalaxyelabs.com'
|
|
171
|
+
* @deprecated Use authServers for multi-server support
|
|
171
172
|
*/
|
|
172
173
|
accountsUrl = '';
|
|
174
|
+
/**
|
|
175
|
+
* Multiple authentication servers configuration
|
|
176
|
+
* Enables platforms to authenticate against different identity providers
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* authServers: {
|
|
180
|
+
* customer: { url: 'https://auth.progalaxyelabs.com', default: true },
|
|
181
|
+
* employee: { url: 'https://admin-auth.progalaxyelabs.com' }
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
authServers;
|
|
173
186
|
firebase = {
|
|
174
187
|
projectId: '',
|
|
175
188
|
appId: '',
|
|
@@ -583,15 +596,157 @@ class AuthService {
|
|
|
583
596
|
signinStatus;
|
|
584
597
|
environment;
|
|
585
598
|
USER_STORAGE_KEY = 'progalaxyapi_user';
|
|
599
|
+
ACTIVE_AUTH_SERVER_KEY = 'progalaxyapi_active_auth_server';
|
|
586
600
|
// Observable user state
|
|
587
601
|
userSubject = new BehaviorSubject(null);
|
|
588
602
|
user$ = this.userSubject.asObservable();
|
|
603
|
+
// Current active auth server name (for multi-server mode)
|
|
604
|
+
activeAuthServer = null;
|
|
589
605
|
constructor(tokens, signinStatus, environment) {
|
|
590
606
|
this.tokens = tokens;
|
|
591
607
|
this.signinStatus = signinStatus;
|
|
592
608
|
this.environment = environment;
|
|
593
609
|
// Restore user from localStorage on initialization
|
|
594
610
|
this.restoreUser();
|
|
611
|
+
// Restore active auth server
|
|
612
|
+
this.restoreActiveAuthServer();
|
|
613
|
+
}
|
|
614
|
+
// ===== Multi-Server Support Methods =====
|
|
615
|
+
/**
|
|
616
|
+
* Get the current accounts URL based on configuration
|
|
617
|
+
* Supports both single-server (accountsUrl) and multi-server (authServers) modes
|
|
618
|
+
* @param serverName - Optional server name for multi-server mode
|
|
619
|
+
*/
|
|
620
|
+
getAccountsUrl(serverName) {
|
|
621
|
+
// Multi-server mode
|
|
622
|
+
if (this.environment.authServers && Object.keys(this.environment.authServers).length > 0) {
|
|
623
|
+
const targetServer = serverName || this.activeAuthServer || this.getDefaultAuthServer();
|
|
624
|
+
if (!targetServer) {
|
|
625
|
+
throw new Error('No auth server specified and no default server configured');
|
|
626
|
+
}
|
|
627
|
+
const serverConfig = this.environment.authServers[targetServer];
|
|
628
|
+
if (!serverConfig) {
|
|
629
|
+
throw new Error(`Auth server '${targetServer}' not found in configuration`);
|
|
630
|
+
}
|
|
631
|
+
return serverConfig.url;
|
|
632
|
+
}
|
|
633
|
+
// Single-server mode (backward compatibility)
|
|
634
|
+
if (this.environment.accountsUrl) {
|
|
635
|
+
return this.environment.accountsUrl;
|
|
636
|
+
}
|
|
637
|
+
throw new Error('No authentication server configured. Set either accountsUrl or authServers in environment config.');
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get the default auth server name
|
|
641
|
+
*/
|
|
642
|
+
getDefaultAuthServer() {
|
|
643
|
+
if (!this.environment.authServers) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
// Find server marked as default
|
|
647
|
+
for (const [name, config] of Object.entries(this.environment.authServers)) {
|
|
648
|
+
if (config.default) {
|
|
649
|
+
return name;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// If no default is marked, use the first server
|
|
653
|
+
const firstServer = Object.keys(this.environment.authServers)[0];
|
|
654
|
+
return firstServer || null;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Restore active auth server from localStorage
|
|
658
|
+
*/
|
|
659
|
+
restoreActiveAuthServer() {
|
|
660
|
+
try {
|
|
661
|
+
const savedServer = localStorage.getItem(this.ACTIVE_AUTH_SERVER_KEY);
|
|
662
|
+
if (savedServer && this.environment.authServers?.[savedServer]) {
|
|
663
|
+
this.activeAuthServer = savedServer;
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
// Set to default if saved server is invalid
|
|
667
|
+
this.activeAuthServer = this.getDefaultAuthServer();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
console.error('Failed to restore active auth server:', error);
|
|
672
|
+
this.activeAuthServer = this.getDefaultAuthServer();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Save active auth server to localStorage
|
|
677
|
+
*/
|
|
678
|
+
saveActiveAuthServer(serverName) {
|
|
679
|
+
try {
|
|
680
|
+
localStorage.setItem(this.ACTIVE_AUTH_SERVER_KEY, serverName);
|
|
681
|
+
this.activeAuthServer = serverName;
|
|
682
|
+
}
|
|
683
|
+
catch (error) {
|
|
684
|
+
console.error('Failed to save active auth server:', error);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Get available auth servers
|
|
689
|
+
* @returns Array of server names or empty array if using single-server mode
|
|
690
|
+
*/
|
|
691
|
+
getAvailableAuthServers() {
|
|
692
|
+
if (!this.environment.authServers) {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
return Object.keys(this.environment.authServers);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Get current active auth server name
|
|
699
|
+
* @returns Server name or null if using single-server mode
|
|
700
|
+
*/
|
|
701
|
+
getActiveAuthServer() {
|
|
702
|
+
return this.activeAuthServer;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Switch to a different auth server
|
|
706
|
+
* @param serverName - Name of the server to switch to
|
|
707
|
+
* @throws Error if server not found in configuration
|
|
708
|
+
*/
|
|
709
|
+
switchAuthServer(serverName) {
|
|
710
|
+
if (!this.environment.authServers) {
|
|
711
|
+
throw new Error('Multi-server mode not configured. Use authServers in environment config.');
|
|
712
|
+
}
|
|
713
|
+
if (!this.environment.authServers[serverName]) {
|
|
714
|
+
throw new Error(`Auth server '${serverName}' not found in configuration`);
|
|
715
|
+
}
|
|
716
|
+
this.saveActiveAuthServer(serverName);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Get auth server configuration
|
|
720
|
+
* @param serverName - Optional server name (uses active server if not specified)
|
|
721
|
+
*/
|
|
722
|
+
getAuthServerConfig(serverName) {
|
|
723
|
+
if (!this.environment.authServers) {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
const targetServer = serverName || this.activeAuthServer || this.getDefaultAuthServer();
|
|
727
|
+
if (!targetServer) {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
return this.environment.authServers[targetServer] || null;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Check if multi-server mode is enabled
|
|
734
|
+
*/
|
|
735
|
+
isMultiServerMode() {
|
|
736
|
+
return !!(this.environment.authServers && Object.keys(this.environment.authServers).length > 0);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Hash UUID to numeric ID for backward compatibility
|
|
740
|
+
* Converts UUID string to a consistent numeric ID for legacy code
|
|
741
|
+
*/
|
|
742
|
+
hashUUID(uuid) {
|
|
743
|
+
let hash = 0;
|
|
744
|
+
for (let i = 0; i < uuid.length; i++) {
|
|
745
|
+
const char = uuid.charCodeAt(i);
|
|
746
|
+
hash = ((hash << 5) - hash) + char;
|
|
747
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
748
|
+
}
|
|
749
|
+
return Math.abs(hash);
|
|
595
750
|
}
|
|
596
751
|
/**
|
|
597
752
|
* Restore user from localStorage
|
|
@@ -628,15 +783,19 @@ class AuthService {
|
|
|
628
783
|
* Update user subject and persist to localStorage
|
|
629
784
|
*/
|
|
630
785
|
updateUser(user) {
|
|
631
|
-
this.
|
|
786
|
+
this.userSubject.next(user);
|
|
632
787
|
this.saveUser(user);
|
|
633
788
|
}
|
|
634
789
|
/**
|
|
635
790
|
* Login with email and password
|
|
791
|
+
* @param email - User email
|
|
792
|
+
* @param password - User password
|
|
793
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
636
794
|
*/
|
|
637
|
-
async loginWithEmail(email, password) {
|
|
795
|
+
async loginWithEmail(email, password, serverName) {
|
|
638
796
|
try {
|
|
639
|
-
const
|
|
797
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
798
|
+
const response = await fetch(`${accountsUrl}/api/auth/login`, {
|
|
640
799
|
method: 'POST',
|
|
641
800
|
headers: { 'Content-Type': 'application/json' },
|
|
642
801
|
credentials: 'include', // Include cookies for refresh token
|
|
@@ -650,8 +809,17 @@ class AuthService {
|
|
|
650
809
|
if (data.success && data.access_token) {
|
|
651
810
|
this.tokens.setAccessToken(data.access_token);
|
|
652
811
|
this.signinStatus.setSigninStatus(true);
|
|
653
|
-
|
|
654
|
-
|
|
812
|
+
// Normalize user object to handle both response formats
|
|
813
|
+
const normalizedUser = {
|
|
814
|
+
user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
|
|
815
|
+
id: data.user.id ?? String(data.user.user_id),
|
|
816
|
+
email: data.user.email,
|
|
817
|
+
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
818
|
+
photo_url: data.user.photo_url,
|
|
819
|
+
is_email_verified: data.user.is_email_verified ?? false
|
|
820
|
+
};
|
|
821
|
+
this.updateUser(normalizedUser);
|
|
822
|
+
return { success: true, user: normalizedUser };
|
|
655
823
|
}
|
|
656
824
|
return {
|
|
657
825
|
success: false,
|
|
@@ -667,55 +835,71 @@ class AuthService {
|
|
|
667
835
|
}
|
|
668
836
|
/**
|
|
669
837
|
* Login with Google OAuth (popup window)
|
|
838
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
670
839
|
*/
|
|
671
|
-
async loginWithGoogle() {
|
|
672
|
-
return this.loginWithOAuth('google');
|
|
840
|
+
async loginWithGoogle(serverName) {
|
|
841
|
+
return this.loginWithOAuth('google', serverName);
|
|
673
842
|
}
|
|
674
843
|
/**
|
|
675
844
|
* Login with GitHub OAuth (popup window)
|
|
845
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
676
846
|
*/
|
|
677
|
-
async loginWithGitHub() {
|
|
678
|
-
return this.loginWithOAuth('github');
|
|
847
|
+
async loginWithGitHub(serverName) {
|
|
848
|
+
return this.loginWithOAuth('github', serverName);
|
|
679
849
|
}
|
|
680
850
|
/**
|
|
681
851
|
* Login with LinkedIn OAuth (popup window)
|
|
852
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
682
853
|
*/
|
|
683
|
-
async loginWithLinkedIn() {
|
|
684
|
-
return this.loginWithOAuth('linkedin');
|
|
854
|
+
async loginWithLinkedIn(serverName) {
|
|
855
|
+
return this.loginWithOAuth('linkedin', serverName);
|
|
685
856
|
}
|
|
686
857
|
/**
|
|
687
858
|
* Login with Apple OAuth (popup window)
|
|
859
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
688
860
|
*/
|
|
689
|
-
async loginWithApple() {
|
|
690
|
-
return this.loginWithOAuth('apple');
|
|
861
|
+
async loginWithApple(serverName) {
|
|
862
|
+
return this.loginWithOAuth('apple', serverName);
|
|
691
863
|
}
|
|
692
864
|
/**
|
|
693
865
|
* Login with Microsoft OAuth (popup window)
|
|
866
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
694
867
|
*/
|
|
695
|
-
async loginWithMicrosoft() {
|
|
696
|
-
return this.loginWithOAuth('microsoft');
|
|
868
|
+
async loginWithMicrosoft(serverName) {
|
|
869
|
+
return this.loginWithOAuth('microsoft', serverName);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Login with Zoho OAuth (popup window)
|
|
873
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
874
|
+
*/
|
|
875
|
+
async loginWithZoho(serverName) {
|
|
876
|
+
return this.loginWithOAuth('zoho', serverName);
|
|
697
877
|
}
|
|
698
878
|
/**
|
|
699
879
|
* Generic provider-based login (supports all OAuth providers)
|
|
700
880
|
* @param provider - The provider identifier
|
|
881
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
701
882
|
*/
|
|
702
|
-
async loginWithProvider(provider) {
|
|
883
|
+
async loginWithProvider(provider, serverName) {
|
|
703
884
|
if (provider === 'emailPassword') {
|
|
704
885
|
throw new Error('Use loginWithEmail() for email/password authentication');
|
|
705
886
|
}
|
|
706
|
-
return this.loginWithOAuth(provider);
|
|
887
|
+
return this.loginWithOAuth(provider, serverName);
|
|
707
888
|
}
|
|
708
889
|
/**
|
|
709
890
|
* Generic OAuth login handler
|
|
710
891
|
* Opens popup window and listens for postMessage
|
|
892
|
+
* @param provider - OAuth provider name
|
|
893
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
711
894
|
*/
|
|
712
|
-
async loginWithOAuth(provider) {
|
|
895
|
+
async loginWithOAuth(provider, serverName) {
|
|
713
896
|
return new Promise((resolve) => {
|
|
714
897
|
const width = 500;
|
|
715
898
|
const height = 600;
|
|
716
899
|
const left = (window.screen.width - width) / 2;
|
|
717
900
|
const top = (window.screen.height - height) / 2;
|
|
718
|
-
const
|
|
901
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
902
|
+
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
719
903
|
`platform=${this.environment.platformCode}&` +
|
|
720
904
|
`mode=popup`;
|
|
721
905
|
const popup = window.open(oauthUrl, `${provider}_login`, `width=${width},height=${height},left=${left},top=${top}`);
|
|
@@ -729,18 +913,27 @@ class AuthService {
|
|
|
729
913
|
// Listen for message from popup
|
|
730
914
|
const messageHandler = (event) => {
|
|
731
915
|
// Verify origin
|
|
732
|
-
if (event.origin !== new URL(
|
|
916
|
+
if (event.origin !== new URL(accountsUrl).origin) {
|
|
733
917
|
return;
|
|
734
918
|
}
|
|
735
919
|
if (event.data.type === 'oauth_success') {
|
|
736
920
|
this.tokens.setAccessToken(event.data.access_token);
|
|
737
921
|
this.signinStatus.setSigninStatus(true);
|
|
738
|
-
|
|
922
|
+
// Normalize user object to handle both response formats
|
|
923
|
+
const normalizedUser = {
|
|
924
|
+
user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
|
|
925
|
+
id: event.data.user.id ?? String(event.data.user.user_id),
|
|
926
|
+
email: event.data.user.email,
|
|
927
|
+
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
928
|
+
photo_url: event.data.user.photo_url,
|
|
929
|
+
is_email_verified: event.data.user.is_email_verified ?? false
|
|
930
|
+
};
|
|
931
|
+
this.updateUser(normalizedUser);
|
|
739
932
|
window.removeEventListener('message', messageHandler);
|
|
740
933
|
popup.close();
|
|
741
934
|
resolve({
|
|
742
935
|
success: true,
|
|
743
|
-
user:
|
|
936
|
+
user: normalizedUser
|
|
744
937
|
});
|
|
745
938
|
}
|
|
746
939
|
else if (event.data.type === 'oauth_error') {
|
|
@@ -768,10 +961,15 @@ class AuthService {
|
|
|
768
961
|
}
|
|
769
962
|
/**
|
|
770
963
|
* Register new user
|
|
964
|
+
* @param email - User email
|
|
965
|
+
* @param password - User password
|
|
966
|
+
* @param displayName - Display name
|
|
967
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
771
968
|
*/
|
|
772
|
-
async register(email, password, displayName) {
|
|
969
|
+
async register(email, password, displayName, serverName) {
|
|
773
970
|
try {
|
|
774
|
-
const
|
|
971
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
972
|
+
const response = await fetch(`${accountsUrl}/api/auth/register`, {
|
|
775
973
|
method: 'POST',
|
|
776
974
|
headers: { 'Content-Type': 'application/json' },
|
|
777
975
|
credentials: 'include',
|
|
@@ -786,10 +984,19 @@ class AuthService {
|
|
|
786
984
|
if (data.success && data.access_token) {
|
|
787
985
|
this.tokens.setAccessToken(data.access_token);
|
|
788
986
|
this.signinStatus.setSigninStatus(true);
|
|
789
|
-
|
|
987
|
+
// Normalize user object to handle both response formats
|
|
988
|
+
const normalizedUser = {
|
|
989
|
+
user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
|
|
990
|
+
id: data.user.id ?? String(data.user.user_id),
|
|
991
|
+
email: data.user.email,
|
|
992
|
+
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
993
|
+
photo_url: data.user.photo_url,
|
|
994
|
+
is_email_verified: data.user.is_email_verified ?? false
|
|
995
|
+
};
|
|
996
|
+
this.updateUser(normalizedUser);
|
|
790
997
|
return {
|
|
791
998
|
success: true,
|
|
792
|
-
user:
|
|
999
|
+
user: normalizedUser,
|
|
793
1000
|
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
794
1001
|
};
|
|
795
1002
|
}
|
|
@@ -807,12 +1014,14 @@ class AuthService {
|
|
|
807
1014
|
}
|
|
808
1015
|
/**
|
|
809
1016
|
* Sign out user
|
|
1017
|
+
* @param serverName - Optional: Specify which auth server to logout from (for multi-server mode)
|
|
810
1018
|
*/
|
|
811
|
-
async signout() {
|
|
1019
|
+
async signout(serverName) {
|
|
812
1020
|
try {
|
|
813
1021
|
const refreshToken = this.tokens.getRefreshToken();
|
|
814
1022
|
if (refreshToken) {
|
|
815
|
-
|
|
1023
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1024
|
+
await fetch(`${accountsUrl}/api/auth/logout`, {
|
|
816
1025
|
method: 'POST',
|
|
817
1026
|
headers: {
|
|
818
1027
|
'Content-Type': 'application/json'
|
|
@@ -835,15 +1044,17 @@ class AuthService {
|
|
|
835
1044
|
}
|
|
836
1045
|
/**
|
|
837
1046
|
* Check for active session (call on app init)
|
|
1047
|
+
* @param serverName - Optional: Specify which auth server to check (for multi-server mode)
|
|
838
1048
|
*/
|
|
839
|
-
async checkSession() {
|
|
1049
|
+
async checkSession(serverName) {
|
|
840
1050
|
if (this.tokens.hasValidAccessToken()) {
|
|
841
1051
|
this.signinStatus.setSigninStatus(true);
|
|
842
1052
|
return true;
|
|
843
1053
|
}
|
|
844
1054
|
// Try to refresh using httpOnly cookie
|
|
845
1055
|
try {
|
|
846
|
-
const
|
|
1056
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1057
|
+
const response = await fetch(`${accountsUrl}/api/auth/refresh`, {
|
|
847
1058
|
method: 'POST',
|
|
848
1059
|
credentials: 'include'
|
|
849
1060
|
});
|
|
@@ -854,7 +1065,18 @@ class AuthService {
|
|
|
854
1065
|
const data = await response.json();
|
|
855
1066
|
if (data.access_token) {
|
|
856
1067
|
this.tokens.setAccessToken(data.access_token);
|
|
857
|
-
|
|
1068
|
+
// Normalize user object to handle both response formats
|
|
1069
|
+
if (data.user) {
|
|
1070
|
+
const normalizedUser = {
|
|
1071
|
+
user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
|
|
1072
|
+
id: data.user.id ?? String(data.user.user_id),
|
|
1073
|
+
email: data.user.email,
|
|
1074
|
+
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
1075
|
+
photo_url: data.user.photo_url,
|
|
1076
|
+
is_email_verified: data.user.is_email_verified ?? false
|
|
1077
|
+
};
|
|
1078
|
+
this.updateUser(normalizedUser);
|
|
1079
|
+
}
|
|
858
1080
|
this.signinStatus.setSigninStatus(true);
|
|
859
1081
|
return true;
|
|
860
1082
|
}
|
|
@@ -889,7 +1111,8 @@ class AuthService {
|
|
|
889
1111
|
return await this.registerTenantWithOAuth(data.tenantName, data.tenantSlug, data.provider);
|
|
890
1112
|
}
|
|
891
1113
|
// Email/password registration
|
|
892
|
-
const
|
|
1114
|
+
const accountsUrl = this.getAccountsUrl();
|
|
1115
|
+
const response = await fetch(`${accountsUrl}/api/auth/register-tenant`, {
|
|
893
1116
|
method: 'POST',
|
|
894
1117
|
headers: { 'Content-Type': 'application/json' },
|
|
895
1118
|
credentials: 'include',
|
|
@@ -908,7 +1131,16 @@ class AuthService {
|
|
|
908
1131
|
this.tokens.setAccessToken(result.access_token);
|
|
909
1132
|
this.signinStatus.setSigninStatus(true);
|
|
910
1133
|
if (result.user) {
|
|
911
|
-
|
|
1134
|
+
// Normalize user object to handle both response formats
|
|
1135
|
+
const normalizedUser = {
|
|
1136
|
+
user_id: result.user.user_id ?? (result.user.id ? this.hashUUID(result.user.id) : 0),
|
|
1137
|
+
id: result.user.id ?? String(result.user.user_id),
|
|
1138
|
+
email: result.user.email,
|
|
1139
|
+
display_name: result.user.display_name ?? result.user.email.split('@')[0],
|
|
1140
|
+
photo_url: result.user.photo_url,
|
|
1141
|
+
is_email_verified: result.user.is_email_verified ?? false
|
|
1142
|
+
};
|
|
1143
|
+
this.updateUser(normalizedUser);
|
|
912
1144
|
}
|
|
913
1145
|
}
|
|
914
1146
|
return result;
|
|
@@ -931,7 +1163,8 @@ class AuthService {
|
|
|
931
1163
|
const left = (window.screen.width - width) / 2;
|
|
932
1164
|
const top = (window.screen.height - height) / 2;
|
|
933
1165
|
// Build OAuth URL with tenant registration params
|
|
934
|
-
const
|
|
1166
|
+
const accountsUrl = this.getAccountsUrl();
|
|
1167
|
+
const oauthUrl = `${accountsUrl}/oauth/${provider}?` +
|
|
935
1168
|
`platform=${this.environment.platformCode}&` +
|
|
936
1169
|
`mode=popup&` +
|
|
937
1170
|
`action=register_tenant&` +
|
|
@@ -948,7 +1181,7 @@ class AuthService {
|
|
|
948
1181
|
// Listen for message from popup
|
|
949
1182
|
const messageHandler = (event) => {
|
|
950
1183
|
// Verify origin
|
|
951
|
-
if (event.origin !== new URL(
|
|
1184
|
+
if (event.origin !== new URL(accountsUrl).origin) {
|
|
952
1185
|
return;
|
|
953
1186
|
}
|
|
954
1187
|
if (event.data.type === 'tenant_register_success') {
|
|
@@ -958,7 +1191,16 @@ class AuthService {
|
|
|
958
1191
|
this.signinStatus.setSigninStatus(true);
|
|
959
1192
|
}
|
|
960
1193
|
if (event.data.user) {
|
|
961
|
-
|
|
1194
|
+
// Normalize user object to handle both response formats
|
|
1195
|
+
const normalizedUser = {
|
|
1196
|
+
user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
|
|
1197
|
+
id: event.data.user.id ?? String(event.data.user.user_id),
|
|
1198
|
+
email: event.data.user.email,
|
|
1199
|
+
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
1200
|
+
photo_url: event.data.user.photo_url,
|
|
1201
|
+
is_email_verified: event.data.user.is_email_verified ?? false
|
|
1202
|
+
};
|
|
1203
|
+
this.updateUser(normalizedUser);
|
|
962
1204
|
}
|
|
963
1205
|
window.removeEventListener('message', messageHandler);
|
|
964
1206
|
popup.close();
|
|
@@ -993,10 +1235,12 @@ class AuthService {
|
|
|
993
1235
|
}
|
|
994
1236
|
/**
|
|
995
1237
|
* Get all tenant memberships for the authenticated user
|
|
1238
|
+
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
996
1239
|
*/
|
|
997
|
-
async getTenantMemberships() {
|
|
1240
|
+
async getTenantMemberships(serverName) {
|
|
998
1241
|
try {
|
|
999
|
-
const
|
|
1242
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1243
|
+
const response = await fetch(`${accountsUrl}/api/auth/memberships`, {
|
|
1000
1244
|
method: 'GET',
|
|
1001
1245
|
headers: {
|
|
1002
1246
|
'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
|
|
@@ -1016,10 +1260,13 @@ class AuthService {
|
|
|
1016
1260
|
/**
|
|
1017
1261
|
* Select a tenant for the current session
|
|
1018
1262
|
* Updates the JWT token with tenant context
|
|
1263
|
+
* @param tenantId - Tenant ID to select
|
|
1264
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1019
1265
|
*/
|
|
1020
|
-
async selectTenant(tenantId) {
|
|
1266
|
+
async selectTenant(tenantId, serverName) {
|
|
1021
1267
|
try {
|
|
1022
|
-
const
|
|
1268
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1269
|
+
const response = await fetch(`${accountsUrl}/api/auth/select-tenant`, {
|
|
1023
1270
|
method: 'POST',
|
|
1024
1271
|
headers: {
|
|
1025
1272
|
'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
|
|
@@ -1050,10 +1297,13 @@ class AuthService {
|
|
|
1050
1297
|
}
|
|
1051
1298
|
/**
|
|
1052
1299
|
* Check if a tenant slug is available
|
|
1300
|
+
* @param slug - Tenant slug to check
|
|
1301
|
+
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
1053
1302
|
*/
|
|
1054
|
-
async checkTenantSlugAvailable(slug) {
|
|
1303
|
+
async checkTenantSlugAvailable(slug, serverName) {
|
|
1055
1304
|
try {
|
|
1056
|
-
const
|
|
1305
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1306
|
+
const response = await fetch(`${accountsUrl}/api/auth/check-tenant-slug/${slug}`, {
|
|
1057
1307
|
method: 'GET',
|
|
1058
1308
|
headers: { 'Content-Type': 'application/json' }
|
|
1059
1309
|
});
|
|
@@ -1136,9 +1386,10 @@ class AuthService {
|
|
|
1136
1386
|
/**
|
|
1137
1387
|
* @deprecated Check if user exists by calling /api/auth/check-email endpoint
|
|
1138
1388
|
*/
|
|
1139
|
-
async getUserProfile(email) {
|
|
1389
|
+
async getUserProfile(email, serverName) {
|
|
1140
1390
|
try {
|
|
1141
|
-
const
|
|
1391
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1392
|
+
const response = await fetch(`${accountsUrl}/api/auth/check-email`, {
|
|
1142
1393
|
method: 'POST',
|
|
1143
1394
|
headers: { 'Content-Type': 'application/json' },
|
|
1144
1395
|
body: JSON.stringify({ email })
|
|
@@ -1152,10 +1403,13 @@ class AuthService {
|
|
|
1152
1403
|
}
|
|
1153
1404
|
/**
|
|
1154
1405
|
* Check if user has completed onboarding (has a tenant)
|
|
1406
|
+
* @param identityId - User identity ID
|
|
1407
|
+
* @param serverName - Optional: Specify which auth server to query (for multi-server mode)
|
|
1155
1408
|
*/
|
|
1156
|
-
async checkOnboardingStatus(identityId) {
|
|
1409
|
+
async checkOnboardingStatus(identityId, serverName) {
|
|
1157
1410
|
try {
|
|
1158
|
-
const
|
|
1411
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1412
|
+
const response = await fetch(`${accountsUrl}/api/auth/onboarding/status?platform_code=${this.environment.platformCode}&identity_id=${identityId}`, {
|
|
1159
1413
|
method: 'GET',
|
|
1160
1414
|
headers: { 'Content-Type': 'application/json' },
|
|
1161
1415
|
credentials: 'include'
|
|
@@ -1171,14 +1425,18 @@ class AuthService {
|
|
|
1171
1425
|
}
|
|
1172
1426
|
/**
|
|
1173
1427
|
* Complete tenant onboarding (create tenant with country + org name)
|
|
1428
|
+
* @param countryCode - Country code
|
|
1429
|
+
* @param tenantName - Tenant organization name
|
|
1430
|
+
* @param serverName - Optional: Specify which auth server to use (for multi-server mode)
|
|
1174
1431
|
*/
|
|
1175
|
-
async completeTenantOnboarding(countryCode, tenantName) {
|
|
1432
|
+
async completeTenantOnboarding(countryCode, tenantName, serverName) {
|
|
1176
1433
|
try {
|
|
1177
1434
|
const accessToken = this.tokens.getAccessToken();
|
|
1178
1435
|
if (!accessToken) {
|
|
1179
1436
|
throw new Error('Not authenticated');
|
|
1180
1437
|
}
|
|
1181
|
-
const
|
|
1438
|
+
const accountsUrl = this.getAccountsUrl(serverName);
|
|
1439
|
+
const response = await fetch(`${accountsUrl}/api/auth/register-tenant`, {
|
|
1182
1440
|
method: 'POST',
|
|
1183
1441
|
headers: {
|
|
1184
1442
|
'Content-Type': 'application/json',
|
|
@@ -1321,12 +1579,16 @@ class TenantLoginComponent {
|
|
|
1321
1579
|
apple: 'Sign in with Apple',
|
|
1322
1580
|
microsoft: 'Sign in with Microsoft',
|
|
1323
1581
|
github: 'Sign in with GitHub',
|
|
1582
|
+
zoho: 'Sign in with Zoho',
|
|
1324
1583
|
emailPassword: 'Sign in with Email'
|
|
1325
1584
|
};
|
|
1326
1585
|
return labels[provider];
|
|
1327
1586
|
}
|
|
1328
1587
|
getProviderIcon(provider) {
|
|
1329
|
-
|
|
1588
|
+
const icons = {
|
|
1589
|
+
zoho: '🔶'
|
|
1590
|
+
};
|
|
1591
|
+
return icons[provider];
|
|
1330
1592
|
}
|
|
1331
1593
|
toggleAuthMethod(event) {
|
|
1332
1594
|
event.preventDefault();
|
|
@@ -1650,7 +1912,7 @@ class TenantLoginComponent {
|
|
|
1650
1912
|
</div>
|
|
1651
1913
|
}
|
|
1652
1914
|
</div>
|
|
1653
|
-
`, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
1915
|
+
`, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
1654
1916
|
}
|
|
1655
1917
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, decorators: [{
|
|
1656
1918
|
type: Component,
|
|
@@ -1819,7 +2081,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
1819
2081
|
</div>
|
|
1820
2082
|
}
|
|
1821
2083
|
</div>
|
|
1822
|
-
`, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"] }]
|
|
2084
|
+
`, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"] }]
|
|
1823
2085
|
}], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
|
|
1824
2086
|
type: Input
|
|
1825
2087
|
}], providers: [{
|
|
@@ -2372,6 +2634,7 @@ class LoginDialogComponent {
|
|
|
2372
2634
|
apple: 'Sign in with Apple',
|
|
2373
2635
|
microsoft: 'Sign in with Microsoft',
|
|
2374
2636
|
github: 'Sign in with GitHub',
|
|
2637
|
+
zoho: 'Sign in with Zoho',
|
|
2375
2638
|
emailPassword: 'Sign in with Email'
|
|
2376
2639
|
};
|
|
2377
2640
|
return labels[provider];
|
|
@@ -2678,6 +2941,7 @@ class TenantRegisterComponent {
|
|
|
2678
2941
|
apple: 'Sign up with Apple',
|
|
2679
2942
|
microsoft: 'Sign up with Microsoft',
|
|
2680
2943
|
github: 'Sign up with GitHub',
|
|
2944
|
+
zoho: 'Sign up with Zoho',
|
|
2681
2945
|
emailPassword: 'Sign up with Email'
|
|
2682
2946
|
};
|
|
2683
2947
|
return labels[provider];
|