mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-28 09:24:24 +01:00
(ui) display authorization callback url directly in interface
This commit is contained in:
parent
63b03568e4
commit
f9e5e7f2a8
@ -28,6 +28,12 @@ class API::AuthProvidersController < API::ApiController
|
||||
end
|
||||
end
|
||||
|
||||
def strategy_name
|
||||
authorize AuthProvider
|
||||
@provider = AuthProvider.new(providable_type: params[:providable_type], name: params[:name])
|
||||
render json: @provider.strategy_name
|
||||
end
|
||||
|
||||
def show
|
||||
authorize AuthProvider
|
||||
end
|
||||
|
@ -31,4 +31,9 @@ export default class AuthProviderAPI {
|
||||
const res: AxiosResponse<MappingFields> = await apiClient.get('/api/auth_providers/mapping_fields');
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async strategyName (authProvider: AuthenticationProvider): Promise<string> {
|
||||
const res: AxiosResponse<string> = await apiClient.get(`/api/auth_providers/strategy_name?providable_type=${authProvider.providable_type}&name=${authProvider.name}`);
|
||||
return res?.data;
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ These components must be written using the following conventions:
|
||||
- All methods in the component must be commented with a comment block.
|
||||
- Other constants and variables must be commented with an inline block.
|
||||
- Depending on if we want to use the `<Suspense>` wrapper or not, we can export the component directly or wrap it in a `<Loader>` wrapper.
|
||||
- When a component is used in angularJS, the wrapped is required. The component must be named like `const Foo` (no export if not used in React) and must have a `const FooWrapper` at the end of its file, which wraps the component in a `<Loader>`.
|
||||
- When a component is used in angularJS, the wrapper is required. The component must be named like `const Foo` (no export if not used in React) and must have a `const FooWrapper` at the end of its file, which wraps the component in a `<Loader>`.
|
||||
- Translations must be grouped per component. For example, the `FooBar` component must have its translations in the `config/locales/app.$SCOPE.en.yml` file, under the `foo_bar` key.
|
||||
|
||||
|
@ -3,15 +3,17 @@ 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';
|
||||
import { FabOutputCopy } from '../base/fab-output-copy';
|
||||
|
||||
interface Oauth2FormProps<TFieldValues> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
callbackUrl?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form to fill the OAuth2 settings for a new/existing authentication provider.
|
||||
*/
|
||||
export const Oauth2Form = <TFieldValues extends FieldValues>({ register }: Oauth2FormProps<TFieldValues>) => {
|
||||
export const Oauth2Form = <TFieldValues extends FieldValues>({ register, callbackUrl }: Oauth2FormProps<TFieldValues>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
// regular expression to validate the the input fields
|
||||
@ -21,6 +23,7 @@ export const Oauth2Form = <TFieldValues extends FieldValues>({ register }: Oauth
|
||||
return (
|
||||
<div className="oauth2-form">
|
||||
<hr/>
|
||||
<FabOutputCopy text={callbackUrl} label={t('app.admin.authentication.oauth2_form.authorization_callback_url')} />
|
||||
<FormInput id="providable_attributes.base_url"
|
||||
register={register}
|
||||
placeholder="https://sso.example.net..."
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useForm, SubmitHandler, useWatch } from 'react-hook-form';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { debounce as _debounce } from 'lodash';
|
||||
import { AuthenticationProvider } from '../../models/authentication-provider';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
@ -17,7 +18,7 @@ declare const Application: IApplication;
|
||||
// list of supported authentication methods
|
||||
const METHODS = {
|
||||
DatabaseProvider: 'local_database',
|
||||
OAuth2Provider: 'o_auth2',
|
||||
OAuth2Provider: 'oauth2',
|
||||
OpenIdConnectProvider: 'openid_connect'
|
||||
};
|
||||
|
||||
@ -35,9 +36,16 @@ type selectProvidableTypeOption = { value: string, label: string };
|
||||
*/
|
||||
export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, onError, onSuccess }) => {
|
||||
const { handleSubmit, register, control } = useForm<AuthenticationProvider>({ defaultValues: { ...provider } });
|
||||
const output = useWatch({ control });
|
||||
const [providableType, setProvidableType] = useState<string>(provider?.providable_type);
|
||||
const [strategyName, setStrategyName] = useState<string>(provider?.strategy_name);
|
||||
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
useEffect(() => {
|
||||
updateStrategyName(output as AuthenticationProvider);
|
||||
}, [output?.providable_type, output?.name]);
|
||||
|
||||
/**
|
||||
* Callback triggered when the form is submitted: process with the provider creation or update.
|
||||
*/
|
||||
@ -66,6 +74,24 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
||||
setProvidableType(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Request the API the strategy name for the current "in-progress" provider.
|
||||
*/
|
||||
const updateStrategyName = useCallback(_debounce((provider: AuthenticationProvider): void => {
|
||||
AuthProviderAPI.strategyName(provider).then(strategyName => {
|
||||
setStrategyName(strategyName);
|
||||
}).catch(error => {
|
||||
onError(error);
|
||||
});
|
||||
}, 400), []);
|
||||
|
||||
/**
|
||||
* Build the callback URL, based on the strategy name.
|
||||
*/
|
||||
const buildCallbackUrl = (): string => {
|
||||
return `${window.location.origin}/users/auth/${strategyName}/callback`;
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="provider-form" onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormInput id="name"
|
||||
@ -80,7 +106,7 @@ export const ProviderForm: React.FC<ProviderFormProps> = ({ action, provider, on
|
||||
onChange={onProvidableTypeChange}
|
||||
readOnly={action === 'update'}
|
||||
rules={{ required: true }} />
|
||||
{providableType === 'OAuth2Provider' && <Oauth2Form register={register} />}
|
||||
{providableType === 'OAuth2Provider' && <Oauth2Form register={register} callbackUrl={buildCallbackUrl()} />}
|
||||
{providableType && providableType !== 'DatabaseProvider' && <DataMappingForm register={register} control={control} />}
|
||||
<div className="main-actions">
|
||||
<FabButton type="submit" className="submit-button">{t('app.admin.authentication.provider_form.save')}</FabButton>
|
||||
|
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
interface FabOutputCopyProps {
|
||||
text: string,
|
||||
onCopy?: () => void,
|
||||
label?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a read-only input text filled with the provided text. A button allows to copy the text to the clipboard.
|
||||
*/
|
||||
export const FabOutputCopy: React.FC<FabOutputCopyProps> = ({ label, text, onCopy }) => {
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
/**
|
||||
* Copy the given text to the clipboard.
|
||||
*/
|
||||
const textToClipboard = () => {
|
||||
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text);
|
||||
if (typeof onCopy === 'function') onCopy();
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fab-output-copy">
|
||||
<label className="form-item">
|
||||
<div className='form-item-header'>
|
||||
<p>{label}</p>
|
||||
</div>
|
||||
<div className='form-item-field'>
|
||||
<input value={text} readOnly />
|
||||
<span className="addon">
|
||||
<button className={copied ? 'copied' : ''} onClick={textToClipboard}><i className="fa fa-clipboard" /></button>
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -23,6 +23,7 @@
|
||||
@import "modules/base/fab-button";
|
||||
@import "modules/base/fab-input";
|
||||
@import "modules/base/fab-modal";
|
||||
@import "modules/base/fab-output-copy";
|
||||
@import "modules/base/fab-popover";
|
||||
@import "modules/base/fab-text-editor";
|
||||
@import "modules/base/labelled-input";
|
||||
|
@ -0,0 +1,22 @@
|
||||
.fab-output-copy {
|
||||
.form-item-field {
|
||||
& > input {
|
||||
background-color: var(--gray-soft);
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
& > .addon > button.copied {
|
||||
position: relative;
|
||||
&:after {
|
||||
content: '\f00c';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background-color: #EFEFEF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class AuthProvider < ApplicationRecord
|
||||
## Return the provider type name without the "Provider" part.
|
||||
## eg. DatabaseProvider will return 'database'
|
||||
def provider_type
|
||||
providable.class.name[0..-9].downcase
|
||||
providable_type[0..-9].downcase
|
||||
end
|
||||
|
||||
## Return the user's profile fields that are currently managed from the SSO
|
||||
|
@ -1,3 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::AuthProvidersController
|
||||
class AuthProviderPolicy < ApplicationPolicy
|
||||
|
||||
class Scope < Scope
|
||||
@ -6,7 +9,7 @@ class AuthProviderPolicy < ApplicationPolicy
|
||||
end
|
||||
end
|
||||
|
||||
%w(index? show? create? update? destroy? mapping_fields?).each do |action|
|
||||
%w[index? show? create? update? destroy? mapping_fields? strategy_name?].each do |action|
|
||||
define_method action do
|
||||
user.admin?
|
||||
end
|
||||
|
@ -41,7 +41,6 @@
|
||||
Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>";
|
||||
Fablab.trackingId = "<%= Setting.get('tracking_id') %>";
|
||||
Fablab.adminSysId = parseInt("<%= User.adminsys&.id %>", 10);
|
||||
Fablab.baseHostUrl = "<%= Rails.application.secrets.default_host %>";
|
||||
|
||||
// i18n stuff
|
||||
Fablab.locale = "<%= Rails.application.secrets.app_locale %>";
|
||||
|
@ -1089,6 +1089,7 @@ en:
|
||||
date: "date"
|
||||
boolean: "boolean"
|
||||
oauth2_form:
|
||||
authorization_callback_url: "Authorization callback URL"
|
||||
common_url: "Server root URL"
|
||||
authorization_endpoint: "Authorization endpoint"
|
||||
token_acquisition_endpoint: "Token acquisition endpoint"
|
||||
@ -1102,7 +1103,7 @@ en:
|
||||
save: "Save"
|
||||
methods:
|
||||
local_database: "Local database"
|
||||
o_auth2: "OAuth 2.0"
|
||||
oauth2: "OAuth 2.0"
|
||||
openid_connect: "OpenID Connect"
|
||||
#create a new authentication provider (SSO)
|
||||
authentication_new:
|
||||
|
@ -151,6 +151,7 @@ Rails.application.routes.draw do
|
||||
get 'mapping_fields', on: :collection
|
||||
get 'active', action: 'active', on: :collection
|
||||
post 'send_code', action: 'send_code', on: :collection
|
||||
get 'strategy_name', action: 'strategy_name', on: :collection
|
||||
end
|
||||
resources :abuses, only: %i[index create destroy]
|
||||
resources :open_api_clients, only: %i[index create update destroy] do
|
||||
|
@ -5,27 +5,23 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as
|
||||
- First, you must have a GitHub account. This is free, so create one if you don't have any.
|
||||
Visit https://github.com/join?source=login to create an account.
|
||||
|
||||
- Secondly, you will need to register your Fab-manager instance as an application in GitHub.
|
||||
Visit https://github.com/settings/applications/new to register your instance.
|
||||
- In `Application name`, we advise you to set the same name as your Fab-manager's instance title.
|
||||
- In `Homepage URL`, put the public URL where your Fab-manager's instance is located (eg. https://example.com).
|
||||
- In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback
|
||||
- **example.com** is your own Fab-manager's address
|
||||
- **oauth2-github** match the provider's "strategy name" in the Fab-manager.
|
||||
It is composed of: **SSO's protocol**, _dash_, **slug of the provider's name**.
|
||||
If you have a doubt about what it will be, set any value, create the authentication provider in your Fab-manager (see below), then the strategy's name will be shown in the providers list.
|
||||
Afterwards, edit your app on GitHub to set the correct name.
|
||||
|
||||
- You'll be redirected to a page displaying two important information: your **Client ID** and your **Client Secret**.
|
||||
|
||||
- Now go to your Fab-manager's instance, login as an administrator, go to `Users management` and `Authentication`.
|
||||
- Go to your Fab-manager's instance, login as an administrator, go to `Users management` and `Authentication`.
|
||||
Click `Add a new authentication provider`, and select _OAuth 2.0_ in the `Authentication type` drop-down list.
|
||||
In `name`, you can set whatever you want, but you must be aware that:
|
||||
1. You will need to type this name in a terminal to activate the provider, so prefer avoiding chars that must be escaped.
|
||||
2. This name will be occasionally displayed to end users, so prefer sweet and speaking names.
|
||||
3. The slug of this name is used in the callback URL provided to the SSO server (eg. /users/auth/oauth2-**github**/callback)
|
||||
|
||||
- Fulfill the form with the following parameters:
|
||||
- You'll see an "Authorization Callback URL" field, generated based on what you typed previously. Copy the content of this field to your clipboard.
|
||||
|
||||
- Now, you will need to register your Fab-manager instance as an application in GitHub.
|
||||
Visit https://github.com/settings/applications/new to register your instance.
|
||||
- In `Application name`, we advise you to set the same name as your Fab-manager's instance title.
|
||||
- In `Homepage URL`, put the public URL where your Fab-manager's instance is located (eg. https://example.com).
|
||||
- In `Authorization callback URL`, you must paste the URL previously copied from Fa-manager.
|
||||
|
||||
- You'll be redirected to a page displaying two important information: your **Client ID** and your **Client Secret**. Keep them safe, you'll need them to configure Fab-manager.
|
||||
|
||||
- Now go back to your Fab-manager's configuration interface and fulfill the remaining form with the following parameters:
|
||||
- **Server root URL**: `https://github.com` This is the domain name of the where the SSO server is located.
|
||||
- **Authorization endpoint**: `/login/oauth/authorize` This URL can be found [here](https://developer.github.com/v3/oauth/).
|
||||
- **Token Acquisition Endpoint**: `/login/oauth/access_token` This URL can be found [here](https://developer.github.com/v3/oauth/).
|
||||
@ -33,7 +29,7 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as
|
||||
- **Client identifier**: Your Client ID, collected just before.
|
||||
- **Client secret**: Your Client Secret, collected just before.
|
||||
|
||||
Please note that in some cases we'll encounter an issue unless the **common URL** must only contain the root domain (e.g. `http://github.com`), and the other parts of the URL must go to **Authorization endpoint** (e.g. `/login/oauth/authorize`) and **Token Acquisition Endpoint** (e.g. `/login/oauth/access_token`).
|
||||
Please note the **common URL** must only contain the root domain (e.g. `http://github.com`), and the other parts of the URL must go to **Authorization endpoint** (e.g. `/login/oauth/authorize`) and **Token Acquisition Endpoint** (e.g. `/login/oauth/access_token`).
|
||||
|
||||
- Then you will need to define the matching of the fields between the Fab-manager and what the external SSO can provide.
|
||||
Please note that the only mandatory field is `User.uid`.
|
||||
|
Loading…
Reference in New Issue
Block a user