@multiplatform.one/keycloak 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +32 -34
- package/routes/index.d.ts +1 -1
- package/routes/index.js +1 -1
- package/src/Authenticated.tsx +12 -13
- package/src/apollo.ts +1 -1
- package/src/authConfig.ts +2 -0
- package/src/hooks/useTokensFromQuery/index.ts +2 -0
- package/src/hooks/useTokensFromState.ts +2 -2
- package/{lib/@types/expoKeycloakAuth.d.d.mts → src/keycloak/config.ts} +17 -6
- package/src/keycloak/context.ts +3 -1
- package/src/keycloak/index.ts +1 -0
- package/src/keycloak/keycloak.native.ts +49 -15
- package/src/keycloak/keycloak.ts +66 -11
- package/src/provider/AfterAuth.tsx +2 -0
- package/src/provider/AuthProvider/AuthProvider.native.tsx +159 -6
- package/src/provider/AuthProvider/AuthProvider.tsx +66 -43
- package/src/provider/KeycloakProvider.tsx +12 -2
- package/src/routes.ts +19 -0
- package/src/session/session.ts +4 -15
- package/src/token.ts +13 -5
- package/lib/@types/expoKeycloakAuth.d.d.ts +0 -25
- package/lib/@types/expoKeycloakAuth.d.js +0 -1
- package/lib/@types/expoKeycloakAuth.d.mjs +0 -0
- package/lib/Authenticated.d.mts +0 -32
- package/lib/Authenticated.d.ts +0 -32
- package/lib/Authenticated.js +0 -20
- package/lib/Authenticated.mjs +0 -20
- package/lib/apollo.d.mts +0 -7
- package/lib/apollo.d.ts +0 -7
- package/lib/apollo.js +0 -15
- package/lib/apollo.mjs +0 -15
- package/lib/authConfig.d.mts +0 -12
- package/lib/authConfig.d.ts +0 -12
- package/lib/authConfig.js +0 -9
- package/lib/authConfig.mjs +0 -9
- package/lib/chunk-2L5TXBDK.mjs +0 -7
- package/lib/chunk-2MYOFYAK.mjs +0 -53
- package/lib/chunk-2VDDEPLF.js +0 -27
- package/lib/chunk-2VGMJ7WP.js +0 -34
- package/lib/chunk-32AVT4GA.js +0 -9
- package/lib/chunk-3ZTSLDX7.js +0 -13
- package/lib/chunk-6APOOEW6.js +0 -28
- package/lib/chunk-6F4PWJZI.js +0 -1
- package/lib/chunk-6FRYV4MK.mjs +0 -27
- package/lib/chunk-6WBHOY7M.js +0 -50
- package/lib/chunk-74FSREBY.js +0 -161
- package/lib/chunk-A2V2S4HP.js +0 -22
- package/lib/chunk-BUZONXAW.js +0 -1
- package/lib/chunk-C6TOWKPW.mjs +0 -245
- package/lib/chunk-DNEUOHZQ.mjs +0 -28
- package/lib/chunk-EGCPD3BX.js +0 -51
- package/lib/chunk-HH32BME2.mjs +0 -0
- package/lib/chunk-HTWZLBA4.mjs +0 -13
- package/lib/chunk-I5FNGKQ6.js +0 -16
- package/lib/chunk-IGSEESI3.mjs +0 -51
- package/lib/chunk-JAFUI2JQ.mjs +0 -9
- package/lib/chunk-JPQI72HR.mjs +0 -0
- package/lib/chunk-KWNIT4G4.mjs +0 -34
- package/lib/chunk-L3HTVJ4B.mjs +0 -13
- package/lib/chunk-MDAAMGTE.js +0 -1
- package/lib/chunk-NQMFZ6RW.mjs +0 -50
- package/lib/chunk-OAUYHIWG.mjs +0 -0
- package/lib/chunk-OBB23GPX.mjs +0 -0
- package/lib/chunk-OYWXU5N7.mjs +0 -161
- package/lib/chunk-P57PW2II.js +0 -11
- package/lib/chunk-TUJRPTZE.js +0 -13
- package/lib/chunk-UESWAK47.mjs +0 -0
- package/lib/chunk-UKSHA3X3.mjs +0 -16
- package/lib/chunk-VS4M7GMO.js +0 -1
- package/lib/chunk-W4LGQZMF.js +0 -245
- package/lib/chunk-WBQAMGXK.mjs +0 -0
- package/lib/chunk-WMH347OL.mjs +0 -22
- package/lib/chunk-WS4IMGVL.js +0 -1
- package/lib/chunk-YACGLW5Q.js +0 -7
- package/lib/chunk-YYYDFAI4.js +0 -53
- package/lib/chunk-ZCBD6HWK.js +0 -1
- package/lib/chunk-ZFXKCRJC.mjs +0 -11
- package/lib/hooks/index.d.mts +0 -5
- package/lib/hooks/index.d.ts +0 -5
- package/lib/hooks/index.js +0 -19
- package/lib/hooks/index.mjs +0 -19
- package/lib/hooks/useAuthConfig.d.mts +0 -6
- package/lib/hooks/useAuthConfig.d.ts +0 -6
- package/lib/hooks/useAuthConfig.js +0 -8
- package/lib/hooks/useAuthConfig.mjs +0 -8
- package/lib/hooks/useTokensFromQuery/index.d.mts +0 -3
- package/lib/hooks/useTokensFromQuery/index.d.ts +0 -3
- package/lib/hooks/useTokensFromQuery/index.js +0 -7
- package/lib/hooks/useTokensFromQuery/index.mjs +0 -7
- package/lib/hooks/useTokensFromQuery/index.native.d.mts +0 -3
- package/lib/hooks/useTokensFromQuery/index.native.d.ts +0 -3
- package/lib/hooks/useTokensFromQuery/index.native.js +0 -7
- package/lib/hooks/useTokensFromQuery/index.native.mjs +0 -7
- package/lib/hooks/useTokensFromState.d.mts +0 -3
- package/lib/hooks/useTokensFromState.d.ts +0 -3
- package/lib/hooks/useTokensFromState.js +0 -9
- package/lib/hooks/useTokensFromState.mjs +0 -9
- package/lib/index.d.mts +0 -18
- package/lib/index.d.ts +0 -18
- package/lib/index.js +0 -70
- package/lib/index.mjs +0 -70
- package/lib/keycloak/context.d.mts +0 -12
- package/lib/keycloak/context.d.ts +0 -12
- package/lib/keycloak/context.js +0 -7
- package/lib/keycloak/context.mjs +0 -7
- package/lib/keycloak/index.d.mts +0 -7
- package/lib/keycloak/index.d.ts +0 -7
- package/lib/keycloak/index.js +0 -14
- package/lib/keycloak/index.mjs +0 -14
- package/lib/keycloak/keycloak.d.mts +0 -37
- package/lib/keycloak/keycloak.d.ts +0 -37
- package/lib/keycloak/keycloak.js +0 -13
- package/lib/keycloak/keycloak.mjs +0 -13
- package/lib/keycloak/keycloak.native.d.mts +0 -33
- package/lib/keycloak/keycloak.native.d.ts +0 -33
- package/lib/keycloak/keycloak.native.js +0 -134
- package/lib/keycloak/keycloak.native.mjs +0 -134
- package/lib/provider/AfterAuth.d.mts +0 -28
- package/lib/provider/AfterAuth.d.ts +0 -28
- package/lib/provider/AfterAuth.js +0 -18
- package/lib/provider/AfterAuth.mjs +0 -18
- package/lib/provider/AuthProvider/AuthProvider.d.mts +0 -37
- package/lib/provider/AuthProvider/AuthProvider.d.ts +0 -37
- package/lib/provider/AuthProvider/AuthProvider.js +0 -19
- package/lib/provider/AuthProvider/AuthProvider.mjs +0 -19
- package/lib/provider/AuthProvider/AuthProvider.native.d.mts +0 -29
- package/lib/provider/AuthProvider/AuthProvider.native.d.ts +0 -29
- package/lib/provider/AuthProvider/AuthProvider.native.js +0 -25
- package/lib/provider/AuthProvider/AuthProvider.native.mjs +0 -25
- package/lib/provider/AuthProvider/index.d.mts +0 -4
- package/lib/provider/AuthProvider/index.d.ts +0 -4
- package/lib/provider/AuthProvider/index.js +0 -20
- package/lib/provider/AuthProvider/index.mjs +0 -20
- package/lib/provider/KeycloakProvider.d.mts +0 -34
- package/lib/provider/KeycloakProvider.d.ts +0 -34
- package/lib/provider/KeycloakProvider.js +0 -21
- package/lib/provider/KeycloakProvider.mjs +0 -21
- package/lib/provider/index.d.mts +0 -8
- package/lib/provider/index.d.ts +0 -8
- package/lib/provider/index.js +0 -28
- package/lib/provider/index.mjs +0 -28
- package/lib/routes.d.mts +0 -34
- package/lib/routes.d.ts +0 -34
- package/lib/routes.js +0 -131
- package/lib/routes.mjs +0 -131
- package/lib/session/index.d.mts +0 -3
- package/lib/session/index.d.ts +0 -3
- package/lib/session/index.js +0 -10
- package/lib/session/index.mjs +0 -10
- package/lib/session/session.d.mts +0 -18
- package/lib/session/session.d.ts +0 -18
- package/lib/session/session.js +0 -9
- package/lib/session/session.mjs +0 -9
- package/lib/session/session.native.d.mts +0 -8
- package/lib/session/session.native.d.ts +0 -8
- package/lib/session/session.native.js +0 -13
- package/lib/session/session.native.mjs +0 -13
- package/lib/state.d.mts +0 -12
- package/lib/state.d.ts +0 -12
- package/lib/state.js +0 -9
- package/lib/state.mjs +0 -9
- package/lib/token.d.mts +0 -17
- package/lib/token.d.ts +0 -17
- package/lib/token.js +0 -9
- package/lib/token.mjs +0 -9
- package/lib/types.d.mts +0 -20
- package/lib/types.d.ts +0 -20
- package/lib/types.js +0 -1
- package/lib/types.mjs +0 -1
|
@@ -19,15 +19,168 @@
|
|
|
19
19
|
* limitations under the License.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import React from 'react';
|
|
23
|
-
import type { AuthProviderProps } from './AuthProvider';
|
|
22
|
+
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
23
|
+
import type { AuthProviderProps, Tokens } from './AuthProvider';
|
|
24
|
+
import type { KeycloakLoginOptions, KeycloakLogoutOptions } from '../../keycloak';
|
|
24
25
|
import { AfterAuth } from '../AfterAuth';
|
|
25
|
-
import {
|
|
26
|
+
import { Keycloak, KeycloakConfigContext } from '../../keycloak';
|
|
27
|
+
import { KeycloakContext } from '../../keycloak/context';
|
|
28
|
+
import { makeRedirectUri, useAuthRequest, useAutoDiscovery, refreshAsync, exchangeCodeAsync } from 'expo-auth-session';
|
|
29
|
+
import { persist, useAuthState } from '../../state';
|
|
30
|
+
import { validOrRefreshableToken, isTokenExpired } from '../../token';
|
|
26
31
|
|
|
27
32
|
export function AuthProvider({ children, keycloakConfig }: AuthProviderProps) {
|
|
33
|
+
const [keycloak, setKeycloak] = useState<Keycloak>();
|
|
34
|
+
const authState = useAuthState();
|
|
35
|
+
const initialRefreshToken = useMemo(() => validOrRefreshableToken((persist && authState.refreshToken) || false), []);
|
|
36
|
+
const keycloakUrl = `${keycloakConfig?.url}/realms/${keycloakConfig?.realm}`;
|
|
37
|
+
const clientId = keycloakConfig.publicClientId || keycloakConfig?.clientId || '';
|
|
38
|
+
const discovery = useAutoDiscovery(keycloakUrl);
|
|
39
|
+
const redirectUri = makeRedirectUri();
|
|
40
|
+
const [tokens, setTokens] = useState<Tokens<false>>({
|
|
41
|
+
refreshToken: initialRefreshToken,
|
|
42
|
+
token: validOrRefreshableToken((persist && authState.token) || false, initialRefreshToken),
|
|
43
|
+
idToken: validOrRefreshableToken((persist && authState.idToken) || false, initialRefreshToken),
|
|
44
|
+
});
|
|
45
|
+
const scopes = useMemo(
|
|
46
|
+
() => [...new Set(['email', 'openid', 'profile', ...(keycloakConfig.scopes || [])])],
|
|
47
|
+
[keycloakConfig.scopes],
|
|
48
|
+
);
|
|
49
|
+
const [request, response, promptAsync] = useAuthRequest(
|
|
50
|
+
{
|
|
51
|
+
clientId,
|
|
52
|
+
redirectUri: redirectUri,
|
|
53
|
+
scopes,
|
|
54
|
+
},
|
|
55
|
+
discovery,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!discovery || !response?.type || !request?.codeVerifier || !clientId) return;
|
|
60
|
+
(async () => {
|
|
61
|
+
if (response?.type === 'success' && request.codeVerifier && response.params.code) {
|
|
62
|
+
const tokenResponse = await exchangeCodeAsync(
|
|
63
|
+
{
|
|
64
|
+
clientId,
|
|
65
|
+
code: response.params.code,
|
|
66
|
+
redirectUri,
|
|
67
|
+
scopes,
|
|
68
|
+
extraParams: {
|
|
69
|
+
code_verifier: request.codeVerifier,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
discovery,
|
|
73
|
+
);
|
|
74
|
+
if (tokenResponse) {
|
|
75
|
+
setTokens({
|
|
76
|
+
idToken: tokenResponse.idToken,
|
|
77
|
+
refreshToken: tokenResponse.refreshToken,
|
|
78
|
+
token: tokenResponse.accessToken,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
}, [
|
|
84
|
+
discovery,
|
|
85
|
+
request?.codeVerifier,
|
|
86
|
+
response?.type === 'success' ? response?.params?.code : undefined,
|
|
87
|
+
clientId,
|
|
88
|
+
scopes,
|
|
89
|
+
redirectUri,
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const resetTokens = useCallback(() => {
|
|
93
|
+
authState.setIdToken('');
|
|
94
|
+
authState.setRefreshToken('');
|
|
95
|
+
authState.setToken('');
|
|
96
|
+
setTokens({
|
|
97
|
+
refreshToken: undefined,
|
|
98
|
+
token: undefined,
|
|
99
|
+
idToken: undefined,
|
|
100
|
+
});
|
|
101
|
+
}, [authState]);
|
|
102
|
+
|
|
103
|
+
const refreshTokens = useCallback(async () => {
|
|
104
|
+
if (!discovery) return;
|
|
105
|
+
if (typeof tokens.refreshToken === 'string') {
|
|
106
|
+
if (!isTokenExpired(tokens.refreshToken)) {
|
|
107
|
+
const tokenResponse = await refreshAsync(
|
|
108
|
+
{
|
|
109
|
+
clientId,
|
|
110
|
+
refreshToken: tokens.refreshToken,
|
|
111
|
+
scopes,
|
|
112
|
+
},
|
|
113
|
+
discovery,
|
|
114
|
+
);
|
|
115
|
+
if (tokenResponse) {
|
|
116
|
+
setTokens({
|
|
117
|
+
idToken: tokenResponse.idToken,
|
|
118
|
+
refreshToken: tokenResponse.refreshToken,
|
|
119
|
+
token: tokenResponse.accessToken,
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
resetTokens();
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
resetTokens();
|
|
126
|
+
}
|
|
127
|
+
} else if (
|
|
128
|
+
authState.refreshToken &&
|
|
129
|
+
typeof authState.refreshToken === 'string' &&
|
|
130
|
+
isTokenExpired(authState.refreshToken)
|
|
131
|
+
) {
|
|
132
|
+
resetTokens();
|
|
133
|
+
}
|
|
134
|
+
}, [discovery, resetTokens, authState.refreshToken, tokens.refreshToken, clientId, scopes]);
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!discovery) return;
|
|
138
|
+
refreshTokens();
|
|
139
|
+
}, [discovery]);
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!request) return;
|
|
143
|
+
let refreshHandle: NodeJS.Timeout;
|
|
144
|
+
(async () => {
|
|
145
|
+
if (tokens.token && isTokenExpired(tokens.token)) {
|
|
146
|
+
await refreshTokens();
|
|
147
|
+
} else {
|
|
148
|
+
const _keycloak = new Keycloak(
|
|
149
|
+
keycloakConfig,
|
|
150
|
+
tokens.token || undefined,
|
|
151
|
+
tokens.idToken || undefined,
|
|
152
|
+
tokens.refreshToken || undefined,
|
|
153
|
+
async (options: KeycloakLoginOptions) => {
|
|
154
|
+
await promptAsync(options);
|
|
155
|
+
},
|
|
156
|
+
async (_options: KeycloakLogoutOptions) => {
|
|
157
|
+
resetTokens();
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
if (_keycloak.refreshTokenParsed?.exp) {
|
|
161
|
+
const timeout = (_keycloak.refreshTokenParsed.exp - Math.floor(Date.now() / 1000) - 10) * 1000;
|
|
162
|
+
if (timeout > 0) {
|
|
163
|
+
refreshHandle = setTimeout(() => {
|
|
164
|
+
refreshTokens();
|
|
165
|
+
}, timeout);
|
|
166
|
+
} else {
|
|
167
|
+
setTimeout(() => _keycloak.logout(), Math.max(0, timeout + 10) * 1000);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
setKeycloak(_keycloak);
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
174
|
+
return () => {
|
|
175
|
+
if (refreshHandle) clearTimeout(refreshHandle);
|
|
176
|
+
};
|
|
177
|
+
}, [request, tokens.token, tokens.idToken, tokens.refreshToken, refreshTokens, resetTokens]);
|
|
178
|
+
|
|
28
179
|
return (
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
180
|
+
<KeycloakConfigContext.Provider value={keycloakConfig}>
|
|
181
|
+
<KeycloakContext.Provider value={keycloak}>
|
|
182
|
+
<AfterAuth>{children}</AfterAuth>
|
|
183
|
+
</KeycloakContext.Provider>
|
|
184
|
+
</KeycloakConfigContext.Provider>
|
|
32
185
|
);
|
|
33
186
|
}
|
|
@@ -19,62 +19,72 @@
|
|
|
19
19
|
* limitations under the License.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
'use client';
|
|
23
|
+
|
|
22
24
|
import KeycloakClient from 'keycloak-js';
|
|
23
25
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
24
|
-
import type {
|
|
26
|
+
import type { KeycloakInitOptions } from 'keycloak-js';
|
|
27
|
+
import type { KeycloakConfig } from '../../keycloak';
|
|
25
28
|
import type { PropsWithChildren } from 'react';
|
|
26
29
|
import type { SessionProviderProps } from 'next-auth/react';
|
|
27
30
|
import { AfterAuth } from '../AfterAuth';
|
|
28
|
-
import { Keycloak } from '../../keycloak';
|
|
31
|
+
import { Keycloak, KeycloakConfigContext } from '../../keycloak';
|
|
29
32
|
import { KeycloakContext } from '../../keycloak/context';
|
|
30
33
|
import { MultiPlatform } from 'multiplatform.one';
|
|
31
34
|
import { SessionProvider } from 'next-auth/react';
|
|
32
35
|
import { persist, useAuthState } from '../../state';
|
|
33
36
|
import { useAuthConfig } from '../../hooks';
|
|
34
37
|
import { useRouter } from 'next/router';
|
|
35
|
-
import {
|
|
38
|
+
import { validOrRefreshableToken } from '../../token';
|
|
36
39
|
|
|
37
40
|
export interface AuthProviderProps extends PropsWithChildren {
|
|
38
|
-
keycloakConfig
|
|
41
|
+
keycloakConfig: KeycloakConfig;
|
|
39
42
|
keycloakInitOptions?: KeycloakInitOptions;
|
|
40
43
|
sessionProvider?: Omit<SessionProviderProps, 'children'>;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const logger = console;
|
|
44
47
|
|
|
48
|
+
export interface Tokens<B = boolean> {
|
|
49
|
+
idToken?: string | B;
|
|
50
|
+
refreshToken?: string | B;
|
|
51
|
+
token?: string | B;
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
export function AuthProvider({ children, sessionProvider, keycloakInitOptions, keycloakConfig }: AuthProviderProps) {
|
|
46
55
|
const { debug, messageHandlerKeys } = useAuthConfig();
|
|
47
56
|
const { query } = MultiPlatform.isNext ? useRouter() : { query: {} };
|
|
48
57
|
const authState = useAuthState();
|
|
49
58
|
const [keycloak, setKeycloak] = useState<Keycloak>();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
const initialRefreshToken = useMemo(
|
|
60
|
+
() =>
|
|
61
|
+
validOrRefreshableToken(
|
|
62
|
+
MultiPlatform.isIframe
|
|
63
|
+
? ('refreshToken' in query && (query.refreshToken?.toString() || true)) ||
|
|
64
|
+
(persist && authState.refreshToken) ||
|
|
65
|
+
false
|
|
66
|
+
: undefined,
|
|
67
|
+
),
|
|
68
|
+
[],
|
|
58
69
|
);
|
|
59
|
-
const [
|
|
60
|
-
|
|
70
|
+
const [tokens, setTokens] = useState<Tokens>({
|
|
71
|
+
refreshToken: initialRefreshToken,
|
|
72
|
+
token: validOrRefreshableToken(
|
|
61
73
|
MultiPlatform.isIframe
|
|
62
74
|
? ('token' in query && (query.token?.toString() || true)) || (persist && authState.token) || false
|
|
63
75
|
: undefined,
|
|
64
|
-
|
|
76
|
+
initialRefreshToken,
|
|
65
77
|
),
|
|
66
|
-
|
|
67
|
-
const [idToken, setIdToken] = useState<string | boolean | undefined>(
|
|
68
|
-
validToken(
|
|
78
|
+
idToken: validOrRefreshableToken(
|
|
69
79
|
MultiPlatform.isIframe
|
|
70
80
|
? ('idToken' in query && (query.idToken?.toString() || true)) || (persist && authState.idToken) || false
|
|
71
81
|
: undefined,
|
|
72
|
-
|
|
82
|
+
initialRefreshToken,
|
|
73
83
|
),
|
|
74
|
-
);
|
|
84
|
+
});
|
|
75
85
|
|
|
76
86
|
useEffect(() => {
|
|
77
|
-
if (token !== true && refreshToken !== true && idToken !== true) return;
|
|
87
|
+
if (tokens.token !== true && tokens.refreshToken !== true && tokens.idToken !== true) return;
|
|
78
88
|
if (debug) logger.debug('post message', JSON.stringify({ type: 'LOADED' }));
|
|
79
89
|
(messageHandlerKeys || []).forEach((key: string) => {
|
|
80
90
|
window?.webkit?.messageHandlers?.[key]?.postMessage(JSON.stringify({ type: 'LOADED' }));
|
|
@@ -84,7 +94,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
84
94
|
}, []);
|
|
85
95
|
|
|
86
96
|
useEffect(() => {
|
|
87
|
-
if (refreshToken !== true) return;
|
|
97
|
+
if (tokens.refreshToken !== true) return;
|
|
88
98
|
const messageCallback = (e: MessageEvent<any>) => {
|
|
89
99
|
let data = e?.data || null;
|
|
90
100
|
if (typeof data === 'string') {
|
|
@@ -97,7 +107,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
97
107
|
if (!message.type) return;
|
|
98
108
|
if (message.type.toUpperCase() === 'REFRESH_TOKEN' && message.payload) {
|
|
99
109
|
if (debug) logger.debug('setting refresh token', message.payload);
|
|
100
|
-
|
|
110
|
+
setTokens((tokens) => ({ ...tokens, refreshToken: validOrRefreshableToken(message.payload) }));
|
|
101
111
|
window.removeEventListener('message', messageCallback);
|
|
102
112
|
}
|
|
103
113
|
};
|
|
@@ -117,7 +127,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
117
127
|
}, []);
|
|
118
128
|
|
|
119
129
|
useEffect(() => {
|
|
120
|
-
if (token !== true) return;
|
|
130
|
+
if (tokens.token !== true) return;
|
|
121
131
|
const messageCallback = (e: MessageEvent<any>) => {
|
|
122
132
|
let data = e?.data || null;
|
|
123
133
|
if (typeof data === 'string') {
|
|
@@ -130,7 +140,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
130
140
|
if (!message.type) return;
|
|
131
141
|
if (message.type.toUpperCase() === 'TOKEN' && message.payload) {
|
|
132
142
|
if (debug) logger.debug('setting token', message.payload);
|
|
133
|
-
|
|
143
|
+
setTokens((tokens) => ({ ...tokens, token: validOrRefreshableToken(message.payload, tokens.refreshToken) }));
|
|
134
144
|
window.removeEventListener('message', messageCallback);
|
|
135
145
|
}
|
|
136
146
|
};
|
|
@@ -150,7 +160,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
150
160
|
}, []);
|
|
151
161
|
|
|
152
162
|
useEffect(() => {
|
|
153
|
-
if (idToken !== true) return;
|
|
163
|
+
if (tokens.idToken !== true) return;
|
|
154
164
|
const messageCallback = (e: MessageEvent<any>) => {
|
|
155
165
|
let data = e?.data || null;
|
|
156
166
|
if (typeof data === 'string') {
|
|
@@ -163,7 +173,7 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
163
173
|
if (!message.type) return;
|
|
164
174
|
if (message.type.toUpperCase() === 'ID_TOKEN' && message.payload) {
|
|
165
175
|
if (debug) logger.debug('setting id token', message.payload);
|
|
166
|
-
|
|
176
|
+
setTokens((tokens) => ({ ...tokens, idToken: validOrRefreshableToken(message.payload, tokens.refreshToken) }));
|
|
167
177
|
window.removeEventListener('message', messageCallback);
|
|
168
178
|
}
|
|
169
179
|
};
|
|
@@ -188,39 +198,50 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
188
198
|
? new KeycloakClient({
|
|
189
199
|
url: keycloakConfig.url,
|
|
190
200
|
realm: keycloakConfig.realm,
|
|
191
|
-
clientId: keycloakConfig.clientId,
|
|
201
|
+
clientId: keycloakConfig.publicClientId || keycloakConfig.clientId,
|
|
192
202
|
})
|
|
193
203
|
: undefined,
|
|
194
|
-
[keycloakInitOptions, keycloakConfig, token, refreshToken, idToken],
|
|
204
|
+
[keycloakInitOptions, keycloakConfig, tokens.token, tokens.refreshToken, tokens.idToken],
|
|
195
205
|
);
|
|
196
206
|
|
|
197
207
|
const initOptions = useMemo(() => {
|
|
198
208
|
if (!MultiPlatform.isIframe) return;
|
|
199
|
-
const initOptions = {
|
|
209
|
+
const initOptions: KeycloakInitOptions = {
|
|
200
210
|
...defaultKeycloakInitOptions,
|
|
201
211
|
...keycloakInitOptions,
|
|
212
|
+
scope: [
|
|
213
|
+
...new Set([
|
|
214
|
+
'email',
|
|
215
|
+
'openid',
|
|
216
|
+
'profile',
|
|
217
|
+
...(defaultKeycloakInitOptions.scope?.split(' ') || []),
|
|
218
|
+
...(keycloakInitOptions?.scope?.split(' ') || []),
|
|
219
|
+
...(keycloakConfig.scopes || []),
|
|
220
|
+
]),
|
|
221
|
+
].join(' '),
|
|
202
222
|
};
|
|
203
223
|
if (debug) initOptions.enableLogging = true;
|
|
204
|
-
if (token && typeof token === 'string') {
|
|
205
|
-
initOptions.token = token;
|
|
224
|
+
if (tokens.token && typeof tokens.token === 'string') {
|
|
225
|
+
initOptions.token = tokens.token;
|
|
206
226
|
initOptions.timeSkew = 0;
|
|
207
|
-
if (idToken && typeof idToken === 'string') initOptions.idToken = idToken;
|
|
208
|
-
if (refreshToken && typeof refreshToken === 'string')
|
|
227
|
+
if (tokens.idToken && typeof tokens.idToken === 'string') initOptions.idToken = tokens.idToken;
|
|
228
|
+
if (tokens.refreshToken && typeof tokens.refreshToken === 'string')
|
|
229
|
+
initOptions.refreshToken = tokens.refreshToken;
|
|
209
230
|
}
|
|
210
|
-
if ((typeof window !== 'undefined' && window.self !== window.top) || (token && !refreshToken)) {
|
|
231
|
+
if ((typeof window !== 'undefined' && window.self !== window.top) || (tokens.token && !tokens.refreshToken)) {
|
|
211
232
|
initOptions.checkLoginIframe = false;
|
|
212
233
|
initOptions.flow = 'implicit';
|
|
213
234
|
initOptions.onLoad = undefined;
|
|
214
235
|
}
|
|
215
236
|
return initOptions;
|
|
216
|
-
}, [keycloakInitOptions, token, idToken, refreshToken]);
|
|
237
|
+
}, [keycloakInitOptions, keycloakConfig.scopes, tokens.token, tokens.idToken, tokens.refreshToken]);
|
|
217
238
|
|
|
218
239
|
useEffect(() => {
|
|
219
240
|
function onError() {
|
|
220
|
-
if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakClient));
|
|
241
|
+
if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakConfig, keycloakClient));
|
|
221
242
|
}
|
|
222
243
|
function onUpdate() {
|
|
223
|
-
if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakClient));
|
|
244
|
+
if (keycloakClient && initOptions) setKeycloak(new Keycloak(keycloakConfig, keycloakClient));
|
|
224
245
|
}
|
|
225
246
|
if (keycloakClient && initOptions) {
|
|
226
247
|
(async () => {
|
|
@@ -234,20 +255,22 @@ export function AuthProvider({ children, sessionProvider, keycloakInitOptions, k
|
|
|
234
255
|
try {
|
|
235
256
|
await keycloakClient.updateToken(5);
|
|
236
257
|
} catch (err) {
|
|
237
|
-
setKeycloak(new Keycloak());
|
|
258
|
+
setKeycloak(new Keycloak(keycloakConfig));
|
|
238
259
|
}
|
|
239
260
|
};
|
|
240
261
|
const authenticated = await keycloakClient.init(initOptions);
|
|
241
|
-
setKeycloak(new Keycloak(authenticated ? keycloakClient : undefined));
|
|
262
|
+
setKeycloak(new Keycloak(keycloakConfig, authenticated ? keycloakClient : undefined));
|
|
242
263
|
})();
|
|
243
264
|
}
|
|
244
265
|
}, []);
|
|
245
266
|
|
|
246
267
|
return (
|
|
247
268
|
<SessionProvider {...sessionProvider}>
|
|
248
|
-
<
|
|
249
|
-
<
|
|
250
|
-
|
|
269
|
+
<KeycloakConfigContext.Provider value={keycloakConfig}>
|
|
270
|
+
<KeycloakContext.Provider value={keycloak}>
|
|
271
|
+
<AfterAuth>{children}</AfterAuth>
|
|
272
|
+
</KeycloakContext.Provider>
|
|
273
|
+
</KeycloakConfigContext.Provider>
|
|
251
274
|
</SessionProvider>
|
|
252
275
|
);
|
|
253
276
|
}
|
|
@@ -29,11 +29,20 @@ import { AuthProvider } from './AuthProvider';
|
|
|
29
29
|
export interface KeycloakProviderProps extends PropsWithChildren, AuthConfig {
|
|
30
30
|
baseUrl?: string;
|
|
31
31
|
clientId?: string;
|
|
32
|
+
publicClientId?: string;
|
|
32
33
|
realm?: string;
|
|
33
34
|
session?: Session;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
export function KeycloakProvider({
|
|
37
|
+
export function KeycloakProvider({
|
|
38
|
+
children,
|
|
39
|
+
debug,
|
|
40
|
+
realm,
|
|
41
|
+
clientId,
|
|
42
|
+
baseUrl,
|
|
43
|
+
publicClientId,
|
|
44
|
+
session,
|
|
45
|
+
}: KeycloakProviderProps) {
|
|
37
46
|
const authConfig = useMemo(() => ({ debug }), [debug]);
|
|
38
47
|
|
|
39
48
|
return (
|
|
@@ -41,8 +50,9 @@ export function KeycloakProvider({ children, debug, realm, clientId, baseUrl, se
|
|
|
41
50
|
<AuthProvider
|
|
42
51
|
sessionProvider={{ session }}
|
|
43
52
|
keycloakConfig={{
|
|
44
|
-
realm: realm || 'main',
|
|
45
53
|
clientId: clientId || 'app',
|
|
54
|
+
publicClientId,
|
|
55
|
+
realm: realm || 'main',
|
|
46
56
|
url: baseUrl,
|
|
47
57
|
}}
|
|
48
58
|
>
|
package/src/routes.ts
CHANGED
|
@@ -74,11 +74,29 @@ export function createLogoutHandler(options: AuthHandlerOptions) {
|
|
|
74
74
|
|
|
75
75
|
export function createNextAuthOptions(options: AuthHandlerOptions) {
|
|
76
76
|
if (_nextAuth) return _nextAuth;
|
|
77
|
+
const authorization =
|
|
78
|
+
options.keycloak.authorization && typeof options.keycloak.authorization === 'object'
|
|
79
|
+
? options.keycloak.authorization
|
|
80
|
+
: {};
|
|
77
81
|
_nextAuth = {
|
|
78
82
|
...options.nextAuth,
|
|
79
83
|
providers: [
|
|
80
84
|
KeycloakProvider({
|
|
81
85
|
issuer: `${options.keycloak?.baseUrl || defaults.keycloak.baseUrl}/realms/${options.keycloak?.realm || defaults.keycloak.realm}`,
|
|
86
|
+
authorization: {
|
|
87
|
+
...authorization,
|
|
88
|
+
params: {
|
|
89
|
+
scope: [
|
|
90
|
+
...new Set([
|
|
91
|
+
'email',
|
|
92
|
+
'openid',
|
|
93
|
+
'profile',
|
|
94
|
+
...(authorization.params?.scope?.split(' ') || []),
|
|
95
|
+
...(options.scopes || []),
|
|
96
|
+
]),
|
|
97
|
+
].join(' '),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
82
100
|
...options.keycloak,
|
|
83
101
|
}),
|
|
84
102
|
...(options.nextAuth?.providers || []),
|
|
@@ -130,6 +148,7 @@ export interface AuthHandlerOptions {
|
|
|
130
148
|
baseUrl: string;
|
|
131
149
|
keycloak: KeycloakOptions;
|
|
132
150
|
nextAuth?: Partial<AuthOptions>;
|
|
151
|
+
scopes?: string[];
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
export interface KeycloakOptions extends OAuthUserConfig<any> {
|
package/src/session/session.ts
CHANGED
|
@@ -20,21 +20,13 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import type { AuthOptions, Session as NextSession } from 'next-auth';
|
|
23
|
+
import type { SessionContextValue as NextSessionContextValue, UseSessionOptions } from 'next-auth/react';
|
|
23
24
|
import { MultiPlatform } from 'multiplatform.one';
|
|
24
25
|
import { useSession as useNextSession, getSession as getNextSession } from 'next-auth/react';
|
|
25
|
-
import type {
|
|
26
|
-
GetSessionParams,
|
|
27
|
-
SessionContextValue as NextSessionContextValue,
|
|
28
|
-
UseSessionOptions,
|
|
29
|
-
} from 'next-auth/react';
|
|
30
26
|
|
|
31
27
|
let _getServerSession: ((options?: AuthOptions, req?: any, res?: any) => Promise<Session | null>) | undefined;
|
|
32
28
|
|
|
33
|
-
export async function getSession(
|
|
34
|
-
options?: AuthOptions | GetSessionParams,
|
|
35
|
-
req?: any,
|
|
36
|
-
res?: any,
|
|
37
|
-
): Promise<Session | null> {
|
|
29
|
+
export async function getSession(options?: AuthOptions, req?: any, res?: any): Promise<Session | null> {
|
|
38
30
|
if (MultiPlatform.isServer) {
|
|
39
31
|
if (typeof _getServerSession === 'function') return _getServerSession(options as AuthOptions, req, res);
|
|
40
32
|
const getSession = (await import('next-auth')).getServerSession;
|
|
@@ -43,17 +35,14 @@ export async function getSession(
|
|
|
43
35
|
};
|
|
44
36
|
return _getServerSession(options as AuthOptions, req, res);
|
|
45
37
|
}
|
|
46
|
-
return getNextSession(
|
|
38
|
+
return getNextSession();
|
|
47
39
|
}
|
|
48
40
|
|
|
49
41
|
export type SessionContextValue<R extends boolean> = Partial<
|
|
50
42
|
Omit<NextSessionContextValue<R> & { session: Session }, 'data'>
|
|
51
43
|
>;
|
|
52
44
|
|
|
53
|
-
let useSession = <R extends boolean>(
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
55
|
-
options?: UseSessionOptions<R>,
|
|
56
|
-
): SessionContextValue<R> => {
|
|
45
|
+
let useSession = <R extends boolean>(_options?: UseSessionOptions<R>): SessionContextValue<R> => {
|
|
57
46
|
return {};
|
|
58
47
|
};
|
|
59
48
|
|
package/src/token.ts
CHANGED
|
@@ -22,21 +22,29 @@
|
|
|
22
22
|
import type { KeycloakTokenParsed } from 'keycloak-js';
|
|
23
23
|
import { jwtDecode } from 'jwt-decode';
|
|
24
24
|
|
|
25
|
-
export function
|
|
25
|
+
export function validOrRefreshableToken(
|
|
26
|
+
token?: string | false,
|
|
27
|
+
refreshToken?: string | boolean,
|
|
28
|
+
): string | false | undefined;
|
|
29
|
+
export function validOrRefreshableToken(
|
|
30
|
+
token?: string | boolean,
|
|
31
|
+
refreshToken?: string | boolean,
|
|
32
|
+
): string | boolean | undefined;
|
|
33
|
+
export function validOrRefreshableToken(token?: string | boolean, refreshToken?: string | boolean) {
|
|
26
34
|
if (typeof token === 'undefined') return;
|
|
27
35
|
if (!token) return false;
|
|
28
36
|
if (typeof token !== 'string') return token;
|
|
29
37
|
if (refreshToken) {
|
|
30
|
-
if (refreshToken === true ||
|
|
38
|
+
if (refreshToken === true || !isTokenExpired(refreshToken)) return token;
|
|
31
39
|
return false;
|
|
32
40
|
}
|
|
33
|
-
return
|
|
41
|
+
return isTokenExpired(token) ? false : token;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
|
-
export function
|
|
44
|
+
export function isTokenExpired(token: string) {
|
|
37
45
|
const { exp } = jwtDecode(token) as TokenParsed;
|
|
38
46
|
if (!exp) return false;
|
|
39
|
-
return Math.floor(Date.now() / 1000)
|
|
47
|
+
return Math.floor(Date.now() / 1000) >= exp;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
export interface TokenParsed extends KeycloakTokenParsed {
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* File: /src/@types/expoKeycloakAuth.d.ts
|
|
3
|
-
* Project: @multiplatform.one/keycloak
|
|
4
|
-
* File Created: 22-06-2023 10:07:56
|
|
5
|
-
* Author: Clay Risser
|
|
6
|
-
* -----
|
|
7
|
-
* BitSpur (c) Copyright 2021 - 2023
|
|
8
|
-
*
|
|
9
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
-
* you may not use this file except in compliance with the License.
|
|
11
|
-
* You may obtain a copy of the License at
|
|
12
|
-
*
|
|
13
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
-
*
|
|
15
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
16
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
-
* See the License for the specific language governing permissions and
|
|
19
|
-
* limitations under the License.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
declare module 'expo-keycloak-auth' {
|
|
23
|
-
export function useKeycloak(): any;
|
|
24
|
-
export const KeycloakProvider: any;
|
|
25
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
File without changes
|
package/lib/Authenticated.d.mts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React__default, { PropsWithChildren, ComponentType } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* File: /src/Authenticated.tsx
|
|
5
|
-
* Project: @multiplatform.one/keycloak
|
|
6
|
-
* File Created: 22-06-2023 10:07:56
|
|
7
|
-
* Author: Clay Risser
|
|
8
|
-
* -----
|
|
9
|
-
* BitSpur (c) Copyright 2021 - 2023
|
|
10
|
-
*
|
|
11
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
-
* you may not use this file except in compliance with the License.
|
|
13
|
-
* You may obtain a copy of the License at
|
|
14
|
-
*
|
|
15
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
-
*
|
|
17
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
-
* See the License for the specific language governing permissions and
|
|
21
|
-
* limitations under the License.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
interface AuthenticatedProps extends PropsWithChildren {
|
|
25
|
-
disabled?: boolean;
|
|
26
|
-
loadingComponent?: ComponentType;
|
|
27
|
-
loggedOutComponent?: ComponentType;
|
|
28
|
-
}
|
|
29
|
-
declare function Authenticated({ children, disabled, loggedOutComponent, loadingComponent }: AuthenticatedProps): React__default.JSX.Element;
|
|
30
|
-
declare function withAuthenticated<P extends object>(Component: ComponentType<P>): (props: P) => React__default.JSX.Element;
|
|
31
|
-
|
|
32
|
-
export { Authenticated, type AuthenticatedProps, withAuthenticated };
|
package/lib/Authenticated.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React__default, { PropsWithChildren, ComponentType } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* File: /src/Authenticated.tsx
|
|
5
|
-
* Project: @multiplatform.one/keycloak
|
|
6
|
-
* File Created: 22-06-2023 10:07:56
|
|
7
|
-
* Author: Clay Risser
|
|
8
|
-
* -----
|
|
9
|
-
* BitSpur (c) Copyright 2021 - 2023
|
|
10
|
-
*
|
|
11
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
-
* you may not use this file except in compliance with the License.
|
|
13
|
-
* You may obtain a copy of the License at
|
|
14
|
-
*
|
|
15
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
-
*
|
|
17
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
-
* See the License for the specific language governing permissions and
|
|
21
|
-
* limitations under the License.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
interface AuthenticatedProps extends PropsWithChildren {
|
|
25
|
-
disabled?: boolean;
|
|
26
|
-
loadingComponent?: ComponentType;
|
|
27
|
-
loggedOutComponent?: ComponentType;
|
|
28
|
-
}
|
|
29
|
-
declare function Authenticated({ children, disabled, loggedOutComponent, loadingComponent }: AuthenticatedProps): React__default.JSX.Element;
|
|
30
|
-
declare function withAuthenticated<P extends object>(Component: ComponentType<P>): (props: P) => React__default.JSX.Element;
|
|
31
|
-
|
|
32
|
-
export { Authenticated, type AuthenticatedProps, withAuthenticated };
|