1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-27 21:54:27 +01:00

(wip)(ui) data mapping configuration interface

This commit is contained in:
Sylvain 2022-04-05 16:56:44 +02:00
parent 7beb1466bf
commit acf5001b37
6 changed files with 110 additions and 5 deletions

View File

@ -1,4 +1,4 @@
import { AuthenticationProvider } from '../models/authentication-provider';
import { AuthenticationProvider, MappingFields } from '../models/authentication-provider';
import { AxiosResponse } from 'axios';
import apiClient from './clients/api-client';
@ -27,5 +27,8 @@ export default class AuthProviderAPI {
await apiClient.delete(`/api/auth_providers/${id}`);
}
static async mappingFields(): Promise<>
static async mappingFields (): Promise<MappingFields> {
const res: AxiosResponse<MappingFields> = await apiClient.get('/api/auth_providers/mapping_fields');
return res?.data;
}
}

View File

@ -0,0 +1,28 @@
import React, { useEffect } from 'react';
import { UseFormRegister } from 'react-hook-form';
import { FieldValues } from 'react-hook-form/dist/types/fields';
import AuthProviderAPI from '../../api/auth-provider';
import { MappingFields } from '../../models/authentication-provider';
import { FormInput } from '../form/form-input';
export interface DataMappingFormProps<TFieldValues> {
register: UseFormRegister<TFieldValues>,
}
export const DataMappingForm = <TFieldValues extends FieldValues>({ register }: DataMappingFormProps<TFieldValues>) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [dataMapping, setDataMapping] = React.useState<MappingFields>(null);
// fetch the mapping data from the API on mount
useEffect(() => {
AuthProviderAPI.mappingFields().then((data) => {
setDataMapping(data);
});
}, []);
return (
<div className="data-mapping-form">
<FormInput id="local_model" register={register} />
</div>
);
};

View File

@ -0,0 +1,57 @@
import React from 'react';
import { FormInput } from '../form/form-input';
import { UseFormRegister } from 'react-hook-form';
import { FieldValues } from 'react-hook-form/dist/types/fields';
import { useTranslation } from 'react-i18next';
interface Oauth2FormProps<TFieldValues> {
register: UseFormRegister<TFieldValues>,
}
/**
* Partial form to fill the OAuth2 settings for a new/existing authentication provider.
*/
export const Oauth2Form = <TFieldValues extends FieldValues>({ register }: Oauth2FormProps<TFieldValues>) => {
const { t } = useTranslation('shared');
// 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="oauth2-form">
<hr/>
<FormInput id="base_url"
register={register}
placeholder="https://sso.example.net..."
label={t('app.shared.oauth2.common_url')}
rules={{ required: true, pattern: urlRegex }} />
<FormInput id="authorization_endpoint"
register={register}
placeholder="/oauth2/auth..."
label={t('app.shared.oauth2.authorization_endpoint')}
rules={{ required: true, pattern: endpointRegex }} />
<FormInput id="token_endpoint"
register={register}
placeholder="/oauth2/token..."
label={t('app.shared.oauth2.token_acquisition_endpoint')}
rules={{ required: true, pattern: endpointRegex }} />
<FormInput id="profile_url"
register={register}
placeholder="https://exemple.net/user..."
label={t('app.shared.oauth2.profil_edition_url')}
rules={{ required: true, pattern: urlRegex }} />
<FormInput id="client_id"
register={register}
label={t('app.shared.oauth2.client_identifier')}
rules={{ required: true }} />
<FormInput id="client_secret"
register={register}
label={t('app.shared.oauth2.client_secret')}
rules={{ required: true }} />
<FormInput id="scopes" register={register}
placeholder="profile,email..."
label={t('app.shared.oauth2.scopes')} />
</div>
);
};

View File

@ -7,6 +7,7 @@ import { IApplication } from '../../models/application';
import { FormInput } from '../form/form-input';
import { useTranslation } from 'react-i18next';
import { FormSelect } from '../form/form-select';
import { Oauth2Form } from './oauth2-form';
declare const Application: IApplication;
@ -26,10 +27,16 @@ interface ProviderFormProps {
type selectProvidableTypeOption = { value: string, label: string };
/**
* Form to create or update an authentication provider.
*/
export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, onError, onSuccess }) => {
const { handleSubmit, register, control } = useForm<AuthenticationProvider>({ defaultValues: { ...provider } });
const { t } = useTranslation('shared');
/**
* Callback triggered when the form is submitted: process with the provider creation or update.
*/
const onSubmit: SubmitHandler<AuthenticationProvider> = (data: AuthenticationProvider) => {
if (data) {
onSuccess('Provider created successfully');
@ -38,6 +45,9 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
}
};
/**
* Build the list of available authentication methods to match with react-select requirements.
*/
const buildProvidableTypeOptions = (): Array<selectProvidableTypeOption> => {
return Object.keys(METHODS).map((method: string) => {
return { value: method, label: t(`app.shared.authentication.${METHODS[method]}`) };
@ -48,6 +58,7 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
<form className="provider-form" onSubmit={handleSubmit(onSubmit)}>
<FormInput id="name" register={register} readOnly={action === 'update'} rules={{ required: true }} label={t('app.shared.authentication.name')} />
<FormSelect id="providable_type" control={control} options={buildProvidableTypeOptions()} label={t('app.shared.authentication.authentication_type')} rules={{ required: true }} />
{provider?.providable_type === 'OAuth2Provider' && <Oauth2Form register={register} />}
<input type={'submit'} />
</form>
);

View File

@ -34,13 +34,12 @@ export const FormSelect = <TFieldValues extends FieldValues, TContext extends ob
{label && <div className="form-select-header">
<p>{label}</p>
</div>}
<div className="form-select-field">
<div>
<Controller name={id as FieldPath<TFieldValues>}
control={control}
defaultValue={valueDefault as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>}
render={({ field: { onChange, value, ref } }) =>
<Select inputRef={ref}
className="form-select-field-input"
value={options.find(c => c.value === value)}
onChange={val => onChange(val.value)}
placeholder={placeholder}

View File

@ -8,6 +8,8 @@ export interface AuthenticationProvider {
providable_attributes?: OAuth2Provider | OpenIdConnectProvider
}
export type mappingType = 'string' | 'text' | 'date' | 'integer' | 'boolean';
export interface AuthenticationProviderMapping {
id?: number,
local_model: 'user' | 'profile',
@ -16,7 +18,7 @@ export interface AuthenticationProviderMapping {
api_endpoint: string,
api_data_type: 'json',
transformation: {
type: 'string' | 'text' | 'date' | 'integer' | 'boolean',
type: mappingType,
format: 'iso8601' | 'rfc2822' | 'rfc3339' | 'timestamp-s' | 'timestamp-ms',
true_value: string,
false_value: string,
@ -66,3 +68,8 @@ export interface OpenIdConnectProvider {
client__end_session_endpoint?: string,
profile_url?: string
}
export interface MappingFields {
user: Array<[string, mappingType]>,
profile: Array<[string, mappingType]>
}