@progalaxyelabs/ngx-stonescriptphp-client 1.10.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();
|
|
@@ -246,6 +246,22 @@ class MyEnvironmentModel {
|
|
|
246
246
|
* ```
|
|
247
247
|
*/
|
|
248
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;
|
|
249
265
|
/**
|
|
250
266
|
* Branding configuration for auth components
|
|
251
267
|
* Allows platforms to customize login/register pages without creating wrappers
|
|
@@ -795,6 +811,59 @@ class AuthService {
|
|
|
795
811
|
}
|
|
796
812
|
throw new Error('No platform API URL configured. Set apiUrl in environment config.');
|
|
797
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
|
+
}
|
|
798
867
|
/**
|
|
799
868
|
* Hash UUID to numeric ID for backward compatibility
|
|
800
869
|
* Converts UUID string to a consistent numeric ID for legacy code
|
|
@@ -858,7 +927,7 @@ class AuthService {
|
|
|
858
927
|
const response = await fetch(`${accountsUrl}/api/auth/login`, {
|
|
859
928
|
method: 'POST',
|
|
860
929
|
headers: { 'Content-Type': 'application/json' },
|
|
861
|
-
credentials: 'include',
|
|
930
|
+
credentials: 'include',
|
|
862
931
|
body: JSON.stringify({
|
|
863
932
|
email,
|
|
864
933
|
password,
|
|
@@ -866,24 +935,21 @@ class AuthService {
|
|
|
866
935
|
})
|
|
867
936
|
});
|
|
868
937
|
const data = await response.json();
|
|
869
|
-
if (
|
|
870
|
-
this.
|
|
938
|
+
if (this.isAuthSuccess(data)) {
|
|
939
|
+
const accessToken = this.getAccessToken(data);
|
|
940
|
+
this.tokens.setAccessToken(accessToken);
|
|
871
941
|
this.signinStatus.setSigninStatus(true);
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
880
|
-
};
|
|
881
|
-
this.updateUser(normalizedUser);
|
|
882
|
-
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 };
|
|
883
949
|
}
|
|
884
950
|
return {
|
|
885
951
|
success: false,
|
|
886
|
-
message: data
|
|
952
|
+
message: this.getErrorMessage(data, 'Invalid credentials')
|
|
887
953
|
};
|
|
888
954
|
}
|
|
889
955
|
catch (error) {
|
|
@@ -979,16 +1045,11 @@ class AuthService {
|
|
|
979
1045
|
if (event.data.type === 'oauth_success') {
|
|
980
1046
|
this.tokens.setAccessToken(event.data.access_token);
|
|
981
1047
|
this.signinStatus.setSigninStatus(true);
|
|
982
|
-
|
|
983
|
-
const normalizedUser =
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
988
|
-
photo_url: event.data.user.photo_url,
|
|
989
|
-
is_email_verified: event.data.user.is_email_verified ?? false
|
|
990
|
-
};
|
|
991
|
-
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
|
+
}
|
|
992
1053
|
window.removeEventListener('message', messageHandler);
|
|
993
1054
|
popup.close();
|
|
994
1055
|
resolve({
|
|
@@ -1041,28 +1102,25 @@ class AuthService {
|
|
|
1041
1102
|
})
|
|
1042
1103
|
});
|
|
1043
1104
|
const data = await response.json();
|
|
1044
|
-
if (
|
|
1045
|
-
this.
|
|
1105
|
+
if (this.isAuthSuccess(data)) {
|
|
1106
|
+
const accessToken = this.getAccessToken(data);
|
|
1107
|
+
this.tokens.setAccessToken(accessToken);
|
|
1046
1108
|
this.signinStatus.setSigninStatus(true);
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
return {
|
|
1058
|
-
success: true,
|
|
1059
|
-
user: normalizedUser,
|
|
1060
|
-
message: data.needs_verification ? 'Please verify your email' : undefined
|
|
1061
|
-
};
|
|
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 };
|
|
1062
1120
|
}
|
|
1063
1121
|
return {
|
|
1064
1122
|
success: false,
|
|
1065
|
-
message: data
|
|
1123
|
+
message: this.getErrorMessage(data, 'Registration failed')
|
|
1066
1124
|
};
|
|
1067
1125
|
}
|
|
1068
1126
|
catch (error) {
|
|
@@ -1119,30 +1177,33 @@ class AuthService {
|
|
|
1119
1177
|
credentials: 'include'
|
|
1120
1178
|
});
|
|
1121
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);
|
|
1122
1184
|
this.signinStatus.setSigninStatus(false);
|
|
1123
1185
|
return false;
|
|
1124
1186
|
}
|
|
1125
1187
|
const data = await response.json();
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
id: data.user.id ?? String(data.user.user_id),
|
|
1133
|
-
email: data.user.email,
|
|
1134
|
-
display_name: data.user.display_name ?? data.user.email.split('@')[0],
|
|
1135
|
-
photo_url: data.user.photo_url,
|
|
1136
|
-
is_email_verified: data.user.is_email_verified ?? false
|
|
1137
|
-
};
|
|
1138
|
-
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));
|
|
1139
1194
|
}
|
|
1140
1195
|
this.signinStatus.setSigninStatus(true);
|
|
1141
1196
|
return true;
|
|
1142
1197
|
}
|
|
1198
|
+
// Response OK but no token — clear stale state
|
|
1199
|
+
this.tokens.clear();
|
|
1200
|
+
this.updateUser(null);
|
|
1143
1201
|
return false;
|
|
1144
1202
|
}
|
|
1145
1203
|
catch (error) {
|
|
1204
|
+
// Network error — clear stale state to avoid retry loops
|
|
1205
|
+
this.tokens.clear();
|
|
1206
|
+
this.updateUser(null);
|
|
1146
1207
|
this.signinStatus.setSigninStatus(false);
|
|
1147
1208
|
return false;
|
|
1148
1209
|
}
|
|
@@ -1187,20 +1248,15 @@ class AuthService {
|
|
|
1187
1248
|
})
|
|
1188
1249
|
});
|
|
1189
1250
|
const result = await response.json();
|
|
1190
|
-
if (
|
|
1191
|
-
this.
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
display_name: result.user.display_name ?? result.user.email.split('@')[0],
|
|
1200
|
-
photo_url: result.user.photo_url,
|
|
1201
|
-
is_email_verified: result.user.is_email_verified ?? false
|
|
1202
|
-
};
|
|
1203
|
-
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));
|
|
1204
1260
|
}
|
|
1205
1261
|
}
|
|
1206
1262
|
return result;
|
|
@@ -1245,22 +1301,13 @@ class AuthService {
|
|
|
1245
1301
|
return;
|
|
1246
1302
|
}
|
|
1247
1303
|
if (event.data.type === 'tenant_register_success') {
|
|
1248
|
-
// Set tokens and user
|
|
1249
1304
|
if (event.data.access_token) {
|
|
1250
1305
|
this.tokens.setAccessToken(event.data.access_token);
|
|
1251
1306
|
this.signinStatus.setSigninStatus(true);
|
|
1252
1307
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
|
|
1257
|
-
id: event.data.user.id ?? String(event.data.user.user_id),
|
|
1258
|
-
email: event.data.user.email,
|
|
1259
|
-
display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
|
|
1260
|
-
photo_url: event.data.user.photo_url,
|
|
1261
|
-
is_email_verified: event.data.user.is_email_verified ?? false
|
|
1262
|
-
};
|
|
1263
|
-
this.updateUser(normalizedUser);
|
|
1308
|
+
const rawUser = event.data.user || this.getUserFromResponse(event.data);
|
|
1309
|
+
if (rawUser) {
|
|
1310
|
+
this.updateUser(this.normalizeUser(rawUser));
|
|
1264
1311
|
}
|
|
1265
1312
|
window.removeEventListener('message', messageHandler);
|
|
1266
1313
|
popup.close();
|
|
@@ -1336,16 +1383,17 @@ class AuthService {
|
|
|
1336
1383
|
body: JSON.stringify({ tenant_id: tenantId })
|
|
1337
1384
|
});
|
|
1338
1385
|
const data = await response.json();
|
|
1339
|
-
if (
|
|
1340
|
-
this.
|
|
1386
|
+
if (this.isAuthSuccess(data)) {
|
|
1387
|
+
const accessToken = this.getAccessToken(data);
|
|
1388
|
+
this.tokens.setAccessToken(accessToken);
|
|
1341
1389
|
return {
|
|
1342
1390
|
success: true,
|
|
1343
|
-
access_token:
|
|
1391
|
+
access_token: accessToken
|
|
1344
1392
|
};
|
|
1345
1393
|
}
|
|
1346
1394
|
return {
|
|
1347
1395
|
success: false,
|
|
1348
|
-
message: data
|
|
1396
|
+
message: this.getErrorMessage(data, 'Failed to select tenant')
|
|
1349
1397
|
};
|
|
1350
1398
|
}
|
|
1351
1399
|
catch (error) {
|