@progalaxyelabs/ngx-stonescriptphp-client 1.9.0 → 1.11.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.
|
@@ -110,8 +110,8 @@ class TokenService {
|
|
|
110
110
|
localStorage.removeItem(this.lsRefreshTokenKey);
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
* Check if there is a
|
|
114
|
-
*
|
|
113
|
+
* Check if there is a non-empty access token.
|
|
114
|
+
* Token is treated as opaque — validity is determined by the auth server.
|
|
115
115
|
*/
|
|
116
116
|
hasValidAccessToken() {
|
|
117
117
|
const token = this.getAccessToken();
|
|
@@ -200,6 +200,12 @@ class MyEnvironmentModel {
|
|
|
200
200
|
messagingSenderId: '',
|
|
201
201
|
measurementId: ''
|
|
202
202
|
};
|
|
203
|
+
/**
|
|
204
|
+
* Platform's own API base URL (e.g., '//api.medstoreapp.in')
|
|
205
|
+
* Used to route registration through the platform API proxy instead of auth directly
|
|
206
|
+
* @example '//api.medstoreapp.in'
|
|
207
|
+
*/
|
|
208
|
+
apiUrl;
|
|
203
209
|
apiServer = { host: '' };
|
|
204
210
|
chatServer = { host: '' };
|
|
205
211
|
/**
|
|
@@ -240,6 +246,22 @@ class MyEnvironmentModel {
|
|
|
240
246
|
* ```
|
|
241
247
|
*/
|
|
242
248
|
customProviders;
|
|
249
|
+
/**
|
|
250
|
+
* Auth response field mapping.
|
|
251
|
+
* Defaults to StoneScriptPHP format: { status: 'ok', data: { access_token, user, ... } }
|
|
252
|
+
*
|
|
253
|
+
* For external auth servers that return flat responses like
|
|
254
|
+
* { access_token, identity, ... }, override with:
|
|
255
|
+
* ```typescript
|
|
256
|
+
* authResponseMap: {
|
|
257
|
+
* accessTokenPath: 'access_token',
|
|
258
|
+
* refreshTokenPath: 'refresh_token',
|
|
259
|
+
* userPath: 'identity',
|
|
260
|
+
* errorMessagePath: 'message'
|
|
261
|
+
* }
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
authResponseMap;
|
|
243
265
|
/**
|
|
244
266
|
* Branding configuration for auth components
|
|
245
267
|
* Allows platforms to customize login/register pages without creating wrappers
|
|
@@ -775,6 +797,73 @@ class AuthService {
|
|
|
775
797
|
isMultiServerMode() {
|
|
776
798
|
return !!(this.environment.authServers && Object.keys(this.environment.authServers).length > 0);
|
|
777
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Get the platform's own API base URL
|
|
802
|
+
* Used for routes that go through the platform API proxy (e.g. register-tenant)
|
|
803
|
+
* @throws Error if no API URL is configured
|
|
804
|
+
*/
|
|
805
|
+
getPlatformApiUrl() {
|
|
806
|
+
if (this.environment.apiUrl) {
|
|
807
|
+
return this.environment.apiUrl;
|
|
808
|
+
}
|
|
809
|
+
if (this.environment.apiServer?.host) {
|
|
810
|
+
return this.environment.apiServer.host;
|
|
811
|
+
}
|
|
812
|
+
throw new Error('No platform API URL configured. Set apiUrl in environment config.');
|
|
813
|
+
}
|
|
814
|
+
// ===== Auth Response Mapping Helpers =====
|
|
815
|
+
/** Default response map: StoneScriptPHP format */
|
|
816
|
+
get responseMap() {
|
|
817
|
+
return this.environment.authResponseMap ?? {
|
|
818
|
+
successPath: 'status',
|
|
819
|
+
successValue: 'ok',
|
|
820
|
+
accessTokenPath: 'data.access_token',
|
|
821
|
+
refreshTokenPath: 'data.refresh_token',
|
|
822
|
+
userPath: 'data.user',
|
|
823
|
+
errorMessagePath: 'message'
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
/** Resolve a dot-notation path on an object (e.g., 'data.access_token') */
|
|
827
|
+
resolvePath(obj, path) {
|
|
828
|
+
return path.split('.').reduce((o, key) => o?.[key], obj);
|
|
829
|
+
}
|
|
830
|
+
/** Check if an auth response indicates success */
|
|
831
|
+
isAuthSuccess(data) {
|
|
832
|
+
const map = this.responseMap;
|
|
833
|
+
if (map.successPath) {
|
|
834
|
+
return this.resolvePath(data, map.successPath) === (map.successValue ?? 'ok');
|
|
835
|
+
}
|
|
836
|
+
// No successPath configured — success = access token present
|
|
837
|
+
return !!this.resolvePath(data, map.accessTokenPath);
|
|
838
|
+
}
|
|
839
|
+
/** Extract access token from auth response */
|
|
840
|
+
getAccessToken(data) {
|
|
841
|
+
return this.resolvePath(data, this.responseMap.accessTokenPath);
|
|
842
|
+
}
|
|
843
|
+
/** Extract refresh token from auth response */
|
|
844
|
+
getRefreshToken(data) {
|
|
845
|
+
return this.resolvePath(data, this.responseMap.refreshTokenPath);
|
|
846
|
+
}
|
|
847
|
+
/** Extract user/identity object from auth response */
|
|
848
|
+
getUserFromResponse(data) {
|
|
849
|
+
return this.resolvePath(data, this.responseMap.userPath);
|
|
850
|
+
}
|
|
851
|
+
/** Extract error message from auth response */
|
|
852
|
+
getErrorMessage(data, fallback) {
|
|
853
|
+
const path = this.responseMap.errorMessagePath ?? 'message';
|
|
854
|
+
return this.resolvePath(data, path) || fallback;
|
|
855
|
+
}
|
|
856
|
+
/** Normalize a user/identity object into the standard User shape */
|
|
857
|
+
normalizeUser(raw) {
|
|
858
|
+
return {
|
|
859
|
+
user_id: raw.user_id ?? (raw.id ? this.hashUUID(raw.id) : 0),
|
|
860
|
+
id: raw.id ?? String(raw.user_id),
|
|
861
|
+
email: raw.email,
|
|
862
|
+
display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
|
|
863
|
+
photo_url: raw.photo_url,
|
|
864
|
+
is_email_verified: raw.is_email_verified ?? false
|
|
865
|
+
};
|
|
866
|
+
}
|
|
778
867
|
/**
|
|
779
868
|
* Hash UUID to numeric ID for backward compatibility
|
|
780
869
|
* Converts UUID string to a consistent numeric ID for legacy code
|
|
@@ -838,7 +927,7 @@ class AuthService {
|
|
|
838
927
|
const response = await fetch(`${accountsUrl}/api/auth/login`, {
|
|
839
928
|
method: 'POST',
|
|
840
929
|
headers: { 'Content-Type': 'application/json' },
|
|
841
|
-
credentials: 'include',
|
|
930
|
+
credentials: 'include',
|
|
842
931
|
body: JSON.stringify({
|
|
843
932
|
email,
|
|
844
933
|
password,
|
|
@@ -846,24 +935,21 @@ class AuthService {
|
|
|
846
935
|
})
|
|
847
936
|
});
|
|
848
937
|
const data = await response.json();
|
|
849
|
-
if (
|
|
850
|
-
this.
|
|
938
|
+
if (this.isAuthSuccess(data)) {
|
|
939
|
+
const accessToken = this.getAccessToken(data);
|
|
940
|
+
this.tokens.setAccessToken(accessToken);
|
|
851
941
|
this.signinStatus.setSigninStatus(true);
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
860
|
-
};
|
|
861
|
-
this.updateUser(normalizedUser);
|
|
862
|
-
return { success: true, user: normalizedUser };
|
|
942
|
+
const rawUser = this.getUserFromResponse(data);
|
|
943
|
+
if (rawUser) {
|
|
944
|
+
const normalizedUser = this.normalizeUser(rawUser);
|
|
945
|
+
this.updateUser(normalizedUser);
|
|
946
|
+
return { success: true, user: normalizedUser };
|
|
947
|
+
}
|
|
948
|
+
return { success: true };
|
|
863
949
|
}
|
|
864
950
|
return {
|
|
865
951
|
success: false,
|
|
866
|
-
message: data
|
|
952
|
+
message: this.getErrorMessage(data, 'Invalid credentials')
|
|
867
953
|
};
|
|
868
954
|
}
|
|
869
955
|
catch (error) {
|
|
@@ -959,16 +1045,11 @@ class AuthService {
|
|
|
959
1045
|
if (event.data.type === 'oauth_success') {
|
|
960
1046
|
this.tokens.setAccessToken(event.data.access_token);
|
|
961
1047
|
this.signinStatus.setSigninStatus(true);
|
|
962
|
-
|
|
963
|
-
const normalizedUser =
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
968
|
-
photo_url: event.data.user.photo_url,
|
|
969
|
-
is_email_verified: event.data.user.is_email_verified ?? false
|
|
970
|
-
};
|
|
971
|
-
this.updateUser(normalizedUser);
|
|
1048
|
+
const rawUser = event.data.user || this.getUserFromResponse(event.data);
|
|
1049
|
+
const normalizedUser = rawUser ? this.normalizeUser(rawUser) : undefined;
|
|
1050
|
+
if (normalizedUser) {
|
|
1051
|
+
this.updateUser(normalizedUser);
|
|
1052
|
+
}
|
|
972
1053
|
window.removeEventListener('message', messageHandler);
|
|
973
1054
|
popup.close();
|
|
974
1055
|
resolve({
|
|
@@ -1021,28 +1102,25 @@ class AuthService {
|
|
|
1021
1102
|
})
|
|
1022
1103
|
});
|
|
1023
1104
|
const data = await response.json();
|
|
1024
|
-
if (
|
|
1025
|
-
this.
|
|
1105
|
+
if (this.isAuthSuccess(data)) {
|
|
1106
|
+
const accessToken = this.getAccessToken(data);
|
|
1107
|
+
this.tokens.setAccessToken(accessToken);
|
|
1026
1108
|
this.signinStatus.setSigninStatus(true);
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
return {
|
|
1038
|
-
success: true,
|
|
1039
|
-
user: normalizedUser,
|
|
1040
|
-
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
1041
|
-
};
|
|
1109
|
+
const rawUser = this.getUserFromResponse(data);
|
|
1110
|
+
if (rawUser) {
|
|
1111
|
+
const normalizedUser = this.normalizeUser(rawUser);
|
|
1112
|
+
this.updateUser(normalizedUser);
|
|
1113
|
+
return {
|
|
1114
|
+
success: true,
|
|
1115
|
+
user: normalizedUser,
|
|
1116
|
+
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
return { success: true };
|
|
1042
1120
|
}
|
|
1043
1121
|
return {
|
|
1044
1122
|
success: false,
|
|
1045
|
-
message: data
|
|
1123
|
+
message: this.getErrorMessage(data, 'Registration failed')
|
|
1046
1124
|
};
|
|
1047
1125
|
}
|
|
1048
1126
|
catch (error) {
|
|
@@ -1099,30 +1177,33 @@ class AuthService {
|
|
|
1099
1177
|
credentials: 'include'
|
|
1100
1178
|
});
|
|
1101
1179
|
if (!response.ok) {
|
|
1180
|
+
// Refresh failed (expired, revoked, wrong keypair) — clear stale tokens
|
|
1181
|
+
// so the user gets a clean login page, not a broken retry loop
|
|
1182
|
+
this.tokens.clear();
|
|
1183
|
+
this.updateUser(null);
|
|
1102
1184
|
this.signinStatus.setSigninStatus(false);
|
|
1103
1185
|
return false;
|
|
1104
1186
|
}
|
|
1105
1187
|
const data = await response.json();
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
id: data.user.id ?? String(data.user.user_id),
|
|
1113
|
-
email: data.user.email,
|
|
1114
|
-
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
1115
|
-
photo_url: data.user.photo_url,
|
|
1116
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
1117
|
-
};
|
|
1118
|
-
this.updateUser(normalizedUser);
|
|
1188
|
+
const accessToken = this.getAccessToken(data) ?? data.access_token;
|
|
1189
|
+
if (accessToken) {
|
|
1190
|
+
this.tokens.setAccessToken(accessToken);
|
|
1191
|
+
const rawUser = this.getUserFromResponse(data) ?? data.user;
|
|
1192
|
+
if (rawUser) {
|
|
1193
|
+
this.updateUser(this.normalizeUser(rawUser));
|
|
1119
1194
|
}
|
|
1120
1195
|
this.signinStatus.setSigninStatus(true);
|
|
1121
1196
|
return true;
|
|
1122
1197
|
}
|
|
1198
|
+
// Response OK but no token — clear stale state
|
|
1199
|
+
this.tokens.clear();
|
|
1200
|
+
this.updateUser(null);
|
|
1123
1201
|
return false;
|
|
1124
1202
|
}
|
|
1125
1203
|
catch (error) {
|
|
1204
|
+
// Network error — clear stale state to avoid retry loops
|
|
1205
|
+
this.tokens.clear();
|
|
1206
|
+
this.updateUser(null);
|
|
1126
1207
|
this.signinStatus.setSigninStatus(false);
|
|
1127
1208
|
return false;
|
|
1128
1209
|
}
|
|
@@ -1150,9 +1231,9 @@ class AuthService {
|
|
|
1150
1231
|
if (data.provider !== 'emailPassword') {
|
|
1151
1232
|
return await this.registerTenantWithOAuth(data.tenantName, data.tenantSlug, data.provider);
|
|
1152
1233
|
}
|
|
1153
|
-
// Email/password registration
|
|
1154
|
-
const
|
|
1155
|
-
const response = await fetch(`${
|
|
1234
|
+
// Email/password registration — route through platform API proxy
|
|
1235
|
+
const apiUrl = this.getPlatformApiUrl();
|
|
1236
|
+
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
1156
1237
|
method: 'POST',
|
|
1157
1238
|
headers: { 'Content-Type': 'application/json' },
|
|
1158
1239
|
credentials: 'include',
|
|
@@ -1167,20 +1248,15 @@ class AuthService {
|
|
|
1167
1248
|
})
|
|
1168
1249
|
});
|
|
1169
1250
|
const result = await response.json();
|
|
1170
|
-
if (
|
|
1171
|
-
this.
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
display_name: result.user.display_name ?? result.user.email.split('@')[0],
|
|
1180
|
-
photo_url: result.user.photo_url,
|
|
1181
|
-
is_email_verified: result.user.is_email_verified ?? false
|
|
1182
|
-
};
|
|
1183
|
-
this.updateUser(normalizedUser);
|
|
1251
|
+
if (this.isAuthSuccess(result)) {
|
|
1252
|
+
const accessToken = this.getAccessToken(result);
|
|
1253
|
+
if (accessToken) {
|
|
1254
|
+
this.tokens.setAccessToken(accessToken);
|
|
1255
|
+
this.signinStatus.setSigninStatus(true);
|
|
1256
|
+
}
|
|
1257
|
+
const rawUser = this.getUserFromResponse(result);
|
|
1258
|
+
if (rawUser) {
|
|
1259
|
+
this.updateUser(this.normalizeUser(rawUser));
|
|
1184
1260
|
}
|
|
1185
1261
|
}
|
|
1186
1262
|
return result;
|
|
@@ -1225,22 +1301,13 @@ class AuthService {
|
|
|
1225
1301
|
return;
|
|
1226
1302
|
}
|
|
1227
1303
|
if (event.data.type === 'tenant_register_success') {
|
|
1228
|
-
// Set tokens and user
|
|
1229
1304
|
if (event.data.access_token) {
|
|
1230
1305
|
this.tokens.setAccessToken(event.data.access_token);
|
|
1231
1306
|
this.signinStatus.setSigninStatus(true);
|
|
1232
1307
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
|
|
1237
|
-
id: event.data.user.id ?? String(event.data.user.user_id),
|
|
1238
|
-
email: event.data.user.email,
|
|
1239
|
-
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
1240
|
-
photo_url: event.data.user.photo_url,
|
|
1241
|
-
is_email_verified: event.data.user.is_email_verified ?? false
|
|
1242
|
-
};
|
|
1243
|
-
this.updateUser(normalizedUser);
|
|
1308
|
+
const rawUser = event.data.user || this.getUserFromResponse(event.data);
|
|
1309
|
+
if (rawUser) {
|
|
1310
|
+
this.updateUser(this.normalizeUser(rawUser));
|
|
1244
1311
|
}
|
|
1245
1312
|
window.removeEventListener('message', messageHandler);
|
|
1246
1313
|
popup.close();
|
|
@@ -1316,16 +1383,17 @@ class AuthService {
|
|
|
1316
1383
|
body: JSON.stringify({ tenant_id: tenantId })
|
|
1317
1384
|
});
|
|
1318
1385
|
const data = await response.json();
|
|
1319
|
-
if (
|
|
1320
|
-
this.
|
|
1386
|
+
if (this.isAuthSuccess(data)) {
|
|
1387
|
+
const accessToken = this.getAccessToken(data);
|
|
1388
|
+
this.tokens.setAccessToken(accessToken);
|
|
1321
1389
|
return {
|
|
1322
1390
|
success: true,
|
|
1323
|
-
access_token:
|
|
1391
|
+
access_token: accessToken
|
|
1324
1392
|
};
|
|
1325
1393
|
}
|
|
1326
1394
|
return {
|
|
1327
1395
|
success: false,
|
|
1328
|
-
message: data
|
|
1396
|
+
message: this.getErrorMessage(data, 'Failed to select tenant')
|
|
1329
1397
|
};
|
|
1330
1398
|
}
|
|
1331
1399
|
catch (error) {
|
|
@@ -1475,8 +1543,9 @@ class AuthService {
|
|
|
1475
1543
|
if (!accessToken) {
|
|
1476
1544
|
throw new Error('Not authenticated');
|
|
1477
1545
|
}
|
|
1478
|
-
|
|
1479
|
-
const
|
|
1546
|
+
// Route through platform API proxy — PHP API adds platform_secret header
|
|
1547
|
+
const apiUrl = this.getPlatformApiUrl();
|
|
1548
|
+
const response = await fetch(`${apiUrl}/auth/register-tenant`, {
|
|
1480
1549
|
method: 'POST',
|
|
1481
1550
|
headers: {
|
|
1482
1551
|
'Content-Type': 'application/json',
|