mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
* Do redirect before implicit * Add unit to implicit and silent * Keep the value as it was before
411 lines
12 KiB
TypeScript
411 lines
12 KiB
TypeScript
/*!
|
|
* @license
|
|
* Copyright 2019 Alfresco Software, Ltd.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import {
|
|
Component, EventEmitter,
|
|
Input, OnInit, Output, TemplateRef, ViewEncapsulation, OnDestroy
|
|
} from '@angular/core';
|
|
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
import { Router, ActivatedRoute, Params } from '@angular/router';
|
|
import { AuthenticationService } from '../../services/authentication.service';
|
|
import { LogService } from '../../services/log.service';
|
|
import { TranslationService } from '../../services/translation.service';
|
|
import { UserPreferencesService } from '../../services/user-preferences.service';
|
|
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
|
|
|
import { LoginErrorEvent } from '../models/login-error.event';
|
|
import { LoginSubmitEvent } from '../models/login-submit.event';
|
|
import { LoginSuccessEvent } from '../models/login-success.event';
|
|
import {
|
|
AppConfigService,
|
|
AppConfigValues
|
|
} from '../../app-config/app-config.service';
|
|
import { OauthConfigModel } from '../../models/oauth-config.model';
|
|
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
|
import { Subject } from 'rxjs';
|
|
import { takeUntil } from 'rxjs/operators';
|
|
|
|
enum LoginSteps {
|
|
Landing = 0,
|
|
Checking = 1,
|
|
Welcome = 2
|
|
}
|
|
|
|
interface ValidationMessage {
|
|
value: string;
|
|
params?: any;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'adf-login',
|
|
templateUrl: './login.component.html',
|
|
styleUrls: ['./login.component.scss'],
|
|
encapsulation: ViewEncapsulation.None,
|
|
host: {
|
|
class: 'adf-login'
|
|
}
|
|
})
|
|
export class LoginComponent implements OnInit, OnDestroy {
|
|
isPasswordShow: boolean = false;
|
|
|
|
/**
|
|
* Should the `Remember me` checkbox be shown? When selected, this
|
|
* option will remember the logged-in user after the browser is closed
|
|
* to avoid logging in repeatedly.
|
|
*/
|
|
@Input()
|
|
showRememberMe: boolean = true;
|
|
|
|
/** Should the extra actions (`Need Help`, `Register`, etc) be shown? */
|
|
@Input()
|
|
showLoginActions: boolean = true;
|
|
|
|
/** Sets the URL of the NEED HELP link in the footer. */
|
|
@Input()
|
|
needHelpLink: string = '';
|
|
|
|
/** Sets the URL of the REGISTER link in the footer. */
|
|
@Input()
|
|
registerLink: string = '';
|
|
|
|
/** Path to a custom logo image. */
|
|
@Input()
|
|
logoImageUrl: string = './assets/images/alfresco-logo.svg';
|
|
|
|
/** Path to a custom background image. */
|
|
@Input()
|
|
backgroundImageUrl: string = './assets/images/background.svg';
|
|
|
|
/** The copyright text below the login box. */
|
|
@Input()
|
|
copyrightText: string = '\u00A9 2016 Alfresco Software, Inc. All Rights Reserved.';
|
|
|
|
/** Custom validation rules for the login form. */
|
|
@Input()
|
|
fieldsValidation: any;
|
|
|
|
/** Route to redirect to on successful login. */
|
|
@Input()
|
|
successRoute: string = null;
|
|
|
|
/** Emitted when the login is successful. */
|
|
@Output()
|
|
success = new EventEmitter<LoginSuccessEvent>();
|
|
|
|
/** Emitted when the login fails. */
|
|
@Output()
|
|
error = new EventEmitter<LoginErrorEvent>();
|
|
|
|
/** Emitted when the login form is submitted. */
|
|
@Output()
|
|
executeSubmit = new EventEmitter<LoginSubmitEvent>();
|
|
|
|
implicitFlow: boolean = false;
|
|
|
|
form: FormGroup;
|
|
isError: boolean = false;
|
|
errorMsg: string;
|
|
actualLoginStep: any = LoginSteps.Landing;
|
|
LoginSteps = LoginSteps;
|
|
rememberMe: boolean = true;
|
|
formError: { [id: string]: string };
|
|
minLength: number = 2;
|
|
footerTemplate: TemplateRef<any>;
|
|
headerTemplate: TemplateRef<any>;
|
|
data: any;
|
|
|
|
private _message: { [id: string]: { [id: string]: ValidationMessage } };
|
|
private onDestroy$ = new Subject<boolean>();
|
|
|
|
constructor(
|
|
private _fb: FormBuilder,
|
|
private authService: AuthenticationService,
|
|
private translateService: TranslationService,
|
|
private logService: LogService,
|
|
private router: Router,
|
|
private appConfig: AppConfigService,
|
|
private userPreferences: UserPreferencesService,
|
|
private route: ActivatedRoute,
|
|
private sanitizer: DomSanitizer,
|
|
private alfrescoApiService: AlfrescoApiService
|
|
) {
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.initFormError();
|
|
this.initFormFieldsDefault();
|
|
this.initFormFieldsMessages();
|
|
|
|
if (this.authService.isLoggedIn()) {
|
|
this.router.navigate([this.successRoute]);
|
|
} else {
|
|
|
|
if (this.authService.isOauth()) {
|
|
const oauth: OauthConfigModel = this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null);
|
|
if (oauth && oauth.silentLogin) {
|
|
this.redirectToImplicitLogin();
|
|
} else if (oauth && oauth.implicitFlow) {
|
|
this.implicitFlow = true;
|
|
}
|
|
}
|
|
|
|
this.route.queryParams.subscribe((params: Params) => {
|
|
const url = params['redirectUrl'];
|
|
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
|
|
|
|
this.authService.setRedirect({ provider, url });
|
|
});
|
|
}
|
|
|
|
if (this.fieldsValidation) {
|
|
this.form = this._fb.group(this.fieldsValidation);
|
|
}
|
|
|
|
this.form.valueChanges
|
|
.pipe(takeUntil(this.onDestroy$))
|
|
.subscribe(data => this.onValueChanged(data));
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.onDestroy$.next(true);
|
|
this.onDestroy$.complete();
|
|
}
|
|
|
|
submit() {
|
|
this.onSubmit(this.form.value);
|
|
}
|
|
|
|
redirectToImplicitLogin() {
|
|
this.alfrescoApiService.getInstance().oauth2Auth.implicitLogin();
|
|
}
|
|
|
|
/**
|
|
* Method called on submit form
|
|
* @param values
|
|
* @param event
|
|
*/
|
|
onSubmit(values: any): void {
|
|
this.disableError();
|
|
|
|
const args = new LoginSubmitEvent({
|
|
controls: { username: this.form.controls.username }
|
|
});
|
|
this.executeSubmit.emit(args);
|
|
|
|
if (!args.defaultPrevented) {
|
|
this.performLogin(values);
|
|
}
|
|
}
|
|
|
|
implicitLogin() {
|
|
if (this.authService.isLoggedIn()) {
|
|
this.router.navigate([this.successRoute]);
|
|
}
|
|
this.authService.ssoImplicitLogin();
|
|
}
|
|
|
|
/**
|
|
* The method check the error in the form and push the error in the formError object
|
|
* @param data
|
|
*/
|
|
onValueChanged(data: any) {
|
|
this.disableError();
|
|
for (const field in this.formError) {
|
|
if (field) {
|
|
this.formError[field] = '';
|
|
const hasError =
|
|
(this.form.controls[field].errors && data[field] !== '') ||
|
|
(this.form.controls[field].dirty &&
|
|
!this.form.controls[field].valid);
|
|
if (hasError) {
|
|
for (const key in this.form.controls[field].errors) {
|
|
if (key) {
|
|
const message = this._message[field][key];
|
|
if (message && message.value) {
|
|
const translated = this.translateService.instant(message.value, message.params);
|
|
this.formError[field] += translated;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private performLogin(values: any) {
|
|
this.actualLoginStep = LoginSteps.Checking;
|
|
this.authService
|
|
.login(values.username, values.password, this.rememberMe)
|
|
.subscribe(
|
|
(token: any) => {
|
|
const redirectUrl = this.authService.getRedirect();
|
|
|
|
this.actualLoginStep = LoginSteps.Welcome;
|
|
this.userPreferences.setStoragePrefix(values.username);
|
|
values.password = null;
|
|
this.success.emit(
|
|
new LoginSuccessEvent(token, values.username, null)
|
|
);
|
|
|
|
if (redirectUrl) {
|
|
this.authService.setRedirect(null);
|
|
this.router.navigateByUrl(redirectUrl);
|
|
} else if (this.successRoute) {
|
|
this.router.navigate([this.successRoute]);
|
|
}
|
|
},
|
|
(err: any) => {
|
|
this.actualLoginStep = LoginSteps.Landing;
|
|
this.displayErrorMessage(err);
|
|
this.isError = true;
|
|
this.error.emit(new LoginErrorEvent(err));
|
|
},
|
|
() => this.logService.info('Login done')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check and display the right error message in the UI
|
|
*/
|
|
private displayErrorMessage(err: any): void {
|
|
if (
|
|
err.error &&
|
|
err.error.crossDomain &&
|
|
err.error.message.indexOf('Access-Control-Allow-Origin') !== -1
|
|
) {
|
|
this.errorMsg = err.error.message;
|
|
} else if (
|
|
err.status === 403 &&
|
|
err.message.indexOf('Invalid CSRF-token') !== -1
|
|
) {
|
|
this.errorMsg = 'LOGIN.MESSAGES.LOGIN-ERROR-CSRF';
|
|
} else if (
|
|
err.status === 403 &&
|
|
err.message.indexOf('The system is currently in read-only mode') !==
|
|
-1
|
|
) {
|
|
this.errorMsg = 'LOGIN.MESSAGES.LOGIN-ECM-LICENSE';
|
|
} else {
|
|
this.errorMsg = 'LOGIN.MESSAGES.LOGIN-ERROR-CREDENTIALS';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a custom form error for a field
|
|
* @param field
|
|
* @param msg
|
|
*/
|
|
public addCustomFormError(field: string, msg: string) {
|
|
this.formError[field] += msg;
|
|
}
|
|
|
|
/**
|
|
* Add a custom validation rule error for a field
|
|
* @param field
|
|
* @param ruleId - i.e. required | minlength | maxlength
|
|
* @param msg
|
|
*/
|
|
addCustomValidationError(
|
|
field: string,
|
|
ruleId: string,
|
|
msg: string,
|
|
params?: any
|
|
) {
|
|
this._message[field][ruleId] = {
|
|
value: msg,
|
|
params
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Display and hide the password value.
|
|
*/
|
|
toggleShowPassword(event: MouseEvent | KeyboardEvent) {
|
|
event.stopPropagation();
|
|
this.isPasswordShow = !this.isPasswordShow;
|
|
}
|
|
|
|
/**
|
|
* The method return if a field is valid or not
|
|
* @param field
|
|
*/
|
|
isErrorStyle(field: AbstractControl) {
|
|
return !field.valid && field.dirty && !field.pristine;
|
|
}
|
|
|
|
/**
|
|
* Trim username
|
|
*/
|
|
trimUsername(event: any) {
|
|
event.target.value = event.target.value.trim();
|
|
}
|
|
|
|
getBackgroundUrlImageUrl(): SafeStyle {
|
|
return this.sanitizer.bypassSecurityTrustStyle(`url(${this.backgroundImageUrl})`);
|
|
}
|
|
|
|
/**
|
|
* Default formError values
|
|
*/
|
|
private initFormError() {
|
|
this.formError = {
|
|
username: '',
|
|
password: ''
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Init form fields messages
|
|
*/
|
|
private initFormFieldsMessages() {
|
|
this._message = {
|
|
username: {
|
|
required: {
|
|
value: 'LOGIN.MESSAGES.USERNAME-REQUIRED'
|
|
},
|
|
minlength: {
|
|
value: 'LOGIN.MESSAGES.USERNAME-MIN',
|
|
params: {
|
|
minLength: this.minLength
|
|
}
|
|
}
|
|
|
|
},
|
|
password: {
|
|
required: {
|
|
value: 'LOGIN.MESSAGES.PASSWORD-REQUIRED'
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private initFormFieldsDefault() {
|
|
this.form = this._fb.group({
|
|
username: ['', Validators.compose([Validators.required, Validators.minLength(this.minLength)])],
|
|
password: ['', Validators.required]
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Disable the error flag
|
|
*/
|
|
private disableError() {
|
|
this.isError = false;
|
|
this.initFormError();
|
|
}
|
|
}
|