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 (
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+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