Skip to content

Commit

Permalink
Add Login for OIDC client
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrunner committed Aug 21, 2024
1 parent 69216af commit 358badc
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 61 deletions.
129 changes: 77 additions & 52 deletions src/auth/FormElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -168,7 +189,7 @@ export default class GmfAuthForm extends GmfBaseElement {
</div>
<div class="form-group">
<input
?hidden="${!this.allowPasswordChange}"
?hidden="${!(this.allowPasswordChange && this.gmfUser.login_type != 'oidc')}"
type="button"
class="form-control btn btn-default"
value=${i18next.t('Change password')}
Expand Down Expand Up @@ -196,58 +217,62 @@ export default class GmfAuthForm extends GmfBaseElement {
`
: ''}
${this.gmfUser.username === null && !this.changingPassword
? html`
<div>
<form name="loginForm" role="form" @submit=${(evt: Event) => this.login(evt)}>
<div class="form-group">
<slot name="gmf-auth-login"></slot>
</div>
<div class="form-group">
<slot name="gmf-auth-password"></slot>
</div>
${this.twoFactorAuth
? html`
<div class="form-group">
${i18next.t('The following field should be kept empty on first login:')}
<input
type="text"
class="form-control"
name="otp"
autocomplete="one-time-code"
placeholder=${i18next.t('Authentication code')}
/>
</div>
`
: ''}
<div class="form-group">
<input type="submit" class="form-control btn prime" value=${i18next.t('Connect')} />
</div>
${this.isLoading
? html`
<div class="login-spinner">
<i class="fa fa-spin"
>${
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
unsafeSVG(loadingSvg)
}</i
>
</div>
`
? this.gmfUser.login_type == 'oidc'
? html`<a class="btn prime form-control" role="button" href="${this.openIdConnectUrl}"
>${i18next.t('Connect')}</a
>`
: html`
<div>
<form name="loginForm" role="form" @submit=${(evt: Event) => this.login(evt)}>
<div class="form-group">
<slot name="gmf-auth-login"></slot>
</div>
<div class="form-group">
<slot name="gmf-auth-password"></slot>
</div>
${this.twoFactorAuth
? html`
<div class="form-group">
${i18next.t('The following field should be kept empty on first login:')}
<input
type="text"
class="form-control"
name="otp"
autocomplete="one-time-code"
placeholder=${i18next.t('Authentication code')}
/>
</div>
`
: ''}
<div class="form-group">
<input type="submit" class="form-control btn prime" value=${i18next.t('Connect')} />
</div>
${this.isLoading
? html`
<div class="login-spinner">
<i class="fa fa-spin"
>${
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
unsafeSVG(loadingSvg)
}</i
>
</div>
`
: ''}
<div ?hidden="${!this.allowPasswordReset}" class="form-group">
<a @click=${(evt: Event) => this.resetPassword(evt)} href=""
>${i18next.t('Password forgotten?')}</a
>
</div>
</form>
${this.resetPasswordShown
? html` <div class="alert alert-info">
${i18next.t('A new password has just been sent to you by e-mail.')}
</div>`
: ''}
<div ?hidden="${!this.allowPasswordReset}" class="form-group">
<a @click=${(evt: Event) => this.resetPassword(evt)} href=""
>${i18next.t('Password forgotten?')}</a
>
</div>
</form>
${this.resetPasswordShown
? html` <div class="alert alert-info">
${i18next.t('A new password has just been sent to you by e-mail.')}
</div>`
: ''}
</div>
`
</div>
`
: ''}
${this.changingPassword
? html`
Expand Down
13 changes: 13 additions & 0 deletions src/auth/component.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
1 change: 1 addition & 0 deletions srcapi/store/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,7 @@ export type Configuration = {
gmfI18nextConfiguration: InitOptions;
pytreeLidarprofileJsonUrl: pytreeLidarprofileJsonUrl;
gmfDatasourceOptions: gmfDatasourceOptions;
gmfOidcLoginUrl: string;
};

export type APIConfig = {
Expand Down
24 changes: 15 additions & 9 deletions srcapi/store/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
},
};
}

Expand Down

0 comments on commit 358badc

Please sign in to comment.