Add Keycloak JS to the project (#3189)

This commit is contained in:
Jon Koops 2022-09-12 11:52:11 +02:00 committed by GitHub
parent a51c9c19d3
commit 243cec90df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 4121 additions and 723 deletions

View file

@ -1,7 +1,15 @@
/** @type {import("eslint").Linter.Config } */
module.exports = {
root: true,
ignorePatterns: ["node_modules", "dist", "keycloak-theme", "server"],
ignorePatterns: [
"node_modules",
"dist",
"keycloak-theme",
"server",
// Keycloak JS follows a completely different and outdated style, so we'll exclude it for now.
// TODO: Eventually align the code-style for Keycloak JS.
"libs/keycloak-js",
],
parserOptions: {
tsconfigRootDir: __dirname,
project: "./tsconfig.eslint.json",

View file

@ -29,7 +29,7 @@ jobs:
run: npm ci
- name: Run build task
run: npm run build --workspace=admin-ui
run: npm run build --workspace=keycloak-js --workspace=admin-ui
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View file

@ -23,6 +23,9 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Build Keycloak JS
run: npm run build --workspace=keycloak-js
- name: Cache setup
uses: actions/cache@v3
with:

4
.gitignore vendored
View file

@ -7,10 +7,8 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# NPM
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*

4
apps/admin-ui/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Vite
dist
dist-ssr
*.local

View file

@ -24,7 +24,7 @@
"flat": "^5.0.2",
"i18next": "^21.9.1",
"i18next-http-backend": "^1.4.1",
"keycloak-js": "^19.0.1",
"keycloak-js": "999.0.0-dev",
"lodash-es": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@ -54,7 +54,6 @@
"@types/flat": "^5.0.2",
"@types/gunzip-maybe": "^1.4.0",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.7.15",
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.16",
"@types/react-router-dom": "^5.3.3",

View file

@ -34,7 +34,7 @@
<nexus.staging.plugin.version>1.6.13</nexus.staging.plugin.version>
<frontend.maven.plugin.version>1.12.1</frontend.maven.plugin.version>
<frontend.maven.plugin.nodeVersion>v18.7.0</frontend.maven.plugin.nodeVersion>
<frontend.maven.plugin.nodeVersion>v18.9.0</frontend.maven.plugin.nodeVersion>
<keycloak.version>19.0.1</keycloak.version>
@ -237,7 +237,7 @@
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build --workspace=admin-ui</arguments>
<arguments>run build --workspace=keycloak-js --workspace=admin-ui</arguments>
</configuration>
</execution>
</executions>

2
libs/keycloak-js/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist/*
!dist/*.d.ts

View file

@ -0,0 +1,3 @@
# Keycloak JS
The documentation can be found in the [Keycloak documentation](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter).

View file

@ -0,0 +1,133 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import Keycloak from './keycloak';
export interface KeycloakAuthorizationPromise {
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
}
export interface AuthorizationRequest {
/**
* An array of objects representing the resource and scopes.
*/
permissions?:ResourcePermission[],
/**
* A permission ticket obtained from a resource server when using UMA authorization protocol.
*/
ticket?:string,
/**
* A boolean value indicating whether the server should create permission requests to the resources
* and scopes referenced by a permission ticket. This parameter will only take effect when used together
* with the ticket parameter as part of a UMA authorization process.
*/
submitRequest?:boolean,
/**
* Defines additional information about this authorization request in order to specify how it should be processed
* by the server.
*/
metadata?:AuthorizationRequestMetadata,
/**
* Defines whether or not this authorization request should include the current RPT. If set to true, the RPT will
* be sent and permissions in the current RPT will be included in the new RPT. Otherwise, only the permissions referenced in this
* authorization request will be granted in the new RPT.
*/
incrementalAuthorization?:boolean
}
export interface AuthorizationRequestMetadata {
/**
* A boolean value indicating to the server if resource names should be included in the RPTs permissions.
* If false, only the resource identifier is included.
*/
responseIncludeResourceName?:any,
/**
* An integer N that defines a limit for the amount of permissions an RPT can have. When used together with
* rpt parameter, only the last N requested permissions will be kept in the RPT.
*/
response_permissions_limit?:number
}
export interface ResourcePermission {
/**
* The id or name of a resource.
*/
id:string,
/**
* An array of strings where each value is the name of a scope associated with the resource.
*/
scopes?:string[]
}
/**
* @deprecated Instead of importing 'KeycloakAuthorizationInstance' you can import 'KeycloakAuthorization' directly as a type.
*/
export type KeycloakAuthorizationInstance = KeycloakAuthorization;
/**
* @deprecated Construct a KeycloakAuthorization instance using the `new` keyword instead.
*/
declare function KeycloakAuthorization(keycloak: Keycloak): KeycloakAuthorization;
declare class KeycloakAuthorization {
/**
* Creates a new Keycloak client instance.
* @param config Path to a JSON config file or a plain config object.
*/
constructor(keycloak: Keycloak)
rpt: any;
config: { rpt_endpoint: string };
init(): void;
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer using UMA protocol.
*
* The authorization request must be provided with a ticket.
*
* @param authorizationRequest An AuthorizationRequest instance with a valid permission ticket set.
* @returns A promise to set functions to be invoked on grant, deny or error.
*/
authorize(authorizationRequest: AuthorizationRequest): KeycloakAuthorizationPromise;
/**
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
*
* @param resourceServerId The id (client id) of the resource server to obtain permissions from.
* @param authorizationRequest An AuthorizationRequest instance.
* @returns A promise to set functions to be invoked on grant, deny or error.
*/
entitlement(resourceServerId: string, authorizationRequest?: AuthorizationRequest): KeycloakAuthorizationPromise;
}
export default KeycloakAuthorization;
/**
* @deprecated The 'KeycloakAuthorization' namespace is deprecated, use named imports instead.
*/
export as namespace KeycloakAuthorization;

653
libs/keycloak-js/dist/keycloak.d.ts vendored Normal file
View file

@ -0,0 +1,653 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export type KeycloakOnLoad = 'login-required'|'check-sso';
export type KeycloakResponseMode = 'query'|'fragment';
export type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
export type KeycloakFlow = 'standard'|'implicit'|'hybrid';
export type KeycloakPkceMethod = 'S256';
export interface KeycloakConfig {
/**
* URL to the Keycloak server, for example: http://keycloak-server/auth
*/
url?: string;
/**
* Name of the realm, for example: 'myrealm'
*/
realm: string;
/**
* Client identifier, example: 'myapp'
*/
clientId: string;
}
export interface Acr {
/**
* Array of values, which will be used inside ID Token `acr` claim sent inside the `claims` parameter to Keycloak server during login.
* Values should correspond to the ACR levels defined in the ACR to Loa mapping for realm or client or to the numbers (levels) inside defined
* Keycloak authentication flow. See section 5.5.1 of OIDC 1.0 specification for the details.
*/
values: string[];
/**
* This parameter specifies if ACR claims is considered essential or not.
*/
essential: boolean;
}
export interface KeycloakInitOptions {
/**
* Adds a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce)
* to verify that the authentication response matches the request.
* @default true
*/
useNonce?: boolean;
/**
*
* Allow usage of different types of adapters or a custom adapter to make Keycloak work in different environments.
*
* The following options are supported:
* - `default` - Use default APIs that are available in browsers.
* - `cordova` - Use a WebView in Cordova.
* - `cordova-native` - Use Cordova native APIs, this is recommended over `cordova`.
*
* It's also possible to pass in a custom adapter for the environment you are running Keycloak in. In order to do so extend the `KeycloakAdapter` interface and implement the methods that are defined there.
*
* For example:
*
* ```ts
* import Keycloak, { KeycloakAdapter } from 'keycloak-js';
*
* // Implement the 'KeycloakAdapter' interface so that all required methods are guaranteed to be present.
* const MyCustomAdapter: KeycloakAdapter = {
* login(options) {
* // Write your own implementation here.
* }
*
* // The other methods go here...
* };
*
* const keycloak = new Keycloak();
*
* keycloak.init({
* adapter: MyCustomAdapter,
* });
* ```
*/
adapter?: 'default' | 'cordova' | 'cordova-native' | KeycloakAdapter;
/**
* Specifies an action to do on load.
*/
onLoad?: KeycloakOnLoad;
/**
* Set an initial value for the token.
*/
token?: string;
/**
* Set an initial value for the refresh token.
*/
refreshToken?: string;
/**
* Set an initial value for the id token (only together with `token` or
* `refreshToken`).
*/
idToken?: string;
/**
* Set an initial value for skew between local time and Keycloak server in
* seconds (only together with `token` or `refreshToken`).
*/
timeSkew?: number;
/**
* Set to enable/disable monitoring login state.
* @default true
*/
checkLoginIframe?: boolean;
/**
* Set the interval to check login state (in seconds).
* @default 5
*/
checkLoginIframeInterval?: number;
/**
* Set the OpenID Connect response mode to send to Keycloak upon login.
* @default fragment After successful authentication Keycloak will redirect
* to JavaScript application with OpenID Connect parameters
* added in URL fragment. This is generally safer and
* recommended over query.
*/
responseMode?: KeycloakResponseMode;
/**
* Specifies a default uri to redirect to after login or logout.
* This is currently supported for adapter 'cordova-native' and 'default'
*/
redirectUri?: string;
/**
* Specifies an uri to redirect to after silent check-sso.
* Silent check-sso will only happen, when this redirect uri is given and
* the specified uri is available within the application.
*/
silentCheckSsoRedirectUri?: string;
/**
* Specifies whether the silent check-sso should fallback to "non-silent"
* check-sso when 3rd party cookies are blocked by the browser. Defaults
* to true.
*/
silentCheckSsoFallback?: boolean;
/**
* Set the OpenID Connect flow.
* @default standard
*/
flow?: KeycloakFlow;
/**
* Configures the Proof Key for Code Exchange (PKCE) method to use.
* The currently allowed method is 'S256'.
* If not configured, PKCE will not be used.
*/
pkceMethod?: KeycloakPkceMethod;
/**
* Enables logging messages from Keycloak to the console.
* @default false
*/
enableLogging?: boolean
/**
* Set the default scope parameter to the login endpoint. Use a space-delimited list of scopes.
* Note that the scope 'openid' will be always be added to the list of scopes by the adapter.
* Note that the default scope specified here is overwritten if the `login()` options specify scope explicitly.
*/
scope?: string
/**
* Configures how long will Keycloak adapter wait for receiving messages from server in ms. This is used,
* for example, when waiting for response of 3rd party cookies check.
*
* @default 10000
*/
messageReceiveTimeout?: number
}
export interface KeycloakLoginOptions {
/**
* Specifies the scope parameter for the login url
* The scope 'openid' will be added to the scope if it is missing or undefined.
*/
scope?: string;
/**
* Specifies the uri to redirect to after login.
*/
redirectUri?: string;
/**
* By default the login screen is displayed if the user is not logged into
* Keycloak. To only authenticate to the application if the user is already
* logged in and not display the login page if the user is not logged in, set
* this option to `'none'`. To always require re-authentication and ignore
* SSO, set this option to `'login'`.
*/
prompt?: 'none'|'login';
/**
* If value is `'register'` then user is redirected to registration page,
* otherwise to login page.
*/
action?: string;
/**
* Used just if user is already authenticated. Specifies maximum time since
* the authentication of user happened. If user is already authenticated for
* longer time than `'maxAge'`, the SSO is ignored and he will need to
* authenticate again.
*/
maxAge?: number;
/**
* Used to pre-fill the username/email field on the login form.
*/
loginHint?: string;
/**
* Sets the `acr` claim of the ID token sent inside the `claims` parameter. See section 5.5.1 of the OIDC 1.0 specification.
*/
acr?: Acr;
/**
* Used to tell Keycloak which IDP the user wants to authenticate with.
*/
idpHint?: string;
/**
* Sets the 'ui_locales' query param in compliance with section 3.1.2.1
* of the OIDC 1.0 specification.
*/
locale?: string;
/**
* Specifies arguments that are passed to the Cordova in-app-browser (if applicable).
* Options 'hidden' and 'location' are not affected by these arguments.
* All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/.
* Example of use: { zoom: "no", hardwareback: "yes" }
*/
cordovaOptions?: { [optionName: string]: string };
}
export interface KeycloakLogoutOptions {
/**
* Specifies the uri to redirect to after logout.
*/
redirectUri?: string;
}
export interface KeycloakRegisterOptions extends Omit<KeycloakLoginOptions, 'action'> { }
export interface KeycloakAccountOptions {
/**
* Specifies the uri to redirect to when redirecting back to the application.
*/
redirectUri?: string;
}
export type KeycloakPromiseCallback<T> = (result: T) => void;
export interface KeycloakPromise<TSuccess, TError> extends Promise<TSuccess> {
/**
* Function to call if the promised action succeeds.
*
* @deprecated Use `.then()` instead.
*/
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
/**
* Function to call if the promised action throws an error.
*
* @deprecated Use `.catch()` instead.
*/
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
}
export interface KeycloakError {
error: string;
error_description: string;
}
export interface KeycloakAdapter {
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>;
register(options?: KeycloakRegisterOptions): KeycloakPromise<void, void>;
accountManagement(): KeycloakPromise<void, void>;
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
}
export interface KeycloakProfile {
id?: string;
username?: string;
email?: string;
firstName?: string;
lastName?: string;
enabled?: boolean;
emailVerified?: boolean;
totp?: boolean;
createdTimestamp?: number;
}
export interface KeycloakTokenParsed {
iss?: string;
sub?: string;
aud?: string;
exp?: number;
iat?: number;
auth_time?: number;
nonce?: string;
acr?: string;
amr?: string;
azp?: string;
session_state?: string;
realm_access?: KeycloakRoles;
resource_access?: KeycloakResourceAccess;
[key: string]: any; // Add other attributes here.
}
export interface KeycloakResourceAccess {
[key: string]: KeycloakRoles
}
export interface KeycloakRoles {
roles: string[];
}
/**
* @deprecated Instead of importing 'KeycloakInstance' you can import 'Keycloak' directly as a type.
*/
export type KeycloakInstance = Keycloak;
/**
* @deprecated Construct a Keycloak instance using the `new` keyword instead.
*/
declare function Keycloak(config?: KeycloakConfig | string): Keycloak;
/**
* A client for the Keycloak authentication server.
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
*/
declare class Keycloak {
/**
* Creates a new Keycloak client instance.
* @param config A configuration object or path to a JSON config file.
*/
constructor(config?: KeycloakConfig | string)
/**
* Is true if the user is authenticated, false otherwise.
*/
authenticated?: boolean;
/**
* The user id.
*/
subject?: string;
/**
* Response mode passed in init (default value is `'fragment'`).
*/
responseMode?: KeycloakResponseMode;
/**
* Response type sent to Keycloak with login requests. This is determined
* based on the flow value used during initialization, but can be overridden
* by setting this value.
*/
responseType?: KeycloakResponseType;
/**
* Flow passed in init.
*/
flow?: KeycloakFlow;
/**
* The realm roles associated with the token.
*/
realmAccess?: KeycloakRoles;
/**
* The resource roles associated with the token.
*/
resourceAccess?: KeycloakResourceAccess;
/**
* The base64 encoded token that can be sent in the Authorization header in
* requests to services.
*/
token?: string;
/**
* The parsed token as a JavaScript object.
*/
tokenParsed?: KeycloakTokenParsed;
/**
* The base64 encoded refresh token that can be used to retrieve a new token.
*/
refreshToken?: string;
/**
* The parsed refresh token as a JavaScript object.
*/
refreshTokenParsed?: KeycloakTokenParsed;
/**
* The base64 encoded ID token.
*/
idToken?: string;
/**
* The parsed id token as a JavaScript object.
*/
idTokenParsed?: KeycloakTokenParsed;
/**
* The estimated time difference between the browser time and the Keycloak
* server in seconds. This value is just an estimation, but is accurate
* enough when determining if a token is expired or not.
*/
timeSkew?: number;
/**
* @private Undocumented.
*/
loginRequired?: boolean;
/**
* @private Undocumented.
*/
authServerUrl?: string;
/**
* @private Undocumented.
*/
realm?: string;
/**
* @private Undocumented.
*/
clientId?: string;
/**
* @private Undocumented.
*/
clientSecret?: string;
/**
* @private Undocumented.
*/
redirectUri?: string;
/**
* @private Undocumented.
*/
sessionId?: string;
/**
* @private Undocumented.
*/
profile?: KeycloakProfile;
/**
* @private Undocumented.
*/
userInfo?: {}; // KeycloakUserInfo;
/**
* Called when the adapter is initialized.
*/
onReady?(authenticated?: boolean): void;
/**
* Called when a user is successfully authenticated.
*/
onAuthSuccess?(): void;
/**
* Called if there was an error during authentication.
*/
onAuthError?(errorData: KeycloakError): void;
/**
* Called when the token is refreshed.
*/
onAuthRefreshSuccess?(): void;
/**
* Called if there was an error while trying to refresh the token.
*/
onAuthRefreshError?(): void;
/**
* Called if the user is logged out (will only be called if the session
* status iframe is enabled, or in Cordova mode).
*/
onAuthLogout?(): void;
/**
* Called when the access token is expired. If a refresh token is available
* the token can be refreshed with Keycloak#updateToken, or in cases where
* it's not (ie. with implicit flow) you can redirect to login screen to
* obtain a new access token.
*/
onTokenExpired?(): void;
/**
* Called when a AIA has been requested by the application.
*/
onActionUpdate?(status: 'success'|'cancelled'|'error'): void;
/**
* Called to initialize the adapter.
* @param initOptions Initialization options.
* @returns A promise to set functions to be invoked on success or error.
*/
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
/**
* Redirects to login form.
* @param options Login options.
*/
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
/**
* Redirects to logout.
* @param options Logout options.
*/
logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>;
/**
* Redirects to registration form.
* @param options The options used for the registration.
*/
register(options?: KeycloakRegisterOptions): KeycloakPromise<void, void>;
/**
* Redirects to the Account Management Console.
*/
accountManagement(): KeycloakPromise<void, void>;
/**
* Returns the URL to login form.
* @param options Supports same options as Keycloak#login.
*/
createLoginUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to logout the user.
* @param options Logout options.
*/
createLogoutUrl(options?: KeycloakLogoutOptions): string;
/**
* Returns the URL to registration page.
* @param options The options used for creating the registration URL.
*/
createRegisterUrl(options?: KeycloakRegisterOptions): string;
/**
* Returns the URL to the Account Management Console.
* @param options The options used for creating the account URL.
*/
createAccountUrl(options?: KeycloakAccountOptions): string;
/**
* Returns true if the token has less than `minValidity` seconds left before
* it expires.
* @param minValidity If not specified, `0` is used.
*/
isTokenExpired(minValidity?: number): boolean;
/**
* If the token expires within `minValidity` seconds, the token is refreshed.
* If the session status iframe is enabled, the session status is also
* checked.
* @returns A promise to set functions that can be invoked if the token is
* still valid, or if the token is no longer valid.
* @example
* ```js
* keycloak.updateToken(5).then(function(refreshed) {
* if (refreshed) {
* alert('Token was successfully refreshed');
* } else {
* alert('Token is still valid');
* }
* }).catch(function() {
* alert('Failed to refresh the token, or the session has expired');
* });
*/
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
/**
* Clears authentication state, including tokens. This can be useful if
* the application has detected the session was expired, for example if
* updating token fails. Invoking this results in Keycloak#onAuthLogout
* callback listener being invoked.
*/
clearToken(): void;
/**
* Returns true if the token has the given realm role.
* @param role A realm role name.
*/
hasRealmRole(role: string): boolean;
/**
* Returns true if the token has the given role for the resource.
* @param role A role name.
* @param resource If not specified, `clientId` is used.
*/
hasResourceRole(role: string, resource?: string): boolean;
/**
* Loads the user's profile.
* @returns A promise to set functions to be invoked on success or error.
*/
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
/**
* @private Undocumented.
*/
loadUserInfo(): KeycloakPromise<{}, void>;
}
export default Keycloak;
/**
* @deprecated The 'Keycloak' namespace is deprecated, use named imports instead.
*/
export as namespace Keycloak;

View file

@ -0,0 +1,42 @@
{
"name": "keycloak-js",
"version": "999.0.0-dev",
"description": "Keycloak Adapter",
"main": "./dist/keycloak.js",
"module": "./dist/keycloak.mjs",
"types": "./dist/keycloak.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "rollup --config --configPlugin typescript",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "https://github.com/keycloak/keycloak-ui"
},
"author": "Keycloak",
"license": "Apache-2.0",
"homepage": "https://www.keycloak.org",
"keywords": [
"keycloak",
"sso",
"oauth",
"oauth2",
"authentication"
],
"devDependencies": {
"@rollup/plugin-commonjs": "^22.0.1",
"@rollup/plugin-inject": "^4.0.4",
"@rollup/plugin-node-resolve": "^13.1.3",
"@rollup/plugin-typescript": "^8.3.0",
"es6-promise": "^4.2.8",
"rollup": "^2.67.1",
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"base64-js": "^1.5.1",
"js-sha256": "^0.9.0"
}
}

View file

@ -0,0 +1,81 @@
import commonjs from "@rollup/plugin-commonjs";
import inject from "@rollup/plugin-inject";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import path from "node:path";
import type { OutputOptions, RollupOptions } from "rollup";
import { defineConfig } from "rollup";
import { terser } from "rollup-plugin-terser";
interface DefineOptionsArgs {
file: string;
name: string;
amdId: string;
}
function defineOptions({
file,
name,
amdId,
}: DefineOptionsArgs): RollupOptions[] {
const sourceDir = "src";
const targetDir = "dist";
const commonOptions: RollupOptions = {
input: path.join(sourceDir, `${file}.js`),
plugins: [commonjs(), nodeResolve()],
};
const umdOutput: OutputOptions = {
format: "umd",
name,
amd: { id: amdId },
};
return [
// Modern ES module variant, with externalized dependencies.
{
...commonOptions,
output: [
{
file: path.join(targetDir, `${file}.mjs`),
},
],
external: ["base64-js", "js-sha256"],
},
// Legacy Universal Module Definition, or “UMD”, with inlined dependencies.
{
...commonOptions,
output: [
{
...umdOutput,
file: path.join(targetDir, `${file}.js`),
},
{
...umdOutput,
file: path.join(targetDir, `${file}.min.js`),
sourcemap: true,
sourcemapExcludeSources: true,
plugins: [terser()],
},
],
plugins: [
...commonOptions.plugins,
inject({
Promise: ["es6-promise/dist/es6-promise.min.js", "Promise"],
}),
],
},
];
}
export default defineConfig([
...defineOptions({
file: "keycloak",
name: "Keycloak",
amdId: "keycloak",
}),
...defineOptions({
file: "keycloak-authz",
name: "KeycloakAuthorization",
amdId: "keycloak-authorization",
}),
]);

View file

@ -0,0 +1,221 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var KeycloakAuthorization = function (keycloak, options) {
var _instance = this;
this.rpt = null;
var resolve = function () {};
var reject = function () {};
// detects if browser supports promises
if (typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1) {
this.ready = new Promise(function (res, rej) {
resolve = res;
reject = rej;
});
}
this.init = function () {
var request = new XMLHttpRequest();
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
_instance.config = JSON.parse(request.responseText);
resolve();
} else {
console.error('Could not obtain configuration from server.');
reject();
}
}
}
request.send(null);
};
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer using UMA protocol.
*
* The authorization request must be provided with a ticket.
*/
this.authorize = function (authorizationRequest) {
this.then = function (onGrant, onDeny, onError) {
if (authorizationRequest && authorizationRequest.ticket) {
var request = new XMLHttpRequest();
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
};
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
if (authorizationRequest.submitRequest != undefined) {
params += "&submit_request=" + authorizationRequest.submitRequest;
}
var metadata = authorizationRequest.metadata;
if (metadata) {
if (metadata.responseIncludeResourceName) {
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
}
if (metadata.responsePermissionsLimit) {
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
}
}
if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
params += "&rpt=" + _instance.rpt;
}
request.send(params);
}
};
return this;
};
/**
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
*/
this.entitlement = function (resourceServerId, authorizationRequest) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
};
if (!authorizationRequest) {
authorizationRequest = {};
}
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
if (authorizationRequest.claimToken) {
params += "&claim_token=" + authorizationRequest.claimToken;
if (authorizationRequest.claimTokenFormat) {
params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
}
}
params += "&audience=" + resourceServerId;
var permissions = authorizationRequest.permissions;
if (!permissions) {
permissions = [];
}
for (var i = 0; i < permissions.length; i++) {
var resource = permissions[i];
var permission = resource.id;
if (resource.scopes && resource.scopes.length > 0) {
permission += "#";
for (j = 0; j < resource.scopes.length; j++) {
var scope = resource.scopes[j];
if (permission.indexOf('#') != permission.length - 1) {
permission += ",";
}
permission += scope;
}
}
params += "&permission=" + permission;
}
var metadata = authorizationRequest.metadata;
if (metadata) {
if (metadata.responseIncludeResourceName) {
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
}
if (metadata.responsePermissionsLimit) {
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
}
}
if (_instance.rpt) {
params += "&rpt=" + _instance.rpt;
}
request.send(params);
};
return this;
};
this.init(this);
return this;
};
export default KeycloakAuthorization;

1740
libs/keycloak-js/src/keycloak.js Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
}

1932
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,14 @@
{
"name": "keycloak-ui",
"workspaces": [
"libs/keycloak-js",
"apps/admin-ui"
],
"scripts": {
"prepare": "husky install"
},
"devDependencies": {
"@types/node": "^18.7.16",
"@typescript-eslint/eslint-plugin": "^5.36.0",
"@typescript-eslint/parser": "^5.36.0",
"eslint": "^8.23.0",