@hxa-rn/rnaa 8.1.0
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/LICENSE +21 -0
- package/README.md +159 -0
- package/harmony/rnaa/LICENSE +21 -0
- package/harmony/rnaa/NOTICE +33 -0
- package/harmony/rnaa/OAT.xml +38 -0
- package/harmony/rnaa/build-profile.json5 +19 -0
- package/harmony/rnaa/hvigorfile.ts +2 -0
- package/harmony/rnaa/index.ets +1 -0
- package/harmony/rnaa/oh-package.json5 +14 -0
- package/harmony/rnaa/src/main/cpp/CMakeLists.txt +15 -0
- package/harmony/rnaa/src/main/cpp/RNAppAuthPackage.h +13 -0
- package/harmony/rnaa/src/main/cpp/generated/RNOH/generated/BaseRnaaPackage.h +72 -0
- package/harmony/rnaa/src/main/cpp/generated/RNOH/generated/turbo_modules/RNAppAuth.cpp +22 -0
- package/harmony/rnaa/src/main/cpp/generated/RNOH/generated/turbo_modules/RNAppAuth.h +16 -0
- package/harmony/rnaa/src/main/ets/DateUtil.ets +28 -0
- package/harmony/rnaa/src/main/ets/OAuthHttpClient.ets +293 -0
- package/harmony/rnaa/src/main/ets/OAuthProtocol.ets +293 -0
- package/harmony/rnaa/src/main/ets/PKCE.ets +105 -0
- package/harmony/rnaa/src/main/ets/RNAppAuthTurboModule.ets +713 -0
- package/harmony/rnaa/src/main/ets/RNAppAuthTurboModulesFactory.ets +18 -0
- package/harmony/rnaa/src/main/ets/Types.ets +98 -0
- package/harmony/rnaa/src/main/ets/generated/index.ets +5 -0
- package/harmony/rnaa/src/main/ets/generated/ts.ts +5 -0
- package/harmony/rnaa/src/main/ets/generated/turboModules/RNAppAuth.ts +38 -0
- package/harmony/rnaa/src/main/ets/generated/turboModules/ts.ts +5 -0
- package/harmony/rnaa/src/main/module.json5 +16 -0
- package/harmony/rnaa/src/main/resources/base/element/string.json +8 -0
- package/harmony/rnaa/src/main/resources/en_US/element/string.json +8 -0
- package/harmony/rnaa/src/main/resources/zh_CN/element/string.json +8 -0
- package/harmony/rnaa.har +0 -0
- package/package.json +69 -0
- package/src/index.d.ts +202 -0
- package/src/index.js +596 -0
- package/src/specs/v1/.gitkeep +1 -0
- package/src/specs/v1/NativeRNAppAuth.ts +138 -0
- package/src/specs/v2/.gitkeep +1 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import NativeRNAppAuth from './specs/v1/NativeRNAppAuth';
|
|
2
|
+
import { AppState, DeviceEventEmitter, Platform } from 'react-native';
|
|
3
|
+
import base64 from 'react-native-base64';
|
|
4
|
+
|
|
5
|
+
export const CONFIGURATION_ERROR_CODE = 'configuration_error';
|
|
6
|
+
|
|
7
|
+
const assertConfig = (condition, message) => {
|
|
8
|
+
if (!condition) {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.code = CONFIGURATION_ERROR_CODE;
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const validateIssuer = issuer => typeof issuer === 'string' && issuer.length > 0;
|
|
16
|
+
const hasServiceEndpoints = serviceConfiguration =>
|
|
17
|
+
serviceConfiguration &&
|
|
18
|
+
typeof serviceConfiguration.authorizationEndpoint === 'string' &&
|
|
19
|
+
serviceConfiguration.authorizationEndpoint.length > 0 &&
|
|
20
|
+
typeof serviceConfiguration.tokenEndpoint === 'string' &&
|
|
21
|
+
serviceConfiguration.tokenEndpoint.length > 0;
|
|
22
|
+
const validateIssuerOrServiceConfigurationEndpoints = (issuer, serviceConfiguration) => {
|
|
23
|
+
assertConfig(
|
|
24
|
+
validateIssuer(issuer) || hasServiceEndpoints(serviceConfiguration),
|
|
25
|
+
'Config error: you must provide either an issuer or a service endpoints'
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
const validateIssuerOrServiceConfigurationRegistrationEndpoint = (issuer, serviceConfiguration) =>
|
|
29
|
+
assertConfig(
|
|
30
|
+
validateIssuer(issuer) ||
|
|
31
|
+
(serviceConfiguration &&
|
|
32
|
+
typeof serviceConfiguration.registrationEndpoint === 'string' &&
|
|
33
|
+
serviceConfiguration.registrationEndpoint.length > 0),
|
|
34
|
+
'Config error: you must provide either an issuer or a registration endpoint'
|
|
35
|
+
);
|
|
36
|
+
const validateIssuerOrServiceConfigurationRevocationEndpoint = (issuer, serviceConfiguration) =>
|
|
37
|
+
assertConfig(
|
|
38
|
+
validateIssuer(issuer) ||
|
|
39
|
+
(serviceConfiguration &&
|
|
40
|
+
typeof serviceConfiguration.revocationEndpoint === 'string' &&
|
|
41
|
+
serviceConfiguration.revocationEndpoint.length > 0),
|
|
42
|
+
'Config error: you must provide either an issuer or a revocation endpoint'
|
|
43
|
+
);
|
|
44
|
+
const validateIssuerOrServiceConfigurationEndSessionEndpoint = (issuer, serviceConfiguration) =>
|
|
45
|
+
assertConfig(
|
|
46
|
+
validateIssuer(issuer) ||
|
|
47
|
+
(serviceConfiguration &&
|
|
48
|
+
typeof serviceConfiguration.endSessionEndpoint === 'string' &&
|
|
49
|
+
serviceConfiguration.endSessionEndpoint.length > 0),
|
|
50
|
+
'Config error: you must provide either an issuer or an end session endpoint'
|
|
51
|
+
);
|
|
52
|
+
const validateClientId = clientId =>
|
|
53
|
+
assertConfig(typeof clientId === 'string', 'Config error: clientId must be a string');
|
|
54
|
+
const validateRedirectUrl = redirectUrl =>
|
|
55
|
+
assertConfig(typeof redirectUrl === 'string', 'Config error: redirectUrl must be a string');
|
|
56
|
+
|
|
57
|
+
const validateHeaders = headers => {
|
|
58
|
+
if (!headers) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const customHeaderTypeErrorMessage =
|
|
62
|
+
'Config error: customHeaders type must be { token?: { [key: string]: string }, authorize?: { [key: string]: string }, register: { [key: string]: string }}';
|
|
63
|
+
|
|
64
|
+
const authorizedKeys = ['token', 'authorize', 'register'];
|
|
65
|
+
const keys = Object.keys(headers);
|
|
66
|
+
const correctKeys = keys.filter(key => authorizedKeys.includes(key));
|
|
67
|
+
assertConfig(
|
|
68
|
+
keys.length <= authorizedKeys.length &&
|
|
69
|
+
correctKeys.length > 0 &&
|
|
70
|
+
correctKeys.length === keys.length,
|
|
71
|
+
customHeaderTypeErrorMessage
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
Object.values(headers).forEach(value => {
|
|
75
|
+
assertConfig(typeof value === 'object', customHeaderTypeErrorMessage);
|
|
76
|
+
assertConfig(
|
|
77
|
+
Object.values(value).filter(key => typeof key !== 'string').length === 0,
|
|
78
|
+
customHeaderTypeErrorMessage
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const validateAdditionalHeaders = headers => {
|
|
84
|
+
if (!headers) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const errorMessage = 'Config error: additionalHeaders must be { [key: string]: string }';
|
|
89
|
+
|
|
90
|
+
assertConfig(typeof headers === 'object', errorMessage);
|
|
91
|
+
assertConfig(
|
|
92
|
+
Object.values(headers).filter(key => typeof key !== 'string').length === 0,
|
|
93
|
+
errorMessage
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const validateConnectionTimeoutSeconds = timeout => {
|
|
98
|
+
if (!timeout) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
assertConfig(typeof timeout === 'number', 'Config error: connectionTimeoutSeconds must be a number');
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const SECOND_IN_MS = 1000;
|
|
106
|
+
export const DEFAULT_TIMEOUT_IOS = 60;
|
|
107
|
+
export const DEFAULT_TIMEOUT_ANDROID = 15;
|
|
108
|
+
|
|
109
|
+
const convertTimeoutForPlatform = (
|
|
110
|
+
platform,
|
|
111
|
+
connectionTimeout = Platform.OS === 'ios' ? DEFAULT_TIMEOUT_IOS : DEFAULT_TIMEOUT_ANDROID
|
|
112
|
+
) =>
|
|
113
|
+
platform === 'android' || platform === 'harmony'
|
|
114
|
+
? connectionTimeout * SECOND_IN_MS
|
|
115
|
+
: connectionTimeout;
|
|
116
|
+
|
|
117
|
+
// --- HarmonyOS browser-redirect bridge ---
|
|
118
|
+
// RNOH emits a 'url' device event when the app is re-opened via a custom
|
|
119
|
+
// scheme redirect (UIAbility.onNewWant -> RNInstance -> emitDeviceEvent('url')).
|
|
120
|
+
// We forward it to the native handleRedirect() so the pending authorize/logout
|
|
121
|
+
// Promise completes. handleRedirect() validates the state and returns false for
|
|
122
|
+
// unrelated URLs (e.g. the app's own deep links), so a single always-on listener
|
|
123
|
+
// is safe.
|
|
124
|
+
let harmonyUrlListenerRegistered = false;
|
|
125
|
+
const registerHarmonyUrlListener = () => {
|
|
126
|
+
if (Platform.OS !== 'harmony' || harmonyUrlListenerRegistered) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
harmonyUrlListenerRegistered = true;
|
|
130
|
+
DeviceEventEmitter.addListener('url', event => {
|
|
131
|
+
if (event && typeof event.url === 'string') {
|
|
132
|
+
NativeRNAppAuth.handleRedirect(event.url);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// --- HarmonyOS user-cancel fallback ---
|
|
138
|
+
// When the user closes the browser without authorizing, no 'url' event is
|
|
139
|
+
// emitted and the native Promise would hang. On app foreground (AppState active)
|
|
140
|
+
// we wait a short window for a matching redirect; if none arrives we ask the
|
|
141
|
+
// native side to reject the pending flow with access_denied (mirrors Android's
|
|
142
|
+
// onActivityResult cancelled result).
|
|
143
|
+
let harmonyPendingCancelFlow = null;
|
|
144
|
+
const registerHarmonyCancelFallback = () => {
|
|
145
|
+
if (Platform.OS !== 'harmony') {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
harmonyPendingCancelFlow = setTimeout(() => {
|
|
149
|
+
harmonyPendingCancelFlow = null;
|
|
150
|
+
NativeRNAppAuth.cancelPendingFlow();
|
|
151
|
+
}, 1000);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const registerHarmonyAppStateListener = () => {
|
|
155
|
+
if (Platform.OS !== 'harmony' || harmonyAppStateListenerRegistered) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
harmonyAppStateListenerRegistered = true;
|
|
159
|
+
AppState.addEventListener('change', nextAppState => {
|
|
160
|
+
if (nextAppState === 'active' && harmonyPendingCancelFlow != null) {
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
if (harmonyPendingCancelFlow != null) {
|
|
163
|
+
clearTimeout(harmonyPendingCancelFlow);
|
|
164
|
+
harmonyPendingCancelFlow = null;
|
|
165
|
+
NativeRNAppAuth.cancelPendingFlow();
|
|
166
|
+
}
|
|
167
|
+
}, 500);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let harmonyAppStateListenerRegistered = false;
|
|
173
|
+
|
|
174
|
+
const OAUTH_ERROR_SEPARATOR = '::';
|
|
175
|
+
|
|
176
|
+
const KNOWN_OAUTH_ERROR_CODES = new Set([
|
|
177
|
+
'access_denied',
|
|
178
|
+
'invalid_request',
|
|
179
|
+
'invalid_client',
|
|
180
|
+
'invalid_grant',
|
|
181
|
+
'unauthorized_client',
|
|
182
|
+
'unsupported_grant_type',
|
|
183
|
+
'invalid_scope',
|
|
184
|
+
'invalid_redirect_uri',
|
|
185
|
+
'invalid_client_metadata',
|
|
186
|
+
'service_configuration_fetch_error',
|
|
187
|
+
'authentication_failed',
|
|
188
|
+
'token_refresh_failed',
|
|
189
|
+
'token_exchange_failed',
|
|
190
|
+
'registration_failed',
|
|
191
|
+
'end_session_failed',
|
|
192
|
+
'authentication_error',
|
|
193
|
+
'run_time_exception',
|
|
194
|
+
'configuration_error',
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Normalize a native rejection into AppAuthError shape { code, message }.
|
|
199
|
+
* Harmony TurboModule may only forward Error.message; native OAuthError embeds
|
|
200
|
+
* "code::message" in message for bridge compatibility with Android/iOS.
|
|
201
|
+
*/
|
|
202
|
+
const readableMessage = err => {
|
|
203
|
+
if (err == null) {
|
|
204
|
+
return 'Unknown error';
|
|
205
|
+
}
|
|
206
|
+
if (typeof err === 'string') {
|
|
207
|
+
return err;
|
|
208
|
+
}
|
|
209
|
+
if (typeof err.message === 'string' && err.message.length > 0 && err.message !== '[object Object]') {
|
|
210
|
+
return err.message;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const jsonText = JSON.stringify(err);
|
|
214
|
+
if (jsonText && jsonText !== '{}' && jsonText !== 'null') {
|
|
215
|
+
return jsonText;
|
|
216
|
+
}
|
|
217
|
+
} catch (_ignored) {
|
|
218
|
+
}
|
|
219
|
+
const fallback = String(err);
|
|
220
|
+
return fallback === '[object Object]' ? 'Network request failed' : fallback;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const normalizeOAuthError = err => {
|
|
224
|
+
if (err == null) {
|
|
225
|
+
const normalized = new Error('Unknown error');
|
|
226
|
+
normalized.code = 'run_time_exception';
|
|
227
|
+
return normalized;
|
|
228
|
+
}
|
|
229
|
+
if (typeof err === 'string') {
|
|
230
|
+
const normalized = new Error(err);
|
|
231
|
+
normalized.code = KNOWN_OAUTH_ERROR_CODES.has(err) ? err : 'run_time_exception';
|
|
232
|
+
return normalized;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const message = readableMessage(err);
|
|
236
|
+
const sepIndex = typeof message === 'string' ? message.indexOf(OAUTH_ERROR_SEPARATOR) : -1;
|
|
237
|
+
if (sepIndex > 0) {
|
|
238
|
+
const code = message.slice(0, sepIndex);
|
|
239
|
+
const detail = message.slice(sepIndex + OAUTH_ERROR_SEPARATOR.length);
|
|
240
|
+
err.code = code;
|
|
241
|
+
err.message = detail.length > 0 ? detail : code;
|
|
242
|
+
return err;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (typeof err.code === 'string' && err.code.length > 0 && err.message !== '[object Object]') {
|
|
246
|
+
return err;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (KNOWN_OAUTH_ERROR_CODES.has(message)) {
|
|
250
|
+
err.code = message;
|
|
251
|
+
err.message = message;
|
|
252
|
+
return err;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
err.code = typeof err.code === 'string' && err.code.length > 0 ? err.code : 'run_time_exception';
|
|
256
|
+
err.message = message;
|
|
257
|
+
return err;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const wrapHarmonyNative = promise =>
|
|
261
|
+
Platform.OS === 'harmony'
|
|
262
|
+
? promise.catch(err => Promise.reject(normalizeOAuthError(err)))
|
|
263
|
+
: promise;
|
|
264
|
+
|
|
265
|
+
export const prefetchConfiguration = async ({
|
|
266
|
+
warmAndPrefetchChrome = false,
|
|
267
|
+
issuer,
|
|
268
|
+
redirectUrl,
|
|
269
|
+
clientId,
|
|
270
|
+
scopes,
|
|
271
|
+
serviceConfiguration,
|
|
272
|
+
dangerouslyAllowInsecureHttpRequests = false,
|
|
273
|
+
customHeaders,
|
|
274
|
+
connectionTimeoutSeconds,
|
|
275
|
+
}) => {
|
|
276
|
+
if (Platform.OS === 'android' || Platform.OS === 'harmony') {
|
|
277
|
+
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
|
|
278
|
+
validateClientId(clientId);
|
|
279
|
+
validateRedirectUrl(redirectUrl);
|
|
280
|
+
validateHeaders(customHeaders);
|
|
281
|
+
validateConnectionTimeoutSeconds(connectionTimeoutSeconds);
|
|
282
|
+
|
|
283
|
+
const nativeMethodArguments = [
|
|
284
|
+
warmAndPrefetchChrome,
|
|
285
|
+
issuer,
|
|
286
|
+
redirectUrl,
|
|
287
|
+
clientId,
|
|
288
|
+
scopes,
|
|
289
|
+
serviceConfiguration,
|
|
290
|
+
dangerouslyAllowInsecureHttpRequests,
|
|
291
|
+
customHeaders,
|
|
292
|
+
convertTimeoutForPlatform(Platform.OS, connectionTimeoutSeconds),
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
return wrapHarmonyNative(
|
|
296
|
+
NativeRNAppAuth.prefetchConfiguration(...nativeMethodArguments)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return undefined;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export const register = ({
|
|
303
|
+
issuer,
|
|
304
|
+
redirectUrls,
|
|
305
|
+
responseTypes,
|
|
306
|
+
grantTypes,
|
|
307
|
+
subjectType,
|
|
308
|
+
tokenEndpointAuthMethod,
|
|
309
|
+
additionalParameters,
|
|
310
|
+
serviceConfiguration,
|
|
311
|
+
dangerouslyAllowInsecureHttpRequests = false,
|
|
312
|
+
customHeaders,
|
|
313
|
+
additionalHeaders,
|
|
314
|
+
connectionTimeoutSeconds,
|
|
315
|
+
}) => {
|
|
316
|
+
validateIssuerOrServiceConfigurationRegistrationEndpoint(issuer, serviceConfiguration);
|
|
317
|
+
validateHeaders(customHeaders);
|
|
318
|
+
validateAdditionalHeaders(additionalHeaders);
|
|
319
|
+
validateConnectionTimeoutSeconds(connectionTimeoutSeconds);
|
|
320
|
+
|
|
321
|
+
assertConfig(
|
|
322
|
+
Array.isArray(redirectUrls) && redirectUrls.every(url => typeof url === 'string'),
|
|
323
|
+
'Config error: redirectUrls must be an Array of strings'
|
|
324
|
+
);
|
|
325
|
+
assertConfig(
|
|
326
|
+
responseTypes == null ||
|
|
327
|
+
(Array.isArray(responseTypes) && responseTypes.every(rt => typeof rt === 'string')),
|
|
328
|
+
'Config error: if provided, responseTypes must be an Array of strings'
|
|
329
|
+
);
|
|
330
|
+
assertConfig(
|
|
331
|
+
grantTypes == null ||
|
|
332
|
+
(Array.isArray(grantTypes) && grantTypes.every(gt => typeof gt === 'string')),
|
|
333
|
+
'Config error: if provided, grantTypes must be an Array of strings'
|
|
334
|
+
);
|
|
335
|
+
assertConfig(
|
|
336
|
+
subjectType == null || typeof subjectType === 'string',
|
|
337
|
+
'Config error: if provided, subjectType must be a string'
|
|
338
|
+
);
|
|
339
|
+
assertConfig(
|
|
340
|
+
tokenEndpointAuthMethod == null || typeof tokenEndpointAuthMethod === 'string',
|
|
341
|
+
'Config error: if provided, tokenEndpointAuthMethod must be a string'
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const nativeMethodArguments = [
|
|
345
|
+
issuer,
|
|
346
|
+
redirectUrls,
|
|
347
|
+
responseTypes,
|
|
348
|
+
grantTypes,
|
|
349
|
+
subjectType,
|
|
350
|
+
tokenEndpointAuthMethod,
|
|
351
|
+
additionalParameters,
|
|
352
|
+
serviceConfiguration,
|
|
353
|
+
convertTimeoutForPlatform(Platform.OS, connectionTimeoutSeconds),
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
if (Platform.OS === 'android' || Platform.OS === 'harmony') {
|
|
357
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
358
|
+
nativeMethodArguments.push(customHeaders);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (Platform.OS === 'ios') {
|
|
362
|
+
nativeMethodArguments.push(additionalHeaders);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return wrapHarmonyNative(NativeRNAppAuth.register(...nativeMethodArguments));
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export const authorize = ({
|
|
369
|
+
issuer,
|
|
370
|
+
redirectUrl,
|
|
371
|
+
clientId,
|
|
372
|
+
clientSecret,
|
|
373
|
+
scopes,
|
|
374
|
+
useNonce = true,
|
|
375
|
+
usePKCE = true,
|
|
376
|
+
additionalParameters,
|
|
377
|
+
serviceConfiguration,
|
|
378
|
+
clientAuthMethod = 'basic',
|
|
379
|
+
dangerouslyAllowInsecureHttpRequests = false,
|
|
380
|
+
customHeaders,
|
|
381
|
+
additionalHeaders,
|
|
382
|
+
skipCodeExchange = false,
|
|
383
|
+
iosCustomBrowser = null,
|
|
384
|
+
androidAllowCustomBrowsers = null,
|
|
385
|
+
androidTrustedWebActivity = false,
|
|
386
|
+
connectionTimeoutSeconds,
|
|
387
|
+
iosPrefersEphemeralSession = false,
|
|
388
|
+
}) => {
|
|
389
|
+
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
|
|
390
|
+
validateClientId(clientId);
|
|
391
|
+
validateRedirectUrl(redirectUrl);
|
|
392
|
+
validateHeaders(customHeaders);
|
|
393
|
+
validateAdditionalHeaders(additionalHeaders);
|
|
394
|
+
validateConnectionTimeoutSeconds(connectionTimeoutSeconds);
|
|
395
|
+
// TODO: validateAdditionalParameters
|
|
396
|
+
|
|
397
|
+
const nativeMethodArguments = [
|
|
398
|
+
issuer,
|
|
399
|
+
redirectUrl,
|
|
400
|
+
clientId,
|
|
401
|
+
clientSecret,
|
|
402
|
+
scopes,
|
|
403
|
+
additionalParameters,
|
|
404
|
+
serviceConfiguration,
|
|
405
|
+
skipCodeExchange,
|
|
406
|
+
convertTimeoutForPlatform(Platform.OS, connectionTimeoutSeconds),
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
if (Platform.OS === 'android') {
|
|
410
|
+
nativeMethodArguments.push(useNonce);
|
|
411
|
+
nativeMethodArguments.push(usePKCE);
|
|
412
|
+
nativeMethodArguments.push(clientAuthMethod);
|
|
413
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
414
|
+
nativeMethodArguments.push(customHeaders);
|
|
415
|
+
nativeMethodArguments.push(androidAllowCustomBrowsers);
|
|
416
|
+
nativeMethodArguments.push(androidTrustedWebActivity);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (Platform.OS === 'harmony') {
|
|
420
|
+
// Harmony keeps Android argument semantics minus the Chrome Custom Tab
|
|
421
|
+
// specific params (androidAllowCustomBrowsers / androidTrustedWebActivity)
|
|
422
|
+
// and iOS-only params (additionalHeaders / iosCustomBrowser / iosPrefersEphemeralSession).
|
|
423
|
+
nativeMethodArguments.push(useNonce);
|
|
424
|
+
nativeMethodArguments.push(usePKCE);
|
|
425
|
+
nativeMethodArguments.push(clientAuthMethod);
|
|
426
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
427
|
+
nativeMethodArguments.push(customHeaders);
|
|
428
|
+
registerHarmonyUrlListener();
|
|
429
|
+
registerHarmonyAppStateListener();
|
|
430
|
+
registerHarmonyCancelFallback();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (Platform.OS === 'ios') {
|
|
434
|
+
nativeMethodArguments.push(additionalHeaders);
|
|
435
|
+
nativeMethodArguments.push(useNonce);
|
|
436
|
+
nativeMethodArguments.push(usePKCE);
|
|
437
|
+
nativeMethodArguments.push(iosCustomBrowser);
|
|
438
|
+
nativeMethodArguments.push(iosPrefersEphemeralSession);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return wrapHarmonyNative(NativeRNAppAuth.authorize(...nativeMethodArguments));
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
export const refresh = (
|
|
445
|
+
{
|
|
446
|
+
issuer,
|
|
447
|
+
redirectUrl,
|
|
448
|
+
clientId,
|
|
449
|
+
clientSecret,
|
|
450
|
+
scopes,
|
|
451
|
+
additionalParameters = {},
|
|
452
|
+
serviceConfiguration,
|
|
453
|
+
clientAuthMethod = 'basic',
|
|
454
|
+
dangerouslyAllowInsecureHttpRequests = false,
|
|
455
|
+
customHeaders,
|
|
456
|
+
additionalHeaders,
|
|
457
|
+
iosCustomBrowser = null,
|
|
458
|
+
androidAllowCustomBrowsers = null,
|
|
459
|
+
connectionTimeoutSeconds,
|
|
460
|
+
},
|
|
461
|
+
{ refreshToken }
|
|
462
|
+
) => {
|
|
463
|
+
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
|
|
464
|
+
validateClientId(clientId);
|
|
465
|
+
validateRedirectUrl(redirectUrl);
|
|
466
|
+
validateHeaders(customHeaders);
|
|
467
|
+
validateAdditionalHeaders(additionalHeaders);
|
|
468
|
+
validateConnectionTimeoutSeconds(connectionTimeoutSeconds);
|
|
469
|
+
assertConfig(refreshToken, 'Please pass in a refresh token');
|
|
470
|
+
// TODO: validateAdditionalParameters
|
|
471
|
+
|
|
472
|
+
const nativeMethodArguments = [
|
|
473
|
+
issuer,
|
|
474
|
+
redirectUrl,
|
|
475
|
+
clientId,
|
|
476
|
+
clientSecret,
|
|
477
|
+
refreshToken,
|
|
478
|
+
scopes,
|
|
479
|
+
additionalParameters,
|
|
480
|
+
serviceConfiguration,
|
|
481
|
+
convertTimeoutForPlatform(Platform.OS, connectionTimeoutSeconds),
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
if (Platform.OS === 'android') {
|
|
485
|
+
nativeMethodArguments.push(clientAuthMethod);
|
|
486
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
487
|
+
nativeMethodArguments.push(customHeaders);
|
|
488
|
+
nativeMethodArguments.push(androidAllowCustomBrowsers);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (Platform.OS === 'harmony') {
|
|
492
|
+
nativeMethodArguments.push(clientAuthMethod);
|
|
493
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
494
|
+
nativeMethodArguments.push(customHeaders);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (Platform.OS === 'ios') {
|
|
498
|
+
nativeMethodArguments.push(additionalHeaders);
|
|
499
|
+
nativeMethodArguments.push(iosCustomBrowser);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return wrapHarmonyNative(NativeRNAppAuth.refresh(...nativeMethodArguments));
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export const revoke = async (
|
|
506
|
+
{ clientId, issuer, serviceConfiguration, clientSecret },
|
|
507
|
+
{ tokenToRevoke, sendClientId = false, includeBasicAuth = false }
|
|
508
|
+
) => {
|
|
509
|
+
assertConfig(tokenToRevoke, 'Please include the token to revoke');
|
|
510
|
+
validateClientId(clientId);
|
|
511
|
+
validateIssuerOrServiceConfigurationRevocationEndpoint(issuer, serviceConfiguration);
|
|
512
|
+
|
|
513
|
+
let revocationEndpoint;
|
|
514
|
+
if (serviceConfiguration && serviceConfiguration.revocationEndpoint) {
|
|
515
|
+
revocationEndpoint = serviceConfiguration.revocationEndpoint;
|
|
516
|
+
} else {
|
|
517
|
+
const response = await fetch(`${issuer}/.well-known/openid-configuration`);
|
|
518
|
+
const openidConfig = await response.json();
|
|
519
|
+
|
|
520
|
+
assertConfig(
|
|
521
|
+
openidConfig.revocation_endpoint,
|
|
522
|
+
'The openid config does not specify a revocation endpoint'
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
revocationEndpoint = openidConfig.revocation_endpoint;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const headers = {
|
|
529
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
530
|
+
};
|
|
531
|
+
if (includeBasicAuth) {
|
|
532
|
+
headers.Authorization = `Basic ${base64.encode(`${clientId}:${clientSecret}`)}`;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
Identity Server insists on client_id being passed in the body,
|
|
536
|
+
but Google does not. According to the spec, Google is right
|
|
537
|
+
so defaulting to no client_id
|
|
538
|
+
https://tools.ietf.org/html/rfc7009#section-2.1
|
|
539
|
+
**/
|
|
540
|
+
return await fetch(revocationEndpoint, {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
headers,
|
|
543
|
+
body: `token=${tokenToRevoke}${sendClientId ? `&client_id=${clientId}` : ''}`,
|
|
544
|
+
}).catch(error => {
|
|
545
|
+
const err = new Error('Failed to revoke token');
|
|
546
|
+
err.code = 'token_revocation_failed';
|
|
547
|
+
if (error) {
|
|
548
|
+
err.cause = error;
|
|
549
|
+
}
|
|
550
|
+
throw err;
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export const logout = (
|
|
555
|
+
{
|
|
556
|
+
issuer,
|
|
557
|
+
serviceConfiguration,
|
|
558
|
+
additionalParameters,
|
|
559
|
+
dangerouslyAllowInsecureHttpRequests = false,
|
|
560
|
+
iosCustomBrowser = null,
|
|
561
|
+
iosPrefersEphemeralSession = false,
|
|
562
|
+
androidAllowCustomBrowsers = null,
|
|
563
|
+
},
|
|
564
|
+
{ idToken, postLogoutRedirectUrl }
|
|
565
|
+
) => {
|
|
566
|
+
validateIssuerOrServiceConfigurationEndSessionEndpoint(issuer, serviceConfiguration);
|
|
567
|
+
validateRedirectUrl(postLogoutRedirectUrl);
|
|
568
|
+
assertConfig(idToken, 'Please pass in the ID token');
|
|
569
|
+
|
|
570
|
+
const nativeMethodArguments = [
|
|
571
|
+
issuer,
|
|
572
|
+
idToken,
|
|
573
|
+
postLogoutRedirectUrl,
|
|
574
|
+
serviceConfiguration,
|
|
575
|
+
additionalParameters,
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
if (Platform.OS === 'android') {
|
|
579
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
580
|
+
nativeMethodArguments.push(androidAllowCustomBrowsers);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (Platform.OS === 'harmony') {
|
|
584
|
+
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
|
|
585
|
+
registerHarmonyUrlListener();
|
|
586
|
+
registerHarmonyAppStateListener();
|
|
587
|
+
registerHarmonyCancelFallback();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (Platform.OS === 'ios') {
|
|
591
|
+
nativeMethodArguments.push(iosCustomBrowser);
|
|
592
|
+
nativeMethodArguments.push(iosPrefersEphemeralSession);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return wrapHarmonyNative(NativeRNAppAuth.logout(...nativeMethodArguments));
|
|
596
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file ensures the directory is tracked by Git even if other files are removed
|