diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js index 1e094c9cf..01a87544f 100644 --- a/app/addons/auth/actions.js +++ b/app/addons/auth/actions.js @@ -41,6 +41,10 @@ export const validatePasswords = (password, passwordConfirm) => { ); }; +export const validateIdP = (idpurl, idpcallback, idpappid) => { + return validate(!_.isEmpty(idpurl), !_.isEmpty(idpcallback), !_.isEmpty(idpappid)); +}; + export const login = (username, password, urlBack) => { if (!validateUser(username, password)) { return errorHandler({message: app.i18n.en_US['auth-missing-credentials']}); @@ -66,6 +70,27 @@ export const login = (username, password, urlBack) => { .catch(errorHandler); }; +export const loginidp = (idpurl, idpcallback, idpappid) => { + if (!validateIdP(idpurl, idpcallback, idpappid)) { + return errorHandler({ message: app.i18n.en_US['auth-missing-idp'] }); + } + return Idp.login(idpurl, idpcallback, idpappid) + .then((resp) => { + if (resp.error) { + errorHandler({ message: resp.reason }); + return resp; + } + + let msg = app.i18n.en_US['auth-logged-in']; + if (msg) { + FauxtonAPI.addNotification({ msg }); + } + + FauxtonAPI.navigate('/'); + }) + .catch(errorHandler); +}; + export const changePassword = (username, password, passwordConfirm, nodes) => () => { if (!validatePasswords(password, passwordConfirm)) { return errorHandler({message: app.i18n.en_US['auth-passwords-not-matching']}); diff --git a/app/addons/auth/components/index.js b/app/addons/auth/components/index.js index 2c38e5aa2..95f08b5fa 100644 --- a/app/addons/auth/components/index.js +++ b/app/addons/auth/components/index.js @@ -11,12 +11,14 @@ // the License. import LoginForm from './loginform.js'; +import LoginFormIdp from './loginformidp.js'; import PasswordModal from './passwordmodal.js'; import CreateAdminForm from './createadminform.js'; import ChangePasswordForm from './changepasswordform.js'; export default { LoginForm, + LoginFormIdp, PasswordModal, CreateAdminForm, ChangePasswordForm diff --git a/app/addons/auth/components/loginform.js b/app/addons/auth/components/loginform.js index 34a56ebcd..837a15868 100644 --- a/app/addons/auth/components/loginform.js +++ b/app/addons/auth/components/loginform.js @@ -13,6 +13,7 @@ import PropTypes from 'prop-types'; import React from "react"; +import FauxtonAPI from '../../../core/base'; import { login } from "./../actions"; import { Button, Form } from 'react-bootstrap'; @@ -57,6 +58,12 @@ class LoginForm extends React.Component { login(username, password) { login(username, password, this.props.urlBack); } + + navigateToIdp(e) { + e.preventDefault(); + FauxtonAPI.navigate('/loginidp'); + } + componentDidMount() { this.usernameField.focus(); } @@ -97,6 +104,13 @@ class LoginForm extends React.Component { +
+
+ +
+
); } diff --git a/app/addons/auth/components/loginformidp.js b/app/addons/auth/components/loginformidp.js new file mode 100644 index 000000000..721a09c04 --- /dev/null +++ b/app/addons/auth/components/loginformidp.js @@ -0,0 +1,155 @@ +// 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. + +import FauxtonAPI from '../../../core/base'; +import React from 'react'; +import { loginidp } from './../actions'; +import { Button, Form } from 'react-bootstrap'; + +class LoginFormIdp extends React.Component { + constructor() { + super(); + this.state = { + idpurl: localStorage.getItem('FauxtonIdpurl') || '', + idpcallback: localStorage.getItem('FauxtonIdpcallback') || '', + idpappid: localStorage.getItem('FauxtonIdpappid') || '' + }; + if (this.state.idpcallback === '') { + let url = new URL(window.location); + let append = url.pathname.startsWith('/_utils') ? '/_utils/' : '/'; + this.state.idpcallback = window.location.origin + append; + } + } + + onIdpurlChange(e) { + this.setState({ idpurl: e.target.value }); + } + onIdpcallbackChange(e) { + this.setState({ idpcallback: e.target.value }); + } + + onIdpappidChange(e) { + this.setState({ idpappid: e.target.value }); + } + + submit(e) { + e.preventDefault(); + if (!this.checkUnrecognizedAutoFill()) { + this.login(this.state.idpurl, this.state.idpcallback, this.state.idpappid); + } + } + + // Safari has a bug where autofill doesn't trigger a change event. This checks for the condition where the state + // and form fields have a mismatch. See: https://issues.apache.org/jira/browse/COUCHDB-2829 + checkUnrecognizedAutoFill() { + if (this.state.idpurl !== '' || this.state.idpcallback !== '' || this.state.idpappid !== '') { + return false; + } + let idpurl = this.props.testBlankIdpurl ? this.props.testBlankIdpurl : this.idpurlField.value; + let idpcallback = this.props.testBlankIdpcallback ? this.props.testBlankIdpcallback : this.idpcallbackField.value; + let idpappid = this.props.testBlankIdpappid ? this.props.testBlankIdpappid : this.idpappidField.value; + + this.setState({ idpurl: idpurl, idpcallback: idpcallback, idpappid: idpappid }); // doesn't set immediately, hence separate login() call + this.login(idpurl, idpcallback, idpappid); + + return true; + } + + login(idpurl, idpcallback, idpappid) { + localStorage.setItem('FauxtonIdpurl', idpurl); + localStorage.setItem('FauxtonIdpcallback', idpcallback); + localStorage.setItem('FauxtonIdpappid', idpappid); + loginidp(idpurl, idpcallback, idpappid); + } + + navigateToLogin(e) { + e.preventDefault(); + FauxtonAPI.navigate('/login'); + } + + render() { + return ( +
+
+
+ +

+ must point to your IdP's /.well-known/openid-configuration +

+
+ (this.idpurlField = node)} + placeholder="IdP URL" + onChange={this.onIdpurlChange.bind(this)} + value={this.state.idpurl} + /> +
+
+
+ +

+ This should be the URL of your CouchDB instance, including the protocol and port number.{' '} + Should we show this? It can be computed +

+
+ (this.idpcallbackField = node)} + placeholder="Callback URL" + onChange={this.onIdpcallbackChange.bind(this)} + value={this.state.idpcallback} + /> +
+
+
+ +

+ The Application ID gets assigned by the IdP admin, suggested standard is fauxton +

+
+ (this.idpappidField = node)} + placeholder="Applicaiton ID" + onChange={this.onIdpappidChange.bind(this)} + value={this.state.idpappid} + /> +
+
+
+
+ +
+
+
+
+
+ +
+
+
+ ); + } +} + +export default LoginFormIdp; \ No newline at end of file diff --git a/app/addons/auth/idp.js b/app/addons/auth/idp.js new file mode 100644 index 000000000..c10344eb4 --- /dev/null +++ b/app/addons/auth/idp.js @@ -0,0 +1,273 @@ +// 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. + +import FauxtonAPI from '../../core/api'; + +/** + * Keeping track of IdP URLs derived from openid-configuration + * with access to auth and token endpoints + */ +const idpCache = {}; + +/** + * Reads the openid-configuration from the IdP URL + * + * @param {string} idpurl + * @returns object with authorization and token endpoints + */ +const getIdPEndpoints = (idpurl) => + new Promise((resolve, reject) => { + if (idpCache[idpurl]) { + return resolve(idpCache[idpurl]); + } + fetch(idpurl) + .then((response) => response.json()) + .then((data) => { + let idpData = { + authorization_endpoint: data.authorization_endpoint, + token_endpoint: data.token_endpoint + }; + idpCache[idpurl] = idpData; + resolve(idpData); + }) + .catch((err) => { + reject(err); + }); + }); + +/** + * Retrieves the auth end-point from the openid-configuration + * + * @param {string} idpurl openid-configuration end-point + * @returns auth end-point + */ +const getAuthEndpoint = (idpurl) => + new Promise((resolve, reject) => { + getIdPEndpoints(idpurl) + .then((idpData) => { + resolve(idpData.authorization_endpoint); + }) + .catch((err) => { + reject(err); + }); + }); + +/** + * Retrieves the token end-point from the openid-configuration + * + * @param {string} idpurl openid-configuration end-point + * @returns token end-point + */ +const getTokenEndpoint = (idpurl) => + new Promise((resolve, reject) => { + getIdPEndpoints(idpurl) + .then((idpData) => { + resolve(idpData.token_endpoint); + }) + .catch((err) => { + reject(err); + }); + }); + + + +/** + * jwtStillValid - Check if a JWT token is still valid + * + * @param {string} token The JWT token + * @return {boolean} True if the token is still valid, false otherwise + */ +export const jwtStillValid = (token) => { + if (!token) { + return false; + } + + const decodedToken = decodeToken(token); + if (!decodedToken) { + return false; + } + + const currentTime = Math.floor(Date.now() / 1000); + let isStillgood = decodedToken.exp > currentTime; + return isStillgood; +}; + +/** + * decodeToken - Decode a JWT token and return the payload + * + * @param {string} token The JWT token + * @return {object} The decoded token payload + */ +export const decodeToken = (token) => { + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = atob(base64); + return JSON.parse(jsonPayload); + } catch (error) { + return null; + } +}; + +export const getExpiry = (token) => { + const decodedToken = decodeToken(token); + return decodedToken ? decodedToken.exp : 0; +}; + +export const login = (idpurl, idpcallback, idpappid) => { + return getAuthEndpoint(idpurl) + .then((authEndpoint) => { + const authUrl = `${authEndpoint}?response_type=code&client_id=${idpappid}&redirect_uri=${idpcallback}&scope=openid#idpresult`; + window.location.href = authUrl; + return Promise.resolve('Authentication initiated'); + }) + .catch((error) => { + console.error('Error fetching auth endpoint:', error); + FauxtonAPI.addNotification({ + msg: error.message, + type: 'error' + }); + }); +}; + +export const logout = () => { + localStorage.removeItem('fauxtonToken'); + localStorage.removeItem('fauxtonRefreshToken'); + window.location.href = '/_session'; +}; + +export const codeToToken = (url) => { + const authCode = url.searchParams.get('code'); + if (authCode) { + const idpurl = localStorage.getItem('FauxtonIdpurl'); + const idpappid = localStorage.getItem('FauxtonIdpappid'); + const callback = localStorage.getItem('FauxtonIdpcallback'); + + getTokenEndpoint(idpurl) + .then((authUrl) => { + return fetch(authUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `grant_type=authorization_code&code=${authCode}&client_id=${idpappid}&redirect_uri=${callback}` + }); + }) + .then((response) => response.json()) + .then((data) => { + const accessToken = data.access_token; + const jwtRefreshToken = data.refresh_token; + localStorage.setItem('fauxtonToken', accessToken); + localStorage.setItem('fauxtonRefreshToken', jwtRefreshToken); + const expiry = getExpiry(accessToken); + setTimeout(() => { + // eslint-disable-next-line no-console + console.log('Refreshing token'); + refreshToken(); + }, (expiry - 60) * 1000); + return FauxtonAPI.navigate('/'); + }) + .catch((error) => { + console.error('Error refreshing token:', error); + FauxtonAPI.addNotification({ + msg: `Error refreshing token: ${error.message}`, + type: 'error' + }); + }); + } else { + FauxtonAPI.addNotification({ + msg: 'No auth code found', + type: 'error' + }); + } +}; + +export const refreshToken = () => { + const jwtRefreshToken = localStorage.getItem('fauxtonRefreshToken'); + const idpurl = localStorage.getItem('FauxtonIdpurl'); + const idpappid = localStorage.getItem('FauxtonIdpappid'); + if (!jwtRefreshToken) { + FauxtonAPI.addNotification({ + msg: `Refresh Token missing`, + type: 'error' + }); + return; + } + getTokenEndpoint(idpurl).then((authUrl) => + fetch(authUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `grant_type=refresh_token&refresh_token=${jwtRefreshToken}&client_id=${idpappid}` + }) + .then((response) => response.json()) + .then((data) => { + const accessToken = data.access_token; + localStorage.setItem('fauxtonToken', accessToken); + const expiry = getExpiry(accessToken); + setTimeout(() => { + refreshToken(); + }, (expiry - 60) * 1000); + }) + .catch((error) => { + console.error('Error refreshing token:', error); + FauxtonAPI.addNotification({ + msg: `Refreshing Token failed: ${error.message}`, + type: 'error' + }); + localStorage.removeItem('fauxtonToken'); + localStorage.removeItem('fauxtonRefreshToken'); + }) + ); +}; + +/** + * addAuthToken - Add the JWT token to the fetch options headers if it exists in local storage + * + * @param {object} fetchOptions - The fetch options object + * @returns {object} the updated fetch options object + */ +export const addAuthToken = (fetchOptions) => { + // eslint-disable-next-line no-console + console.debug('addAuthToken', fetchOptions); + const token = localStorage.getItem('fauxtonToken'); + if (token && jwtStillValid(token)) { + fetchOptions.headers = { + ...fetchOptions.headers, + Authorization: `Bearer ${token}` + }; + } else { + localStorage.removeItem('fauxtonToken'); + } + return fetchOptions; +}; + +export const addAuthHeader = (httpRequest) => { + const token = localStorage.getItem('fauxtonToken'); + if (token && jwtStillValid(token)) { + httpRequest.setRequestHeader('Authorization', `Bearer ${token}`); + } + return httpRequest; +}; + +export default { + login, + logout, + refreshToken, + codeToToken, + jwtStillValid, + decodeToken, + getExpiry, + addAuthToken, + addAuthHeader +}; diff --git a/app/addons/auth/routes/auth.js b/app/addons/auth/routes/auth.js index d274c089e..c99830664 100644 --- a/app/addons/auth/routes/auth.js +++ b/app/addons/auth/routes/auth.js @@ -17,9 +17,11 @@ import { AuthLayout } from "./../layout"; import app from "../../../app"; import Components from "./../components"; import {logout} from '../actions'; +import Idp from '../idp'; const { LoginForm, + LoginFormIdp, CreateAdminForm } = Components; @@ -29,7 +31,9 @@ export default FauxtonAPI.RouteObject.extend({ routes: { "login?*extra": "login", "login": "login", + "loginidp": "loginidp", "logout": "logout", + "session_state*": "idpCallback", "createAdmin": "checkNodes", "createAdmin/:node": "createAdminForNode" }, @@ -44,9 +48,28 @@ export default FauxtonAPI.RouteObject.extend({ /> ); }, + loginidp() { + return ( + } + /> + ); + }, logout() { logout(); }, + idpCallback() { + const urlParams = new URLSearchParams(window.location.hash); + const accessToken = urlParams.get('access_token'); + const refreshToken = urlParams.get('refresh_token'); + localStorage.setItem('fauxtonToken', accessToken); + localStorage.setItem('fauxtonRefreshToken', refreshToken); + // Extract expiry from the access token + const expiry = Idp.getExpiry(accessToken); + console.log('Expiry:', expiry); + //setTimeout(Idp.refreshToken, (expiry - 60) * 1000); + }, createAdminForNode() { ClusterActions.fetchNodes(); const crumbs = [{ name: "Create Admin" }]; diff --git a/app/addons/documents/doc-editor/actions.js b/app/addons/documents/doc-editor/actions.js index de1373a32..370afdee7 100644 --- a/app/addons/documents/doc-editor/actions.js +++ b/app/addons/documents/doc-editor/actions.js @@ -13,6 +13,7 @@ import FauxtonAPI from '../../../core/api'; import { deleteRequest } from '../../../core/ajax'; import ActionTypes from './actiontypes'; +import { addAuthHeader } from '../../auth/idp'; var currentUploadHttpRequest; @@ -204,6 +205,7 @@ const uploadAttachment = (params) => (dispatch) => { }); }; const httpRequest = new XMLHttpRequest(); + addAuthHeader(httpRequest); // for JWT currentUploadHttpRequest = httpRequest; httpRequest.withCredentials = true; if (httpRequest.upload) { diff --git a/app/core/ajax.js b/app/core/ajax.js index 95e622462..d174453f2 100644 --- a/app/core/ajax.js +++ b/app/core/ajax.js @@ -1,6 +1,7 @@ import 'whatwg-fetch'; import {defaultsDeep} from "lodash"; import {Subject} from 'rxjs'; +import { addAuthToken } from '../addons/auth/idp'; /* Add a multicast observer so that all fetch requests can be observed Some usage examples: @@ -68,7 +69,8 @@ export const json = (url, method = "GET", opts = {}) => { cache: "no-cache" } ); - return _preFetchFn(url, fetchOptions).then((result) => { + const updatedFetchOptions = addAuthToken(fetchOptions); + return _preFetchFn(url, updatedFetchOptions).then((result) => { return fetch( result.url, result.options, @@ -82,7 +84,6 @@ export const json = (url, method = "GET", opts = {}) => { }); }; - /** * get - Get request * diff --git a/app/core/api.js b/app/core/api.js index 7927397d9..378454c79 100644 --- a/app/core/api.js +++ b/app/core/api.js @@ -21,6 +21,16 @@ import $ from "jquery"; import Backbone from "backbone"; import _ from "lodash"; import Promise from "bluebird"; +import { addAuthHeader } from '../addons/auth/idp'; + +// Monkey patching Backbone.ajax to add the Auth header +// for JWT authentication +$.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader('X-Clacks-Overhead', 'GNU Terry Pratchett'); + addAuthHeader(xhr); + } +}); Backbone.$ = $; Backbone.ajax = function () { diff --git a/devserver.js b/devserver.js index 32fba7055..56de535eb 100644 --- a/devserver.js +++ b/devserver.js @@ -51,8 +51,11 @@ const devSetup = function (cb) { }); }; -const defaultHeaderValue = "default-src 'self'; child-src 'self' blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; " + - "script-src 'self'; style-src 'self'; object-src 'none';"; +// const defaultHeaderValue = "default-src 'self'; child-src 'self' blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; " + +// "script-src 'self'; style-src 'self'; object-src 'none';"; +const defaultHeaderValue = + "default-src 'self'; child-src 'self' blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; connect-src 'self' http://localhost:8090; " + + "script-src 'self'; style-src 'self'; object-src 'none';"; function getCspHeaders () { if (!settings.contentSecurityPolicy) { return; diff --git a/docker/admin_role.json b/docker/admin_role.json new file mode 100644 index 000000000..c754f4a60 --- /dev/null +++ b/docker/admin_role.json @@ -0,0 +1,5 @@ +{ + "name": "_admin", + "description": "CouchDB Administrator", + "attributes": {} +} \ No newline at end of file diff --git a/docker/couchdb-idp.sh b/docker/couchdb-idp.sh new file mode 100755 index 000000000..7e8c2b382 --- /dev/null +++ b/docker/couchdb-idp.sh @@ -0,0 +1,214 @@ +#!/bin/bash +#launches the CouchDB and keycloak containers +# then configure both to interact with each other + +# Part1: Setup +KC_URL="http://localhost:8090" +COUCHDB_URL="http://localhost:5984" +COUCHDB_USER=tester +COUCHDB_PASSWORD=testerpass + +# Part2: reusable functions +function kc_tokens() { + echo "Loading tokens" + local url=${KC_URL}/realms/master/protocol/openid-connect/token + + # Post the form data and store the response + local response=$(curl $url \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --no-progress-meter \ + --data-urlencode 'client_id=admin-cli' \ + --data-urlencode 'username=admin' \ + --data-urlencode 'password=password' \ + --data-urlencode 'grant_type=password') + + # Extract the access_token and refresh_token from the response + local access_token=$(echo "$response" | jq -r '.access_token') + local refresh_token=$(echo "$response" | jq -r '.refresh_token') + + # Set the environment variables + export KC_ACCESS_TOKEN="$access_token" + export KC_REFRESH_TOKEN="$refresh_token" + echo "Tokens loaded" +} + +function kc_refresh() { + local url=${KC_URL}/realms/master/protocol/openid-connect/token + echo "Refreshing tokens from $url" + + # Post the form data and store the response + local response=$(curl $url \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --no-progress-meter \ + --data-urlencode 'client_id=admin-cli' \ + --data-urlencode 'refresh_token={{$KC_REFRESH_TOKEN}}' \ + --data-urlencode 'grant_type=refresh_token') + + # Extract the access_token and refresh_token from the response + local access_token=$(echo "$response" | jq -r '.access_token') + local refresh_token=$(echo "$response" | jq -r '.refresh_token') + + # Set the environment variables + export KC_ACCESS_TOKEN="$access_token" + export KC_REFRESH_TOKEN="$refresh_token" + + echo "Tokens refreshed" +} + +function kc_get() { + local url=${KC_URL}$1 + + # Get an URL and store the response + echo GET from $url >&2 + local response=$(curl $url \ + --no-progress-meter \ + --header 'Content-Type: application/json' \ + --header "Authorization: Bearer $KC_ACCESS_TOKEN") + echo $response +} + +function kc_post() { + local url=${KC_URL}$1 + local data=$2 + echo POST to $url >&2 + # Post the form data and store the response + local response=$(curl -S -X POST $url \ + --header 'Content-Type: application/json' \ + --no-progress-meter \ + --header "Authorization: Bearer $KC_ACCESS_TOKEN" \ + --data @docker/$data) + echo $response +} + +function kc_config() { + # Get Tokens + kc_tokens + + # Create realm sofa + kc_post "/admin/realms" sofa_realm.json + + # Create _admin Role + kc_post "/admin/realms/sofa/roles" admin_role.json + adminroleRaw=$(kc_get "/admin/realms/sofa/roles?first=0&max=101&q=_admin") + adminrole=$(echo $adminroleRaw | jq -r .[0].id) + echo adminrole $adminrole + echo '[{"id": "'${adminrole}'",' >docker/johndoe_role.json + echo '"name": "_admin", "description": "CouchDB Administrator",' >>docker/johndoe_role.json + echo '"composite": false,"clientRole": false,"containerId": "sofa"}]' >>docker/johndoe_role.json + + # Create user johndoe and assign _admin role + kc_post "/admin/realms/sofa/users" johndoe_user.json + johndoe=$(kc_get "/admin/realms/sofa/ui-ext/brute-force-user?briefRepresentation=true&first=0&max=1" | jq -r .[0].id) + echo "johndoe $johndoe" + kc_post "/admin/realms/sofa/users/${johndoe}/role-mappings/realm" johndoe_role.json + + # Create client fauxton + kc_post "/admin/realms/sofa/clients" fauxton_client.json +} + +function couch_get() { + local url=${COUCHDB_URL}$1 + + # Get an URL and store the response + echo GET from $url + local response=$(curl $url \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD \ + --no-progress-meter \ + --header 'Content-Type: application/json') + echo $response +} + +function couch_post() { + local url=${COUCHDB_URL}$1 + local data=$2 + echo POST to $url + # Post the form data and store the response + local response=$(curl -S -X POST $url \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD \ + --header 'Content-Type: application/json' \ + --no-progress-meter \ + --data @docker/$data) + echo $response +} + +function couch_put() { + local url=${COUCHDB_URL}$1 + echo PUT to $url + # Post the form data and store the response + local response=$(curl -S -X PUT $url \ + --no-progress-meter \ + --header 'Content-Type: text/plain' \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD) + echo $response +} + +# Part3: Launch containers +# docker compose -f docker/couchdb-idp.yml pull +docker compose -f docker/couchdb-idp.yml up -d + +# Pre part 4: Wait for Keycloak to start +curl -k \ + --retry 10 \ + --retry-delay 10 \ + --retry-all-errors \ + --no-progress-meter \ + --fail \ + ${KC_URL}/admin/master/console/ >/dev/null + +if [ "$?" -ne 0 ]; then + echo "Failed to start Keycloak" + exit 1 +fi + +# Part4: Configure Keycloak + +kc_config + +# Pre part 5: Wait for Couchdb to start +curl -k \ + --retry 10 \ + --retry-delay 10 \ + --retry-all-errors \ + --no-progress-meter \ + --fail \ + ${COUCHDB_URL} + +# Part5: Configure CouchDB +couch_put /_users +couch_put /_replicator +couch_put /_global_changes + +# activate jwt authentication +curl --request PUT ${COUCHDB_URL}/_node/_local/_config/chttpd/authentication_handlers \ + --header 'Content-Type: text/plain' \ + --no-progress-meter \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD \ + --data '"{chttpd_auth, cookie_authentication_handler}, {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, default_authentication_handler}"' + +# Retrieve the public key from Keycloak +jwks_uri=$(curl --no-progress-meter "${KC_URL}/realms/sofa/.well-known/openid-configuration" | jq -r .jwks_uri) +raw_key=$(curl $jwks_uri --no-progress-meter | jq -r '.keys[0]') +kid=$(echo $raw_key | jq -r .kid) +flat_key=$(echo "$raw_key" | tr -d '\n') +node docker/extractpem.js "$flat_key" >tmp.key + +#Post it to CouchDB +curl --request PUT ${COUCHDB_URL}/_node/nonode@nohost/_config/jwt_keys/rsa:${kid} \ + --header 'Content-Type: text/plain' \ + --no-progress-meter \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD \ + --data @tmp.key + +rm tmp.key + +# Path to roles +curl --request PUT ${COUCHDB_URL}/_node/nonode@nohost/_config/jwt_auth/roles_claim_path \ + --header 'Content-Type: text/plain' \ + --no-progress-meter \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD \ + --data '"realm_access.roles"' + +# Restart CouchDB +curl --request POST ${COUCHDB_URL}/_node/_local/_restart \ + --no-progress-meter \ + --user $COUCHDB_USER:$COUCHDB_PASSWORD diff --git a/docker/couchdb-idp.yml b/docker/couchdb-idp.yml new file mode 100644 index 000000000..fd3186e95 --- /dev/null +++ b/docker/couchdb-idp.yml @@ -0,0 +1,20 @@ +services: + couchdb: + container_name: couchdb + image: couchdb:latest + environment: + COUCHDB_USER: tester + COUCHDB_PASSWORD: testerpass + ports: + - "5984:5984" + depends_on: + - keycloak + keycloak: + container_name: keycloak + image: quay.io/keycloak/keycloak:latest + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: password + ports: + - "8090:8080" + command: start-dev diff --git a/docker/extractpem.js b/docker/extractpem.js new file mode 100644 index 000000000..7636724ee --- /dev/null +++ b/docker/extractpem.js @@ -0,0 +1,16 @@ +// 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 + +const jwkToPem = require('jwk-to-pem'); +let key = process.argv[2]; +let pem = jwkToPem(JSON.parse(key)); +let flatpem = pem.replace(/\n/g, '\\\\n'); +console.log('"' + flatpem + '"'); diff --git a/docker/fauxton_client.json b/docker/fauxton_client.json new file mode 100644 index 000000000..dbf39ab84 --- /dev/null +++ b/docker/fauxton_client.json @@ -0,0 +1,32 @@ +{ + "protocol": "openid-connect", + "clientId": "fauxton", + "name": "Fauxton", + "description": "Fauxton for CouchDB administration", + "publicClient": true, + "authorizationServicesEnabled": false, + "serviceAccountsEnabled": false, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "standardFlowEnabled": true, + "frontchannelLogout": true, + "attributes": { + "saml_idp_initiated_sso_url_name": "", + "oauth2.device.authorization.grant.enabled": false, + "oidc.ciba.grant.enabled": false + }, + "alwaysDisplayInConsole": false, + "rootUrl": "", + "baseUrl": "http://localhost:8000", + "redirectUris": [ + "http://localhost:8000", + "http://localhost:8000/_callback", + "http://localhost:5984", + "http://localhost:5984/_callback" + ], + "webOrigins": [ + "*", + "http://localhost:8000", + "http://localhost:5984" + ] +} \ No newline at end of file diff --git a/docker/johndoe_role.json b/docker/johndoe_role.json new file mode 100644 index 000000000..5dbbb1740 --- /dev/null +++ b/docker/johndoe_role.json @@ -0,0 +1,3 @@ +[{"id": "29dc4a00-c700-432a-909c-a6fb02556b0c", +"name": "_admin", "description": "CouchDB Administrator", +"composite": false,"clientRole": false,"containerId": "sofa"}] diff --git a/docker/johndoe_user.json b/docker/johndoe_user.json new file mode 100644 index 000000000..a8c195a4e --- /dev/null +++ b/docker/johndoe_user.json @@ -0,0 +1,20 @@ +{ + "attributes": { + "locale": "" + }, + "requiredActions": [], + "emailVerified": true, + "username": "johndoe", + "email": "john.doe@exsample.com", + "firstName": "John", + "lastName": "Doe", + "groups": [], + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "password", + "temporary": false + } + ] +} \ No newline at end of file diff --git a/docker/sofa_realm.json b/docker/sofa_realm.json new file mode 100644 index 000000000..3abc3d865 --- /dev/null +++ b/docker/sofa_realm.json @@ -0,0 +1,13 @@ +{ + "id": "sofa", + "realm": "sofa", + "displayName": "Where documents live", + "enabled": true, + "sslRequired": "NONE", + "registrationAllowed": true, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": true, + "bruteForceProtected": true +} \ No newline at end of file diff --git a/i18n.json.default.json b/i18n.json.default.json index dee3cc0d1..1ccb6c796 100644 --- a/i18n.json.default.json +++ b/i18n.json.default.json @@ -17,6 +17,7 @@ "replication-username-input-placeholder": "Username", "replication-password-input-placeholder": "Password", "auth-missing-credentials": "Username or password cannot be blank.", + "auth-missing-idp": "You need IdPUrl, CallbackUrl and ClientId", "auth-logged-in": "You have been logged in.", "auth-admin-created": "CouchDB admin created", "auth-change-password": "Your password has been updated.", diff --git a/idp.md b/idp.md new file mode 100644 index 000000000..3713f3aa1 --- /dev/null +++ b/idp.md @@ -0,0 +1,73 @@ +# Configuring an Identity provider for Fauxton + +!!! note Configure CouchDB first + + To successfully use an Identiy Provider (IdP), one must first configure + CouchDB to recognize the public key of the IdP. Follow [the documentation](https://docs.couchdb.org/en/stable/api/server/authn.html#jwt-authentication) + to complete this task. + + Once you are ready for production you might consider [automating key management](https://github.com/beyonddemise/couchdb-idp-updater). + +## Preparation + +You need: + +| Item | Description | Provided by | +| ----------- | ------------------------------------------------ | ----------- | +| IdP Url | derived from `/.well-known/openid-configuration` | IdP admin | +| client id | a name, suggestion is `fauxton` | IdP admin | +| CallbackURL | Your couchdb server | You | + +- The callback URL is either `http(s)://yourserver/_utils` when you run Fauxton from your CouchDB server or `http(s)://yourserver/` when you run Fauxton standalone. +- On [Keycloak](https://www.keycloak.org/) (The IdP we develop with) access is organized in realms, so the openid configuration includes the realm name. E.g. when your realm is `sofa`, your openid url is `http(s)://yourkeycloak/realms/sofa/.well-known/openid-configuration`, There you look for `authorization_endpoint` nad use that minus the `/auth`, like this: `http(s)://yourkeycloak/realms/sofa/protocol/openid-connect` + +## CouchDB setup + +Follow [the documentation](https://docs.couchdb.org/en/stable/api/server/authn.html#jwt-authentication). For role mapping check what the IdP is emitting. + +For Keycloak, this works: + +```ini +[jwt_auth] +roles_claim_path = realm_access.roles +``` + +## Development + +In the docker directory there is a shell script `couchdb-idp.sh` that uses the `couchdb-idp.yml` configurtion to spin up a couchDB instance and a Keycloak container. Using `curl` it then configures both to interact: + +- creates a realm `sofa` +- creates a user `johndoe` with password `password` +- creates a client `fauxton` +- configures couchDB to recognize the Keycloak public key + +To make that shell script work you need some utility helpers: + +- [jq](https://jqlang.github.io/jq/) command line json processor +- [curl](https://curl.se/) http command line processor +- [OpenSSL](https://www.openssl.org/) to deal with certificates + +Keycloak and couchDB in this setting don't persist values. + +## CORS Setup + +Too many moving parts.... later + +## Authenticate + +On the login page there is a new button `Log In with your Identity provider`, click that and it will open the Idp Login page. + +![Login screen](https://github.com/user-attachments/assets/5d15c0ec-93c9-434f-b13f-429eaf813495) + +Provide the 3 required values and click login (The values will be retained in localstore). You should get redirected to your IdP's login page. Your IdP could be configured with any authentication method: username/password, 2FA, Social etc. + +![IdP Login screen](https://github.com/user-attachments/assets/93d3d11f-decd-4658-9ae8-df588ee2beff) + +After succesful login you get redirected to Fauxton and should see the list of databases + +## Troubleshooting + +- Check the CouchDB [JWT configuration](https://docs.couchdb.org/en/stable/api/server/authn.html#jwt-authentication) +- Do you have the `_admin` role?, Configure that in CouchDB and you IdP +- Is the CORS configuration correct? Might require a restart +- The Chrome developer tools are your friend diff --git a/index.js b/index.js index 9846388da..6a4c1cd3c 100644 --- a/index.js +++ b/index.js @@ -49,8 +49,11 @@ module.exports = function (options) { accept = req.headers.accept.split(','); } if (setContentSecurityPolicy) { - var headerValue = "default-src 'self'; child-src 'self' data: blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; " + - "script-src 'self'; style-src 'self'; object-src 'none';"; + //var headerValue = "default-src 'self'; child-src 'self' data: blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; " + + // "script-src 'self'; style-src 'self'; object-src 'none';"; + const headerValue = + "default-src 'self'; child-src 'self' data: blob: https://blog.couchdb.org; img-src 'self' data:; font-src 'self'; connect-src http://localhost:8090 'self'; " + + "script-src 'self'; style-src 'self'; object-src 'none';"; res.setHeader('Content-Security-Policy', headerValue); } diff --git a/package-lock.json b/package-lock.json index 51d86184f..48ec70413 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "html-webpack-plugin": "^5.5.0", "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", + "jwk-to-pem": "^2.0.5", "mini-css-extract-plugin": "^2.6.1", "mock-local-storage": "^1.1.23", "nightwatch": "^3.2.0", @@ -5052,6 +5053,18 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -5564,6 +5577,12 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -5759,6 +5778,12 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -7187,6 +7212,27 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" }, + "node_modules/elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -9839,6 +9885,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -9859,6 +9915,17 @@ "he": "bin/he" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -13224,6 +13291,17 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/jwk-to-pem": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.6.tgz", + "integrity": "sha512-zPC/5vjyR08TpknpTGW6Z3V3lDf9dU92oHbf0jJlG8tGOzslF9xk2UiO/seSx2llCUrNAe+AvmuGTICSXiYU7A==", + "dev": true, + "dependencies": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.7", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -13865,6 +13943,12 @@ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "node_modules/minimatch": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", @@ -22943,6 +23027,18 @@ "get-intrinsic": "^1.1.3" } }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -23334,6 +23430,12 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -23478,6 +23580,12 @@ "fill-range": "^7.1.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -24519,6 +24627,29 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" }, + "elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -26479,6 +26610,16 @@ "has-symbols": "^1.0.3" } }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -26493,6 +26634,17 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -28961,6 +29113,17 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "jwk-to-pem": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.6.tgz", + "integrity": "sha512-zPC/5vjyR08TpknpTGW6Z3V3lDf9dU92oHbf0jJlG8tGOzslF9xk2UiO/seSx2llCUrNAe+AvmuGTICSXiYU7A==", + "dev": true, + "requires": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.7", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -29480,6 +29643,12 @@ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "minimatch": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", diff --git a/package.json b/package.json index 7679e1465..beb542ee4 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "html-webpack-plugin": "^5.5.0", "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", + "jwk-to-pem": "^2.0.5", "mini-css-extract-plugin": "^2.6.1", "mock-local-storage": "^1.1.23", "nightwatch": "^3.2.0", diff --git a/readme.md b/readme.md index bad0cb160..0a0721613 100644 --- a/readme.md +++ b/readme.md @@ -21,15 +21,15 @@ Please note that [node.js](http://nodejs.org/) and npm is required. Specifically 1. Fork this repo (see [GitHub help](https://help.github.com/articles/fork-a-repo/) for details) 1. Clone your fork: `git clone https://github.com/YOUR-USERNAME/couchdb-fauxton.git` 1. Go to your cloned copy: `cd couchdb-fauxton` -1. Set up the upstream repo: +1. Set up the upstream repo: * `git remote add upstream https://github.com/apache/couchdb-fauxton.git` * `git fetch upstream` * `git branch --set-upstream-to=upstream/main main` 1. Download all dependencies: `npm install` 1. Make sure you have CouchDB installed. - Option 1 (**recommended**): Use `npm run docker:up` to start a Docker container running CouchDB with user `tester` and password `testerpass`. - - You need to have [Docker](https://docs.docker.com/engine/installation/) installed to use this option. - - Option 2: Follow instructions + - You need to have [Docker](https://docs.docker.com/engine/installation/) installed to use this option. + - Option 2: Follow instructions [found here](http://couchdb.readthedocs.org/en/latest/install/index.html) @@ -52,7 +52,7 @@ You should be able to access Fauxton at `http://localhost:8000` ### Preparing a Fauxton Release -Follow the "Setting up Fauxton" section above, then edit the `settings.json` variable root where the document will live, +Follow the "Setting up Fauxton" section above, then edit the `settings.json` variable root where the document will live, e.g. `/_utils/`. Then type: ``` @@ -84,7 +84,7 @@ part of the deployable release artifact. -## More information +## More information Check out the following pages for a lot more information about Fauxton: @@ -93,7 +93,7 @@ Check out the following pages for a lot more information about Fauxton: - [Testing Fauxton](https://github.com/apache/couchdb-fauxton/blob/main/tests.md) - [Extensions](https://github.com/apache/couchdb-fauxton/blob/main/extensions.md) - [How to contribute](https://github.com/apache/couchdb-fauxton/blob/main/CONTRIBUTING.md) - +- [Setting up Fauxton for IdP auth](idp.md) ------ diff --git a/test/idp_rsources/realm-export.json b/test/idp_rsources/realm-export.json new file mode 100644 index 000000000..aa5cc8e27 --- /dev/null +++ b/test/idp_rsources/realm-export.json @@ -0,0 +1,2255 @@ +{ + "id": "936d58a2-1369-4f57-ae5c-b4d6a0e834b5", + "realm": "sofa", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "4318da69-06df-4763-b498-4e8d0977ba53", + "name": "default-roles-sofa", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "manage-account", + "view-profile" + ] + } + }, + "clientRole": false, + "containerId": "936d58a2-1369-4f57-ae5c-b4d6a0e834b5", + "attributes": {} + }, + { + "id": "14331674-e7c0-4c59-addd-60885bb3516c", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "936d58a2-1369-4f57-ae5c-b4d6a0e834b5", + "attributes": {} + }, + { + "id": "fd34fe3c-d593-4001-9244-cccc9e5dfd3a", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "936d58a2-1369-4f57-ae5c-b4d6a0e834b5", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "e81bbc67-9537-4cfc-b543-3e87df6daeb0", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "a0449a8e-8cb2-42d8-84b4-2e041868824b", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "0cc6807f-bb36-49e0-99ec-cda8d48212ce", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "d240072f-d040-40a4-9f36-60a563bf5cda", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "fcceda4e-eb74-46cf-9bbb-5fd07d5d5f7d", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-clients", + "create-client", + "impersonation", + "view-events", + "manage-clients", + "view-realm", + "manage-realm", + "manage-identity-providers", + "view-authorization", + "view-clients", + "query-realms", + "manage-users", + "manage-events", + "query-users", + "view-identity-providers", + "view-users", + "manage-authorization" + ] + } + }, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "7b913548-5b34-4677-b2b9-1cd56bdfb97f", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "4fdf0312-06a8-4236-a2b9-a1dc46dd7056", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "1c587d37-2da1-4b65-ab0d-05a65e8faa88", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "db6901bb-b50a-4d76-b2f9-2cd4c9e452b3", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "3ed25b30-c725-4ace-9ee0-3f72d69debb9", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "833cd5f3-febe-4c2f-a6af-b074bb42db17", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "b1827b17-e73f-4f57-9987-7261e7e2f30e", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "466001cb-5d83-42f0-bd30-15967f2b85d3", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "a6241fa4-2ec7-4c16-b563-f52ca2a3f6d8", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "89c1b9da-93f5-42cd-8f07-1e7ffe1a4a59", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "c47319d0-b28f-4144-a1d9-bfcf7b31df98", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "8774285a-4fd4-4b56-a6ce-aec8416ee5da", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "f692bd24-83f1-4fcc-88a2-d52886106e7e", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + }, + { + "id": "861d7f74-ada8-4dad-931f-2fa7192d95c3", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "d6a3304e-54fe-4da5-8f70-be24ef01908e", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "82634da3-a788-433a-ab39-3a21c603a5ef", + "attributes": {} + } + ], + "account": [ + { + "id": "283b4ccd-a072-4be0-b770-31a263f103f3", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "1bf2eec8-b4c5-40c6-b62e-bfa6145e545a", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "503c637f-625c-446e-87d3-f943f527772e", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "afdc96d6-8292-4178-bb11-2687cc2b180f", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "20321c70-8863-40c3-a0bf-acdc4f40c51f", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "aed7b2ea-1b45-408a-a234-93761b2420b9", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "f140c442-ea8a-4a4b-826f-fd304714ca09", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + }, + { + "id": "857ace6b-f4a5-45ff-9edb-ef1981da3239", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "4821bf2e-32d4-4441-9fc3-777355441a68", + "attributes": {} + } + ], + "fauxton": [] + } + }, + "groups": [], + "defaultRole": { + "id": "4318da69-06df-4763-b498-4e8d0977ba53", + "name": "default-roles-sofa", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "936d58a2-1369-4f57-ae5c-b4d6a0e834b5" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "4821bf2e-32d4-4441-9fc3-777355441a68", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/sofa/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/sofa/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "8ccb7dc9-b493-40c1-83fd-054adafc789c", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/sofa/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/sofa/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "bd5ba6bf-282a-4aeb-97a8-6e6f970be399", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4b873bc3-1258-4e43-9b7b-c4ea0faf4aa5", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "82634da3-a788-433a-ab39-3a21c603a5ef", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ac8b775c-fe15-4782-9bc8-b09f53152f8f", + "clientId": "fauxton", + "name": "Fauxton for CouchDB", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "http://localhost:8000", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://localhost:8000/", + "http://localhost:8000/callback" + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "e866cebd-64b8-4b84-acda-e4c84928b77e", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "879a372b-0195-4d2b-a194-588507fbb3ed", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/sofa/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/sofa/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "75e84032-586e-4d19-bf4e-ffbf6a7c302e", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "86ffc5b0-288e-48f5-b615-76c04f644536", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "1103427b-5b36-4b8c-a3e9-1b8cf62dafd6", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "52ebab91-71d3-4aec-9351-d9a9f400f056", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "6a0715ab-51a4-4712-b1eb-64102dbb4280", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "88738b3a-0bf6-43ae-9a78-d36a60126391", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "c461cd14-8ff1-4979-a349-547de48198a0", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "05a8ac6e-c0da-4e2e-8aeb-85aa990e8ded", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "d97168fb-2f0e-49be-8b2e-8070c409a3a6", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "9e5662b4-0f2f-408a-8ec9-cfbeb46e7f7d", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "e6898180-d6a3-4c8e-a034-c48c99ba8614", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "7ceb1035-b2f0-462b-a94a-f4e9bee23393", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "c97cdc8f-48cd-4911-be63-b1b12afae622", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "edd167f7-89cd-4978-a6ec-a631658b71f7", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "ca0d0518-c63d-4cef-9a85-2126a8ee1ffb", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "5d9c3420-61fd-487b-82e6-806c036bd1de", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "a8c2e33e-52bb-4ba1-979e-713bde6f4951", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "fe332107-f694-41ae-a69d-f5085ab908da", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "d50bf0ce-9fc3-48c5-a5d8-253295cd534b", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "0b896c11-f53c-4509-b226-f3c94ee8b514", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "2c1cfe80-b4d2-4504-8592-ad41286909ea", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "6cf8e2a4-7a86-4bc9-9e9b-8fca7016ba00", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "d8a69957-eb47-4291-90b8-6c59b1bc65e4", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "d3c2fcef-ffb4-4177-a99a-b90ba18cb6e5", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "1aed987a-268a-43ea-b334-9cde54557faa", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "387d9268-5390-4fa3-abb4-a3a6c8f7a6ae", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "15b27a11-2d53-4ddf-b55a-3c7ae7316752", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "c369636b-4b3a-4b5e-bde2-62ea3180b74f", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "b6228ee3-204e-4741-9057-e6522dbbf82c", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "e4ba77b0-2762-4eba-8d0e-007429c43371", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "98de4f81-f4d8-4508-b8c8-02bc8cc38fc5", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "dd6f1142-0e53-496a-8c2e-863a59287e63", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "3e9c7ca9-0ab0-4f90-a0c6-665463980825", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "1870cdf3-50de-4fed-ac13-00b22560d6f9", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "fb3b99e4-bbdc-4b07-9747-102938111927", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "81856a94-dfb5-4cd0-8616-e40a211893d2", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "638ec729-19ab-4d92-893f-6a0f0c6c49ba", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "2ae030fa-cadd-4d7a-9b07-1d50e88fe608", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "912d9d6c-ccf3-4b42-8c3e-3c4e2a15ca45", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "5bd1da03-bb42-4a98-be6e-a43e606a30b0", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "4848cc4c-c9f8-4e25-ba59-cf12ea330332", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "b8edbac2-039a-46ec-8a4a-e57b37330fa4", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "b0838edf-491b-4efd-b62a-bb3feaddcc86", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "f48a74fd-fd0b-4c02-af7e-4e76a47d5a6e", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "02b53ba0-cf24-4bb0-a3ff-3815625ec93b", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-property-mapper", + "saml-role-list-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper" + ] + } + }, + { + "id": "0b3b17cc-c710-4f57-9170-4fc0fb643c31", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "bc7748a3-51f6-4ef5-ad96-8650adbd17b4", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "e91f3d3e-91c7-4c18-9081-c42b5904a883", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "3885af6c-426e-4c4b-bad9-e242ed0d96ac", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "3db5be8c-fbe5-49d0-a8f6-74c7365adaef", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "1d8c6348-21f0-406d-9aed-fcda2e4ff52f", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + }, + { + "id": "80b15dbf-afc7-448a-b5c2-5a94096beb2e", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "63f1abb8-7cf5-45a3-877a-80b7e64dc890", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "93298e16-bc5e-4016-875c-2b834cfa7fb5", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "873fa755-d547-414f-8967-fcbbd8f79e5a", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "15296b22-c60a-407a-8312-6cbb241578a0", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f7da4765-2cf0-445a-8439-23256410d542", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "386a7dc2-fd5a-4784-841f-7bd33be79109", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "364e28a9-b3b9-4fd2-906b-da80b5468073", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "bc233079-0e53-4e96-9aca-0a1e7d3f33d6", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "9ee52713-a1ca-4f2a-badb-5bae89d9d5cf", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "3b8bc7c5-5b22-4531-95da-e65922914a78", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "7935abb2-5ab6-478a-afe5-5fde8b2ce1eb", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "1be6d346-c304-4d39-af8a-657e5a948d17", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "e3a05a32-e375-49f6-b42b-dfaac7e3dd4a", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "c7b5a73e-34d2-4634-8f3e-075d22a1895e", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "3b087de4-9f1a-4ce5-be4e-44de03125ea1", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "db32ea56-f6d6-48f8-8003-f7b043a89f75", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "a145481e-d0a5-4782-afb2-4640d29e667d", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "13f5be91-d7cf-461c-a9c8-dfdf1a5d23e0", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "2210b5f3-17b1-4955-a342-e86c1e1f6f09", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "5541ab27-9a24-43d1-ad89-59bc37463dff", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "9d4d82a3-9a12-42ab-b20a-6aafd366530e", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "25.0.1", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file