mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-27 21:54:27 +01:00
(ui) openid connect configuration interface
This commit is contained in:
parent
9a0de78da7
commit
0e35616710
@ -7,23 +7,30 @@ import { FabOutputCopy } from '../base/fab-output-copy';
|
|||||||
|
|
||||||
interface Oauth2FormProps<TFieldValues> {
|
interface Oauth2FormProps<TFieldValues> {
|
||||||
register: UseFormRegister<TFieldValues>,
|
register: UseFormRegister<TFieldValues>,
|
||||||
callbackUrl?: string,
|
strategyName?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partial form to fill the OAuth2 settings for a new/existing authentication provider.
|
* Partial form to fill the OAuth2 settings for a new/existing authentication provider.
|
||||||
*/
|
*/
|
||||||
export const Oauth2Form = <TFieldValues extends FieldValues>({ register, callbackUrl }: Oauth2FormProps<TFieldValues>) => {
|
export const Oauth2Form = <TFieldValues extends FieldValues>({ register, strategyName }: Oauth2FormProps<TFieldValues>) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
// regular expression to validate the the input fields
|
// regular expression to validate the the input fields
|
||||||
const endpointRegex = /^\/?([-._~:?#[\]@!$&'()*+,;=%\w]+\/?)*$/;
|
const endpointRegex = /^\/?([-._~:?#[\]@!$&'()*+,;=%\w]+\/?)*$/;
|
||||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z0-9.]{2,30})([/\w .-]*)*\/?$/;
|
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z0-9.]{2,30})([/\w .-]*)*\/?$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the callback URL, based on the strategy name.
|
||||||
|
*/
|
||||||
|
const buildCallbackUrl = (): string => {
|
||||||
|
return `${window.location.origin}/users/auth/${strategyName}/callback`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="oauth2-form">
|
<div className="oauth2-form">
|
||||||
<hr/>
|
<hr/>
|
||||||
<FabOutputCopy text={callbackUrl} label={t('app.admin.authentication.oauth2_form.authorization_callback_url')} />
|
<FabOutputCopy text={buildCallbackUrl()} label={t('app.admin.authentication.oauth2_form.authorization_callback_url')} />
|
||||||
<FormInput id="providable_attributes.base_url"
|
<FormInput id="providable_attributes.base_url"
|
||||||
register={register}
|
register={register}
|
||||||
placeholder="https://sso.example.net..."
|
placeholder="https://sso.example.net..."
|
||||||
@ -42,7 +49,8 @@ export const Oauth2Form = <TFieldValues extends FieldValues>({ register, callbac
|
|||||||
<FormInput id="providable_attributes.profile_url"
|
<FormInput id="providable_attributes.profile_url"
|
||||||
register={register}
|
register={register}
|
||||||
placeholder="https://exemple.net/user..."
|
placeholder="https://exemple.net/user..."
|
||||||
label={t('app.admin.authentication.oauth2_form.profil_edition_url')}
|
label={t('app.admin.authentication.oauth2_form.profile_edition_url')}
|
||||||
|
tooltip={t('app.admin.authentication.oauth2_form.profile_edition_url_help')}
|
||||||
rules={{ required: true, pattern: urlRegex }} />
|
rules={{ required: true, pattern: urlRegex }} />
|
||||||
<FormInput id="providable_attributes.client_id"
|
<FormInput id="providable_attributes.client_id"
|
||||||
register={register}
|
register={register}
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { UseFormRegister } from 'react-hook-form';
|
||||||
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FormInput } from '../form/form-input';
|
||||||
|
import { FormSelect } from '../form/form-select';
|
||||||
|
import { Control } from 'react-hook-form/dist/types/form';
|
||||||
|
import { HtmlTranslate } from '../base/html-translate';
|
||||||
|
import { OpenIdConnectProvider } from '../../models/authentication-provider';
|
||||||
|
|
||||||
|
interface OpenidConnectFormProps<TFieldValues, TContext extends object> {
|
||||||
|
register: UseFormRegister<TFieldValues>,
|
||||||
|
control: Control<TFieldValues, TContext>,
|
||||||
|
currentFormValues: OpenIdConnectProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OpenidConnectForm = <TFieldValues extends FieldValues, TContext extends object>({ register, control, currentFormValues }: OpenidConnectFormProps<TFieldValues, TContext>) => {
|
||||||
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
|
// regular expression to validate the the input fields
|
||||||
|
const endpointRegex = /^\/?([-._~:?#[\]@!$&'()*+,;=%\w]+\/?)*$/;
|
||||||
|
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z0-9.]{2,30})([/\w .-]*)*\/?$/;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="openid-connect-form">
|
||||||
|
<hr/>
|
||||||
|
<FormInput id="providable_attributes.issuer"
|
||||||
|
register={register}
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.issuer')}
|
||||||
|
placeholder="https://myprovider.com"
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.issuer_help')}
|
||||||
|
rules={{ required: true, pattern: urlRegex }} />
|
||||||
|
<FormSelect id="providable_attributes.discovery"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.discovery')}
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.discovery_help')}
|
||||||
|
options={[
|
||||||
|
{ value: true, label: t('app.admin.authentication.openid_connect_form.discovery_enabled') },
|
||||||
|
{ value: false, label: t('app.admin.authentication.openid_connect_form.discovery_disabled') }
|
||||||
|
]}
|
||||||
|
valueDefault={false}
|
||||||
|
control={control} />
|
||||||
|
<FormSelect id="providable_attributes.client_auth_method"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client_auth_method')}
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.client_auth_method_help')}
|
||||||
|
options={[
|
||||||
|
{ value: 'basic', label: t('app.admin.authentication.openid_connect_form.client_auth_method_basic') },
|
||||||
|
{ value: 'jwks', label: t('app.admin.authentication.openid_connect_form.client_auth_method_jwks') }
|
||||||
|
]}
|
||||||
|
valueDefault={'basic'}
|
||||||
|
control={control} />
|
||||||
|
<FormInput id="providable_attributes.scope"
|
||||||
|
register={register}
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.scope')}
|
||||||
|
placeholder="openid,profile,email"
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.scope_help')} />
|
||||||
|
<FormSelect id="providable_attributes.response_type"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.response_type')}
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.response_type_help')}
|
||||||
|
options={[
|
||||||
|
{ value: 'code', label: t('app.admin.authentication.openid_connect_form.response_type_code') },
|
||||||
|
{ value: 'id_token', label: t('app.admin.authentication.openid_connect_form.response_type_id_token') }
|
||||||
|
]}
|
||||||
|
valueDefault={'code'}
|
||||||
|
control={control} />
|
||||||
|
<FormSelect id="providable_attributes.response_mode"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.response_mode')}
|
||||||
|
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.response_mode_help_html" />}
|
||||||
|
options={[
|
||||||
|
{ value: 'query', label: t('app.admin.authentication.openid_connect_form.response_mode_query') },
|
||||||
|
{ value: 'fragment', label: t('app.admin.authentication.openid_connect_form.response_mode_fragment') },
|
||||||
|
{ value: 'form_post', label: t('app.admin.authentication.openid_connect_form.response_mode_form_post') },
|
||||||
|
{ value: 'web_message', label: t('app.admin.authentication.openid_connect_form.response_mode_web_message') }
|
||||||
|
]}
|
||||||
|
clearable
|
||||||
|
control={control} />
|
||||||
|
<FormSelect id="providable_attributes.display"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.display')}
|
||||||
|
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.display_help_html" />}
|
||||||
|
options={[
|
||||||
|
{ value: 'page', label: t('app.admin.authentication.openid_connect_form.display_page') },
|
||||||
|
{ value: 'popup', label: t('app.admin.authentication.openid_connect_form.display_popup') },
|
||||||
|
{ value: 'touch', label: t('app.admin.authentication.openid_connect_form.display_touch') },
|
||||||
|
{ value: 'wap', label: t('app.admin.authentication.openid_connect_form.display_wap') }
|
||||||
|
]}
|
||||||
|
clearable
|
||||||
|
control={control} />
|
||||||
|
<FormSelect id="providable_attributes.prompt"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.prompt')}
|
||||||
|
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.prompt_help_html" />}
|
||||||
|
options={[
|
||||||
|
{ value: 'none', label: t('app.admin.authentication.openid_connect_form.prompt_none') },
|
||||||
|
{ value: 'login', label: t('app.admin.authentication.openid_connect_form.prompt_login') },
|
||||||
|
{ value: 'consent', label: t('app.admin.authentication.openid_connect_form.prompt_consent') },
|
||||||
|
{ value: 'select_account', label: t('app.admin.authentication.openid_connect_form.prompt_select_account') }
|
||||||
|
]}
|
||||||
|
clearable
|
||||||
|
control={control} />
|
||||||
|
<FormSelect id="providable_attributes.send_scope_to_token_endpoint"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.send_scope_to_token_endpoint')}
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.send_scope_to_token_endpoint_help')}
|
||||||
|
options={[
|
||||||
|
{ value: false, label: t('app.admin.authentication.openid_connect_form.send_scope_to_token_endpoint_false') },
|
||||||
|
{ value: true, label: t('app.admin.authentication.openid_connect_form.send_scope_to_token_endpoint_true') }
|
||||||
|
]}
|
||||||
|
valueDefault={true}
|
||||||
|
control={control} />
|
||||||
|
<FormInput id="providable_attributes.uid_field"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.uid_field')}
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.uid_field_help')}
|
||||||
|
defaultValue="sub"
|
||||||
|
placeholder="user_id"
|
||||||
|
register={register} />
|
||||||
|
<h4>{t('app.admin.authentication.openid_connect_form.client_options')}</h4>
|
||||||
|
<FormInput id="providable_attributes.client__identifier"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__identifier')}
|
||||||
|
rules={{ required: true }}
|
||||||
|
register={register} />
|
||||||
|
<FormInput id="providable_attributes.client__secret"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__secret')}
|
||||||
|
rules={{ required: true }}
|
||||||
|
register={register} />
|
||||||
|
{!currentFormValues?.discovery && <div className="client-options-without-discovery">
|
||||||
|
<FormInput id="providable_attributes.client__authorization_endpoint"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__authorization_endpoint')}
|
||||||
|
placeholder="/authorize"
|
||||||
|
rules={{ required: !currentFormValues?.discovery, pattern: endpointRegex }}
|
||||||
|
register={register} />
|
||||||
|
<FormInput id="providable_attributes.client__token_endpoint"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__token_endpoint')}
|
||||||
|
placeholder="/token"
|
||||||
|
rules={{ required: !currentFormValues?.discovery, pattern: endpointRegex }}
|
||||||
|
register={register} />
|
||||||
|
<FormInput id="providable_attributes.client__userinfo_endpoint"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__userinfo_endpoint')}
|
||||||
|
placeholder="/userinfo"
|
||||||
|
rules={{ required: !currentFormValues?.discovery, pattern: endpointRegex }}
|
||||||
|
register={register} />
|
||||||
|
{currentFormValues.client_auth_method === 'jwks' && <FormInput id="providable_attributes.client__jwks_uri"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__jwks_uri')}
|
||||||
|
rules={{ required: currentFormValues.client_auth_method === 'jwks', pattern: endpointRegex }}
|
||||||
|
placeholder="/jwk"
|
||||||
|
register={register} />}
|
||||||
|
<FormInput id="providable_attributes.client__end_session_endpoint"
|
||||||
|
label={t('app.admin.authentication.openid_connect_form.client__end_session_endpoint')}
|
||||||
|
tooltip={t('app.admin.authentication.openid_connect_form.client__end_session_endpoint_help')}
|
||||||
|
rules={{ pattern: endpointRegex }}
|
||||||
|
register={register} />
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||||||
import { useForm, SubmitHandler, useWatch } from 'react-hook-form';
|
import { useForm, SubmitHandler, useWatch } from 'react-hook-form';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { debounce as _debounce } from 'lodash';
|
import { debounce as _debounce } from 'lodash';
|
||||||
import { AuthenticationProvider } from '../../models/authentication-provider';
|
import { AuthenticationProvider, OpenIdConnectProvider } from '../../models/authentication-provider';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import { FormInput } from '../form/form-input';
|
import { FormInput } from '../form/form-input';
|
||||||
@ -12,6 +12,7 @@ import { Oauth2Form } from './oauth2-form';
|
|||||||
import { DataMappingForm } from './data-mapping-form';
|
import { DataMappingForm } from './data-mapping-form';
|
||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
import AuthProviderAPI from '../../api/auth-provider';
|
import AuthProviderAPI from '../../api/auth-provider';
|
||||||
|
import { OpenidConnectForm } from './openid-connect-form';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ type selectProvidableTypeOption = { value: string, label: string };
|
|||||||
*/
|
*/
|
||||||
export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, onError, onSuccess }) => {
|
export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, onError, onSuccess }) => {
|
||||||
const { handleSubmit, register, control } = useForm<AuthenticationProvider>({ defaultValues: { ...provider } });
|
const { handleSubmit, register, control } = useForm<AuthenticationProvider>({ defaultValues: { ...provider } });
|
||||||
const output = useWatch({ control });
|
const output = useWatch<AuthenticationProvider>({ control });
|
||||||
const [providableType, setProvidableType] = useState<string>(provider?.providable_type);
|
const [providableType, setProvidableType] = useState<string>(provider?.providable_type);
|
||||||
const [strategyName, setStrategyName] = useState<string>(provider?.strategy_name);
|
const [strategyName, setStrategyName] = useState<string>(provider?.strategy_name);
|
||||||
|
|
||||||
@ -85,13 +86,6 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
|||||||
});
|
});
|
||||||
}, 400), []);
|
}, 400), []);
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the callback URL, based on the strategy name.
|
|
||||||
*/
|
|
||||||
const buildCallbackUrl = (): string => {
|
|
||||||
return `${window.location.origin}/users/auth/${strategyName}/callback`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="provider-form" onSubmit={handleSubmit(onSubmit)}>
|
<form className="provider-form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<FormInput id="name"
|
<FormInput id="name"
|
||||||
@ -106,7 +100,8 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
|||||||
onChange={onProvidableTypeChange}
|
onChange={onProvidableTypeChange}
|
||||||
readOnly={action === 'update'}
|
readOnly={action === 'update'}
|
||||||
rules={{ required: true }} />
|
rules={{ required: true }} />
|
||||||
{providableType === 'OAuth2Provider' && <Oauth2Form register={register} callbackUrl={buildCallbackUrl()} />}
|
{providableType === 'OAuth2Provider' && <Oauth2Form register={register} strategyName={strategyName} />}
|
||||||
|
{providableType === 'OpenIdConnectProvider' && <OpenidConnectForm register={register} control={control} currentFormValues={output.providable_attributes as OpenIdConnectProvider} />}
|
||||||
{providableType && providableType !== 'DatabaseProvider' && <DataMappingForm register={register} control={control} />}
|
{providableType && providableType !== 'DatabaseProvider' && <DataMappingForm register={register} control={control} />}
|
||||||
<div className="main-actions">
|
<div className="main-actions">
|
||||||
<FabButton type="submit" className="submit-button">{t('app.admin.authentication.provider_form.save')}</FabButton>
|
<FabButton type="submit" className="submit-button">{t('app.admin.authentication.provider_form.save')}</FabButton>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import { Controller, Path } from 'react-hook-form';
|
import { Controller, Path } from 'react-hook-form';
|
||||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
@ -9,6 +9,7 @@ import { FormControlledComponent } from '../../models/form-component';
|
|||||||
interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext> {
|
interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext> {
|
||||||
id: string,
|
id: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
|
tooltip?: ReactNode,
|
||||||
options: Array<selectOption<TOptionValue>>,
|
options: Array<selectOption<TOptionValue>>,
|
||||||
valuesDefault?: Array<TOptionValue>,
|
valuesDefault?: Array<TOptionValue>,
|
||||||
onChange?: (values: Array<TOptionValue>) => void,
|
onChange?: (values: Array<TOptionValue>) => void,
|
||||||
@ -27,7 +28,7 @@ type selectOption<TOptionValue> = { value: TOptionValue, label: string };
|
|||||||
* This component is a wrapper around react-select to use with react-hook-form.
|
* This component is a wrapper around react-select to use with react-hook-form.
|
||||||
* It is a multi-select component.
|
* It is a multi-select component.
|
||||||
*/
|
*/
|
||||||
export const FormMultiSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, className, control, placeholder, options, valuesDefault, error, rules, disabled, onChange }: FormSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
export const FormMultiSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, tooltip, className, control, placeholder, options, valuesDefault, error, rules, disabled, onChange }: FormSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
||||||
const classNames = `
|
const classNames = `
|
||||||
form-multi-select form-item ${className || ''}
|
form-multi-select form-item ${className || ''}
|
||||||
${error && error[id] ? 'is-incorrect' : ''}
|
${error && error[id] ? 'is-incorrect' : ''}
|
||||||
@ -48,6 +49,10 @@ export const FormMultiSelect = <TFieldValues extends FieldValues, TContext exten
|
|||||||
<label className={classNames}>
|
<label className={classNames}>
|
||||||
{label && <div className="form-item-header">
|
{label && <div className="form-item-header">
|
||||||
<p>{label}</p>
|
<p>{label}</p>
|
||||||
|
{tooltip && <div className="item-tooltip">
|
||||||
|
<span className="trigger"><i className="fa fa-question-circle" /></span>
|
||||||
|
<div className="content">{tooltip}</div>
|
||||||
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
<div className="form-item-field">
|
<div className="form-item-field">
|
||||||
<Controller name={id as FieldPath<TFieldValues>}
|
<Controller name={id as FieldPath<TFieldValues>}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import { Controller, Path } from 'react-hook-form';
|
import { Controller, Path } from 'react-hook-form';
|
||||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
@ -9,6 +9,7 @@ import { FormControlledComponent } from '../../models/form-component';
|
|||||||
interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext> {
|
interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext> {
|
||||||
id: string,
|
id: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
|
tooltip?: ReactNode,
|
||||||
options: Array<selectOption<TOptionValue>>,
|
options: Array<selectOption<TOptionValue>>,
|
||||||
valueDefault?: TOptionValue,
|
valueDefault?: TOptionValue,
|
||||||
onChange?: (value: TOptionValue) => void,
|
onChange?: (value: TOptionValue) => void,
|
||||||
@ -16,6 +17,7 @@ interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue> e
|
|||||||
placeholder?: string,
|
placeholder?: string,
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
readOnly?: boolean,
|
readOnly?: boolean,
|
||||||
|
clearable?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +29,7 @@ type selectOption<TOptionValue> = { value: TOptionValue, label: string };
|
|||||||
/**
|
/**
|
||||||
* This component is a wrapper for react-select to use with react-hook-form
|
* This component is a wrapper for react-select to use with react-hook-form
|
||||||
*/
|
*/
|
||||||
export const FormSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, className, control, placeholder, options, valueDefault, error, rules, disabled, onChange, readOnly }: FormSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
export const FormSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, tooltip, className, control, placeholder, options, valueDefault, error, rules, disabled, onChange, readOnly, clearable }: FormSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
||||||
const classNames = `
|
const classNames = `
|
||||||
form-select form-item ${className || ''}
|
form-select form-item ${className || ''}
|
||||||
${error && error[id] ? 'is-incorrect' : ''}
|
${error && error[id] ? 'is-incorrect' : ''}
|
||||||
@ -48,6 +50,10 @@ export const FormSelect = <TFieldValues extends FieldValues, TContext extends ob
|
|||||||
<label className={classNames}>
|
<label className={classNames}>
|
||||||
{label && <div className="form-item-header">
|
{label && <div className="form-item-header">
|
||||||
<p>{label}</p>
|
<p>{label}</p>
|
||||||
|
{tooltip && <div className="item-tooltip">
|
||||||
|
<span className="trigger"><i className="fa fa-question-circle" /></span>
|
||||||
|
<div className="content">{tooltip}</div>
|
||||||
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
<div className="form-item-field">
|
<div className="form-item-field">
|
||||||
<Controller name={id as FieldPath<TFieldValues>}
|
<Controller name={id as FieldPath<TFieldValues>}
|
||||||
@ -64,6 +70,7 @@ export const FormSelect = <TFieldValues extends FieldValues, TContext extends ob
|
|||||||
}}
|
}}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
isDisabled={readOnly}
|
isDisabled={readOnly}
|
||||||
|
isClearable={clearable}
|
||||||
options={options} />
|
options={options} />
|
||||||
} />
|
} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +44,7 @@ export interface OpenIdConnectProvider {
|
|||||||
id?: string,
|
id?: string,
|
||||||
issuer: string,
|
issuer: string,
|
||||||
discovery: boolean,
|
discovery: boolean,
|
||||||
client_auth_method?: string,
|
client_auth_method?: 'basic' | 'jwks',
|
||||||
scope?: string,
|
scope?: string,
|
||||||
response_type?: 'code' | 'id_token',
|
response_type?: 'code' | 'id_token',
|
||||||
response_mode?: 'query' | 'fragment' | 'form_post' | 'web_message',
|
response_mode?: 'query' | 'fragment' | 'form_post' | 'web_message',
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
.item-tooltip {
|
.item-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.trigger {
|
.trigger {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -15,6 +15,10 @@ class OpenIdConnectProvider < ApplicationRecord
|
|||||||
validates :response_mode, inclusion: { in: %w[query fragment form_post web_message], allow_nil: true }
|
validates :response_mode, inclusion: { in: %w[query fragment form_post web_message], allow_nil: true }
|
||||||
validates :display, inclusion: { in: %w[page popup touch wap], allow_nil: true }
|
validates :display, inclusion: { in: %w[page popup touch wap], allow_nil: true }
|
||||||
validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true }
|
validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true }
|
||||||
|
validates :client_auth_method, inclusion: { in: %w[basic jwks] }
|
||||||
|
|
||||||
|
before_save :set_post_logout_redirect_uri
|
||||||
|
before_save :set_client_scheme_host_port
|
||||||
|
|
||||||
def config
|
def config
|
||||||
OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n|
|
OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n|
|
||||||
@ -27,4 +31,20 @@ class OpenIdConnectProvider < ApplicationRecord
|
|||||||
[n.sub('client__', ''), send(n)]
|
[n.sub('client__', ''), send(n)]
|
||||||
end.to_h
|
end.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_post_logout_redirect_uri
|
||||||
|
self.post_logout_redirect_uri = "#{ENV.fetch('DEFAULT_PROTOCOL')}://#{ENV.fetch('DEFAULT_HOST')}/sessions/sign_out"
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_client_scheme_host_port
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
URI.parse(issuer).tap do |uri|
|
||||||
|
self.client__scheme = uri.scheme
|
||||||
|
self.client__host = uri.host
|
||||||
|
self.client__port = uri.port
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1090,10 +1090,63 @@ en:
|
|||||||
common_url: "Server root URL"
|
common_url: "Server root URL"
|
||||||
authorization_endpoint: "Authorization endpoint"
|
authorization_endpoint: "Authorization endpoint"
|
||||||
token_acquisition_endpoint: "Token acquisition endpoint"
|
token_acquisition_endpoint: "Token acquisition endpoint"
|
||||||
profil_edition_url: "Profil edition URL"
|
profile_edition_url: "Profil edition URL"
|
||||||
|
profile_edition_url_help: "The URL of the page where the user can edit his profile."
|
||||||
client_identifier: "Client identifier"
|
client_identifier: "Client identifier"
|
||||||
client_secret: "Client secret"
|
client_secret: "Client secret"
|
||||||
scopes: "Scopes"
|
scopes: "Scopes"
|
||||||
|
openid_connect_form:
|
||||||
|
issuer: "Issuer"
|
||||||
|
issuer_help: "Root url for the authorization server."
|
||||||
|
discovery: "Discovery"
|
||||||
|
discovery_help: "Should OpenID discovery be used. This is recommended if the IDP provides a discovery endpoint."
|
||||||
|
discovery_enabled: "Enable discovery"
|
||||||
|
discovery_disabled: "Disable discovery"
|
||||||
|
client_auth_method: "Client authentication method"
|
||||||
|
client_auth_method_help: "Which authentication method to use to authenticate Fab-manager with the authorization server."
|
||||||
|
client_auth_method_basic: "Basic"
|
||||||
|
client_auth_method_jwks: "JWKS"
|
||||||
|
scope: "Scope"
|
||||||
|
scope_help: "Which OpenID scopes to include (openid is always required)"
|
||||||
|
response_type: "Response type"
|
||||||
|
response_type_help: "Which OpenID response type to use with the authorization request. This is usually 'code'"
|
||||||
|
response_type_code: "Code"
|
||||||
|
response_type_id_token: "Id token"
|
||||||
|
response_mode: "Response mode"
|
||||||
|
response_mode_help_html: "Specifies the method to use to send the resulting authorization code to Fab-manager. <br> <b>Query</b> - the authorization code is included in the redirect URL. <br> <b>Fragment</b> - the authorization code is included in the redirect URL as a URL fragment. <br> <b>Form post</b> - the authorization code is included in a POST body. <br> <b>Web message</b> - the authorization code uses HTML5 Web Messaging (a.k.a window.postMessage())."
|
||||||
|
response_mode_query: "Query"
|
||||||
|
response_mode_fragment: "Fragment"
|
||||||
|
response_mode_form_post: "Form post"
|
||||||
|
response_mode_web_message: "Web message"
|
||||||
|
display: "Display"
|
||||||
|
display_help_html: "How the authorization server should display the authorization page to the user. <br> <b>Page</b> - the authorization page is displayed in a new browser window. <br> <b>Popup</b> - the authorization page is displayed in a popup window. <br> <b>Touch</b> - the authorization page is displayed consistently with devices that leverages a touch interface. <br> <b>Wap</b> - the authorization page is displayed consistently with a "feature phone" type display."
|
||||||
|
display_page: "Page"
|
||||||
|
display_popup: "Popup"
|
||||||
|
display_touch: "Touch"
|
||||||
|
display_wap: "WAP"
|
||||||
|
prompt: "Prompt"
|
||||||
|
prompt_help_html: "Which OpenID pages the user will be shown. <br> <b>None</b> - no authentication or consent user interface pages are shown. <br> <b>Login</b> - the authorization server prompt the user for reauthentication. <br> <b>Consent</b> - the authorization server prompt the user for consent before returning information to Fab-manager. <br> <b>Select account</b> - the authorization server prompt the user to select a user account."
|
||||||
|
prompt_none: "None"
|
||||||
|
prompt_login: "Login"
|
||||||
|
prompt_consent: "Consent"
|
||||||
|
prompt_select_account: "Select account"
|
||||||
|
send_scope_to_token_endpoint: "Send scope to token endpoint?"
|
||||||
|
send_scope_to_token_endpoint_help: "Should the scope parameter be sent to the authorization token endpoint?"
|
||||||
|
send_scope_to_token_endpoint_false: "No"
|
||||||
|
send_scope_to_token_endpoint_true: "Yes"
|
||||||
|
uid_field: "UID field"
|
||||||
|
uid_field_help: "The field of the user info response to be used as a unique id."
|
||||||
|
extra_authorize_params: "Extra authorize params"
|
||||||
|
extra_authorize_params_help_html: "A list of extra fixed parameters that will be merged to the authorization request.<br>The list is expected to be in a JSON-like format.<br> <b>Eg.</b> {tenant: common, max_age: 3600}"
|
||||||
|
client_options: "Client options"
|
||||||
|
client__identifier: "Identifier"
|
||||||
|
client__secret: "Secret"
|
||||||
|
client__authorization_endpoint: "Authorization endpoint"
|
||||||
|
client__token_endpoint: "Token endpoint"
|
||||||
|
client__userinfo_endpoint: "Userinfo endpoint"
|
||||||
|
client__jwks_uri: "JWKS URI"
|
||||||
|
client__end_session_endpoint: "End session endpoint"
|
||||||
|
client__end_session_endpoint_help: "The url to call to log the user out at the authorization server."
|
||||||
provider_form:
|
provider_form:
|
||||||
name: "Name"
|
name: "Name"
|
||||||
authentication_type: "Authentication type"
|
authentication_type: "Authentication type"
|
||||||
|
@ -17,6 +17,10 @@ Rails.application.routes.draw do
|
|||||||
get '/sso-redirect', to: 'application#sso_redirect', as: :sso_redirect
|
get '/sso-redirect', to: 'application#sso_redirect', as: :sso_redirect
|
||||||
end
|
end
|
||||||
|
|
||||||
|
devise_scope :user do
|
||||||
|
get '/sessions/sign_out', to: 'devise/sessions#destroy'
|
||||||
|
end
|
||||||
|
|
||||||
## The priority is based upon order of creation: first created -> highest priority.
|
## The priority is based upon order of creation: first created -> highest priority.
|
||||||
## See how all your routes lay out with "rake routes".
|
## See how all your routes lay out with "rake routes".
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user