From 358badcdfafba35938e31e9dd2c00cca173f8524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Mon, 19 Aug 2024 17:36:04 +0200 Subject: [PATCH] Add Login for OIDC client --- src/auth/FormElement.ts | 129 ++++++++++++++++++++-------------- src/auth/component.stories.ts | 13 ++++ srcapi/store/config.ts | 1 + srcapi/store/user.ts | 24 ++++--- 4 files changed, 106 insertions(+), 61 deletions(-) diff --git a/src/auth/FormElement.ts b/src/auth/FormElement.ts index 6b86f5a4dffa..71abb96b1d3f 100644 --- a/src/auth/FormElement.ts +++ b/src/auth/FormElement.ts @@ -54,11 +54,15 @@ export default class GmfAuthForm extends GmfBaseElement { @state() private allowPasswordReset = false; @state() private changingPassword = false; @state() private userMustChangeItsPassword = false; + @state() private openIdConnectUrl = ''; @state() private error = false; @state() private otpImage = ''; @state() private gmfUser: User = null; @state() private customCSS_ = ''; private changingPasswordUsername_ = ''; + private initialApplicationUrl = window.location.href; + private currentApplicationUrl = window.location.href; + private openIdConnectBaseUrl = ''; connectedCallback(): void { super.connectedCallback(); @@ -74,13 +78,21 @@ export default class GmfAuthForm extends GmfBaseElement { } }, }), + user.getLoginMessage().subscribe({ next: (message: string) => { this.loginInfoMessage = message; + this._updateOpenIdConnectUrl(); }, }), ); + window.addEventListener('popstate', () => { + this.currentApplicationUrl = window.location.href; + this._updateOpenIdConnectUrl(); + }); + this._updateOpenIdConnectUrl(); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const loginField = document.body.querySelector('input[slot=gmf-auth-login]') as HTMLInputElement; // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -98,11 +110,20 @@ export default class GmfAuthForm extends GmfBaseElement { }); } + _updateOpenIdConnectUrl(): void { + const applicationUrl = this.loginInfoMessage ? this.currentApplicationUrl : this.initialApplicationUrl; + const params = new URLSearchParams({ + came_from: applicationUrl, + }); + this.openIdConnectUrl = `${this.openIdConnectBaseUrl}?${params.toString()}`; + } + // override default initConfig initConfig(configuration: Configuration): void { this.twoFactorAuth = configuration.gmfTwoFactorAuth; this.allowPasswordChange = configuration.gmfAuthenticationConfig.allowPasswordChange; this.allowPasswordReset = configuration.gmfAuthenticationConfig.allowPasswordReset; + this.openIdConnectBaseUrl = configuration.gmfOidcLoginUrl; if (configuration.gmfCustomCSS && configuration.gmfCustomCSS.authentication !== undefined) { this.customCSS_ = configuration.gmfCustomCSS.authentication; } @@ -168,7 +189,7 @@ export default class GmfAuthForm extends GmfBaseElement {
-
this.login(evt)}> -
- -
-
- -
- ${this.twoFactorAuth - ? html` -
- ${i18next.t('The following field should be kept empty on first login:')} - -
- ` - : ''} -
- -
- ${this.isLoading - ? html` - - ` + ? this.gmfUser.login_type == 'oidc' + ? html`${i18next.t('Connect')}` + : html` +
+ this.login(evt)}> +
+ +
+
+ +
+ ${this.twoFactorAuth + ? html` +
+ ${i18next.t('The following field should be kept empty on first login:')} + +
+ ` + : ''} +
+ +
+ ${this.isLoading + ? html` + + ` + : ''} + + + + ${this.resetPasswordShown + ? html`
+ ${i18next.t('A new password has just been sent to you by e-mail.')} +
` : ''} - - - - ${this.resetPasswordShown - ? html`
- ${i18next.t('A new password has just been sent to you by e-mail.')} -
` - : ''} -
- ` +
+ ` : ''} ${this.changingPassword ? html` diff --git a/src/auth/component.stories.ts b/src/auth/component.stories.ts index 31af7bd17067..ca92f8acc223 100644 --- a/src/auth/component.stories.ts +++ b/src/auth/component.stories.ts @@ -79,6 +79,19 @@ const login = user.getEmptyUserProperties(); login.username = 'George'; WithUser.args.user = login; +export const EmptyOidc: any = Template.bind({}); +EmptyOidc.args = {...defaultProperties}; +const loginEmptyOidc = user.getEmptyUserProperties(); +loginEmptyOidc.login_type = 'oidc'; +EmptyOidc.args.user = loginEmptyOidc; + +export const WithUserOidc: any = Template.bind({}); +WithUserOidc.args = {...defaultProperties}; +const loginOidc = user.getEmptyUserProperties(); +loginOidc.login_type = 'oidc'; +loginOidc.username = 'George OIDC'; +WithUserOidc.args.user = loginOidc; + /** * @returns The HTML of the story */ diff --git a/srcapi/store/config.ts b/srcapi/store/config.ts index 061a03d24a4b..a5980b5df895 100644 --- a/srcapi/store/config.ts +++ b/srcapi/store/config.ts @@ -1471,6 +1471,7 @@ export type Configuration = { gmfI18nextConfiguration: InitOptions; pytreeLidarprofileJsonUrl: pytreeLidarprofileJsonUrl; gmfDatasourceOptions: gmfDatasourceOptions; + gmfOidcLoginUrl: string; }; export type APIConfig = { diff --git a/srcapi/store/user.ts b/srcapi/store/user.ts index d57641c6b87d..48927e0e387b 100644 --- a/srcapi/store/user.ts +++ b/srcapi/store/user.ts @@ -92,6 +92,10 @@ export interface User { * The two-factor authentication secret on first login */ two_factor_totp_secret: string; + /** + * The server-side login type (oidc or local) + */ + login_type?: string; } export enum UserState { @@ -203,15 +207,17 @@ export class UserModel { */ getEmptyUserProperties(): User { return { - email: null, - is_intranet: null, - functionalities: null, - is_password_changed: null, - roles: null, - username: null, - otp_key: null, - otp_uri: null, - two_factor_totp_secret: null, + ...{ + email: null, + is_intranet: null, + functionalities: null, + is_password_changed: null, + roles: null, + username: null, + otp_key: null, + otp_uri: null, + two_factor_totp_secret: null, + }, }; }