@multiplatform.one/keycloak 6.0.3 → 6.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 +19 -6
- package/dist/cjs/index.native.cjs +717 -0
- package/dist/esm/index.js +18 -5
- package/dist/esm/index.native.js +684 -0
- package/package.json +14 -10
- package/src/Authenticated.tsx +3 -1
- package/src/Loading.tsx +10 -2
- package/src/betterAuth/expoAuthFlow.native.ts +187 -0
- package/src/frappeToken.native.ts +45 -0
- package/src/frappeToken.ts +10 -0
- package/src/index.ts +1 -0
- package/src/provider/KeycloakProvider.tsx +11 -0
- package/src/provider/authProvider/index.native.tsx +72 -64
- package/src/session/index.native.ts +32 -7
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
// src/Authenticated.tsx
|
|
2
|
+
import { isIframe as isIframe2, isServer as isServer2 } from "@multiplatform.one/platform";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { Text } from "react-native";
|
|
5
|
+
|
|
6
|
+
// src/hooks/useAuthConfig.ts
|
|
7
|
+
import { useContext } from "react";
|
|
8
|
+
|
|
9
|
+
// src/authConfig.ts
|
|
10
|
+
import { createContext } from "react";
|
|
11
|
+
var defaultAuthConfig = {};
|
|
12
|
+
var AuthConfigContext = createContext(defaultAuthConfig);
|
|
13
|
+
|
|
14
|
+
// src/hooks/useAuthConfig.ts
|
|
15
|
+
function useAuthConfig() {
|
|
16
|
+
return useContext(AuthConfigContext);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/hooks/useTokensFromQuery/index.native.ts
|
|
20
|
+
var useTokensFromQuery = () => false;
|
|
21
|
+
|
|
22
|
+
// src/state.ts
|
|
23
|
+
import { createStore, useStore } from "@multiplatform.one/store";
|
|
24
|
+
import { isBrowser, isIframe, isServer, isTauri } from "@multiplatform.one/platform";
|
|
25
|
+
var persist = isIframe || isTauri || !isBrowser && !isServer;
|
|
26
|
+
var authStore = createStore(
|
|
27
|
+
{ idToken: "", refreshToken: "", token: "" },
|
|
28
|
+
{ name: "auth", persist }
|
|
29
|
+
);
|
|
30
|
+
function useAuthStore() {
|
|
31
|
+
const state2 = useStore(authStore);
|
|
32
|
+
return {
|
|
33
|
+
...state2,
|
|
34
|
+
setIdToken: (idToken) => authStore.setState((prev) => ({ ...prev, idToken })),
|
|
35
|
+
setRefreshToken: (refreshToken) => authStore.setState((prev) => ({ ...prev, refreshToken })),
|
|
36
|
+
setToken: (token) => authStore.setState((prev) => ({ ...prev, token })),
|
|
37
|
+
setTokens: (tokens) => authStore.setState((prev) => ({ ...prev, ...tokens }))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/token.ts
|
|
42
|
+
import { jwtDecode } from "jwt-decode";
|
|
43
|
+
function getWalletsFromToken(token) {
|
|
44
|
+
try {
|
|
45
|
+
const decoded = jwtDecode(token);
|
|
46
|
+
return decoded.crypto?.wallets ?? {};
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getWalletAddresses(token, walletType = "ethereum") {
|
|
52
|
+
const wallets = getWalletsFromToken(token);
|
|
53
|
+
return wallets[walletType]?.addresses ?? [];
|
|
54
|
+
}
|
|
55
|
+
function hasWallets(token) {
|
|
56
|
+
const wallets = getWalletsFromToken(token);
|
|
57
|
+
return Object.values(wallets).some((info) => info.addresses && info.addresses.length > 0);
|
|
58
|
+
}
|
|
59
|
+
function getAllWalletAddresses(token) {
|
|
60
|
+
const wallets = getWalletsFromToken(token);
|
|
61
|
+
const result = [];
|
|
62
|
+
for (const [walletType, info] of Object.entries(wallets)) {
|
|
63
|
+
if (info.addresses) {
|
|
64
|
+
for (const address of info.addresses) {
|
|
65
|
+
result.push({ walletType, address });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function validOrRefreshableToken(token, refreshToken) {
|
|
72
|
+
if (typeof token === "undefined") return;
|
|
73
|
+
if (!token) return false;
|
|
74
|
+
if (typeof token !== "string") return token;
|
|
75
|
+
if (refreshToken) {
|
|
76
|
+
if (refreshToken === true || !isTokenExpired(refreshToken)) return token;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return isTokenExpired(token) ? false : token;
|
|
80
|
+
}
|
|
81
|
+
function isTokenExpired(token) {
|
|
82
|
+
const { exp } = jwtDecode(token);
|
|
83
|
+
if (!exp) return false;
|
|
84
|
+
return Math.floor(Date.now() / 1e3) >= exp;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/hooks/useTokensFromState.ts
|
|
88
|
+
function useTokensFromStore() {
|
|
89
|
+
const authStore2 = useAuthStore();
|
|
90
|
+
return !!validOrRefreshableToken(authStore2.token, authStore2.refreshToken);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/keycloak/index.native.ts
|
|
94
|
+
import { isStorybook } from "@multiplatform.one/platform";
|
|
95
|
+
import { useContext as useContext3 } from "react";
|
|
96
|
+
|
|
97
|
+
// src/keycloak/base.ts
|
|
98
|
+
import { jwtDecode as jwtDecode2 } from "jwt-decode";
|
|
99
|
+
import { useContext as useContext2 } from "react";
|
|
100
|
+
|
|
101
|
+
// src/keycloak/context.ts
|
|
102
|
+
import { createContext as createContext2 } from "react";
|
|
103
|
+
var KeycloakContext = createContext2(void 0);
|
|
104
|
+
|
|
105
|
+
// src/keycloak/base.ts
|
|
106
|
+
var BaseKeycloak = class {
|
|
107
|
+
constructor(config, input, idToken, refreshToken, login, logout) {
|
|
108
|
+
this.config = config;
|
|
109
|
+
this.authenticated = false;
|
|
110
|
+
this.clientId = this.config.clientId;
|
|
111
|
+
this.realm = this.config.realm;
|
|
112
|
+
if (typeof input === "string") {
|
|
113
|
+
this.token = input;
|
|
114
|
+
this.idToken = idToken;
|
|
115
|
+
this.refreshToken = refreshToken;
|
|
116
|
+
this._parseTokens();
|
|
117
|
+
} else if (typeof input === "object") {
|
|
118
|
+
this._handleInputObject(input);
|
|
119
|
+
}
|
|
120
|
+
this._login = login;
|
|
121
|
+
this._logout = logout;
|
|
122
|
+
this._sync();
|
|
123
|
+
}
|
|
124
|
+
_handleInputObject(input) {
|
|
125
|
+
if (typeof input.init === "function") {
|
|
126
|
+
this._keycloakClient = input;
|
|
127
|
+
} else {
|
|
128
|
+
this._mock = input;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
_parseTokens() {
|
|
132
|
+
if (this.token && !this.tokenParsed) {
|
|
133
|
+
this.tokenParsed = jwtDecode2(this.token);
|
|
134
|
+
}
|
|
135
|
+
if (this.idToken && !this.idTokenParsed) {
|
|
136
|
+
this.idTokenParsed = jwtDecode2(this.idToken);
|
|
137
|
+
}
|
|
138
|
+
if (this.refreshToken && !this.refreshTokenParsed) {
|
|
139
|
+
this.refreshTokenParsed = jwtDecode2(this.refreshToken);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
_clear() {
|
|
143
|
+
this.authenticated = false;
|
|
144
|
+
this.email = void 0;
|
|
145
|
+
this.idToken = void 0;
|
|
146
|
+
this.idTokenParsed = void 0;
|
|
147
|
+
this.realmAccess = void 0;
|
|
148
|
+
this.refreshToken = void 0;
|
|
149
|
+
this.refreshTokenParsed = void 0;
|
|
150
|
+
this.resourceAccess = void 0;
|
|
151
|
+
this.sessionId = void 0;
|
|
152
|
+
this.subject = void 0;
|
|
153
|
+
this.token = void 0;
|
|
154
|
+
this.tokenParsed = void 0;
|
|
155
|
+
this.username = void 0;
|
|
156
|
+
}
|
|
157
|
+
_sync() {
|
|
158
|
+
if (this._mock) {
|
|
159
|
+
this.authenticated = true;
|
|
160
|
+
this.email = this._mock.email;
|
|
161
|
+
this.username = this._mock.username;
|
|
162
|
+
} else if (this._keycloakClient) {
|
|
163
|
+
this.authenticated = !!this._keycloakClient.authenticated;
|
|
164
|
+
this.email = this._keycloakClient?.tokenParsed?.email;
|
|
165
|
+
this.idToken = this._keycloakClient?.idToken;
|
|
166
|
+
this.idTokenParsed = this._keycloakClient?.idTokenParsed;
|
|
167
|
+
this.realmAccess = this._keycloakClient?.realmAccess;
|
|
168
|
+
this.refreshToken = this._keycloakClient?.refreshToken;
|
|
169
|
+
this.refreshTokenParsed = this._keycloakClient?.refreshTokenParsed;
|
|
170
|
+
this.resourceAccess = this._keycloakClient?.resourceAccess;
|
|
171
|
+
this.sessionId = this._keycloakClient?.sessionId;
|
|
172
|
+
this.subject = this._keycloakClient?.subject;
|
|
173
|
+
this.token = this._keycloakClient?.token;
|
|
174
|
+
this.tokenParsed = this._keycloakClient?.tokenParsed;
|
|
175
|
+
this.username = this.tokenParsed?.preferred_username;
|
|
176
|
+
if (this._keycloakClient.realm) this.realm = this._keycloakClient.realm;
|
|
177
|
+
if (this._keycloakClient.clientId) {
|
|
178
|
+
this.clientId = this._keycloakClient.clientId;
|
|
179
|
+
}
|
|
180
|
+
} else if (this.tokenParsed) {
|
|
181
|
+
this.clientId = this.tokenParsed.azp || this.config.clientId;
|
|
182
|
+
this.email = this.tokenParsed.email;
|
|
183
|
+
this.realm = this.tokenParsed.iss?.split("/").pop() || this.config.realm;
|
|
184
|
+
this.realmAccess = this.tokenParsed.realm_access;
|
|
185
|
+
this.resourceAccess = this.tokenParsed.resource_access;
|
|
186
|
+
this.sessionId = this.tokenParsed.session_state;
|
|
187
|
+
this.subject = this.tokenParsed.sub;
|
|
188
|
+
this.authenticated = !!(this.tokenParsed?.exp && this.idToken && this.tokenParsed.exp > Date.now() / 1e3);
|
|
189
|
+
this.username = this.tokenParsed?.preferred_username;
|
|
190
|
+
} else {
|
|
191
|
+
return this._clear();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async getUserInfo() {
|
|
195
|
+
if (!this.authenticated) return;
|
|
196
|
+
const response = await fetch(
|
|
197
|
+
`${this.config.url}/realms/${this.realm}/protocol/openid-connect/userinfo`,
|
|
198
|
+
{
|
|
199
|
+
method: "GET",
|
|
200
|
+
headers: {
|
|
201
|
+
Authorization: `Bearer ${this.token}`,
|
|
202
|
+
Accept: "application/json"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
if (response.ok) return response.json();
|
|
207
|
+
}
|
|
208
|
+
async login(options = {}) {
|
|
209
|
+
this._clear();
|
|
210
|
+
if (this._keycloakClient) {
|
|
211
|
+
await this._keycloakClient.login(options);
|
|
212
|
+
} else {
|
|
213
|
+
await this._login?.(options);
|
|
214
|
+
}
|
|
215
|
+
this._sync();
|
|
216
|
+
}
|
|
217
|
+
async logout(options = {}) {
|
|
218
|
+
if (this._keycloakClient) {
|
|
219
|
+
await this._keycloakClient.logout(options);
|
|
220
|
+
} else {
|
|
221
|
+
await this._logout?.(options);
|
|
222
|
+
}
|
|
223
|
+
this._clear();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/keycloak/config.ts
|
|
228
|
+
import { createContext as createContext3 } from "react";
|
|
229
|
+
var KeycloakConfigContext = createContext3({
|
|
230
|
+
clientId: "",
|
|
231
|
+
realm: "",
|
|
232
|
+
url: ""
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// src/keycloak/index.native.ts
|
|
236
|
+
var Keycloak = class extends BaseKeycloak {
|
|
237
|
+
};
|
|
238
|
+
function useKeycloak() {
|
|
239
|
+
const keycloak = useContext3(KeycloakContext);
|
|
240
|
+
const keycloakConfig = useContext3(KeycloakConfigContext);
|
|
241
|
+
const { disabled } = useAuthConfig();
|
|
242
|
+
if (disabled) return null;
|
|
243
|
+
if (keycloak) return keycloak;
|
|
244
|
+
if (isStorybook) {
|
|
245
|
+
return new Keycloak(keycloakConfig, {
|
|
246
|
+
email: "storybook@example.com",
|
|
247
|
+
username: "storybook"
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/Authenticated.tsx
|
|
253
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
254
|
+
function Authenticated({
|
|
255
|
+
children,
|
|
256
|
+
disabled,
|
|
257
|
+
loggedOutComponent,
|
|
258
|
+
loadingComponent
|
|
259
|
+
}) {
|
|
260
|
+
const authConfig = useAuthConfig();
|
|
261
|
+
const keycloak = useKeycloak();
|
|
262
|
+
const tokensFromQuery = useTokensFromQuery();
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (!keycloak || !authConfig.iframeSso && isIframe2 || isServer2 || keycloak.authenticated || tokensFromQuery) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
keycloak?.login({
|
|
268
|
+
redirectUri: authConfig.loginRedirectUri
|
|
269
|
+
});
|
|
270
|
+
}, [keycloak?.authenticated]);
|
|
271
|
+
if (typeof disabled === "undefined") disabled = authConfig.disabled;
|
|
272
|
+
if (disabled) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
273
|
+
if (typeof keycloak === "undefined") {
|
|
274
|
+
const LoadingComponent = loadingComponent;
|
|
275
|
+
return LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : /* @__PURE__ */ jsx(Text, { children: authConfig.debug ? "loading" : null });
|
|
276
|
+
}
|
|
277
|
+
if (keycloak === null || keycloak.authenticated) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
278
|
+
const LoggedOutComponent = loggedOutComponent;
|
|
279
|
+
return LoggedOutComponent ? /* @__PURE__ */ jsx(LoggedOutComponent, {}) : /* @__PURE__ */ jsx(Text, { children: authConfig.debug ? "not authenticated" : null });
|
|
280
|
+
}
|
|
281
|
+
function withAuthenticated(Component, options = {}) {
|
|
282
|
+
return (props) => /* @__PURE__ */ jsx(Authenticated, { ...options, children: /* @__PURE__ */ jsx(Component, { ...props }) });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/provider/AfterAuth.tsx
|
|
286
|
+
import { logger } from "@multiplatform.one/logger";
|
|
287
|
+
import { useEffect as useEffect2 } from "react";
|
|
288
|
+
import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
|
|
289
|
+
function AfterAuth({ children, loadingComponent: _loadingComponent }) {
|
|
290
|
+
const authConfig = useAuthConfig();
|
|
291
|
+
const authStore2 = useAuthStore();
|
|
292
|
+
const keycloak = useKeycloak();
|
|
293
|
+
useEffect2(() => {
|
|
294
|
+
if (!persist || !keycloak?.authenticated) return;
|
|
295
|
+
if (keycloak.token) {
|
|
296
|
+
authStore2.setTokens({
|
|
297
|
+
token: keycloak.token,
|
|
298
|
+
...keycloak.idToken && { idToken: keycloak.idToken },
|
|
299
|
+
...keycloak.refreshToken && {
|
|
300
|
+
refreshToken: keycloak.refreshToken
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}, [
|
|
305
|
+
authStore2,
|
|
306
|
+
persist,
|
|
307
|
+
keycloak?.authenticated,
|
|
308
|
+
keycloak?.token,
|
|
309
|
+
keycloak?.idToken,
|
|
310
|
+
keycloak?.refreshToken
|
|
311
|
+
]);
|
|
312
|
+
useEffect2(() => {
|
|
313
|
+
if (authConfig.debug && keycloak?.token) {
|
|
314
|
+
logger.debug("token", keycloak.token);
|
|
315
|
+
}
|
|
316
|
+
}, [authConfig.debug, keycloak?.token]);
|
|
317
|
+
useEffect2(() => {
|
|
318
|
+
if (authConfig.debug && keycloak?.idToken) {
|
|
319
|
+
logger.debug("idToken", keycloak.idToken);
|
|
320
|
+
}
|
|
321
|
+
}, [authConfig.debug, keycloak?.idToken]);
|
|
322
|
+
useEffect2(() => {
|
|
323
|
+
if (authConfig.debug && keycloak?.refreshToken) {
|
|
324
|
+
logger.debug("refreshToken", keycloak.refreshToken);
|
|
325
|
+
}
|
|
326
|
+
}, [authConfig.debug, keycloak?.refreshToken]);
|
|
327
|
+
useEffect2(() => {
|
|
328
|
+
if (authConfig.debug && keycloak?.authenticated) {
|
|
329
|
+
logger.debug("authenticated", keycloak.authenticated);
|
|
330
|
+
}
|
|
331
|
+
}, [authConfig.debug, keycloak?.authenticated]);
|
|
332
|
+
useEffect2(() => {
|
|
333
|
+
if (authConfig.debug && keycloak === null) {
|
|
334
|
+
logger.debug("keycloak disabled");
|
|
335
|
+
}
|
|
336
|
+
}, [authConfig.debug, keycloak]);
|
|
337
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/provider/authProvider/index.native.tsx
|
|
341
|
+
import { useEffect as useEffect3, useMemo, useState } from "react";
|
|
342
|
+
import * as Linking2 from "expo-linking";
|
|
343
|
+
|
|
344
|
+
// src/Loading.tsx
|
|
345
|
+
import { ActivityIndicator, Text as Text2, View } from "react-native";
|
|
346
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
347
|
+
function Loading({ loadingComponent }) {
|
|
348
|
+
const { debug } = useAuthConfig();
|
|
349
|
+
const LoadingComponent = loadingComponent;
|
|
350
|
+
if (typeof LoadingComponent === "undefined") {
|
|
351
|
+
return /* @__PURE__ */ jsxs(View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: [
|
|
352
|
+
/* @__PURE__ */ jsx3(ActivityIndicator, {}),
|
|
353
|
+
debug ? /* @__PURE__ */ jsx3(Text2, { children: "loading" }) : null
|
|
354
|
+
] });
|
|
355
|
+
}
|
|
356
|
+
return /* @__PURE__ */ jsx3(LoadingComponent, {});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/betterAuth/expoAuthFlow.native.ts
|
|
360
|
+
import * as Linking from "expo-linking";
|
|
361
|
+
import * as SecureStore from "expo-secure-store";
|
|
362
|
+
import * as WebBrowser from "expo-web-browser";
|
|
363
|
+
var COOKIE_KEY = "better-auth_cookie";
|
|
364
|
+
var SECURE_COOKIE_PREFIX = "__Secure-";
|
|
365
|
+
function readJar() {
|
|
366
|
+
try {
|
|
367
|
+
return JSON.parse(SecureStore.getItem(COOKIE_KEY) || "{}");
|
|
368
|
+
} catch {
|
|
369
|
+
return {};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function writeJar(jar) {
|
|
373
|
+
SecureStore.setItem(COOKIE_KEY, JSON.stringify(jar));
|
|
374
|
+
}
|
|
375
|
+
function cookieHeader() {
|
|
376
|
+
return Object.entries(readJar()).reduce((acc, [key, v]) => {
|
|
377
|
+
if (v.expires && new Date(v.expires) < /* @__PURE__ */ new Date()) return acc;
|
|
378
|
+
return acc ? `${acc}; ${key}=${v.value}` : `${key}=${v.value}`;
|
|
379
|
+
}, "");
|
|
380
|
+
}
|
|
381
|
+
function absorbSetCookie(header) {
|
|
382
|
+
if (!header) return;
|
|
383
|
+
const jar = readJar();
|
|
384
|
+
for (const part of header.split(/,(?=[^;,]+?=)/)) {
|
|
385
|
+
const [pair, ...attrs] = part.split(";");
|
|
386
|
+
const eq = pair.indexOf("=");
|
|
387
|
+
if (eq === -1) continue;
|
|
388
|
+
const name = pair.slice(0, eq).trim();
|
|
389
|
+
const value = pair.slice(eq + 1).trim();
|
|
390
|
+
let expires = null;
|
|
391
|
+
let dead = false;
|
|
392
|
+
for (const attr of attrs) {
|
|
393
|
+
const [k, v] = attr.split("=").map((s) => s?.trim());
|
|
394
|
+
const lk = (k || "").toLowerCase();
|
|
395
|
+
if (lk === "max-age") {
|
|
396
|
+
const n = Number(v);
|
|
397
|
+
if (n <= 0) dead = true;
|
|
398
|
+
else expires = new Date(Date.now() + n * 1e3).toISOString();
|
|
399
|
+
} else if (lk === "expires" && !expires && v) {
|
|
400
|
+
const d = new Date(part.slice(part.toLowerCase().indexOf("expires=") + 8).split(";")[0]);
|
|
401
|
+
if (!Number.isNaN(d.getTime())) {
|
|
402
|
+
if (d.getTime() <= Date.now()) dead = true;
|
|
403
|
+
else expires = d.toISOString();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (dead) delete jar[name];
|
|
408
|
+
else jar[name] = { value, expires };
|
|
409
|
+
}
|
|
410
|
+
writeJar(jar);
|
|
411
|
+
}
|
|
412
|
+
function getOAuthState() {
|
|
413
|
+
const jar = readJar();
|
|
414
|
+
for (const name of [
|
|
415
|
+
`${SECURE_COOKIE_PREFIX}better-auth.oauth_state`,
|
|
416
|
+
"better-auth.oauth_state"
|
|
417
|
+
]) {
|
|
418
|
+
if (jar[name]?.value) return jar[name].value;
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
var activeFlow = null;
|
|
423
|
+
function getActiveExpoAuthFlow() {
|
|
424
|
+
return activeFlow;
|
|
425
|
+
}
|
|
426
|
+
function createExpoAuthFlow(baseURL, scheme) {
|
|
427
|
+
const base = baseURL.replace(/\/$/, "");
|
|
428
|
+
const origin = () => Linking.createURL("/", scheme ? { scheme } : void 0);
|
|
429
|
+
async function authFetch(path, init) {
|
|
430
|
+
const cookie = cookieHeader();
|
|
431
|
+
const res = await fetch(`${base}/api/auth${path}`, {
|
|
432
|
+
...init,
|
|
433
|
+
credentials: "omit",
|
|
434
|
+
headers: {
|
|
435
|
+
"Content-Type": "application/json",
|
|
436
|
+
...cookie ? { cookie } : {},
|
|
437
|
+
"expo-origin": origin(),
|
|
438
|
+
"x-skip-oauth-proxy": "true",
|
|
439
|
+
...init?.headers || {}
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
absorbSetCookie(res.headers.get("set-cookie"));
|
|
443
|
+
return res;
|
|
444
|
+
}
|
|
445
|
+
const flow = {
|
|
446
|
+
async signIn(provider, callbackURL) {
|
|
447
|
+
const to = callbackURL || Linking.createURL("/");
|
|
448
|
+
const res = await authFetch("/sign-in/social", {
|
|
449
|
+
method: "POST",
|
|
450
|
+
body: JSON.stringify({ provider, callbackURL: to })
|
|
451
|
+
});
|
|
452
|
+
if (!res.ok) {
|
|
453
|
+
const body = await res.text().catch(() => "");
|
|
454
|
+
throw new Error(`sign-in failed (${res.status}): ${body.slice(0, 200)}`);
|
|
455
|
+
}
|
|
456
|
+
const data = await res.json();
|
|
457
|
+
if (!data?.url) throw new Error("sign-in response had no authorization url");
|
|
458
|
+
const params = new URLSearchParams({ authorizationURL: data.url });
|
|
459
|
+
const oauthState = getOAuthState();
|
|
460
|
+
if (oauthState) params.append("oauthState", oauthState);
|
|
461
|
+
const proxyURL = `${base}/api/auth/expo-authorization-proxy?${params.toString()}`;
|
|
462
|
+
const result = await WebBrowser.openAuthSessionAsync(proxyURL, to);
|
|
463
|
+
if (result.type !== "success") {
|
|
464
|
+
throw new Error(`sign-in ${result.type}`);
|
|
465
|
+
}
|
|
466
|
+
const cookie = new URL(result.url).searchParams.get("cookie");
|
|
467
|
+
if (cookie) absorbSetCookie(cookie);
|
|
468
|
+
},
|
|
469
|
+
async signOut() {
|
|
470
|
+
try {
|
|
471
|
+
await authFetch("/sign-out", { method: "POST", body: "{}" });
|
|
472
|
+
} finally {
|
|
473
|
+
writeJar({});
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
async getSession() {
|
|
477
|
+
const res = await authFetch("/get-session");
|
|
478
|
+
if (!res.ok) return null;
|
|
479
|
+
const data = await res.json().catch(() => null);
|
|
480
|
+
return data && Object.keys(data).length ? data : null;
|
|
481
|
+
},
|
|
482
|
+
async getAccessToken(providerId) {
|
|
483
|
+
const res = await authFetch("/get-access-token", {
|
|
484
|
+
method: "POST",
|
|
485
|
+
body: JSON.stringify({ providerId })
|
|
486
|
+
});
|
|
487
|
+
if (!res.ok) return null;
|
|
488
|
+
return await res.json().catch(() => null);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
activeFlow = flow;
|
|
492
|
+
return flow;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/session/index.native.ts
|
|
496
|
+
import { useSyncExternalStore } from "react";
|
|
497
|
+
var state = { session: null, status: "loading" };
|
|
498
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
499
|
+
function setNativeSession(session, status) {
|
|
500
|
+
state = {
|
|
501
|
+
session,
|
|
502
|
+
status: status ?? (session?.user ? "authenticated" : "unauthenticated")
|
|
503
|
+
};
|
|
504
|
+
for (const l of listeners) l();
|
|
505
|
+
}
|
|
506
|
+
function subscribe(listener) {
|
|
507
|
+
listeners.add(listener);
|
|
508
|
+
return () => listeners.delete(listener);
|
|
509
|
+
}
|
|
510
|
+
async function getSession() {
|
|
511
|
+
return state.session;
|
|
512
|
+
}
|
|
513
|
+
function useSession(_options) {
|
|
514
|
+
return useSyncExternalStore(
|
|
515
|
+
subscribe,
|
|
516
|
+
() => state,
|
|
517
|
+
() => state
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/provider/authProvider/index.native.tsx
|
|
522
|
+
import { Fragment as Fragment3, jsx as jsx4 } from "react/jsx-runtime";
|
|
523
|
+
function AuthProvider({
|
|
524
|
+
children,
|
|
525
|
+
disabled,
|
|
526
|
+
keycloakConfig,
|
|
527
|
+
loadingComponent
|
|
528
|
+
}) {
|
|
529
|
+
const [keycloak, setKeycloak] = useState();
|
|
530
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
531
|
+
const serverBaseUrl = useMemo(() => {
|
|
532
|
+
return keycloakConfig.betterAuthBaseUrl || "http://localhost:8000";
|
|
533
|
+
}, [keycloakConfig]);
|
|
534
|
+
const scheme = useMemo(() => keycloakConfig.expoScheme, [keycloakConfig]);
|
|
535
|
+
const flow = useMemo(
|
|
536
|
+
() => disabled ? null : createExpoAuthFlow(serverBaseUrl, scheme),
|
|
537
|
+
[disabled, serverBaseUrl, scheme]
|
|
538
|
+
);
|
|
539
|
+
useEffect3(() => {
|
|
540
|
+
if (disabled || !flow) {
|
|
541
|
+
setIsLoading(false);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
let alive = true;
|
|
545
|
+
const refresh = async () => {
|
|
546
|
+
let session = null;
|
|
547
|
+
try {
|
|
548
|
+
session = await flow.getSession();
|
|
549
|
+
} catch (err) {
|
|
550
|
+
console.error("[keycloak] native session check failed:", err);
|
|
551
|
+
}
|
|
552
|
+
const _keycloak = new Keycloak(
|
|
553
|
+
keycloakConfig,
|
|
554
|
+
void 0,
|
|
555
|
+
void 0,
|
|
556
|
+
void 0,
|
|
557
|
+
async (options) => {
|
|
558
|
+
await flow.signIn("keycloak", options.redirectUri || Linking2.createURL("/"));
|
|
559
|
+
await refresh();
|
|
560
|
+
return void 0;
|
|
561
|
+
},
|
|
562
|
+
async (_options) => {
|
|
563
|
+
await flow.signOut();
|
|
564
|
+
await refresh();
|
|
565
|
+
return void 0;
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
if (session?.user) {
|
|
569
|
+
_keycloak.authenticated = true;
|
|
570
|
+
_keycloak.email = session.user.email || void 0;
|
|
571
|
+
_keycloak.username = session.user.name || void 0;
|
|
572
|
+
_keycloak.subject = session.user.id;
|
|
573
|
+
}
|
|
574
|
+
if (!alive) return;
|
|
575
|
+
setNativeSession(
|
|
576
|
+
session?.user ? {
|
|
577
|
+
user: {
|
|
578
|
+
id: session.user.id,
|
|
579
|
+
name: session.user.name ?? null,
|
|
580
|
+
email: session.user.email ?? null
|
|
581
|
+
}
|
|
582
|
+
} : null
|
|
583
|
+
);
|
|
584
|
+
setKeycloak(_keycloak);
|
|
585
|
+
};
|
|
586
|
+
refresh().finally(() => {
|
|
587
|
+
if (alive) setIsLoading(false);
|
|
588
|
+
});
|
|
589
|
+
return () => {
|
|
590
|
+
alive = false;
|
|
591
|
+
};
|
|
592
|
+
}, [disabled, flow, keycloakConfig]);
|
|
593
|
+
if (disabled) return /* @__PURE__ */ jsx4(Fragment3, { children });
|
|
594
|
+
if (isLoading || !keycloak) {
|
|
595
|
+
return /* @__PURE__ */ jsx4(Loading, { loadingComponent });
|
|
596
|
+
}
|
|
597
|
+
return /* @__PURE__ */ jsx4(KeycloakConfigContext.Provider, { value: keycloakConfig, children: /* @__PURE__ */ jsx4(KeycloakContext.Provider, { value: keycloak, children: /* @__PURE__ */ jsx4(AfterAuth, { children }) }) });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/provider/KeycloakProvider.tsx
|
|
601
|
+
import { useMemo as useMemo2 } from "react";
|
|
602
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
603
|
+
function KeycloakProvider({
|
|
604
|
+
baseUrl,
|
|
605
|
+
betterAuthBaseUrl,
|
|
606
|
+
children,
|
|
607
|
+
clientId,
|
|
608
|
+
expoScheme,
|
|
609
|
+
loginRedirectUri,
|
|
610
|
+
debug,
|
|
611
|
+
disabled,
|
|
612
|
+
iframeSso,
|
|
613
|
+
publicClientId,
|
|
614
|
+
realm
|
|
615
|
+
}) {
|
|
616
|
+
const authConfig = useMemo2(
|
|
617
|
+
() => ({ debug, disabled, iframeSso, loginRedirectUri }),
|
|
618
|
+
[debug, disabled, iframeSso, loginRedirectUri]
|
|
619
|
+
);
|
|
620
|
+
return /* @__PURE__ */ jsx5(AuthConfigContext.Provider, { value: authConfig, children: /* @__PURE__ */ jsx5(
|
|
621
|
+
AuthProvider,
|
|
622
|
+
{
|
|
623
|
+
disabled,
|
|
624
|
+
keycloakConfig: {
|
|
625
|
+
clientId: clientId || "app",
|
|
626
|
+
publicClientId,
|
|
627
|
+
realm: realm || "main",
|
|
628
|
+
url: baseUrl ?? "",
|
|
629
|
+
betterAuthBaseUrl,
|
|
630
|
+
expoScheme
|
|
631
|
+
},
|
|
632
|
+
children
|
|
633
|
+
}
|
|
634
|
+
) });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/frappeToken.native.ts
|
|
638
|
+
var cached;
|
|
639
|
+
async function getKeycloakBearerToken() {
|
|
640
|
+
if (cached && cached.expiresAt - 6e4 > Date.now()) return cached.token;
|
|
641
|
+
const flow = getActiveExpoAuthFlow();
|
|
642
|
+
if (!flow) return void 0;
|
|
643
|
+
try {
|
|
644
|
+
const res = await flow.getAccessToken("keycloak");
|
|
645
|
+
if (!res?.accessToken) {
|
|
646
|
+
cached = void 0;
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
649
|
+
cached = {
|
|
650
|
+
token: res.accessToken,
|
|
651
|
+
expiresAt: res.accessTokenExpiresAt ? new Date(res.accessTokenExpiresAt).getTime() : Date.now() + 6e4
|
|
652
|
+
};
|
|
653
|
+
return cached.token;
|
|
654
|
+
} catch {
|
|
655
|
+
cached = void 0;
|
|
656
|
+
return void 0;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
export {
|
|
660
|
+
AfterAuth,
|
|
661
|
+
AuthConfigContext,
|
|
662
|
+
AuthProvider,
|
|
663
|
+
Authenticated,
|
|
664
|
+
BaseKeycloak,
|
|
665
|
+
Keycloak,
|
|
666
|
+
KeycloakConfigContext,
|
|
667
|
+
KeycloakProvider,
|
|
668
|
+
defaultAuthConfig,
|
|
669
|
+
getAllWalletAddresses,
|
|
670
|
+
getKeycloakBearerToken,
|
|
671
|
+
getSession,
|
|
672
|
+
getWalletAddresses,
|
|
673
|
+
getWalletsFromToken,
|
|
674
|
+
hasWallets,
|
|
675
|
+
isTokenExpired,
|
|
676
|
+
setNativeSession,
|
|
677
|
+
useAuthConfig,
|
|
678
|
+
useKeycloak,
|
|
679
|
+
useSession,
|
|
680
|
+
useTokensFromQuery,
|
|
681
|
+
useTokensFromStore,
|
|
682
|
+
validOrRefreshableToken,
|
|
683
|
+
withAuthenticated
|
|
684
|
+
};
|