@tidecloak/js 0.13.1 → 0.13.5
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/README.md +170 -5
- package/dist/cjs/lib/tidecloak.js +1759 -1629
- package/dist/cjs/lib/tidecloak.js.map +1 -1
- package/dist/cjs/src/IAMService.js +526 -32
- package/dist/cjs/src/IAMService.js.map +1 -1
- package/dist/cjs/src/index.js +4 -21
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/policy-react.js +3 -0
- package/dist/cjs/src/policy-react.js.map +1 -0
- package/dist/cjs/src/policy.css +1 -0
- package/dist/cjs/src/utils/fetch.js +36 -0
- package/dist/cjs/src/utils/fetch.js.map +1 -0
- package/dist/cjs/src/utils/index.js +3 -0
- package/dist/cjs/src/utils/index.js.map +1 -0
- package/dist/cjs/src/utils/pkce.js +43 -0
- package/dist/cjs/src/utils/pkce.js.map +1 -0
- package/dist/esm/lib/tidecloak.js +1760 -1619
- package/dist/esm/lib/tidecloak.js.map +1 -1
- package/dist/esm/src/IAMService.js +523 -23
- package/dist/esm/src/IAMService.js.map +1 -1
- package/dist/esm/src/index.js +2 -6
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/policy-react.js +3 -0
- package/dist/esm/src/policy-react.js.map +1 -0
- package/dist/esm/src/policy.css +1 -0
- package/dist/esm/src/utils/fetch.js +36 -0
- package/dist/esm/src/utils/fetch.js.map +1 -0
- package/dist/esm/src/utils/index.js +3 -0
- package/dist/esm/src/utils/index.js.map +1 -0
- package/dist/esm/src/utils/pkce.js +43 -0
- package/dist/esm/src/utils/pkce.js.map +1 -0
- package/dist/types/IAMService.d.ts +328 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/lib/tidecloak.d.ts +325 -31
- package/dist/types/policy-react.d.ts +1 -0
- package/dist/types/src/IAMService.d.ts +245 -23
- package/dist/types/src/index.d.ts +2 -2
- package/dist/types/src/policy-react.d.ts +1 -0
- package/dist/types/src/utils/fetch.d.ts +11 -0
- package/dist/types/src/utils/index.d.ts +2 -0
- package/dist/types/src/utils/pkce.d.ts +24 -0
- package/dist/types/utils/fetch.d.ts +11 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/pkce.d.ts +24 -0
- package/package.json +18 -29
- package/scripts/postinstall.cjs +36 -0
- package/silent-check-sso.html +1 -0
- package/scripts/postinstall.js +0 -43
|
@@ -1,33 +1,327 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
constructor(config:
|
|
1
|
+
export default class TideCloak {
|
|
2
|
+
/**
|
|
3
|
+
* @param {KeycloakConfig} config
|
|
4
|
+
*/
|
|
5
|
+
constructor(config: KeycloakConfig);
|
|
6
6
|
didInitialize: boolean;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
7
|
+
authenticated: boolean;
|
|
8
|
+
loginRequired: boolean;
|
|
9
|
+
/** @type {KeycloakResponseMode} */
|
|
10
|
+
responseMode: KeycloakResponseMode;
|
|
11
|
+
/** @type {KeycloakResponseType} */
|
|
12
|
+
responseType: KeycloakResponseType;
|
|
13
|
+
/** @type {KeycloakFlow} */
|
|
14
|
+
flow: KeycloakFlow;
|
|
15
|
+
/**
|
|
16
|
+
* Matches Keycloak: number | undefined (unset when skew unknown)
|
|
17
|
+
* @type {number | undefined}
|
|
18
|
+
*/
|
|
19
|
+
timeSkew: number | undefined;
|
|
20
|
+
/** @type {string=} */
|
|
21
|
+
redirectUri: string | undefined;
|
|
22
|
+
/** @type {string=} */
|
|
23
|
+
silentCheckSsoRedirectUri: string | undefined;
|
|
24
|
+
/** @type {boolean} */
|
|
25
|
+
silentCheckSsoFallback: boolean;
|
|
26
|
+
/** @type {KeycloakPkceMethod} */
|
|
27
|
+
pkceMethod: KeycloakPkceMethod;
|
|
28
|
+
enableLogging: boolean;
|
|
29
|
+
/** @type {'GET' | 'POST'} */
|
|
30
|
+
logoutMethod: "GET" | "POST";
|
|
31
|
+
/** @type {string=} */
|
|
32
|
+
scope: string | undefined;
|
|
33
|
+
/** @type {string | undefined} */
|
|
34
|
+
acrValues: string | undefined;
|
|
35
|
+
messageReceiveTimeout: number;
|
|
36
|
+
/** @type {string=} */
|
|
37
|
+
idToken: string | undefined;
|
|
38
|
+
/** @type {KeycloakTokenParsed=} */
|
|
39
|
+
idTokenParsed: KeycloakTokenParsed | undefined;
|
|
40
|
+
/** @type {string=} */
|
|
41
|
+
token: string | undefined;
|
|
42
|
+
/** @type {KeycloakTokenParsed=} */
|
|
43
|
+
tokenParsed: KeycloakTokenParsed | undefined;
|
|
44
|
+
/** @type {string=} */
|
|
45
|
+
refreshToken: string | undefined;
|
|
46
|
+
/** @type {KeycloakTokenParsed=} */
|
|
47
|
+
refreshTokenParsed: KeycloakTokenParsed | undefined;
|
|
48
|
+
/** @type {string | undefined} */
|
|
49
|
+
doken: string | undefined;
|
|
50
|
+
/** @type {KeycloakTokenParsed | undefined} */
|
|
51
|
+
dokenParsed: KeycloakTokenParsed | undefined;
|
|
52
|
+
/** @type {any} */
|
|
53
|
+
requestEnclave: any;
|
|
54
|
+
/** @type {any} */
|
|
55
|
+
approvalEnclave: any;
|
|
56
|
+
/** @type {string=} */
|
|
57
|
+
clientId: string | undefined;
|
|
58
|
+
/** @type {string=} */
|
|
59
|
+
sessionId: string | undefined;
|
|
60
|
+
/** @type {string=} */
|
|
61
|
+
subject: string | undefined;
|
|
62
|
+
/** @type {string=} */
|
|
63
|
+
authServerUrl: string | undefined;
|
|
64
|
+
/** @type {string=} */
|
|
65
|
+
realm: string | undefined;
|
|
66
|
+
/** @type {KeycloakRoles=} */
|
|
67
|
+
realmAccess: KeycloakRoles | undefined;
|
|
68
|
+
/** @type {KeycloakResourceAccess=} */
|
|
69
|
+
resourceAccess: KeycloakResourceAccess | undefined;
|
|
70
|
+
/** @type {KeycloakProfile=} */
|
|
71
|
+
profile: KeycloakProfile | undefined;
|
|
72
|
+
/** @type {KeycloakUserInfo | undefined} */
|
|
73
|
+
userInfo: KeycloakUserInfo | undefined;
|
|
74
|
+
/** @type {Endpoints} */
|
|
75
|
+
endpoints: Endpoints;
|
|
76
|
+
/** @type {number=} */
|
|
77
|
+
tokenTimeoutHandle: number | undefined;
|
|
78
|
+
/** @type {() => void=} */
|
|
79
|
+
onAuthSuccess: (() => void) | undefined;
|
|
80
|
+
/** @type {(errorData?: KeycloakError) => void=} */
|
|
81
|
+
onAuthError: ((errorData?: KeycloakError) => void) | undefined;
|
|
82
|
+
/** @type {() => void=} */
|
|
83
|
+
onAuthRefreshSuccess: (() => void) | undefined;
|
|
84
|
+
/** @type {() => void=} */
|
|
85
|
+
onAuthRefreshError: (() => void) | undefined;
|
|
86
|
+
/** @type {() => void=} */
|
|
87
|
+
onTokenExpired: (() => void) | undefined;
|
|
88
|
+
/** @type {() => void=} */
|
|
89
|
+
onAuthLogout: (() => void) | undefined;
|
|
90
|
+
/** @type {(authenticated: boolean) => void=} */
|
|
91
|
+
onReady: ((authenticated: boolean) => void) | undefined;
|
|
92
|
+
/** @type {(status: 'success' | 'cancelled' | 'error', action: string) => void=} */
|
|
93
|
+
onActionUpdate: ((status: "success" | "cancelled" | "error", action: string) => void) | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* @param {KeycloakInitOptions} initOptions
|
|
96
|
+
* @returns {Promise<boolean>}
|
|
97
|
+
*/
|
|
98
|
+
init(initOptions?: KeycloakInitOptions): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* @param {KeycloakLoginOptions} [options]
|
|
101
|
+
* @returns {Promise<void>}
|
|
102
|
+
*/
|
|
103
|
+
login(options?: KeycloakLoginOptions): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Ensure the access token is valid, refreshing if needed.
|
|
106
|
+
* @returns {Promise<void>}
|
|
107
|
+
*/
|
|
108
|
+
ensureTokenReady(): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* @param {KeycloakLoginOptions} [options]
|
|
111
|
+
* @returns {Promise<string>}
|
|
112
|
+
*/
|
|
113
|
+
createLoginUrl(options?: KeycloakLoginOptions): Promise<string>;
|
|
114
|
+
/**
|
|
115
|
+
* @param {KeycloakLogoutOptions} [options]
|
|
116
|
+
* @returns {Promise<void>}
|
|
117
|
+
*/
|
|
118
|
+
logout(options?: KeycloakLogoutOptions): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* @param {KeycloakLogoutOptions} [options]
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
createLogoutUrl(options?: KeycloakLogoutOptions): string;
|
|
124
|
+
/**
|
|
125
|
+
* @param {KeycloakRegisterOptions} [options]
|
|
126
|
+
* @returns {Promise<void>}
|
|
127
|
+
*/
|
|
128
|
+
register(options?: KeycloakRegisterOptions): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* @param {KeycloakRegisterOptions} [options]
|
|
131
|
+
* @returns {Promise<string>}
|
|
132
|
+
*/
|
|
133
|
+
createRegisterUrl(options?: KeycloakRegisterOptions): Promise<string>;
|
|
134
|
+
/**
|
|
135
|
+
* @param {KeycloakAccountOptions} [options]
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
createAccountUrl(options?: KeycloakAccountOptions): string;
|
|
139
|
+
/**
|
|
140
|
+
* @returns {Promise<void>}
|
|
141
|
+
*/
|
|
142
|
+
accountManagement(): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* @param {string} role
|
|
145
|
+
* @returns {boolean}
|
|
146
|
+
*/
|
|
147
|
+
hasRealmRole(role: string): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} role
|
|
150
|
+
* @param {string} [resource]
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
hasResourceRole(role: string, resource?: string): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* @returns {Promise<KeycloakProfile>}
|
|
156
|
+
*/
|
|
157
|
+
loadUserProfile(): Promise<KeycloakProfile>;
|
|
158
|
+
/**
|
|
159
|
+
* @returns {Promise<KeycloakUserInfo>}
|
|
160
|
+
*/
|
|
161
|
+
loadUserInfo(): Promise<KeycloakUserInfo>;
|
|
162
|
+
/**
|
|
163
|
+
* @param {number} [minValidity]
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
166
|
+
isTokenExpired(minValidity?: number): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Matches Keycloak: minValidity is optional.
|
|
169
|
+
* @param {number} [minValidity]
|
|
170
|
+
* @returns {Promise<boolean>}
|
|
171
|
+
*/
|
|
172
|
+
updateToken(minValidity?: number): Promise<boolean>;
|
|
173
|
+
clearToken(): void;
|
|
174
|
+
/**
|
|
175
|
+
* Initialize Tide RequestEnclave.
|
|
176
|
+
*/
|
|
177
|
+
initRequestEnclave(): void;
|
|
178
|
+
/**
|
|
179
|
+
* Initialize Tide ApprovalEnclave.
|
|
180
|
+
*/
|
|
181
|
+
initApprovalEnclave(): void;
|
|
182
|
+
/**
|
|
183
|
+
* Role-based encryption via Tide RequestEnclave.
|
|
184
|
+
* @param {{ data: string | Uint8Array, tags: string[] }[]} toEncrypt
|
|
185
|
+
* @returns {Promise<(string | Uint8Array)[]>}
|
|
186
|
+
*/
|
|
187
|
+
encrypt(toEncrypt: {
|
|
188
|
+
data: string | Uint8Array;
|
|
189
|
+
tags: string[];
|
|
190
|
+
}[]): Promise<(string | Uint8Array)[]>;
|
|
191
|
+
/**
|
|
192
|
+
* Initialize a Tide request that requires operator approvals.
|
|
193
|
+
* @param {Uint8Array} encodedRequest
|
|
194
|
+
* @returns {Promise<Uint8Array>}
|
|
195
|
+
*/
|
|
196
|
+
createTideRequest(encodedRequest: Uint8Array): Promise<Uint8Array>;
|
|
197
|
+
/**
|
|
198
|
+
* Request Tide operator approval.
|
|
199
|
+
* @param {{id: string, request: Uint8Array}[]} requests
|
|
200
|
+
* @returns {Promise<{approved: {id: string, request: Uint8Array}[], denied: {id: string}[], pending: {id: string}[]}>}
|
|
201
|
+
*/
|
|
202
|
+
requestTideOperatorApproval(requests: {
|
|
203
|
+
id: string;
|
|
204
|
+
request: Uint8Array;
|
|
205
|
+
}[]): Promise<{
|
|
206
|
+
approved: {
|
|
207
|
+
id: string;
|
|
208
|
+
request: Uint8Array;
|
|
209
|
+
}[];
|
|
210
|
+
denied: {
|
|
211
|
+
id: string;
|
|
212
|
+
}[];
|
|
213
|
+
pending: {
|
|
214
|
+
id: string;
|
|
215
|
+
}[];
|
|
216
|
+
}>;
|
|
217
|
+
/**
|
|
218
|
+
* Execute a Tide Sign Request
|
|
219
|
+
* @param {Uint8Array} request
|
|
220
|
+
* @param {boolean} [waitForAll=false]
|
|
221
|
+
* @returns {Promise<Array>} Array of signatures
|
|
222
|
+
*/
|
|
223
|
+
executeSignRequest(request: Uint8Array, waitForAll?: boolean): Promise<any[]>;
|
|
224
|
+
/**
|
|
225
|
+
* Role-based decryption via Tide RequestEnclave.
|
|
226
|
+
* @param {{ encrypted: string | Uint8Array, tags: string[] }[]} toDecrypt
|
|
227
|
+
* @returns {Promise<(string | Uint8Array)[]>}
|
|
228
|
+
*/
|
|
229
|
+
decrypt(toDecrypt: {
|
|
230
|
+
encrypted: string | Uint8Array;
|
|
231
|
+
tags: string[];
|
|
232
|
+
}[]): Promise<(string | Uint8Array)[]>;
|
|
233
|
+
#private;
|
|
32
234
|
}
|
|
33
|
-
|
|
235
|
+
/**
|
|
236
|
+
* @typedef {Object} NetworkErrorOptionsProperties
|
|
237
|
+
* @property {Response} response
|
|
238
|
+
* @typedef {ErrorOptions & NetworkErrorOptionsProperties} NetworkErrorOptions
|
|
239
|
+
*/
|
|
240
|
+
export class NetworkError extends Error {
|
|
241
|
+
/**
|
|
242
|
+
* @param {string} message
|
|
243
|
+
* @param {NetworkErrorOptions} options
|
|
244
|
+
*/
|
|
245
|
+
constructor(message: string, options: NetworkErrorOptions);
|
|
246
|
+
/** @type {Response} */
|
|
247
|
+
response: Response;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The JSON version of the adapter configuration.
|
|
251
|
+
*/
|
|
252
|
+
export type JsonConfig = {
|
|
253
|
+
/**
|
|
254
|
+
* The URL of the authentication server.
|
|
255
|
+
*/
|
|
256
|
+
"auth-server-url": string;
|
|
257
|
+
/**
|
|
258
|
+
* The name of the realm.
|
|
259
|
+
*/
|
|
260
|
+
realm: string;
|
|
261
|
+
/**
|
|
262
|
+
* The name of the resource, usually the client ID.
|
|
263
|
+
*/
|
|
264
|
+
resource: string;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* The successful token response from the authorization server, based on the {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 OAuth 2.0 Authorization Framework specification}.
|
|
268
|
+
*/
|
|
269
|
+
export type AccessTokenResponse = {
|
|
270
|
+
/**
|
|
271
|
+
* The access token issued by the authorization server.
|
|
272
|
+
*/
|
|
273
|
+
access_token: string;
|
|
274
|
+
/**
|
|
275
|
+
* The type of the token issued by the authorization server.
|
|
276
|
+
*/
|
|
277
|
+
token_type: string;
|
|
278
|
+
/**
|
|
279
|
+
* The lifetime in seconds of the access token.
|
|
280
|
+
*/
|
|
281
|
+
expires_in?: number | undefined;
|
|
282
|
+
/**
|
|
283
|
+
* The refresh token issued by the authorization server.
|
|
284
|
+
*/
|
|
285
|
+
refresh_token?: string | undefined;
|
|
286
|
+
/**
|
|
287
|
+
* The ID token issued by the authorization server, if requested.
|
|
288
|
+
*/
|
|
289
|
+
id_token?: string | undefined;
|
|
290
|
+
/**
|
|
291
|
+
* The scope of the access token.
|
|
292
|
+
*/
|
|
293
|
+
scope?: string | undefined;
|
|
294
|
+
};
|
|
295
|
+
export type Endpoints = {
|
|
296
|
+
authorize: () => string;
|
|
297
|
+
token: () => string;
|
|
298
|
+
logout: () => string;
|
|
299
|
+
checkSessionIframe: () => string;
|
|
300
|
+
thirdPartyCookiesIframe?: (() => string) | undefined;
|
|
301
|
+
register: () => string;
|
|
302
|
+
userinfo: () => string;
|
|
303
|
+
};
|
|
304
|
+
export type LoginIframe = {
|
|
305
|
+
enable: boolean;
|
|
306
|
+
callbackList: ((error: Error | null, value?: boolean) => void)[];
|
|
307
|
+
interval: number;
|
|
308
|
+
iframe?: HTMLIFrameElement | undefined;
|
|
309
|
+
iframeOrigin?: string | undefined;
|
|
310
|
+
};
|
|
311
|
+
export type CallbackState = {
|
|
312
|
+
state: string;
|
|
313
|
+
nonce: string;
|
|
314
|
+
redirectUri: string;
|
|
315
|
+
loginOptions?: KeycloakLoginOptions;
|
|
316
|
+
prompt?: KeycloakLoginOptions["prompt"];
|
|
317
|
+
pkceCodeVerifier?: string | undefined;
|
|
318
|
+
};
|
|
319
|
+
export type CallbackStorage = {
|
|
320
|
+
get: (state?: string) => CallbackState | null;
|
|
321
|
+
add: (state: CallbackState) => void;
|
|
322
|
+
};
|
|
323
|
+
export type NetworkErrorOptionsProperties = {
|
|
324
|
+
response: Response;
|
|
325
|
+
};
|
|
326
|
+
export type NetworkErrorOptions = ErrorOptions & NetworkErrorOptionsProperties;
|
|
327
|
+
export { RequestEnclave, ApprovalEnclave, ApprovalEnclaveNew, TideMemory, BaseTideRequest, PolicySignRequest, Policy, PolicyParameters } from "heimdall-tide";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,9 +4,16 @@ declare const IAMServiceInstance: IAMService;
|
|
|
4
4
|
/**
|
|
5
5
|
* Singleton IAMService wrapping the TideCloak client.
|
|
6
6
|
*
|
|
7
|
+
* Supports two modes:
|
|
8
|
+
* - **Front-channel mode**: Browser handles all token operations (standard OIDC)
|
|
9
|
+
* - **Hybrid/BFF mode**: Browser handles PKCE, backend exchanges code for tokens (more secure)
|
|
10
|
+
*
|
|
11
|
+
* ---
|
|
12
|
+
* ## Front-channel Mode
|
|
13
|
+
*
|
|
7
14
|
* Usage A: pass an onReady callback directly
|
|
8
15
|
* ```js
|
|
9
|
-
* import { IAMService } from 'tidecloak
|
|
16
|
+
* import { IAMService } from '@tidecloak/js';
|
|
10
17
|
* import tidecloakConfig from './tidecloakAdapter.json';
|
|
11
18
|
*
|
|
12
19
|
* IAMService.initIAM(tidecloakConfig, authenticated => {
|
|
@@ -22,11 +29,99 @@ declare const IAMServiceInstance: IAMService;
|
|
|
22
29
|
*
|
|
23
30
|
* await IAMService.initIAM(tidecloakConfig);
|
|
24
31
|
* ```
|
|
32
|
+
*
|
|
33
|
+
* ---
|
|
34
|
+
* ## Hybrid/BFF Mode (Backend-For-Frontend)
|
|
35
|
+
*
|
|
36
|
+
* In hybrid mode, the browser generates PKCE and redirects to the IdP, but the
|
|
37
|
+
* backend exchanges the authorization code for tokens. This keeps tokens server-side
|
|
38
|
+
* for improved security.
|
|
39
|
+
*
|
|
40
|
+
* ### Config shape:
|
|
41
|
+
* ```js
|
|
42
|
+
* const hybridConfig = {
|
|
43
|
+
* authMode: "hybrid",
|
|
44
|
+
* oidc: {
|
|
45
|
+
* authorizationEndpoint: "https://auth.example.com/realms/myrealm/protocol/openid-connect/auth",
|
|
46
|
+
* clientId: "my-client",
|
|
47
|
+
* redirectUri: "https://app.example.com/auth/callback",
|
|
48
|
+
* scope: "openid profile email", // optional, defaults to "openid profile email"
|
|
49
|
+
* prompt: "login" // optional
|
|
50
|
+
* },
|
|
51
|
+
* tokenExchange: {
|
|
52
|
+
* endpoint: "/api/authenticate", // Backend endpoint that exchanges code for tokens
|
|
53
|
+
* provider: "tidecloak-auth", // optional, defaults to "tidecloak-auth"
|
|
54
|
+
* headers: () => ({ // optional, custom headers (e.g., CSRF token)
|
|
55
|
+
* "anti-csrf-token": getCSRFToken()
|
|
56
|
+
* })
|
|
57
|
+
* }
|
|
58
|
+
* };
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* ### Login Page:
|
|
62
|
+
* ```js
|
|
63
|
+
* // Load config and trigger login
|
|
64
|
+
* await IAMService.loadConfig(hybridConfig);
|
|
65
|
+
* IAMService.doLogin("/dashboard"); // returnUrl after successful auth
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* ### Redirect/Callback Page:
|
|
69
|
+
* ```js
|
|
70
|
+
* // initIAM handles the callback automatically - exchanges code for tokens via backend
|
|
71
|
+
* const authenticated = await IAMService.initIAM(hybridConfig);
|
|
72
|
+
* if (authenticated) {
|
|
73
|
+
* const returnUrl = IAMService.getReturnUrl() || "/";
|
|
74
|
+
* window.location.assign(returnUrl);
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* ### Token Exchange Request Format:
|
|
79
|
+
* The backend endpoint receives a POST request with:
|
|
80
|
+
* ```json
|
|
81
|
+
* {
|
|
82
|
+
* "accessToken": "{\"code\":\"...\",\"code_verifier\":\"...\",\"redirect_uri\":\"...\"}",
|
|
83
|
+
* "provider": "tidecloak-auth"
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* ---
|
|
88
|
+
* ## Events
|
|
89
|
+
* - `ready` - Emitted when initialization completes (with authenticated boolean)
|
|
90
|
+
* - `initError` - Emitted when initialization fails
|
|
91
|
+
* - `authSuccess` - Emitted on successful authentication
|
|
92
|
+
* - `authError` - Emitted on authentication failure
|
|
93
|
+
* - `authRefreshSuccess` - Emitted when token refresh succeeds (front-channel only)
|
|
94
|
+
* - `authRefreshError` - Emitted when token refresh fails (front-channel only)
|
|
95
|
+
* - `logout` - Emitted on logout
|
|
96
|
+
* - `tokenExpired` - Emitted when token expires (front-channel only)
|
|
25
97
|
*/
|
|
26
98
|
declare class IAMService {
|
|
27
99
|
_tc: TideCloak | null;
|
|
28
100
|
_config: Object | null;
|
|
29
101
|
_listeners: {};
|
|
102
|
+
_hybridAuthenticated: boolean;
|
|
103
|
+
_hybridReturnUrl: string | null;
|
|
104
|
+
_hybridCallbackHandled: boolean;
|
|
105
|
+
_hybridCallbackPromise: any;
|
|
106
|
+
_cachedCallbackData: {
|
|
107
|
+
isCallback: boolean;
|
|
108
|
+
code: string;
|
|
109
|
+
verifier: string;
|
|
110
|
+
redirectUri: any;
|
|
111
|
+
returnUrl: string;
|
|
112
|
+
provider: any;
|
|
113
|
+
error: string | null;
|
|
114
|
+
errorDescription: string | null;
|
|
115
|
+
} | {
|
|
116
|
+
isCallback: boolean;
|
|
117
|
+
code: string;
|
|
118
|
+
verifier: string;
|
|
119
|
+
redirectUri: any;
|
|
120
|
+
returnUrl: string;
|
|
121
|
+
provider: any;
|
|
122
|
+
error: null;
|
|
123
|
+
errorDescription: null;
|
|
124
|
+
} | null;
|
|
30
125
|
/**
|
|
31
126
|
* Register an event listener.
|
|
32
127
|
* @param {'ready'|'initError'|'authSuccess'|'authError'|'authRefreshSuccess'|'authRefreshError'|'logout'|'tokenExpired'} event
|
|
@@ -43,6 +138,11 @@ declare class IAMService {
|
|
|
43
138
|
off(event: string, handler: Function): this;
|
|
44
139
|
/** @private */
|
|
45
140
|
private _emit;
|
|
141
|
+
/**
|
|
142
|
+
* Check if running in hybrid mode.
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
isHybridMode(): boolean;
|
|
46
146
|
/**
|
|
47
147
|
* Load TideCloak configuration and instantiate the client once.
|
|
48
148
|
* @param {Object} config - TideCloak configuration object.
|
|
@@ -51,6 +151,7 @@ declare class IAMService {
|
|
|
51
151
|
loadConfig(config: Object): Promise<Object | null>;
|
|
52
152
|
/**
|
|
53
153
|
* Initialize the TideCloak SSO client with silent SSO check.
|
|
154
|
+
* In hybrid mode, handles the redirect callback if present.
|
|
54
155
|
* @param {Object} config - TideCloak configuration object.
|
|
55
156
|
* @param {Function} [onReady] - Optional callback for the 'ready' event.
|
|
56
157
|
* @returns {Promise<boolean>} true if authenticated, else false.
|
|
@@ -60,47 +161,168 @@ declare class IAMService {
|
|
|
60
161
|
private getTideCloakClient;
|
|
61
162
|
/** @returns {Object} Loaded config */
|
|
62
163
|
getConfig(): Object;
|
|
63
|
-
/** @returns {boolean} Whether there's a valid token */
|
|
164
|
+
/** @returns {boolean} Whether there's a valid token (or session in hybrid mode) */
|
|
64
165
|
isLoggedIn(): boolean;
|
|
65
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* Get valid token (refreshing if needed).
|
|
168
|
+
* @returns {Promise<string>}
|
|
169
|
+
* @throws {Error} In hybrid mode (tokens are server-side)
|
|
170
|
+
*/
|
|
66
171
|
getToken(): Promise<string>;
|
|
67
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Seconds until token expiry.
|
|
174
|
+
* @returns {number}
|
|
175
|
+
* @throws {Error} In hybrid mode (tokens are server-side)
|
|
176
|
+
*/
|
|
68
177
|
getTokenExp(): number;
|
|
69
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* Get ID token.
|
|
180
|
+
* @returns {string}
|
|
181
|
+
* @throws {Error} In hybrid mode (tokens are server-side)
|
|
182
|
+
*/
|
|
70
183
|
getIDToken(): string;
|
|
71
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Get username (preferred_username claim).
|
|
186
|
+
* @returns {string}
|
|
187
|
+
* @throws {Error} In hybrid mode (tokens are server-side)
|
|
188
|
+
*/
|
|
72
189
|
getName(): string;
|
|
73
190
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
191
|
+
* Get the return URL after successful hybrid authentication.
|
|
192
|
+
* Only available in hybrid mode after successful auth.
|
|
193
|
+
* @returns {string|null}
|
|
194
|
+
*/
|
|
195
|
+
getReturnUrl(): string | null;
|
|
196
|
+
/**
|
|
197
|
+
* Check if user has a realm role.
|
|
198
|
+
* @param {string} role - the name of the role to check
|
|
199
|
+
* @returns {boolean} Whether the user has a given realm role
|
|
200
|
+
* @throws {Error} In hybrid mode (role checks not available client-side)
|
|
201
|
+
*/
|
|
76
202
|
hasRealmRole(role: string): boolean;
|
|
77
203
|
/**
|
|
204
|
+
* Check if user has a client role.
|
|
78
205
|
* @param {string} role - the name of the role to check
|
|
79
206
|
* @param {string} [client] - optional client-ID (defaults to the configured adapter resource)
|
|
80
207
|
* @returns {boolean} - whether the user has that role
|
|
208
|
+
* @throws {Error} In hybrid mode (role checks not available client-side)
|
|
81
209
|
*/
|
|
82
210
|
hasClientRole(role: string, client?: string): boolean;
|
|
83
211
|
/**
|
|
212
|
+
* Get custom claim from access token.
|
|
84
213
|
* @param {string} key - The name of the claim to retrieve from the Access token's payload.
|
|
85
|
-
* @returns {*} Custom claim from access token
|
|
214
|
+
* @returns {*} Custom claim from access token
|
|
215
|
+
* @throws {Error} In hybrid mode (tokens are server-side)
|
|
216
|
+
*/
|
|
86
217
|
getValueFromToken(key: string): any;
|
|
87
218
|
/**
|
|
219
|
+
* Get custom claim from ID token.
|
|
88
220
|
* @param {string} key - The name of the claim to retrieve from the ID token's payload.
|
|
89
|
-
* @returns {*} Custom claim from
|
|
221
|
+
* @returns {*} Custom claim from ID token
|
|
222
|
+
* @throws {Error} In hybrid mode (tokens are server-side)
|
|
223
|
+
*/
|
|
90
224
|
getValueFromIDToken(key: string): any;
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Refreshes token if expired or about to expire.
|
|
227
|
+
* @returns {Promise<boolean>}
|
|
228
|
+
* @throws {Error} In hybrid mode (token refresh handled server-side)
|
|
229
|
+
*/
|
|
230
|
+
updateIAMToken(): Promise<boolean>;
|
|
231
|
+
/**
|
|
232
|
+
* Force immediate refresh (min validity = -1).
|
|
233
|
+
* @returns {Promise<boolean>}
|
|
234
|
+
* @throws {Error} In hybrid mode (token refresh handled server-side)
|
|
235
|
+
*/
|
|
236
|
+
forceUpdateToken(): Promise<boolean>;
|
|
237
|
+
/**
|
|
238
|
+
* Start login redirect.
|
|
239
|
+
* In hybrid mode, initiates PKCE flow and redirects to IdP.
|
|
240
|
+
* @param {string} [returnUrl] - URL to redirect to after successful auth (hybrid mode only)
|
|
241
|
+
*/
|
|
242
|
+
doLogin(returnUrl?: string): Promise<void> | undefined;
|
|
243
|
+
/**
|
|
244
|
+
* Encrypt data via adapter.
|
|
245
|
+
* Not available in hybrid mode (encryption requires client-side doken).
|
|
246
|
+
*/
|
|
247
|
+
doEncrypt(data: any): Promise<(string | Uint8Array<ArrayBufferLike>)[]>;
|
|
248
|
+
/**
|
|
249
|
+
* Decrypt data via adapter.
|
|
250
|
+
* Not available in hybrid mode (decryption requires client-side doken).
|
|
251
|
+
*/
|
|
252
|
+
doDecrypt(data: any): Promise<(string | Uint8Array<ArrayBufferLike>)[]>;
|
|
253
|
+
/**
|
|
254
|
+
* Logout, clear cookie/session, then redirect.
|
|
255
|
+
* In hybrid mode, clears local state and emits logout event.
|
|
256
|
+
*/
|
|
102
257
|
doLogout(): void;
|
|
103
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* Base URL for Tidecloak realm (no trailing slash).
|
|
260
|
+
* In hybrid mode returns empty string.
|
|
261
|
+
*/
|
|
104
262
|
getBaseUrl(): any;
|
|
263
|
+
/**
|
|
264
|
+
* Start hybrid login flow: generate PKCE, store verifier, redirect to IdP.
|
|
265
|
+
* @private
|
|
266
|
+
* @param {string} returnUrl - URL to redirect to after successful auth
|
|
267
|
+
*/
|
|
268
|
+
private _startHybridLogin;
|
|
269
|
+
/**
|
|
270
|
+
* Get hybrid callback data for custom token exchange.
|
|
271
|
+
* Use this when you need to handle token exchange with your own auth system
|
|
272
|
+
* (e.g., using a custom useLogin hook) instead of IAMService's built-in fetchJson.
|
|
273
|
+
*
|
|
274
|
+
* @param {Object} [opts] - Options
|
|
275
|
+
* @param {boolean} [opts.clearStorage=true] - Whether to clear sessionStorage after reading
|
|
276
|
+
* @param {string} [opts.redirectUri] - Override redirect URI (use when config isn't loaded)
|
|
277
|
+
* @param {string} [opts.provider] - Override provider (defaults to "tidecloak-auth")
|
|
278
|
+
* @returns {{
|
|
279
|
+
* isCallback: boolean,
|
|
280
|
+
* code: string,
|
|
281
|
+
* verifier: string,
|
|
282
|
+
* redirectUri: string,
|
|
283
|
+
* returnUrl: string,
|
|
284
|
+
* provider: string,
|
|
285
|
+
* error: string|null,
|
|
286
|
+
* errorDescription: string|null
|
|
287
|
+
* }}
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* // With redirectUri override (recommended for callback pages after full navigation)
|
|
291
|
+
* const data = IAMService.getHybridCallbackData({
|
|
292
|
+
* redirectUri: process.env.KEYCLOAK_REDIRECTURI,
|
|
293
|
+
* });
|
|
294
|
+
* if (data.isCallback && data.code && data.verifier) {
|
|
295
|
+
* login.execute({
|
|
296
|
+
* accessToken: JSON.stringify({
|
|
297
|
+
* code: data.code,
|
|
298
|
+
* code_verifier: data.verifier,
|
|
299
|
+
* redirect_uri: data.redirectUri,
|
|
300
|
+
* }),
|
|
301
|
+
* provider: data.provider,
|
|
302
|
+
* });
|
|
303
|
+
* }
|
|
304
|
+
*/
|
|
305
|
+
getHybridCallbackData(opts?: {
|
|
306
|
+
clearStorage?: boolean | undefined;
|
|
307
|
+
redirectUri?: string | undefined;
|
|
308
|
+
provider?: string | undefined;
|
|
309
|
+
}): {
|
|
310
|
+
isCallback: boolean;
|
|
311
|
+
code: string;
|
|
312
|
+
verifier: string;
|
|
313
|
+
redirectUri: string;
|
|
314
|
+
returnUrl: string;
|
|
315
|
+
provider: string;
|
|
316
|
+
error: string | null;
|
|
317
|
+
errorDescription: string | null;
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Handle hybrid redirect callback: exchange code for tokens via backend endpoint.
|
|
321
|
+
* @private
|
|
322
|
+
* @param {Object} opts - Options
|
|
323
|
+
* @param {string} [opts.onMissingVerifierRedirectTo] - URL to redirect if verifier is missing
|
|
324
|
+
* @returns {Promise<{handled: boolean, authenticated: boolean, returnUrl: string}>}
|
|
325
|
+
*/
|
|
326
|
+
private _handleHybridRedirectCallback;
|
|
105
327
|
}
|
|
106
|
-
import TideCloak from "../lib/tidecloak";
|
|
328
|
+
import TideCloak from "../lib/tidecloak.js";
|