@multiplatform.one/keycloak 0.0.2 → 0.0.4
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/esm/Authenticated.js +8 -19
- package/esm/Authenticated.js.map +1 -1
- package/esm/authConfig.js +2 -2
- package/esm/authConfig.js.map +1 -1
- package/esm/expo/ExpoKeycloakContext.js +28 -0
- package/esm/expo/ExpoKeycloakContext.js.map +1 -0
- package/esm/expo/ExpoKeycloakProvider.js +113 -0
- package/esm/expo/ExpoKeycloakProvider.js.map +1 -0
- package/esm/expo/KeycloakContext.js +28 -0
- package/esm/expo/KeycloakContext.js.map +1 -0
- package/esm/expo/KeycloakProvider.js +142 -0
- package/esm/expo/KeycloakProvider.js.map +1 -0
- package/esm/expo/const.js +42 -0
- package/esm/expo/const.js.map +1 -0
- package/esm/expo/helpers.js +56 -0
- package/esm/expo/helpers.js.map +1 -0
- package/esm/expo/index.js +28 -0
- package/esm/expo/index.js.map +1 -0
- package/esm/expo/useExpoKeycloak.js +44 -0
- package/esm/expo/useExpoKeycloak.js.map +1 -0
- package/esm/expo/useTokenStorage.js +153 -0
- package/esm/expo/useTokenStorage.js.map +1 -0
- package/esm/hooks/useCurrentRouteName.js +2 -3
- package/esm/hooks/useCurrentRouteName.js.map +1 -1
- package/esm/hooks/useKeycloak/index.js +1 -1
- package/esm/hooks/useKeycloak/index.js.map +1 -1
- package/esm/hooks/useKeycloak/useKeycloak.js +17 -24
- package/esm/hooks/useKeycloak/useKeycloak.js.map +1 -1
- package/esm/hooks/useKeycloak/useKeycloak.native.js +39 -24
- package/esm/hooks/useKeycloak/useKeycloak.native.js.map +1 -1
- package/esm/hooks/useLogin.js +12 -30
- package/esm/hooks/useLogin.js.map +1 -1
- package/esm/provider/afterAuth.js +21 -39
- package/esm/provider/afterAuth.js.map +1 -1
- package/esm/provider/index.js +11 -28
- package/esm/provider/index.js.map +1 -1
- package/esm/provider/keycloakProvider.js +55 -100
- package/esm/provider/keycloakProvider.js.map +1 -1
- package/esm/provider/keycloakProvider.native.js +12 -11
- package/esm/provider/keycloakProvider.native.js.map +1 -1
- package/esm/state/index.js +2 -2
- package/esm/state/index.js.map +1 -1
- package/lib/Authenticated.js +38 -26
- package/lib/Authenticated.js.map +1 -1
- package/lib/authConfig.js +2 -4
- package/lib/authConfig.js.map +1 -1
- package/lib/expo/ExpoKeycloakContext.d.ts +32 -0
- package/lib/expo/ExpoKeycloakContext.js +47 -0
- package/lib/expo/ExpoKeycloakContext.js.map +1 -0
- package/lib/expo/ExpoKeycloakProvider.d.ts +37 -0
- package/lib/expo/ExpoKeycloakProvider.js +130 -0
- package/lib/expo/ExpoKeycloakProvider.js.map +1 -0
- package/lib/expo/KeycloakContext.d.ts +32 -0
- package/lib/expo/KeycloakContext.js +49 -0
- package/lib/expo/KeycloakContext.js.map +1 -0
- package/lib/expo/KeycloakProvider.d.ts +37 -0
- package/lib/expo/KeycloakProvider.js +138 -0
- package/lib/expo/KeycloakProvider.js.map +1 -0
- package/lib/expo/const.d.ts +33 -0
- package/lib/expo/const.js +66 -0
- package/lib/expo/const.js.map +1 -0
- package/lib/expo/helpers.d.ts +41 -0
- package/lib/expo/helpers.js +77 -0
- package/lib/expo/helpers.js.map +1 -0
- package/lib/expo/index.d.ts +26 -0
- package/lib/expo/index.js +50 -0
- package/lib/expo/index.js.map +1 -0
- package/lib/expo/useExpoKeycloak.d.ts +31 -0
- package/lib/expo/useExpoKeycloak.js +62 -0
- package/lib/expo/useExpoKeycloak.js.map +1 -0
- package/lib/expo/useTokenStorage.d.ts +36 -0
- package/lib/expo/useTokenStorage.js +166 -0
- package/lib/expo/useTokenStorage.js.map +1 -0
- package/lib/hooks/index.js +3 -9
- package/lib/hooks/index.js.map +1 -1
- package/lib/hooks/useAuthConfig.js +0 -2
- package/lib/hooks/useAuthConfig.js.map +1 -1
- package/lib/hooks/useCurrentRouteName.js +6 -8
- package/lib/hooks/useCurrentRouteName.js.map +1 -1
- package/lib/hooks/useKeycloak/index.d.ts +9 -5
- package/lib/hooks/useKeycloak/index.js +1 -7
- package/lib/hooks/useKeycloak/index.js.map +1 -1
- package/lib/hooks/useKeycloak/useKeycloak.js +45 -30
- package/lib/hooks/useKeycloak/useKeycloak.js.map +1 -1
- package/lib/hooks/useKeycloak/useKeycloak.native.d.ts +12 -4
- package/lib/hooks/useKeycloak/useKeycloak.native.js +40 -28
- package/lib/hooks/useKeycloak/useKeycloak.native.js.map +1 -1
- package/lib/hooks/useLogin.js +16 -25
- package/lib/hooks/useLogin.js.map +1 -1
- package/lib/index.js +4 -10
- package/lib/index.js.map +1 -1
- package/lib/provider/afterAuth.js +52 -52
- package/lib/provider/afterAuth.js.map +1 -1
- package/lib/provider/index.js +44 -43
- package/lib/provider/index.js.map +1 -1
- package/lib/provider/keycloakProvider.js +93 -114
- package/lib/provider/keycloakProvider.js.map +1 -1
- package/lib/provider/keycloakProvider.native.js +39 -15
- package/lib/provider/keycloakProvider.native.js.map +1 -1
- package/lib/state/index.js +3 -5
- package/lib/state/index.js.map +1 -1
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/types.js +2 -4
- package/lib/types.js.map +1 -1
- package/package.json +3 -2
- package/src/expo/ExpoKeycloakContext.ts +41 -0
- package/src/expo/ExpoKeycloakProvider.tsx +139 -0
- package/src/expo/NOTES +1 -0
- package/src/expo/const.ts +42 -0
- package/src/expo/helpers.ts +74 -0
- package/src/{@types/expoKeycloakAuth.d.ts → expo/index.ts} +6 -7
- package/src/expo/useExpoKeycloak.ts +44 -0
- package/src/expo/useTokenStorage.ts +168 -0
- package/src/hooks/useKeycloak/index.ts +13 -5
- package/src/hooks/useKeycloak/useKeycloak.native.ts +48 -11
- package/src/provider/keycloakProvider.native.tsx +8 -4
package/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * File: /src/types.ts\n * Project: @multiplatform.one/keycloak\n * File Created: 25-11-2022 10:27:53\n * Author: Clay Risser\n * -----\n * Last Modified: 25-11-2022 10:28:25\n * Modified By: Clay Risser\n * -----\n * Risser Labs LLC (c) Copyright 2022\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\ndeclare global {\n interface Window {\n ReactNativeWebView: {\n postMessage: (body: string) => unknown;\n };\n webkit: {\n messageHandlers: {\n [key: string]: {\n postMessage: (body: string) => unknown;\n };\n };\n };\n }\n}\n\nexport default null;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * File: /src/types.ts\n * Project: @multiplatform.one/keycloak\n * File Created: 25-11-2022 10:27:53\n * Author: Clay Risser\n * -----\n * Last Modified: 25-11-2022 10:28:25\n * Modified By: Clay Risser\n * -----\n * Risser Labs LLC (c) Copyright 2022\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\ndeclare global {\n interface Window {\n ReactNativeWebView: {\n postMessage: (body: string) => unknown;\n };\n webkit: {\n messageHandlers: {\n [key: string]: {\n postMessage: (body: string) => unknown;\n };\n };\n };\n }\n}\n\nexport default null;\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EAtBA,eAuCe,IAAI;EAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/keycloak",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "keycloak client for multiplatform.one ecosystem",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -135,7 +135,8 @@
|
|
|
135
135
|
"dependencies": {
|
|
136
136
|
"@react-keycloak/ssr": "^3.3.0",
|
|
137
137
|
"@react-keycloak/web": "^3.4.0",
|
|
138
|
-
"
|
|
138
|
+
"@react-native-async-storage/async-storage": "^1.17.11",
|
|
139
|
+
"expo-auth-session": "^3.7.2",
|
|
139
140
|
"keycloak-js": "^20.0.1"
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/expo/ExpoKeycloakContext.ts
|
|
3
|
+
* Project: @multiplatform.one/keycloak
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 26-11-2022 08:38:25
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* Risser Labs LLC (c) Copyright 2022
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
AuthRequestPromptOptions,
|
|
27
|
+
AuthSessionResult,
|
|
28
|
+
TokenResponse,
|
|
29
|
+
} from "expo-auth-session";
|
|
30
|
+
import { createContext } from "react";
|
|
31
|
+
import { KC_INITIAL_VALUE } from "./const";
|
|
32
|
+
|
|
33
|
+
export const ExpoKeycloakContext = createContext<{
|
|
34
|
+
ready: boolean;
|
|
35
|
+
isLoggedIn?: boolean;
|
|
36
|
+
login: (
|
|
37
|
+
options?: AuthRequestPromptOptions
|
|
38
|
+
) => Promise<AuthSessionResult | undefined>;
|
|
39
|
+
logout: () => undefined;
|
|
40
|
+
token: TokenResponse | null;
|
|
41
|
+
}>(KC_INITIAL_VALUE);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/expo/KeycloakProvider.ts
|
|
3
|
+
* Project: @multiplatform.one/keycloak
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 26-11-2022 08:06:40
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* Risser Labs LLC (c) Copyright 2022
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import React, { ReactNode, useCallback, useEffect } from "react";
|
|
26
|
+
import { Platform } from "react-native";
|
|
27
|
+
import * as AuthSession from "expo-auth-session";
|
|
28
|
+
import {
|
|
29
|
+
useAuthRequest,
|
|
30
|
+
useAutoDiscovery,
|
|
31
|
+
AuthRequestConfig,
|
|
32
|
+
} from "expo-auth-session";
|
|
33
|
+
import { ExpoKeycloakContext } from "./ExpoKeycloakContext";
|
|
34
|
+
import { useTokenStorage } from "./useTokenStorage";
|
|
35
|
+
import { handleTokenExchange, getRealmURL } from "./helpers";
|
|
36
|
+
import { NATIVE_REDIRECT_PATH } from "./const";
|
|
37
|
+
|
|
38
|
+
export interface IKeycloakConfiguration extends Partial<AuthRequestConfig> {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
clientId: string;
|
|
41
|
+
disableAutoRefresh?: boolean;
|
|
42
|
+
nativeRedirectPath?: string;
|
|
43
|
+
realm: string;
|
|
44
|
+
refreshTimeBuffer?: number;
|
|
45
|
+
scheme?: string;
|
|
46
|
+
tokenStorageKey?: string;
|
|
47
|
+
url: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const ExpoKeycloakProvider = ({
|
|
51
|
+
realm,
|
|
52
|
+
clientId,
|
|
53
|
+
url,
|
|
54
|
+
extraParams,
|
|
55
|
+
children,
|
|
56
|
+
...options
|
|
57
|
+
}: IKeycloakConfiguration) => {
|
|
58
|
+
const discovery = useAutoDiscovery(getRealmURL({ realm, url }));
|
|
59
|
+
const redirectUri = AuthSession.makeRedirectUri({
|
|
60
|
+
native: `${options.scheme ?? "exp"}://${
|
|
61
|
+
options.nativeRedirectPath ?? NATIVE_REDIRECT_PATH
|
|
62
|
+
}`,
|
|
63
|
+
useProxy: !options.scheme,
|
|
64
|
+
});
|
|
65
|
+
const config = {
|
|
66
|
+
clientId,
|
|
67
|
+
extraParams,
|
|
68
|
+
realm,
|
|
69
|
+
redirectUri,
|
|
70
|
+
url,
|
|
71
|
+
};
|
|
72
|
+
const [request, response, promptAsync] = useAuthRequest(
|
|
73
|
+
{ usePKCE: false, ...config },
|
|
74
|
+
discovery
|
|
75
|
+
);
|
|
76
|
+
const [currentToken, updateToken] = useTokenStorage(
|
|
77
|
+
options,
|
|
78
|
+
config,
|
|
79
|
+
discovery!
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const handleLogin = useCallback(
|
|
83
|
+
(options: AuthSession.AuthRequestPromptOptions = {}) =>
|
|
84
|
+
promptAsync(options),
|
|
85
|
+
[request]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
function handleLogout() {
|
|
89
|
+
if (!currentToken) throw new Error("Not logged in.");
|
|
90
|
+
try {
|
|
91
|
+
if (discovery?.revocationEndpoint) {
|
|
92
|
+
AuthSession.revokeAsync(
|
|
93
|
+
{ token: currentToken?.accessToken, ...config },
|
|
94
|
+
discovery
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (discovery?.endSessionEndpoint) {
|
|
98
|
+
fetch(`${discovery.endSessionEndpoint}`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
102
|
+
},
|
|
103
|
+
body: `client_id=${clientId}&refresh_token=${currentToken.refreshToken}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (Platform.OS === "ios") {
|
|
107
|
+
AuthSession.dismiss();
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.log(error);
|
|
111
|
+
}
|
|
112
|
+
updateToken(null);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (response) {
|
|
118
|
+
handleTokenExchange({ response, discovery: discovery!, config }).then(
|
|
119
|
+
updateToken
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}, [response]);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<ExpoKeycloakContext.Provider
|
|
126
|
+
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
|
127
|
+
value={{
|
|
128
|
+
isLoggedIn: currentToken === undefined ? undefined : !!currentToken,
|
|
129
|
+
login: handleLogin,
|
|
130
|
+
logout: handleLogout,
|
|
131
|
+
ready:
|
|
132
|
+
discovery !== null && request !== null && currentToken !== undefined,
|
|
133
|
+
token: currentToken,
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
{children}
|
|
137
|
+
</ExpoKeycloakContext.Provider>
|
|
138
|
+
);
|
|
139
|
+
};
|
package/src/expo/NOTES
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This folder contains code by rubhiauliatirta (https://github.com/rubhiauliatirta)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/expo/const.ts
|
|
3
|
+
* Project: @multiplatform.one/keycloak
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 26-11-2022 08:17:10
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* Risser Labs LLC (c) Copyright 2022
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const logger = console;
|
|
26
|
+
|
|
27
|
+
export const KC_INITIAL_VALUE = {
|
|
28
|
+
ready: false,
|
|
29
|
+
isLoggedIn: false,
|
|
30
|
+
login: async () => {
|
|
31
|
+
logger.error("kc not initialized");
|
|
32
|
+
return undefined;
|
|
33
|
+
},
|
|
34
|
+
logout: () => {
|
|
35
|
+
logger.error("not logged in");
|
|
36
|
+
return undefined;
|
|
37
|
+
},
|
|
38
|
+
token: null,
|
|
39
|
+
};
|
|
40
|
+
export const NATIVE_REDIRECT_PATH = "auth/redirect";
|
|
41
|
+
export const TOKEN_STORAGE_KEY = "keycloak_token";
|
|
42
|
+
export const REFRESH_TIME_BUFFER = 20;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/expo/helpers.ts
|
|
3
|
+
* Project: @multiplatform.one/keycloak
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 26-11-2022 08:11:15
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* Risser Labs LLC (c) Copyright 2022
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
AccessTokenRequestConfig,
|
|
27
|
+
exchangeCodeAsync,
|
|
28
|
+
DiscoveryDocument,
|
|
29
|
+
AuthSessionResult,
|
|
30
|
+
} from "expo-auth-session";
|
|
31
|
+
|
|
32
|
+
export function getRealmURL(config: RealmURLConfig) {
|
|
33
|
+
const { url, realm } = config;
|
|
34
|
+
const slash = url.endsWith("/") ? "" : "/";
|
|
35
|
+
return `${url + slash}realms/${encodeURIComponent(realm)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getCurrentTimeInSeconds() {
|
|
39
|
+
return Math.floor(Date.now() / 1000);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function handleTokenExchange({
|
|
43
|
+
response,
|
|
44
|
+
discovery,
|
|
45
|
+
config,
|
|
46
|
+
}: TokenExchange) {
|
|
47
|
+
try {
|
|
48
|
+
if (response?.type === "success" && !!discovery?.tokenEndpoint) {
|
|
49
|
+
return exchangeCodeAsync(
|
|
50
|
+
{ code: response.params.code, ...config },
|
|
51
|
+
discovery
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (response?.type === "error") return null;
|
|
55
|
+
return null;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TokenExchange {
|
|
62
|
+
response: AuthSessionResult;
|
|
63
|
+
discovery: Pick<DiscoveryDocument, "tokenEndpoint">;
|
|
64
|
+
config: Omit<AccessTokenRequestConfig, "code"> & { code?: string };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface TokenExchangeDiscovery {
|
|
68
|
+
tokenEndpoint?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface RealmURLConfig {
|
|
72
|
+
realm: string;
|
|
73
|
+
url: string;
|
|
74
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File: /src
|
|
2
|
+
* File: /src/expo/index.ts
|
|
3
3
|
* Project: @multiplatform.one/keycloak
|
|
4
|
-
* File Created:
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
5
|
* Author: Clay Risser
|
|
6
6
|
* -----
|
|
7
|
-
* Last Modified:
|
|
7
|
+
* Last Modified: 26-11-2022 08:38:41
|
|
8
8
|
* Modified By: Clay Risser
|
|
9
9
|
* -----
|
|
10
10
|
* Risser Labs LLC (c) Copyright 2022
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
* limitations under the License.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
25
|
+
export * from "./ExpoKeycloakProvider";
|
|
26
|
+
export * from "./useExpoKeycloak";
|
|
27
|
+
export * from "./const";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/expo/useExpoKeycloak.ts
|
|
3
|
+
* Project: @multiplatform.one/keycloak
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 26-11-2022 08:38:57
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* Risser Labs LLC (c) Copyright 2022
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useContext } from "react";
|
|
26
|
+
import { ExpoKeycloakContext } from "./ExpoKeycloakContext";
|
|
27
|
+
|
|
28
|
+
export function useExpoKeycloak() {
|
|
29
|
+
const {
|
|
30
|
+
isLoggedIn,
|
|
31
|
+
login,
|
|
32
|
+
logout,
|
|
33
|
+
ready = false,
|
|
34
|
+
token = null,
|
|
35
|
+
} = useContext(ExpoKeycloakContext);
|
|
36
|
+
return {
|
|
37
|
+
isLoggedIn,
|
|
38
|
+
login,
|
|
39
|
+
logout,
|
|
40
|
+
ready,
|
|
41
|
+
token: token?.accessToken ?? null,
|
|
42
|
+
refreshToken: token?.refreshToken ?? null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /src/expo/useTokenStorage.ts
|
|
3
|
+
* Project: @multiplatform.one/keycloak
|
|
4
|
+
* File Created: 26-11-2022 06:58:16
|
|
5
|
+
* Author: Clay Risser
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: 26-11-2022 08:08:00
|
|
8
|
+
* Modified By: Clay Risser
|
|
9
|
+
* -----
|
|
10
|
+
* Risser Labs LLC (c) Copyright 2022
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { AppState, AppStateStatus, Platform } from "react-native";
|
|
26
|
+
import { DiscoveryDocument, refreshAsync } from "expo-auth-session";
|
|
27
|
+
import { REFRESH_TIME_BUFFER, TOKEN_STORAGE_KEY } from "./const";
|
|
28
|
+
import { dismiss, revokeAsync, TokenResponse } from "expo-auth-session";
|
|
29
|
+
import { getCurrentTimeInSeconds } from "./helpers";
|
|
30
|
+
import { useAsyncStorage } from "@react-native-async-storage/async-storage";
|
|
31
|
+
import { useEffect, useRef, useState } from "react";
|
|
32
|
+
|
|
33
|
+
const logger = console;
|
|
34
|
+
|
|
35
|
+
export function useTokenStorage(
|
|
36
|
+
{
|
|
37
|
+
disableAutoRefresh = false,
|
|
38
|
+
refreshTimeBuffer = REFRESH_TIME_BUFFER,
|
|
39
|
+
tokenStorageKey = TOKEN_STORAGE_KEY,
|
|
40
|
+
},
|
|
41
|
+
config: {
|
|
42
|
+
clientId: string;
|
|
43
|
+
extraParams?: Record<string, string>;
|
|
44
|
+
realm: string;
|
|
45
|
+
redirectUri: string;
|
|
46
|
+
url: string;
|
|
47
|
+
},
|
|
48
|
+
discovery: DiscoveryDocument
|
|
49
|
+
): [TokenResponse | null, (token: TokenResponse | null) => Promise<void>] {
|
|
50
|
+
const [token, setToken] = useState<TokenResponse | null>();
|
|
51
|
+
const { getItem, setItem, removeItem } = useAsyncStorage(tokenStorageKey);
|
|
52
|
+
const refreshHandler = useRef<NodeJS.Timeout>(null);
|
|
53
|
+
const appState = useRef(AppState.currentState);
|
|
54
|
+
const refreshTime = useRef<number>(null);
|
|
55
|
+
const tokenData = useRef<TokenResponse>(null);
|
|
56
|
+
|
|
57
|
+
async function updateAndSaveToken(newToken: TokenResponse | null) {
|
|
58
|
+
try {
|
|
59
|
+
setToken(newToken);
|
|
60
|
+
if (newToken !== null) {
|
|
61
|
+
const stringifiedValue = JSON.stringify(newToken);
|
|
62
|
+
await setItem(stringifiedValue);
|
|
63
|
+
} else {
|
|
64
|
+
await removeItem();
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function handleTokenRefresh(token: TokenResponse | null) {
|
|
72
|
+
try {
|
|
73
|
+
const tokenResponse = await refreshAsync(
|
|
74
|
+
{ refreshToken: token?.refreshToken, ...config },
|
|
75
|
+
discovery
|
|
76
|
+
);
|
|
77
|
+
updateAndSaveToken(tokenResponse);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
updateAndSaveToken(null);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
function handleAppState(nextAppState: AppStateStatus) {
|
|
85
|
+
if (
|
|
86
|
+
appState.current.match(/inactive|background/) &&
|
|
87
|
+
nextAppState === "active"
|
|
88
|
+
) {
|
|
89
|
+
if (refreshHandler.current !== null) {
|
|
90
|
+
clearTimeout(refreshHandler.current);
|
|
91
|
+
const now = getCurrentTimeInSeconds();
|
|
92
|
+
if (Number(refreshTime.current) <= now) {
|
|
93
|
+
setToken(null);
|
|
94
|
+
} else {
|
|
95
|
+
const timeout = 1000 * (Number(refreshTime.current) - now);
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
refreshHandler.current = setTimeout(() => {
|
|
98
|
+
handleTokenRefresh(tokenData.current);
|
|
99
|
+
}, timeout);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
appState.current = nextAppState;
|
|
104
|
+
}
|
|
105
|
+
const subscription = AppState.addEventListener("change", handleAppState);
|
|
106
|
+
return () => {
|
|
107
|
+
if (subscription) {
|
|
108
|
+
subscription.remove();
|
|
109
|
+
} else {
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
AppState.removeEventListener("change", handleAppState);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
async function getTokenFromStorage() {
|
|
118
|
+
try {
|
|
119
|
+
const tokenFromStorage = await getItem();
|
|
120
|
+
if (!tokenFromStorage) throw new Error("No token in storage");
|
|
121
|
+
const token = JSON.parse(tokenFromStorage);
|
|
122
|
+
if (!TokenResponse.isTokenFresh(token, -refreshTimeBuffer)) {
|
|
123
|
+
handleTokenRefresh(token);
|
|
124
|
+
} else {
|
|
125
|
+
setToken(token);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
setToken(null);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (discovery) getTokenFromStorage();
|
|
132
|
+
}, [discovery]);
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
// trigger every token update
|
|
136
|
+
// @ts-ignore
|
|
137
|
+
tokenData.current = token || null;
|
|
138
|
+
if (token !== undefined && !disableAutoRefresh) {
|
|
139
|
+
if (refreshHandler.current !== null) {
|
|
140
|
+
clearTimeout(refreshHandler.current);
|
|
141
|
+
}
|
|
142
|
+
if (token?.expiresIn) {
|
|
143
|
+
const now = getCurrentTimeInSeconds();
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
refreshTime.current =
|
|
146
|
+
token.issuedAt + token.expiresIn - refreshTimeBuffer;
|
|
147
|
+
const timeout = 1000 * (refreshTime.current - now);
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
refreshHandler.current = setTimeout(() => {
|
|
150
|
+
handleTokenRefresh(token);
|
|
151
|
+
}, timeout);
|
|
152
|
+
}
|
|
153
|
+
if (token === null && tokenData.current !== null) {
|
|
154
|
+
revokeAsync(
|
|
155
|
+
{ token: tokenData.current?.accessToken, ...config },
|
|
156
|
+
discovery
|
|
157
|
+
);
|
|
158
|
+
Platform.OS === "ios" && dismiss();
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
refreshTime.current = null;
|
|
161
|
+
// @ts-ignore
|
|
162
|
+
tokenData.current = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}, [token]);
|
|
166
|
+
|
|
167
|
+
return [token || null, updateAndSaveToken];
|
|
168
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* File Created: 08-11-2022 14:10:44
|
|
5
5
|
* Author: Clay Risser
|
|
6
6
|
* -----
|
|
7
|
-
* Last Modified:
|
|
7
|
+
* Last Modified: 26-11-2022 09:28:49
|
|
8
8
|
* Modified By: Clay Risser
|
|
9
9
|
* -----
|
|
10
10
|
* Risser Labs LLC (c) Copyright 2021 - 2022
|
|
@@ -22,14 +22,22 @@
|
|
|
22
22
|
* limitations under the License.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
KeycloakResourceAccess,
|
|
27
|
+
KeycloakRoles,
|
|
28
|
+
KeycloakTokenParsed,
|
|
29
|
+
} from "keycloak-js";
|
|
26
30
|
export * from "./useKeycloak";
|
|
27
31
|
|
|
28
32
|
export interface IKeycloak {
|
|
29
33
|
authenticated?: boolean;
|
|
30
|
-
|
|
34
|
+
realmAccess?: KeycloakRoles;
|
|
31
35
|
refreshToken?: string;
|
|
36
|
+
refreshTokenParsed?: KeycloakTokenParsed;
|
|
37
|
+
resourceAccess?: KeycloakResourceAccess;
|
|
38
|
+
subject?: string;
|
|
39
|
+
token?: string;
|
|
32
40
|
tokenParsed?: KeycloakTokenParsed;
|
|
33
|
-
login: () => unknown;
|
|
34
|
-
logout: () => unknown;
|
|
41
|
+
login: () => Promise<unknown> | unknown;
|
|
42
|
+
logout: () => Promise<unknown> | unknown;
|
|
35
43
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* File Created: 08-11-2022 14:10:25
|
|
5
5
|
* Author: Clay Risser
|
|
6
6
|
* -----
|
|
7
|
-
* Last Modified:
|
|
7
|
+
* Last Modified: 26-11-2022 09:05:23
|
|
8
8
|
* Modified By: Clay Risser
|
|
9
9
|
* -----
|
|
10
10
|
* Risser Labs LLC (c) Copyright 2021 - 2022
|
|
@@ -22,15 +22,30 @@
|
|
|
22
22
|
* limitations under the License.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
25
|
+
import "core-js/actual/atob";
|
|
26
|
+
import "core-js/actual/escape";
|
|
27
|
+
import { AuthRequestPromptOptions, AuthSessionResult } from "expo-auth-session";
|
|
28
28
|
import { IKeycloak } from "./index";
|
|
29
|
+
import {
|
|
30
|
+
KeycloakResourceAccess,
|
|
31
|
+
KeycloakRoles,
|
|
32
|
+
KeycloakTokenParsed,
|
|
33
|
+
} from "keycloak-js";
|
|
34
|
+
import { MultiPlatform } from "multiplatform.one";
|
|
35
|
+
import { useExpoKeycloak } from "../../expo";
|
|
29
36
|
|
|
30
37
|
export function useKeycloak() {
|
|
31
38
|
if (MultiPlatform.isStorybook()) return { authenticated: true } as IKeycloak;
|
|
32
|
-
const { ready, login, isLoggedIn, token, logout } =
|
|
33
|
-
|
|
39
|
+
const { ready, login, isLoggedIn, token, logout, refreshToken } =
|
|
40
|
+
useExpoKeycloak();
|
|
41
|
+
const keycloak = new ExpoKeycloak(
|
|
42
|
+
ready,
|
|
43
|
+
isLoggedIn,
|
|
44
|
+
login,
|
|
45
|
+
logout,
|
|
46
|
+
token || undefined,
|
|
47
|
+
refreshToken || undefined
|
|
48
|
+
);
|
|
34
49
|
return keycloak as IKeycloak;
|
|
35
50
|
}
|
|
36
51
|
|
|
@@ -39,17 +54,39 @@ export class ExpoKeycloak implements IKeycloak {
|
|
|
39
54
|
|
|
40
55
|
tokenParsed?: KeycloakTokenParsed;
|
|
41
56
|
|
|
57
|
+
refreshTokenParsed?: KeycloakTokenParsed;
|
|
58
|
+
|
|
59
|
+
subject?: string;
|
|
60
|
+
|
|
61
|
+
realmAccess?: KeycloakRoles;
|
|
62
|
+
|
|
63
|
+
resourceAccess?: KeycloakResourceAccess;
|
|
64
|
+
|
|
65
|
+
login: () => Promise<unknown> | unknown;
|
|
66
|
+
|
|
42
67
|
constructor(
|
|
43
68
|
ready: boolean,
|
|
44
|
-
isLoggedIn: boolean,
|
|
45
|
-
|
|
69
|
+
isLoggedIn: boolean | undefined,
|
|
70
|
+
login: (
|
|
71
|
+
options?: AuthRequestPromptOptions
|
|
72
|
+
) => Promise<AuthSessionResult | undefined>,
|
|
46
73
|
public logout: () => unknown,
|
|
47
|
-
public token?: string
|
|
74
|
+
public token?: string,
|
|
75
|
+
public refreshToken?: string
|
|
48
76
|
) {
|
|
49
77
|
if (ready) this.authenticated = isLoggedIn;
|
|
50
|
-
if (this.authenticated
|
|
51
|
-
this.tokenParsed = decodeToken(this.token);
|
|
78
|
+
if (this.authenticated) {
|
|
79
|
+
if (this.token) this.tokenParsed = decodeToken(this.token);
|
|
80
|
+
if (this.refreshToken) {
|
|
81
|
+
this.refreshTokenParsed = decodeToken(this.refreshToken);
|
|
82
|
+
}
|
|
83
|
+
if (this.tokenParsed) {
|
|
84
|
+
this.subject = this.tokenParsed.sub;
|
|
85
|
+
this.realmAccess = this.tokenParsed.realm_access;
|
|
86
|
+
this.resourceAccess = this.tokenParsed.resource_access;
|
|
87
|
+
}
|
|
52
88
|
}
|
|
89
|
+
this.login = () => login();
|
|
53
90
|
}
|
|
54
91
|
}
|
|
55
92
|
|