@capgo/capacitor-social-login 8.2.25 → 8.3.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.
- package/Package.swift +1 -1
- package/README.md +191 -22
- package/android/src/main/java/ee/forgr/capacitor/social/login/OAuth2Provider.java +464 -82
- package/android/src/main/java/ee/forgr/capacitor/social/login/SocialLoginPlugin.java +93 -1
- package/dist/docs.json +317 -5
- package/dist/esm/definitions.d.ts +187 -5
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/oauth2-provider.d.ts +18 -1
- package/dist/esm/oauth2-provider.js +227 -40
- package/dist/esm/oauth2-provider.js.map +1 -1
- package/dist/esm/web.d.ts +37 -2
- package/dist/esm/web.js +77 -17
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +304 -57
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +304 -57
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/SocialLoginPlugin/OAuth2Provider.swift +281 -103
- package/ios/Sources/SocialLoginPlugin/SocialLoginPlugin.swift +129 -1
- package/package.json +7 -7
package/dist/plugin.cjs.js
CHANGED
|
@@ -904,26 +904,106 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
904
904
|
this.TOKENS_KEY_PREFIX = 'capgo_social_login_oauth2_tokens_';
|
|
905
905
|
this.STATE_PREFIX = 'capgo_social_login_oauth2_state_';
|
|
906
906
|
}
|
|
907
|
+
normalizeScopeValue(scope) {
|
|
908
|
+
if (!scope)
|
|
909
|
+
return '';
|
|
910
|
+
if (typeof scope === 'string')
|
|
911
|
+
return scope;
|
|
912
|
+
if (Array.isArray(scope))
|
|
913
|
+
return scope.filter(Boolean).join(' ');
|
|
914
|
+
return '';
|
|
915
|
+
}
|
|
916
|
+
normalizeConfig(providerId, config) {
|
|
917
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
918
|
+
const appId = (_a = config.appId) !== null && _a !== void 0 ? _a : config.clientId;
|
|
919
|
+
const authorizationBaseUrl = (_b = config.authorizationBaseUrl) !== null && _b !== void 0 ? _b : config.authorizationEndpoint;
|
|
920
|
+
const accessTokenEndpoint = (_c = config.accessTokenEndpoint) !== null && _c !== void 0 ? _c : config.tokenEndpoint;
|
|
921
|
+
const logoutUrl = (_d = config.logoutUrl) !== null && _d !== void 0 ? _d : config.endSessionEndpoint;
|
|
922
|
+
const scopeSource = (_e = config.scope) !== null && _e !== void 0 ? _e : config.scopes;
|
|
923
|
+
if (!appId) {
|
|
924
|
+
throw new Error(`OAuth2 provider '${providerId}' requires appId (or clientId).`);
|
|
925
|
+
}
|
|
926
|
+
if (!config.redirectUrl) {
|
|
927
|
+
throw new Error(`OAuth2 provider '${providerId}' requires redirectUrl.`);
|
|
928
|
+
}
|
|
929
|
+
if (!authorizationBaseUrl && !config.issuerUrl) {
|
|
930
|
+
throw new Error(`OAuth2 provider '${providerId}' requires authorizationBaseUrl (or authorizationEndpoint) or issuerUrl.`);
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
appId,
|
|
934
|
+
issuerUrl: config.issuerUrl,
|
|
935
|
+
authorizationBaseUrl,
|
|
936
|
+
accessTokenEndpoint,
|
|
937
|
+
redirectUrl: config.redirectUrl,
|
|
938
|
+
resourceUrl: config.resourceUrl,
|
|
939
|
+
responseType: ((_f = config.responseType) !== null && _f !== void 0 ? _f : 'code'),
|
|
940
|
+
pkceEnabled: (_g = config.pkceEnabled) !== null && _g !== void 0 ? _g : true,
|
|
941
|
+
scope: this.normalizeScopeValue(scopeSource),
|
|
942
|
+
additionalParameters: config.additionalParameters,
|
|
943
|
+
loginHint: config.loginHint,
|
|
944
|
+
prompt: config.prompt,
|
|
945
|
+
additionalTokenParameters: config.additionalTokenParameters,
|
|
946
|
+
additionalResourceHeaders: config.additionalResourceHeaders,
|
|
947
|
+
logoutUrl,
|
|
948
|
+
postLogoutRedirectUrl: config.postLogoutRedirectUrl,
|
|
949
|
+
additionalLogoutParameters: config.additionalLogoutParameters,
|
|
950
|
+
logsEnabled: (_h = config.logsEnabled) !== null && _h !== void 0 ? _h : false,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
async ensureDiscovered(providerId) {
|
|
954
|
+
const config = this.providers.get(providerId);
|
|
955
|
+
if (!(config === null || config === void 0 ? void 0 : config.issuerUrl))
|
|
956
|
+
return;
|
|
957
|
+
// Resolve endpoints lazily.
|
|
958
|
+
if (config.authorizationBaseUrl && config.accessTokenEndpoint)
|
|
959
|
+
return;
|
|
960
|
+
const issuer = config.issuerUrl.replace(/\/+$/, '');
|
|
961
|
+
const discoveryUrl = `${issuer}/.well-known/openid-configuration`;
|
|
962
|
+
const resp = await fetch(discoveryUrl);
|
|
963
|
+
if (!resp.ok) {
|
|
964
|
+
const text = await resp.text().catch(() => '');
|
|
965
|
+
throw new Error(`OAuth2 discovery failed (${resp.status}): ${text || discoveryUrl}`);
|
|
966
|
+
}
|
|
967
|
+
const payload = (await resp.json());
|
|
968
|
+
const authorizationEndpoint = payload['authorization_endpoint'];
|
|
969
|
+
const tokenEndpoint = payload['token_endpoint'];
|
|
970
|
+
const endSessionEndpoint = payload['end_session_endpoint'];
|
|
971
|
+
if (!config.authorizationBaseUrl && typeof authorizationEndpoint === 'string') {
|
|
972
|
+
config.authorizationBaseUrl = authorizationEndpoint;
|
|
973
|
+
}
|
|
974
|
+
if (!config.accessTokenEndpoint && typeof tokenEndpoint === 'string') {
|
|
975
|
+
config.accessTokenEndpoint = tokenEndpoint;
|
|
976
|
+
}
|
|
977
|
+
if (!config.logoutUrl && typeof endSessionEndpoint === 'string') {
|
|
978
|
+
config.logoutUrl = endSessionEndpoint;
|
|
979
|
+
}
|
|
980
|
+
if (config.logsEnabled) {
|
|
981
|
+
console.log(`[OAuth2:${providerId}] Discovery resolved`, {
|
|
982
|
+
authorizationBaseUrl: config.authorizationBaseUrl,
|
|
983
|
+
accessTokenEndpoint: config.accessTokenEndpoint,
|
|
984
|
+
logoutUrl: config.logoutUrl,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
907
988
|
/**
|
|
908
989
|
* Initialize multiple OAuth2 providers
|
|
909
990
|
*/
|
|
910
991
|
async initializeProviders(configs) {
|
|
911
|
-
var _a, _b, _c, _d;
|
|
912
992
|
for (const [providerId, config] of Object.entries(configs)) {
|
|
913
|
-
|
|
914
|
-
throw new Error(`OAuth2 provider '${providerId}' requires appId, authorizationBaseUrl, and redirectUrl`);
|
|
915
|
-
}
|
|
916
|
-
const internalConfig = Object.assign(Object.assign({}, config), { responseType: (_a = config.responseType) !== null && _a !== void 0 ? _a : 'code', pkceEnabled: (_b = config.pkceEnabled) !== null && _b !== void 0 ? _b : true, scope: (_c = config.scope) !== null && _c !== void 0 ? _c : '', logsEnabled: (_d = config.logsEnabled) !== null && _d !== void 0 ? _d : false });
|
|
993
|
+
const internalConfig = this.normalizeConfig(providerId, config);
|
|
917
994
|
this.providers.set(providerId, internalConfig);
|
|
918
995
|
if (internalConfig.logsEnabled) {
|
|
919
996
|
console.log(`[OAuth2:${providerId}] Initialized with config:`, {
|
|
920
|
-
appId:
|
|
921
|
-
|
|
922
|
-
|
|
997
|
+
appId: internalConfig.appId,
|
|
998
|
+
issuerUrl: internalConfig.issuerUrl,
|
|
999
|
+
authorizationBaseUrl: internalConfig.authorizationBaseUrl,
|
|
1000
|
+
redirectUrl: internalConfig.redirectUrl,
|
|
923
1001
|
responseType: internalConfig.responseType,
|
|
924
1002
|
pkceEnabled: internalConfig.pkceEnabled,
|
|
925
1003
|
});
|
|
926
1004
|
}
|
|
1005
|
+
// Pre-resolve discovery on web if issuerUrl is provided.
|
|
1006
|
+
await this.ensureDiscovered(providerId);
|
|
927
1007
|
}
|
|
928
1008
|
}
|
|
929
1009
|
getProvider(providerId) {
|
|
@@ -937,13 +1017,14 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
937
1017
|
return `${this.TOKENS_KEY_PREFIX}${providerId}`;
|
|
938
1018
|
}
|
|
939
1019
|
async login(options) {
|
|
940
|
-
var _a, _b, _c, _d;
|
|
1020
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
941
1021
|
const { providerId } = options;
|
|
942
1022
|
const config = this.getProvider(providerId);
|
|
1023
|
+
await this.ensureDiscovered(providerId);
|
|
943
1024
|
const redirectUri = (_a = options.redirectUrl) !== null && _a !== void 0 ? _a : config.redirectUrl;
|
|
944
|
-
const scope = (_b = options.scope) !== null && _b !== void 0 ? _b : config.scope;
|
|
945
|
-
const state = (
|
|
946
|
-
const codeVerifier = (
|
|
1025
|
+
const scope = this.normalizeScopeValue((_c = (_b = options.scope) !== null && _b !== void 0 ? _b : options.scopes) !== null && _c !== void 0 ? _c : config.scope);
|
|
1026
|
+
const state = (_d = options.state) !== null && _d !== void 0 ? _d : this.generateState();
|
|
1027
|
+
const codeVerifier = (_e = options.codeVerifier) !== null && _e !== void 0 ? _e : this.generateCodeVerifier();
|
|
947
1028
|
// Build authorization URL
|
|
948
1029
|
const params = new URLSearchParams({
|
|
949
1030
|
response_type: config.responseType,
|
|
@@ -954,21 +1035,25 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
954
1035
|
if (scope) {
|
|
955
1036
|
params.set('scope', scope);
|
|
956
1037
|
}
|
|
1038
|
+
// Convenience OIDC options
|
|
1039
|
+
const mergedAdditionalParams = Object.assign(Object.assign({}, ((_f = config.additionalParameters) !== null && _f !== void 0 ? _f : {})), ((_g = options.additionalParameters) !== null && _g !== void 0 ? _g : {}));
|
|
1040
|
+
const loginHint = (_h = options.loginHint) !== null && _h !== void 0 ? _h : config.loginHint;
|
|
1041
|
+
const prompt = (_j = options.prompt) !== null && _j !== void 0 ? _j : config.prompt;
|
|
1042
|
+
if (loginHint && !('login_hint' in mergedAdditionalParams)) {
|
|
1043
|
+
mergedAdditionalParams.login_hint = loginHint;
|
|
1044
|
+
}
|
|
1045
|
+
if (prompt && !('prompt' in mergedAdditionalParams)) {
|
|
1046
|
+
mergedAdditionalParams.prompt = prompt;
|
|
1047
|
+
}
|
|
957
1048
|
// Add PKCE for code flow
|
|
958
1049
|
if (config.responseType === 'code' && config.pkceEnabled) {
|
|
959
1050
|
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
960
1051
|
params.set('code_challenge', codeChallenge);
|
|
961
1052
|
params.set('code_challenge_method', 'S256');
|
|
962
1053
|
}
|
|
963
|
-
// Add additional parameters
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
params.set(key, value);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
// Add additional parameters from login options
|
|
970
|
-
if (options.additionalParameters) {
|
|
971
|
-
for (const [key, value] of Object.entries(options.additionalParameters)) {
|
|
1054
|
+
// Add merged additional parameters
|
|
1055
|
+
for (const [key, value] of Object.entries(mergedAdditionalParams)) {
|
|
1056
|
+
if (value !== undefined) {
|
|
972
1057
|
params.set(key, value);
|
|
973
1058
|
}
|
|
974
1059
|
}
|
|
@@ -980,10 +1065,19 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
980
1065
|
scope,
|
|
981
1066
|
});
|
|
982
1067
|
localStorage.setItem(BaseSocialLogin.OAUTH_STATE_KEY, JSON.stringify({ provider: 'oauth2', providerId, state }));
|
|
1068
|
+
if (!config.authorizationBaseUrl) {
|
|
1069
|
+
throw new Error(`OAuth2 provider '${providerId}' is missing authorizationBaseUrl (discovery may have failed).`);
|
|
1070
|
+
}
|
|
983
1071
|
const authUrl = `${config.authorizationBaseUrl}?${params.toString()}`;
|
|
984
1072
|
if (config.logsEnabled) {
|
|
985
1073
|
console.log(`[OAuth2:${providerId}] Opening authorization URL:`, authUrl);
|
|
986
1074
|
}
|
|
1075
|
+
if (options.flow === 'redirect') {
|
|
1076
|
+
// Trigger a full-page redirect. The promise will not resolve because the page navigates away.
|
|
1077
|
+
window.location.assign(authUrl);
|
|
1078
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
1079
|
+
return new Promise(() => { });
|
|
1080
|
+
}
|
|
987
1081
|
// Open popup window
|
|
988
1082
|
const width = 500;
|
|
989
1083
|
const height = 650;
|
|
@@ -1089,11 +1183,31 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1089
1183
|
});
|
|
1090
1184
|
}
|
|
1091
1185
|
async logout(providerId) {
|
|
1186
|
+
await this.ensureDiscovered(providerId);
|
|
1092
1187
|
const config = this.providers.get(providerId);
|
|
1188
|
+
const stored = this.getStoredTokens(providerId);
|
|
1093
1189
|
localStorage.removeItem(this.getTokensKey(providerId));
|
|
1094
|
-
// If logout URL is configured,
|
|
1190
|
+
// If logout URL is configured, build an end-session URL (OIDC) when possible.
|
|
1095
1191
|
if (config === null || config === void 0 ? void 0 : config.logoutUrl) {
|
|
1096
|
-
|
|
1192
|
+
try {
|
|
1193
|
+
const url = new URL(config.logoutUrl);
|
|
1194
|
+
if (stored === null || stored === void 0 ? void 0 : stored.idToken) {
|
|
1195
|
+
url.searchParams.set('id_token_hint', stored.idToken);
|
|
1196
|
+
}
|
|
1197
|
+
const postLogout = config.postLogoutRedirectUrl;
|
|
1198
|
+
if (postLogout) {
|
|
1199
|
+
url.searchParams.set('post_logout_redirect_uri', postLogout);
|
|
1200
|
+
}
|
|
1201
|
+
if (config.additionalLogoutParameters) {
|
|
1202
|
+
for (const [k, v] of Object.entries(config.additionalLogoutParameters)) {
|
|
1203
|
+
url.searchParams.set(k, v);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
window.open(url.toString(), '_blank');
|
|
1207
|
+
}
|
|
1208
|
+
catch (_a) {
|
|
1209
|
+
window.open(config.logoutUrl, '_blank');
|
|
1210
|
+
}
|
|
1097
1211
|
}
|
|
1098
1212
|
}
|
|
1099
1213
|
async isLoggedIn(providerId) {
|
|
@@ -1118,15 +1232,52 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1118
1232
|
};
|
|
1119
1233
|
}
|
|
1120
1234
|
async refresh(providerId) {
|
|
1121
|
-
|
|
1122
|
-
|
|
1235
|
+
await this.refreshToken(providerId);
|
|
1236
|
+
}
|
|
1237
|
+
async refreshToken(providerId, refreshToken, additionalParameters) {
|
|
1238
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1239
|
+
await this.ensureDiscovered(providerId);
|
|
1240
|
+
const config = this.getProvider(providerId);
|
|
1241
|
+
const stored = this.getStoredTokens(providerId);
|
|
1242
|
+
const effectiveRefreshToken = refreshToken !== null && refreshToken !== void 0 ? refreshToken : stored === null || stored === void 0 ? void 0 : stored.refreshToken;
|
|
1243
|
+
if (!effectiveRefreshToken) {
|
|
1123
1244
|
throw new Error(`No OAuth2 refresh token is available for provider '${providerId}'. Include offline_access scope to receive one.`);
|
|
1124
1245
|
}
|
|
1125
|
-
const config = this.getProvider(providerId);
|
|
1126
1246
|
if (!config.accessTokenEndpoint) {
|
|
1127
1247
|
throw new Error(`No accessTokenEndpoint configured for provider '${providerId}'.`);
|
|
1128
1248
|
}
|
|
1129
|
-
await this.refreshWithRefreshToken(providerId,
|
|
1249
|
+
const tokenResponse = await this.refreshWithRefreshToken(providerId, effectiveRefreshToken, additionalParameters);
|
|
1250
|
+
const expiresAt = tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1000 : Date.now() + 3600000;
|
|
1251
|
+
const scopeArray = (_c = (_b = (_a = tokenResponse.scope) === null || _a === void 0 ? void 0 : _a.split(' ').filter(Boolean)) !== null && _b !== void 0 ? _b : stored === null || stored === void 0 ? void 0 : stored.scope) !== null && _c !== void 0 ? _c : [];
|
|
1252
|
+
// Fetch resource data if configured
|
|
1253
|
+
let resourceData = null;
|
|
1254
|
+
if (config.resourceUrl) {
|
|
1255
|
+
resourceData = await this.fetchResource(providerId, tokenResponse.access_token);
|
|
1256
|
+
}
|
|
1257
|
+
const nextRefreshToken = (_d = tokenResponse.refresh_token) !== null && _d !== void 0 ? _d : effectiveRefreshToken;
|
|
1258
|
+
this.persistTokens(providerId, {
|
|
1259
|
+
accessToken: tokenResponse.access_token,
|
|
1260
|
+
refreshToken: nextRefreshToken,
|
|
1261
|
+
idToken: tokenResponse.id_token,
|
|
1262
|
+
expiresAt,
|
|
1263
|
+
scope: scopeArray,
|
|
1264
|
+
tokenType: tokenResponse.token_type,
|
|
1265
|
+
});
|
|
1266
|
+
return {
|
|
1267
|
+
providerId,
|
|
1268
|
+
accessToken: {
|
|
1269
|
+
token: tokenResponse.access_token,
|
|
1270
|
+
tokenType: tokenResponse.token_type,
|
|
1271
|
+
expires: new Date(expiresAt).toISOString(),
|
|
1272
|
+
refreshToken: nextRefreshToken,
|
|
1273
|
+
},
|
|
1274
|
+
idToken: (_e = tokenResponse.id_token) !== null && _e !== void 0 ? _e : null,
|
|
1275
|
+
refreshToken: nextRefreshToken !== null && nextRefreshToken !== void 0 ? nextRefreshToken : null,
|
|
1276
|
+
resourceData,
|
|
1277
|
+
scope: scopeArray,
|
|
1278
|
+
tokenType: tokenResponse.token_type,
|
|
1279
|
+
expiresIn: (_f = tokenResponse.expires_in) !== null && _f !== void 0 ? _f : null,
|
|
1280
|
+
};
|
|
1130
1281
|
}
|
|
1131
1282
|
async handleOAuthRedirect(url, expectedState) {
|
|
1132
1283
|
var _a, _b, _c, _d, _e;
|
|
@@ -1147,6 +1298,7 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1147
1298
|
return { error: 'OAuth2 login session expired or state mismatch.' };
|
|
1148
1299
|
}
|
|
1149
1300
|
const { providerId } = pending;
|
|
1301
|
+
await this.ensureDiscovered(providerId);
|
|
1150
1302
|
const config = this.providers.get(providerId);
|
|
1151
1303
|
if (!config) {
|
|
1152
1304
|
localStorage.removeItem(BaseSocialLogin.OAUTH_STATE_KEY);
|
|
@@ -1241,6 +1393,11 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1241
1393
|
if (config.pkceEnabled) {
|
|
1242
1394
|
params.set('code_verifier', pending.codeVerifier);
|
|
1243
1395
|
}
|
|
1396
|
+
if (config.additionalTokenParameters) {
|
|
1397
|
+
for (const [k, v] of Object.entries(config.additionalTokenParameters)) {
|
|
1398
|
+
params.set(k, v);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1244
1401
|
if (config.logsEnabled) {
|
|
1245
1402
|
console.log(`[OAuth2:${providerId}] Exchanging code at:`, config.accessTokenEndpoint);
|
|
1246
1403
|
}
|
|
@@ -1257,8 +1414,7 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1257
1414
|
}
|
|
1258
1415
|
return (await response.json());
|
|
1259
1416
|
}
|
|
1260
|
-
async refreshWithRefreshToken(providerId, refreshToken) {
|
|
1261
|
-
var _a, _b, _c;
|
|
1417
|
+
async refreshWithRefreshToken(providerId, refreshToken, additionalParameters) {
|
|
1262
1418
|
const config = this.getProvider(providerId);
|
|
1263
1419
|
if (!config.accessTokenEndpoint) {
|
|
1264
1420
|
throw new Error(`No accessTokenEndpoint configured for provider '${providerId}'.`);
|
|
@@ -1268,6 +1424,16 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1268
1424
|
refresh_token: refreshToken,
|
|
1269
1425
|
client_id: config.appId,
|
|
1270
1426
|
});
|
|
1427
|
+
if (config.additionalTokenParameters) {
|
|
1428
|
+
for (const [k, v] of Object.entries(config.additionalTokenParameters)) {
|
|
1429
|
+
params.set(k, v);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (additionalParameters) {
|
|
1433
|
+
for (const [k, v] of Object.entries(additionalParameters)) {
|
|
1434
|
+
params.set(k, v);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1271
1437
|
const response = await fetch(config.accessTokenEndpoint, {
|
|
1272
1438
|
method: 'POST',
|
|
1273
1439
|
headers: {
|
|
@@ -1279,17 +1445,7 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1279
1445
|
const text = await response.text();
|
|
1280
1446
|
throw new Error(`OAuth2 refresh failed (${response.status}): ${text}`);
|
|
1281
1447
|
}
|
|
1282
|
-
|
|
1283
|
-
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : Date.now() + 3600000;
|
|
1284
|
-
const scopeArray = (_b = (_a = tokens.scope) === null || _a === void 0 ? void 0 : _a.split(' ').filter(Boolean)) !== null && _b !== void 0 ? _b : [];
|
|
1285
|
-
this.persistTokens(providerId, {
|
|
1286
|
-
accessToken: tokens.access_token,
|
|
1287
|
-
refreshToken: (_c = tokens.refresh_token) !== null && _c !== void 0 ? _c : refreshToken,
|
|
1288
|
-
idToken: tokens.id_token,
|
|
1289
|
-
expiresAt,
|
|
1290
|
-
scope: scopeArray,
|
|
1291
|
-
tokenType: tokens.token_type,
|
|
1292
|
-
});
|
|
1448
|
+
return (await response.json());
|
|
1293
1449
|
}
|
|
1294
1450
|
async fetchResource(providerId, accessToken) {
|
|
1295
1451
|
const config = this.getProvider(providerId);
|
|
@@ -1366,6 +1522,37 @@ class OAuth2SocialLogin extends BaseSocialLogin {
|
|
|
1366
1522
|
buffer.forEach((b) => (binary += String.fromCharCode(b)));
|
|
1367
1523
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
1368
1524
|
}
|
|
1525
|
+
decodeIdToken(idToken) {
|
|
1526
|
+
const parts = idToken.split('.');
|
|
1527
|
+
if (parts.length < 2) {
|
|
1528
|
+
throw new Error('Invalid JWT: missing parts');
|
|
1529
|
+
}
|
|
1530
|
+
const payload = parts[1];
|
|
1531
|
+
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
1532
|
+
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
|
|
1533
|
+
const json = atob(padded);
|
|
1534
|
+
return JSON.parse(json);
|
|
1535
|
+
}
|
|
1536
|
+
getAccessTokenExpirationDate(providerId) {
|
|
1537
|
+
const tokens = this.getStoredTokens(providerId);
|
|
1538
|
+
if (!(tokens === null || tokens === void 0 ? void 0 : tokens.expiresAt))
|
|
1539
|
+
return { expirationDate: null };
|
|
1540
|
+
return { expirationDate: new Date(tokens.expiresAt).toISOString() };
|
|
1541
|
+
}
|
|
1542
|
+
isAccessTokenAvailable(providerId) {
|
|
1543
|
+
const tokens = this.getStoredTokens(providerId);
|
|
1544
|
+
return { isAvailable: !!(tokens === null || tokens === void 0 ? void 0 : tokens.accessToken) };
|
|
1545
|
+
}
|
|
1546
|
+
isAccessTokenExpired(providerId) {
|
|
1547
|
+
const tokens = this.getStoredTokens(providerId);
|
|
1548
|
+
if (!(tokens === null || tokens === void 0 ? void 0 : tokens.expiresAt))
|
|
1549
|
+
return { isExpired: true };
|
|
1550
|
+
return { isExpired: tokens.expiresAt <= Date.now() };
|
|
1551
|
+
}
|
|
1552
|
+
isRefreshTokenAvailable(providerId) {
|
|
1553
|
+
const tokens = this.getStoredTokens(providerId);
|
|
1554
|
+
return { isAvailable: !!(tokens === null || tokens === void 0 ? void 0 : tokens.refreshToken) };
|
|
1555
|
+
}
|
|
1369
1556
|
}
|
|
1370
1557
|
|
|
1371
1558
|
var __rest = (undefined && undefined.__rest) || function (s, e) {
|
|
@@ -1757,16 +1944,23 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
1757
1944
|
this.facebookProvider = new FacebookSocialLogin();
|
|
1758
1945
|
this.twitterProvider = new TwitterSocialLogin();
|
|
1759
1946
|
this.oauth2Provider = new OAuth2SocialLogin();
|
|
1760
|
-
//
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1947
|
+
// Auto-finish OAuth redirects only when running inside a popup window.
|
|
1948
|
+
// For redirect-based flows (full page navigation), the app should call `handleRedirectCallback()` explicitly.
|
|
1949
|
+
const hasPending = !!localStorage.getItem(SocialLoginWeb.OAUTH_STATE_KEY);
|
|
1950
|
+
const isPopup = !!window.opener || SocialLoginWeb.POPUP_WINDOW_NAMES.has(window.name);
|
|
1951
|
+
if (hasPending && isPopup) {
|
|
1952
|
+
this.finishOAuthRedirectInPopup().catch((error) => {
|
|
1764
1953
|
console.error('Failed to finish OAuth redirect', error);
|
|
1765
|
-
|
|
1954
|
+
try {
|
|
1955
|
+
window.close();
|
|
1956
|
+
}
|
|
1957
|
+
catch (_a) {
|
|
1958
|
+
// ignore
|
|
1959
|
+
}
|
|
1766
1960
|
});
|
|
1767
1961
|
}
|
|
1768
1962
|
}
|
|
1769
|
-
async
|
|
1963
|
+
async parseRedirectResult() {
|
|
1770
1964
|
var _a;
|
|
1771
1965
|
const url = new URL(window.location.href);
|
|
1772
1966
|
const stateRaw = localStorage.getItem(SocialLoginWeb.OAUTH_STATE_KEY);
|
|
@@ -1797,13 +1991,18 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
1797
1991
|
result = this.googleProvider.handleOAuthRedirect(url);
|
|
1798
1992
|
break;
|
|
1799
1993
|
}
|
|
1800
|
-
|
|
1994
|
+
return { provider, state, nonce, result };
|
|
1995
|
+
}
|
|
1996
|
+
async finishOAuthRedirectInPopup() {
|
|
1997
|
+
var _a;
|
|
1998
|
+
const parsed = await this.parseRedirectResult();
|
|
1999
|
+
const result = parsed.result;
|
|
2000
|
+
if (!result)
|
|
1801
2001
|
return;
|
|
1802
|
-
}
|
|
1803
2002
|
// Build the message to send
|
|
1804
2003
|
let message;
|
|
1805
2004
|
if ('error' in result) {
|
|
1806
|
-
const resolvedProvider = provider !== null &&
|
|
2005
|
+
const resolvedProvider = (_a = parsed.provider) !== null && _a !== void 0 ? _a : null;
|
|
1807
2006
|
message = {
|
|
1808
2007
|
type: 'oauth-error',
|
|
1809
2008
|
provider: resolvedProvider,
|
|
@@ -1819,7 +2018,7 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
1819
2018
|
window.opener.postMessage(message, window.location.origin);
|
|
1820
2019
|
}
|
|
1821
2020
|
}
|
|
1822
|
-
catch (
|
|
2021
|
+
catch (_b) {
|
|
1823
2022
|
// Cross-origin error - window.opener may not be accessible
|
|
1824
2023
|
console.log('postMessage to opener failed, using BroadcastChannel');
|
|
1825
2024
|
}
|
|
@@ -1828,14 +2027,14 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
1828
2027
|
try {
|
|
1829
2028
|
// Determine the channel name based on provider and state/nonce
|
|
1830
2029
|
let channelName = null;
|
|
1831
|
-
if (provider === 'oauth2' && state) {
|
|
1832
|
-
channelName = `oauth2_${state}`;
|
|
2030
|
+
if (parsed.provider === 'oauth2' && parsed.state) {
|
|
2031
|
+
channelName = `oauth2_${parsed.state}`;
|
|
1833
2032
|
}
|
|
1834
|
-
else if (provider === 'twitter' && state) {
|
|
1835
|
-
channelName = `twitter_oauth_${state}`;
|
|
2033
|
+
else if (parsed.provider === 'twitter' && parsed.state) {
|
|
2034
|
+
channelName = `twitter_oauth_${parsed.state}`;
|
|
1836
2035
|
}
|
|
1837
|
-
else if (provider === 'google' && nonce) {
|
|
1838
|
-
channelName = `google_oauth_${nonce}`;
|
|
2036
|
+
else if (parsed.provider === 'google' && parsed.nonce) {
|
|
2037
|
+
channelName = `google_oauth_${parsed.nonce}`;
|
|
1839
2038
|
}
|
|
1840
2039
|
if (channelName) {
|
|
1841
2040
|
const channel = new BroadcastChannel(channelName);
|
|
@@ -1843,7 +2042,7 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
1843
2042
|
channel.close();
|
|
1844
2043
|
}
|
|
1845
2044
|
}
|
|
1846
|
-
catch (
|
|
2045
|
+
catch (_c) {
|
|
1847
2046
|
// BroadcastChannel not supported or other error
|
|
1848
2047
|
console.log('BroadcastChannel not available');
|
|
1849
2048
|
}
|
|
@@ -1966,6 +2165,53 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
1966
2165
|
async providerSpecificCall(options) {
|
|
1967
2166
|
throw new Error(`Provider specific call for ${options.call} is not implemented`);
|
|
1968
2167
|
}
|
|
2168
|
+
async refreshToken(options) {
|
|
2169
|
+
if (options.provider !== 'oauth2') {
|
|
2170
|
+
throw new Error('refreshToken is only implemented for oauth2 on web');
|
|
2171
|
+
}
|
|
2172
|
+
return this.oauth2Provider.refreshToken(options.providerId, options.refreshToken, options.additionalParameters);
|
|
2173
|
+
}
|
|
2174
|
+
async handleRedirectCallback() {
|
|
2175
|
+
const parsed = await this.parseRedirectResult();
|
|
2176
|
+
const result = parsed.result;
|
|
2177
|
+
if (!result)
|
|
2178
|
+
return null;
|
|
2179
|
+
if ('error' in result) {
|
|
2180
|
+
throw new Error(result.error);
|
|
2181
|
+
}
|
|
2182
|
+
return result;
|
|
2183
|
+
}
|
|
2184
|
+
async decodeIdToken(options) {
|
|
2185
|
+
var _a;
|
|
2186
|
+
const token = (_a = options === null || options === void 0 ? void 0 : options.idToken) !== null && _a !== void 0 ? _a : options === null || options === void 0 ? void 0 : options.token;
|
|
2187
|
+
if (!token) {
|
|
2188
|
+
throw new Error('idToken (or token) is required');
|
|
2189
|
+
}
|
|
2190
|
+
const claims = this.oauth2Provider.decodeIdToken(token);
|
|
2191
|
+
return { claims };
|
|
2192
|
+
}
|
|
2193
|
+
async getAccessTokenExpirationDate(options) {
|
|
2194
|
+
if (typeof (options === null || options === void 0 ? void 0 : options.accessTokenExpirationDate) !== 'number') {
|
|
2195
|
+
throw new Error('accessTokenExpirationDate is required');
|
|
2196
|
+
}
|
|
2197
|
+
return { date: new Date(options.accessTokenExpirationDate).toISOString() };
|
|
2198
|
+
}
|
|
2199
|
+
async isAccessTokenAvailable(options) {
|
|
2200
|
+
var _a;
|
|
2201
|
+
const token = (_a = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _a !== void 0 ? _a : null;
|
|
2202
|
+
return { isAvailable: typeof token === 'string' && token.length > 0 };
|
|
2203
|
+
}
|
|
2204
|
+
async isAccessTokenExpired(options) {
|
|
2205
|
+
if (typeof (options === null || options === void 0 ? void 0 : options.accessTokenExpirationDate) !== 'number') {
|
|
2206
|
+
throw new Error('accessTokenExpirationDate is required');
|
|
2207
|
+
}
|
|
2208
|
+
return { isExpired: options.accessTokenExpirationDate <= Date.now() };
|
|
2209
|
+
}
|
|
2210
|
+
async isRefreshTokenAvailable(options) {
|
|
2211
|
+
var _a;
|
|
2212
|
+
const token = (_a = options === null || options === void 0 ? void 0 : options.refreshToken) !== null && _a !== void 0 ? _a : null;
|
|
2213
|
+
return { isAvailable: typeof token === 'string' && token.length > 0 };
|
|
2214
|
+
}
|
|
1969
2215
|
async getPluginVersion() {
|
|
1970
2216
|
return { version: 'web' };
|
|
1971
2217
|
}
|
|
@@ -2004,6 +2250,7 @@ class SocialLoginWeb extends core.WebPlugin {
|
|
|
2004
2250
|
}
|
|
2005
2251
|
}
|
|
2006
2252
|
SocialLoginWeb.OAUTH_STATE_KEY = 'social_login_oauth_pending';
|
|
2253
|
+
SocialLoginWeb.POPUP_WINDOW_NAMES = new Set(['OAuth2Login', 'XLogin', 'Google Sign In', 'Authorization']);
|
|
2007
2254
|
|
|
2008
2255
|
var web = /*#__PURE__*/Object.freeze({
|
|
2009
2256
|
__proto__: null,
|