@multiplatform.one/keycloak 6.7.0 → 7.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/dist/cjs/index.cjs +129 -55
- package/dist/cjs/index.native.cjs +239 -130
- package/dist/esm/index.js +131 -57
- package/dist/esm/index.native.js +239 -130
- package/package.json +15 -10
- package/src/betterAuth/expoAuthFlow.native.spec.ts +307 -0
- package/src/betterAuth/expoAuthFlow.native.ts +189 -55
- package/src/frappeToken.native.spec.ts +104 -0
- package/src/frappeToken.native.ts +46 -2
- package/src/frappeToken.spec.ts +141 -0
- package/src/frappeToken.ts +106 -14
- package/src/index.ts +1 -1
- package/src/keycloak/index.ts +24 -7
- package/src/keycloak/index.webext.ts +16 -0
- package/src/oauth/authorizationUrl.spec.ts +83 -0
- package/src/oauth/authorizationUrl.ts +74 -0
- package/src/oauth/callback.spec.ts +72 -0
- package/src/oauth/callback.ts +46 -0
- package/src/oauth/callbackPage.spec.ts +61 -0
- package/src/oauth/callbackPage.ts +57 -0
- package/src/oauth/endpoints.spec.ts +193 -0
- package/src/oauth/endpoints.ts +135 -0
- package/src/oauth/errors.ts +18 -0
- package/src/oauth/form.spec.ts +35 -0
- package/src/oauth/form.ts +30 -0
- package/src/oauth/idToken.spec.ts +191 -0
- package/src/oauth/idToken.ts +153 -0
- package/src/oauth/index.ts +10 -0
- package/src/oauth/jwt.ts +90 -0
- package/src/oauth/loopbackFlow.spec.ts +226 -0
- package/src/oauth/loopbackFlow.ts +114 -0
- package/src/oauth/pkce.spec.ts +60 -0
- package/src/oauth/pkce.ts +80 -0
- package/src/oauth/tokenExchange.spec.ts +331 -0
- package/src/oauth/tokenExchange.ts +232 -0
- package/src/one.ts +30 -3
- package/src/provider/AfterAuth.tsx +7 -0
- package/src/provider/authProvider/index.native.tsx +37 -4
- package/src/session/index.ts +2 -2
- package/src/state.ts +5 -2
- package/types/betterAuth/expoAuthFlow.native.d.ts +50 -3
- package/types/betterAuth/expoAuthFlow.native.d.ts.map +1 -1
- package/types/frappeToken.d.ts +14 -1
- package/types/frappeToken.d.ts.map +1 -1
- package/types/frappeToken.native.d.ts +16 -2
- package/types/frappeToken.native.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/keycloak/index.d.ts.map +1 -1
- package/types/keycloak/index.webext.d.ts.map +1 -1
- package/types/oauth/authorizationUrl.d.ts +32 -0
- package/types/oauth/authorizationUrl.d.ts.map +1 -0
- package/types/oauth/callback.d.ts +26 -0
- package/types/oauth/callback.d.ts.map +1 -0
- package/types/oauth/callbackPage.d.ts +34 -0
- package/types/oauth/callbackPage.d.ts.map +1 -0
- package/types/oauth/endpoints.d.ts +68 -0
- package/types/oauth/endpoints.d.ts.map +1 -0
- package/types/oauth/errors.d.ts +17 -0
- package/types/oauth/errors.d.ts.map +1 -0
- package/types/oauth/form.d.ts +16 -0
- package/types/oauth/form.d.ts.map +1 -0
- package/types/oauth/idToken.d.ts +83 -0
- package/types/oauth/idToken.d.ts.map +1 -0
- package/types/oauth/index.d.ts +11 -0
- package/types/oauth/index.d.ts.map +1 -0
- package/types/oauth/jwt.d.ts +31 -0
- package/types/oauth/jwt.d.ts.map +1 -0
- package/types/oauth/loopbackFlow.d.ts +64 -0
- package/types/oauth/loopbackFlow.d.ts.map +1 -0
- package/types/oauth/pkce.d.ts +49 -0
- package/types/oauth/pkce.d.ts.map +1 -0
- package/types/oauth/tokenExchange.d.ts +89 -0
- package/types/oauth/tokenExchange.d.ts.map +1 -0
- package/types/one.d.ts +7 -0
- package/types/one.d.ts.map +1 -1
- package/types/provider/AfterAuth.d.ts.map +1 -1
- package/types/provider/authProvider/index.native.d.ts.map +1 -1
- package/types/state.d.ts.map +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
useTokensFromQuery: () => useTokensFromQuery,
|
|
52
52
|
useTokensFromStore: () => useTokensFromStore,
|
|
53
53
|
validOrRefreshableToken: () => validOrRefreshableToken,
|
|
54
|
+
watchKeycloakBearerTokenRevalidate: () => watchKeycloakBearerTokenRevalidate,
|
|
54
55
|
withAuthenticated: () => withAuthenticated
|
|
55
56
|
});
|
|
56
57
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -87,7 +88,7 @@ function useTokensFromQuery() {
|
|
|
87
88
|
// src/state.ts
|
|
88
89
|
var import_store = require("@multiplatform.one/store");
|
|
89
90
|
var import_platform2 = require("@multiplatform.one/platform");
|
|
90
|
-
var persist = import_platform2.isIframe ||
|
|
91
|
+
var persist = import_platform2.isIframe || !import_platform2.isBrowser && !import_platform2.isServer;
|
|
91
92
|
var authStore = (0, import_store.createStore)(
|
|
92
93
|
{ idToken: "", refreshToken: "", token: "" },
|
|
93
94
|
{ name: "auth", persist }
|
|
@@ -173,12 +174,122 @@ function getAuthClient(baseURL) {
|
|
|
173
174
|
return _client;
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
// src/frappeToken.ts
|
|
178
|
+
var cached;
|
|
179
|
+
var pending;
|
|
180
|
+
var signedOutUntil = 0;
|
|
181
|
+
var unavailableUntil = 0;
|
|
182
|
+
var SIGNED_OUT_COOLDOWN_MS = 15e3;
|
|
183
|
+
var TRANSIENT_COOLDOWN_MS = 3e3;
|
|
184
|
+
var RETRY_DELAYS_MS = [0, 400, 1e3];
|
|
185
|
+
var FAILED_TO_GET_ACCESS_TOKEN = "FAILED_TO_GET_ACCESS_TOKEN";
|
|
186
|
+
function classifyAccessTokenResponse(status, body) {
|
|
187
|
+
if (status === 401 || status === 403) return "signed-out";
|
|
188
|
+
if (status === 400 && body?.code === FAILED_TO_GET_ACCESS_TOKEN) return "signed-out";
|
|
189
|
+
return "transient";
|
|
190
|
+
}
|
|
191
|
+
function notifySessionInvalidated() {
|
|
192
|
+
try {
|
|
193
|
+
const client = getAuthClient();
|
|
194
|
+
client.$store?.notify?.("$sessionSignal");
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function sleep(ms) {
|
|
199
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
200
|
+
}
|
|
201
|
+
async function getKeycloakBearerToken() {
|
|
202
|
+
if (typeof window === "undefined") return void 0;
|
|
203
|
+
if (cached && cached.expiresAt - 6e4 > Date.now()) return cached.token;
|
|
204
|
+
if (Date.now() < signedOutUntil) return void 0;
|
|
205
|
+
if (Date.now() < unavailableUntil) return void 0;
|
|
206
|
+
if (pending) return pending;
|
|
207
|
+
pending = fetchToken().finally(() => {
|
|
208
|
+
pending = void 0;
|
|
209
|
+
});
|
|
210
|
+
return pending;
|
|
211
|
+
}
|
|
212
|
+
async function fetchTokenOnce() {
|
|
213
|
+
try {
|
|
214
|
+
const res = await fetch(`${window.location.origin}/api/auth/get-access-token`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
credentials: "include",
|
|
217
|
+
headers: { "Content-Type": "application/json" },
|
|
218
|
+
body: JSON.stringify({ providerId: "keycloak" })
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
const body = await res.json().catch(() => null);
|
|
222
|
+
return { ok: false, kind: classifyAccessTokenResponse(res.status, body) };
|
|
223
|
+
}
|
|
224
|
+
const data = await res.json().catch(() => null);
|
|
225
|
+
if (!data?.accessToken) {
|
|
226
|
+
return { ok: false, kind: "signed-out" };
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
token: data.accessToken,
|
|
231
|
+
expiresAt: data.accessTokenExpiresAt ? new Date(data.accessTokenExpiresAt).getTime() : Date.now() + 6e4
|
|
232
|
+
};
|
|
233
|
+
} catch {
|
|
234
|
+
return { ok: false, kind: "transient" };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function fetchToken() {
|
|
238
|
+
let last = "transient";
|
|
239
|
+
for (let i = 0; i < RETRY_DELAYS_MS.length; i++) {
|
|
240
|
+
const wait = RETRY_DELAYS_MS[i];
|
|
241
|
+
if (wait) await sleep(wait);
|
|
242
|
+
const result = await fetchTokenOnce();
|
|
243
|
+
if (result.ok) {
|
|
244
|
+
cached = { token: result.token, expiresAt: result.expiresAt };
|
|
245
|
+
return result.token;
|
|
246
|
+
}
|
|
247
|
+
last = result.kind;
|
|
248
|
+
if (result.kind === "signed-out") {
|
|
249
|
+
cached = void 0;
|
|
250
|
+
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
251
|
+
notifySessionInvalidated();
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
cached = void 0;
|
|
256
|
+
if (last === "transient") {
|
|
257
|
+
unavailableUntil = Date.now() + TRANSIENT_COOLDOWN_MS;
|
|
258
|
+
}
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
function clearKeycloakBearerToken() {
|
|
262
|
+
cached = void 0;
|
|
263
|
+
pending = void 0;
|
|
264
|
+
signedOutUntil = 0;
|
|
265
|
+
unavailableUntil = 0;
|
|
266
|
+
}
|
|
267
|
+
function watchKeycloakBearerTokenRevalidate() {
|
|
268
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
269
|
+
return () => {
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const onVisible = () => {
|
|
273
|
+
if (document.visibilityState !== "visible") return;
|
|
274
|
+
cached = void 0;
|
|
275
|
+
signedOutUntil = 0;
|
|
276
|
+
unavailableUntil = 0;
|
|
277
|
+
void getKeycloakBearerToken();
|
|
278
|
+
};
|
|
279
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
280
|
+
window.addEventListener("focus", onVisible);
|
|
281
|
+
return () => {
|
|
282
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
283
|
+
window.removeEventListener("focus", onVisible);
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
176
287
|
// src/session/index.ts
|
|
177
288
|
var import_platform4 = require("@multiplatform.one/platform");
|
|
178
289
|
var useSession = (_options) => {
|
|
179
290
|
return { session: null, status: "unauthenticated" };
|
|
180
291
|
};
|
|
181
|
-
if (import_platform4.isBrowser
|
|
292
|
+
if (import_platform4.isBrowser) {
|
|
182
293
|
useSession = (_options) => {
|
|
183
294
|
const client = getAuthClient();
|
|
184
295
|
const { data, isPending, error: _error } = client.useSession();
|
|
@@ -369,21 +480,29 @@ var Keycloak = class extends BaseKeycloak {
|
|
|
369
480
|
async logout(options = {}) {
|
|
370
481
|
if (this._keycloakClient) {
|
|
371
482
|
await this._keycloakClient.logout(options);
|
|
483
|
+
} else if (this._logout) {
|
|
484
|
+
await this._logout(options);
|
|
485
|
+
} else if (typeof window !== "undefined" && window.location?.href) {
|
|
486
|
+
clearKeycloakBearerToken();
|
|
487
|
+
const origin = window.location.origin;
|
|
488
|
+
let callbackURL = options.redirectUri || "/";
|
|
489
|
+
try {
|
|
490
|
+
const url = new URL(callbackURL, origin);
|
|
491
|
+
callbackURL = url.origin === origin ? `${url.pathname}${url.search}${url.hash}` || "/" : url.toString();
|
|
492
|
+
} catch {
|
|
493
|
+
callbackURL = "/";
|
|
494
|
+
}
|
|
495
|
+
window.location.href = `${origin}/api/auth/sign-out/sso?callbackURL=${encodeURIComponent(callbackURL)}`;
|
|
496
|
+
return;
|
|
372
497
|
} else if (import_platform5.isBrowser && !import_platform5.isServer) {
|
|
373
498
|
const client = getAuthClient();
|
|
374
499
|
await client.signOut({
|
|
375
500
|
fetchOptions: {
|
|
376
501
|
onSuccess: () => {
|
|
377
|
-
if (options.redirectUri)
|
|
378
|
-
if (import_platform5.isWeb) window.location.href = options.redirectUri;
|
|
379
|
-
} else {
|
|
380
|
-
if (import_platform5.isWeb) window.location.reload();
|
|
381
|
-
}
|
|
502
|
+
if (options.redirectUri && import_platform5.isWeb) window.location.href = options.redirectUri;
|
|
382
503
|
}
|
|
383
504
|
}
|
|
384
505
|
});
|
|
385
|
-
} else {
|
|
386
|
-
await this._logout?.(options);
|
|
387
506
|
}
|
|
388
507
|
this._clear();
|
|
389
508
|
}
|
|
@@ -497,6 +616,7 @@ function AfterAuth({ children, loadingComponent: _loadingComponent }) {
|
|
|
497
616
|
import_logger.logger.debug("keycloak disabled");
|
|
498
617
|
}
|
|
499
618
|
}, [authConfig.debug, keycloak]);
|
|
619
|
+
(0, import_react9.useEffect)(() => watchKeycloakBearerTokenRevalidate(), []);
|
|
500
620
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
501
621
|
}
|
|
502
622
|
|
|
@@ -894,49 +1014,3 @@ function KeycloakProvider({
|
|
|
894
1014
|
}
|
|
895
1015
|
) });
|
|
896
1016
|
}
|
|
897
|
-
|
|
898
|
-
// src/frappeToken.ts
|
|
899
|
-
var cached;
|
|
900
|
-
var pending;
|
|
901
|
-
var signedOutUntil = 0;
|
|
902
|
-
var SIGNED_OUT_COOLDOWN_MS = 15e3;
|
|
903
|
-
async function getKeycloakBearerToken() {
|
|
904
|
-
if (typeof window === "undefined") return void 0;
|
|
905
|
-
if (cached && cached.expiresAt - 6e4 > Date.now()) return cached.token;
|
|
906
|
-
if (Date.now() < signedOutUntil) return void 0;
|
|
907
|
-
if (pending) return pending;
|
|
908
|
-
pending = fetchToken().finally(() => {
|
|
909
|
-
pending = void 0;
|
|
910
|
-
});
|
|
911
|
-
return pending;
|
|
912
|
-
}
|
|
913
|
-
async function fetchToken() {
|
|
914
|
-
try {
|
|
915
|
-
const res = await fetch(`${window.location.origin}/api/auth/get-access-token`, {
|
|
916
|
-
method: "POST",
|
|
917
|
-
credentials: "include",
|
|
918
|
-
headers: { "Content-Type": "application/json" },
|
|
919
|
-
body: JSON.stringify({ providerId: "keycloak" })
|
|
920
|
-
});
|
|
921
|
-
if (!res.ok) {
|
|
922
|
-
cached = void 0;
|
|
923
|
-
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
924
|
-
return void 0;
|
|
925
|
-
}
|
|
926
|
-
const data = await res.json().catch(() => null);
|
|
927
|
-
if (!data?.accessToken) {
|
|
928
|
-
cached = void 0;
|
|
929
|
-
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
930
|
-
return void 0;
|
|
931
|
-
}
|
|
932
|
-
cached = {
|
|
933
|
-
token: data.accessToken,
|
|
934
|
-
expiresAt: data.accessTokenExpiresAt ? new Date(data.accessTokenExpiresAt).getTime() : Date.now() + 6e4
|
|
935
|
-
};
|
|
936
|
-
return cached.token;
|
|
937
|
-
} catch {
|
|
938
|
-
cached = void 0;
|
|
939
|
-
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
940
|
-
return void 0;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
useTokensFromQuery: () => useTokensFromQuery,
|
|
54
54
|
useTokensFromStore: () => useTokensFromStore,
|
|
55
55
|
validOrRefreshableToken: () => validOrRefreshableToken,
|
|
56
|
+
watchKeycloakBearerTokenRevalidate: () => watchKeycloakBearerTokenRevalidate,
|
|
56
57
|
withAuthenticated: () => withAuthenticated
|
|
57
58
|
});
|
|
58
59
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -81,7 +82,7 @@ var useTokensFromQuery = () => false;
|
|
|
81
82
|
// src/state.ts
|
|
82
83
|
var import_store = require("@multiplatform.one/store");
|
|
83
84
|
var import_platform = require("@multiplatform.one/platform");
|
|
84
|
-
var persist = import_platform.isIframe ||
|
|
85
|
+
var persist = import_platform.isIframe || !import_platform.isBrowser && !import_platform.isServer;
|
|
85
86
|
var authStore = (0, import_store.createStore)(
|
|
86
87
|
{ idToken: "", refreshToken: "", token: "" },
|
|
87
88
|
{ name: "auth", persist }
|
|
@@ -344,82 +345,19 @@ function withAuthenticated(Component, options = {}) {
|
|
|
344
345
|
// src/provider/AfterAuth.tsx
|
|
345
346
|
var import_logger = require("@multiplatform.one/logger");
|
|
346
347
|
var import_react8 = require("react");
|
|
347
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
348
|
-
function AfterAuth({ children, loadingComponent: _loadingComponent }) {
|
|
349
|
-
const authConfig = useAuthConfig();
|
|
350
|
-
const authStore2 = useAuthStore();
|
|
351
|
-
const keycloak = useKeycloak();
|
|
352
|
-
(0, import_react8.useEffect)(() => {
|
|
353
|
-
if (!persist || !keycloak?.authenticated) return;
|
|
354
|
-
if (keycloak.token) {
|
|
355
|
-
authStore2.setTokens({
|
|
356
|
-
token: keycloak.token,
|
|
357
|
-
...keycloak.idToken && { idToken: keycloak.idToken },
|
|
358
|
-
...keycloak.refreshToken && {
|
|
359
|
-
refreshToken: keycloak.refreshToken
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
}, [
|
|
364
|
-
authStore2,
|
|
365
|
-
persist,
|
|
366
|
-
keycloak?.authenticated,
|
|
367
|
-
keycloak?.token,
|
|
368
|
-
keycloak?.idToken,
|
|
369
|
-
keycloak?.refreshToken
|
|
370
|
-
]);
|
|
371
|
-
(0, import_react8.useEffect)(() => {
|
|
372
|
-
if (authConfig.debug && keycloak?.token) {
|
|
373
|
-
import_logger.logger.debug("token", keycloak.token);
|
|
374
|
-
}
|
|
375
|
-
}, [authConfig.debug, keycloak?.token]);
|
|
376
|
-
(0, import_react8.useEffect)(() => {
|
|
377
|
-
if (authConfig.debug && keycloak?.idToken) {
|
|
378
|
-
import_logger.logger.debug("idToken", keycloak.idToken);
|
|
379
|
-
}
|
|
380
|
-
}, [authConfig.debug, keycloak?.idToken]);
|
|
381
|
-
(0, import_react8.useEffect)(() => {
|
|
382
|
-
if (authConfig.debug && keycloak?.refreshToken) {
|
|
383
|
-
import_logger.logger.debug("refreshToken", keycloak.refreshToken);
|
|
384
|
-
}
|
|
385
|
-
}, [authConfig.debug, keycloak?.refreshToken]);
|
|
386
|
-
(0, import_react8.useEffect)(() => {
|
|
387
|
-
if (authConfig.debug && keycloak?.authenticated) {
|
|
388
|
-
import_logger.logger.debug("authenticated", keycloak.authenticated);
|
|
389
|
-
}
|
|
390
|
-
}, [authConfig.debug, keycloak?.authenticated]);
|
|
391
|
-
(0, import_react8.useEffect)(() => {
|
|
392
|
-
if (authConfig.debug && keycloak === null) {
|
|
393
|
-
import_logger.logger.debug("keycloak disabled");
|
|
394
|
-
}
|
|
395
|
-
}, [authConfig.debug, keycloak]);
|
|
396
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
397
|
-
}
|
|
398
348
|
|
|
399
|
-
// src/
|
|
400
|
-
var
|
|
401
|
-
var Linking2 = __toESM(require("expo-linking"));
|
|
402
|
-
|
|
403
|
-
// src/Loading.tsx
|
|
404
|
-
var import_react_native2 = require("react-native");
|
|
405
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
406
|
-
function Loading({ loadingComponent }) {
|
|
407
|
-
const { debug } = useAuthConfig();
|
|
408
|
-
const LoadingComponent = loadingComponent;
|
|
409
|
-
if (typeof LoadingComponent === "undefined") {
|
|
410
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: [
|
|
411
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.ActivityIndicator, {}),
|
|
412
|
-
debug ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { children: "loading" }) : null
|
|
413
|
-
] });
|
|
414
|
-
}
|
|
415
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoadingComponent, {});
|
|
416
|
-
}
|
|
349
|
+
// src/frappeToken.native.ts
|
|
350
|
+
var import_react_native3 = require("react-native");
|
|
417
351
|
|
|
418
352
|
// src/betterAuth/expoAuthFlow.native.ts
|
|
419
|
-
var
|
|
353
|
+
var import_react_native2 = require("react-native");
|
|
420
354
|
var Linking = __toESM(require("expo-linking"));
|
|
421
355
|
var SecureStore = __toESM(require("expo-secure-store"));
|
|
422
356
|
var WebBrowser = __toESM(require("expo-web-browser"));
|
|
357
|
+
try {
|
|
358
|
+
WebBrowser.maybeCompleteAuthSession?.();
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
423
361
|
var COOKIE_KEY = "better-auth_cookie";
|
|
424
362
|
var SECURE_COOKIE_PREFIX = "__Secure-";
|
|
425
363
|
function readJar() {
|
|
@@ -479,6 +417,78 @@ function getOAuthState() {
|
|
|
479
417
|
}
|
|
480
418
|
return null;
|
|
481
419
|
}
|
|
420
|
+
function rewriteLoopbackHostForNative(configured) {
|
|
421
|
+
const deviceHost = import_react_native2.Platform.OS === "android" ? "10.0.2.2" : "127.0.0.1";
|
|
422
|
+
return configured.replace(/^(https?:\/\/)(localhost|127\.0\.0\.1)(?=[:/]|$)/i, `$1${deviceHost}`);
|
|
423
|
+
}
|
|
424
|
+
function normalizeForReturnMatch(url) {
|
|
425
|
+
return url.split("?")[0].split("#")[0].replace(/:\/{2,}/, "://").replace(/\/+$/, "");
|
|
426
|
+
}
|
|
427
|
+
function ensureReturnUrlAuthority(url, segment) {
|
|
428
|
+
return url.replace(/^([a-z0-9.+-]+:)\/{2,3}(?=\?|#|$)/i, `$1//${segment}`);
|
|
429
|
+
}
|
|
430
|
+
function extractCookieFromReturnUrl(returnUrl) {
|
|
431
|
+
const q = returnUrl.indexOf("?");
|
|
432
|
+
if (q === -1) return null;
|
|
433
|
+
const query = returnUrl.slice(q + 1).split("#")[0];
|
|
434
|
+
for (const part of query.split("&")) {
|
|
435
|
+
const eq = part.indexOf("=");
|
|
436
|
+
const key = eq === -1 ? part : part.slice(0, eq);
|
|
437
|
+
let decodedKey;
|
|
438
|
+
try {
|
|
439
|
+
decodedKey = decodeURIComponent(key.replace(/\+/g, " "));
|
|
440
|
+
} catch {
|
|
441
|
+
decodedKey = key;
|
|
442
|
+
}
|
|
443
|
+
if (decodedKey !== "cookie") continue;
|
|
444
|
+
const raw = eq === -1 ? "" : part.slice(eq + 1);
|
|
445
|
+
try {
|
|
446
|
+
return decodeURIComponent(raw.replace(/\+/g, " "));
|
|
447
|
+
} catch {
|
|
448
|
+
return raw;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
async function openAuthBrowser(url, returnUrlBase, options) {
|
|
454
|
+
const returnBase = normalizeForReturnMatch(returnUrlBase);
|
|
455
|
+
let returned;
|
|
456
|
+
const linkSub = Linking.addEventListener("url", ({ url: next }) => {
|
|
457
|
+
if (!next || returned) return;
|
|
458
|
+
if (!normalizeForReturnMatch(next).startsWith(returnBase)) return;
|
|
459
|
+
returned = next;
|
|
460
|
+
try {
|
|
461
|
+
WebBrowser.dismissAuthSession();
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
const timer = options?.timeoutMs ? setTimeout(() => {
|
|
466
|
+
if (returned) return;
|
|
467
|
+
returned = "";
|
|
468
|
+
try {
|
|
469
|
+
WebBrowser.dismissAuthSession();
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}, options.timeoutMs) : void 0;
|
|
473
|
+
try {
|
|
474
|
+
const result = await WebBrowser.openAuthSessionAsync(url, returnUrlBase, {
|
|
475
|
+
preferEphemeralSession: false
|
|
476
|
+
});
|
|
477
|
+
if (!returned && result.type === "success" && "url" in result && result.url) {
|
|
478
|
+
returned = result.url;
|
|
479
|
+
}
|
|
480
|
+
if (!returned) {
|
|
481
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
482
|
+
}
|
|
483
|
+
return returned || void 0;
|
|
484
|
+
} finally {
|
|
485
|
+
if (timer) clearTimeout(timer);
|
|
486
|
+
try {
|
|
487
|
+
linkSub.remove();
|
|
488
|
+
} catch {
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
482
492
|
var activeFlow = null;
|
|
483
493
|
function getActiveExpoAuthFlow() {
|
|
484
494
|
return activeFlow;
|
|
@@ -520,45 +530,32 @@ function createExpoAuthFlow(baseURL, scheme) {
|
|
|
520
530
|
if (oauthState) params.append("oauthState", oauthState);
|
|
521
531
|
const proxyURL = `${base}/api/auth/expo-authorization-proxy?${params.toString()}`;
|
|
522
532
|
const toBase = to.split("?")[0];
|
|
523
|
-
|
|
524
|
-
if (import_react_native3.Platform.OS === "ios") {
|
|
525
|
-
returnUrl = await new Promise((resolve, reject) => {
|
|
526
|
-
let settled = false;
|
|
527
|
-
const linkSub = Linking.addEventListener("url", ({ url }) => {
|
|
528
|
-
if (settled || !url || !url.startsWith(toBase)) return;
|
|
529
|
-
settled = true;
|
|
530
|
-
try {
|
|
531
|
-
linkSub.remove();
|
|
532
|
-
} catch {
|
|
533
|
-
}
|
|
534
|
-
resolve(url);
|
|
535
|
-
});
|
|
536
|
-
Linking.openURL(proxyURL).catch((e) => {
|
|
537
|
-
if (settled) return;
|
|
538
|
-
settled = true;
|
|
539
|
-
try {
|
|
540
|
-
linkSub.remove();
|
|
541
|
-
} catch {
|
|
542
|
-
}
|
|
543
|
-
reject(e instanceof Error ? e : new Error(`sign-in openURL failed: ${e}`));
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
} else {
|
|
547
|
-
const result = await WebBrowser.openAuthSessionAsync(proxyURL, toBase, {
|
|
548
|
-
preferEphemeralSession: false
|
|
549
|
-
// keep the SSO + siww nonce cookie across the wallet bounce
|
|
550
|
-
});
|
|
551
|
-
returnUrl = result.type === "success" && "url" in result ? result.url : void 0;
|
|
552
|
-
}
|
|
533
|
+
const returnUrl = await openAuthBrowser(proxyURL, toBase);
|
|
553
534
|
if (!returnUrl) {
|
|
554
535
|
return;
|
|
555
536
|
}
|
|
556
|
-
const cookie =
|
|
537
|
+
const cookie = extractCookieFromReturnUrl(returnUrl);
|
|
557
538
|
if (cookie) absorbSetCookie(cookie);
|
|
558
539
|
},
|
|
559
|
-
async signOut() {
|
|
540
|
+
async signOut(postLogoutRedirectUri) {
|
|
541
|
+
const to = ensureReturnUrlAuthority(
|
|
542
|
+
postLogoutRedirectUri || Linking.createURL("/"),
|
|
543
|
+
"signed-out"
|
|
544
|
+
);
|
|
560
545
|
try {
|
|
561
|
-
await authFetch(
|
|
546
|
+
const res = await authFetch(
|
|
547
|
+
`/sign-out/sso?mode=json&callbackURL=${encodeURIComponent(to)}`,
|
|
548
|
+
{ method: "GET" }
|
|
549
|
+
);
|
|
550
|
+
if (!res.ok) throw new Error(`sign-out/sso failed (${res.status})`);
|
|
551
|
+
const data = await res.json().catch(() => null);
|
|
552
|
+
if (data?.redirect && data.url) {
|
|
553
|
+
await openAuthBrowser(data.url, to.split("?")[0], { timeoutMs: 12e4 }).catch(
|
|
554
|
+
() => void 0
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
await authFetch("/sign-out", { method: "POST", body: "{}" }).catch(() => void 0);
|
|
562
559
|
} finally {
|
|
563
560
|
writeJar({});
|
|
564
561
|
}
|
|
@@ -582,6 +579,131 @@ function createExpoAuthFlow(baseURL, scheme) {
|
|
|
582
579
|
return flow;
|
|
583
580
|
}
|
|
584
581
|
|
|
582
|
+
// src/frappeToken.native.ts
|
|
583
|
+
var cached;
|
|
584
|
+
var pending;
|
|
585
|
+
var signedOutUntil = 0;
|
|
586
|
+
var SIGNED_OUT_COOLDOWN_MS = 15e3;
|
|
587
|
+
async function getKeycloakBearerToken() {
|
|
588
|
+
if (cached && cached.expiresAt - 6e4 > Date.now()) return cached.token;
|
|
589
|
+
if (Date.now() < signedOutUntil) return void 0;
|
|
590
|
+
if (pending) return pending;
|
|
591
|
+
pending = fetchToken().finally(() => {
|
|
592
|
+
pending = void 0;
|
|
593
|
+
});
|
|
594
|
+
return pending;
|
|
595
|
+
}
|
|
596
|
+
async function fetchToken() {
|
|
597
|
+
const flow = getActiveExpoAuthFlow();
|
|
598
|
+
if (!flow) return void 0;
|
|
599
|
+
try {
|
|
600
|
+
const res = await flow.getAccessToken("keycloak");
|
|
601
|
+
if (!res?.accessToken) {
|
|
602
|
+
cached = void 0;
|
|
603
|
+
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
cached = {
|
|
607
|
+
token: res.accessToken,
|
|
608
|
+
expiresAt: res.accessTokenExpiresAt ? new Date(res.accessTokenExpiresAt).getTime() : Date.now() + 6e4
|
|
609
|
+
};
|
|
610
|
+
return cached.token;
|
|
611
|
+
} catch {
|
|
612
|
+
cached = void 0;
|
|
613
|
+
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
614
|
+
return void 0;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function clearKeycloakBearerToken() {
|
|
618
|
+
cached = void 0;
|
|
619
|
+
pending = void 0;
|
|
620
|
+
signedOutUntil = 0;
|
|
621
|
+
}
|
|
622
|
+
function watchKeycloakBearerTokenRevalidate() {
|
|
623
|
+
const onChange = (next) => {
|
|
624
|
+
if (next !== "active") return;
|
|
625
|
+
cached = void 0;
|
|
626
|
+
signedOutUntil = 0;
|
|
627
|
+
void getKeycloakBearerToken();
|
|
628
|
+
};
|
|
629
|
+
const sub = import_react_native3.AppState.addEventListener("change", onChange);
|
|
630
|
+
return () => sub.remove();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/provider/AfterAuth.tsx
|
|
634
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
635
|
+
function AfterAuth({ children, loadingComponent: _loadingComponent }) {
|
|
636
|
+
const authConfig = useAuthConfig();
|
|
637
|
+
const authStore2 = useAuthStore();
|
|
638
|
+
const keycloak = useKeycloak();
|
|
639
|
+
(0, import_react8.useEffect)(() => {
|
|
640
|
+
if (!persist || !keycloak?.authenticated) return;
|
|
641
|
+
if (keycloak.token) {
|
|
642
|
+
authStore2.setTokens({
|
|
643
|
+
token: keycloak.token,
|
|
644
|
+
...keycloak.idToken && { idToken: keycloak.idToken },
|
|
645
|
+
...keycloak.refreshToken && {
|
|
646
|
+
refreshToken: keycloak.refreshToken
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}, [
|
|
651
|
+
authStore2,
|
|
652
|
+
persist,
|
|
653
|
+
keycloak?.authenticated,
|
|
654
|
+
keycloak?.token,
|
|
655
|
+
keycloak?.idToken,
|
|
656
|
+
keycloak?.refreshToken
|
|
657
|
+
]);
|
|
658
|
+
(0, import_react8.useEffect)(() => {
|
|
659
|
+
if (authConfig.debug && keycloak?.token) {
|
|
660
|
+
import_logger.logger.debug("token", keycloak.token);
|
|
661
|
+
}
|
|
662
|
+
}, [authConfig.debug, keycloak?.token]);
|
|
663
|
+
(0, import_react8.useEffect)(() => {
|
|
664
|
+
if (authConfig.debug && keycloak?.idToken) {
|
|
665
|
+
import_logger.logger.debug("idToken", keycloak.idToken);
|
|
666
|
+
}
|
|
667
|
+
}, [authConfig.debug, keycloak?.idToken]);
|
|
668
|
+
(0, import_react8.useEffect)(() => {
|
|
669
|
+
if (authConfig.debug && keycloak?.refreshToken) {
|
|
670
|
+
import_logger.logger.debug("refreshToken", keycloak.refreshToken);
|
|
671
|
+
}
|
|
672
|
+
}, [authConfig.debug, keycloak?.refreshToken]);
|
|
673
|
+
(0, import_react8.useEffect)(() => {
|
|
674
|
+
if (authConfig.debug && keycloak?.authenticated) {
|
|
675
|
+
import_logger.logger.debug("authenticated", keycloak.authenticated);
|
|
676
|
+
}
|
|
677
|
+
}, [authConfig.debug, keycloak?.authenticated]);
|
|
678
|
+
(0, import_react8.useEffect)(() => {
|
|
679
|
+
if (authConfig.debug && keycloak === null) {
|
|
680
|
+
import_logger.logger.debug("keycloak disabled");
|
|
681
|
+
}
|
|
682
|
+
}, [authConfig.debug, keycloak]);
|
|
683
|
+
(0, import_react8.useEffect)(() => watchKeycloakBearerTokenRevalidate(), []);
|
|
684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/provider/authProvider/index.native.tsx
|
|
688
|
+
var import_react10 = require("react");
|
|
689
|
+
var import_react_native5 = require("react-native");
|
|
690
|
+
var Linking2 = __toESM(require("expo-linking"));
|
|
691
|
+
|
|
692
|
+
// src/Loading.tsx
|
|
693
|
+
var import_react_native4 = require("react-native");
|
|
694
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
695
|
+
function Loading({ loadingComponent }) {
|
|
696
|
+
const { debug } = useAuthConfig();
|
|
697
|
+
const LoadingComponent = loadingComponent;
|
|
698
|
+
if (typeof LoadingComponent === "undefined") {
|
|
699
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: [
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.ActivityIndicator, {}),
|
|
701
|
+
debug ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { children: "loading" }) : null
|
|
702
|
+
] });
|
|
703
|
+
}
|
|
704
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoadingComponent, {});
|
|
705
|
+
}
|
|
706
|
+
|
|
585
707
|
// src/session/index.native.ts
|
|
586
708
|
var import_react9 = require("react");
|
|
587
709
|
var state = { session: null, status: "loading" };
|
|
@@ -619,7 +741,9 @@ function AuthProvider({
|
|
|
619
741
|
const [keycloak, setKeycloak] = (0, import_react10.useState)();
|
|
620
742
|
const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
|
|
621
743
|
const serverBaseUrl = (0, import_react10.useMemo)(() => {
|
|
622
|
-
return
|
|
744
|
+
return rewriteLoopbackHostForNative(
|
|
745
|
+
keycloakConfig.betterAuthBaseUrl || "http://localhost:8000"
|
|
746
|
+
);
|
|
623
747
|
}, [keycloakConfig]);
|
|
624
748
|
const scheme = (0, import_react10.useMemo)(() => keycloakConfig.expoScheme, [keycloakConfig]);
|
|
625
749
|
const flow = (0, import_react10.useMemo)(
|
|
@@ -646,11 +770,14 @@ function AuthProvider({
|
|
|
646
770
|
void 0,
|
|
647
771
|
async (options) => {
|
|
648
772
|
await flow.signIn("keycloak", options.redirectUri || Linking2.createURL("/"));
|
|
773
|
+
clearKeycloakBearerToken();
|
|
649
774
|
await refresh();
|
|
650
775
|
return void 0;
|
|
651
776
|
},
|
|
652
|
-
async (
|
|
653
|
-
|
|
777
|
+
async (options) => {
|
|
778
|
+
const redirectUri = options.redirectUri && options.redirectUri.includes("://") ? options.redirectUri : Linking2.createURL("/");
|
|
779
|
+
await flow.signOut(redirectUri);
|
|
780
|
+
clearKeycloakBearerToken();
|
|
654
781
|
await refresh();
|
|
655
782
|
return void 0;
|
|
656
783
|
}
|
|
@@ -676,8 +803,13 @@ function AuthProvider({
|
|
|
676
803
|
refresh().finally(() => {
|
|
677
804
|
if (alive) setIsLoading(false);
|
|
678
805
|
});
|
|
806
|
+
const onAppState = (next) => {
|
|
807
|
+
if (next === "active") refresh().catch(() => void 0);
|
|
808
|
+
};
|
|
809
|
+
const appSub = import_react_native5.AppState.addEventListener("change", onAppState);
|
|
679
810
|
return () => {
|
|
680
811
|
alive = false;
|
|
812
|
+
appSub.remove();
|
|
681
813
|
};
|
|
682
814
|
}, [disabled, flow, keycloakConfig]);
|
|
683
815
|
if (disabled) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
|
|
@@ -723,26 +855,3 @@ function KeycloakProvider({
|
|
|
723
855
|
}
|
|
724
856
|
) });
|
|
725
857
|
}
|
|
726
|
-
|
|
727
|
-
// src/frappeToken.native.ts
|
|
728
|
-
var cached;
|
|
729
|
-
async function getKeycloakBearerToken() {
|
|
730
|
-
if (cached && cached.expiresAt - 6e4 > Date.now()) return cached.token;
|
|
731
|
-
const flow = getActiveExpoAuthFlow();
|
|
732
|
-
if (!flow) return void 0;
|
|
733
|
-
try {
|
|
734
|
-
const res = await flow.getAccessToken("keycloak");
|
|
735
|
-
if (!res?.accessToken) {
|
|
736
|
-
cached = void 0;
|
|
737
|
-
return void 0;
|
|
738
|
-
}
|
|
739
|
-
cached = {
|
|
740
|
-
token: res.accessToken,
|
|
741
|
-
expiresAt: res.accessTokenExpiresAt ? new Date(res.accessTokenExpiresAt).getTime() : Date.now() + 6e4
|
|
742
|
-
};
|
|
743
|
-
return cached.token;
|
|
744
|
-
} catch {
|
|
745
|
-
cached = void 0;
|
|
746
|
-
return void 0;
|
|
747
|
-
}
|
|
748
|
-
}
|