harbor/src/portal/src/app/account/sign-in/sign-in.component.ts

319 lines
10 KiB
TypeScript

// Copyright Project Harbor Authors
//
// 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 {
AfterViewChecked,
Component,
Input,
OnInit,
ViewChild,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { NgForm } from '@angular/forms';
import { SessionService } from '../../shared/services/session.service';
import { SignUpComponent } from '../sign-up/sign-up.component';
import { ForgotPasswordComponent } from '../../base/password-setting/forgot-password/forgot-password.component';
import { AppConfigService } from '../../services/app-config.service';
import { AppConfig } from '../../services/app-config';
import { User } from '../../base/left-side-nav/user/user';
import { CookieOptions, CookieService } from 'ngx-cookie';
import { SkinableConfig } from '../../services/skinable-config.service';
import { ModalEvent } from '../../base/modal-event';
import { modalEvents } from '../../base/modal-events.const';
import { AboutDialogComponent } from '../../shared/components/about-dialog/about-dialog.component';
import {
CommonRoutes,
CONFIG_AUTH_MODE,
} from '../../shared/entities/shared.const';
import { SignInCredential } from './sign-in-credential';
import { UserPermissionService } from '../../shared/services';
import { finalize } from 'rxjs/operators';
// Define status flags for signing in states
export const signInStatusNormal = 0;
export const signInStatusOnGoing = 1;
export const signInStatusError = -1;
const remCookieKey = 'rem-username';
const expireDays = 10;
@Component({
selector: 'sign-in',
templateUrl: 'sign-in.component.html',
styleUrls: ['sign-in.component.scss'],
})
export class SignInComponent implements AfterViewChecked, OnInit {
showPwd: boolean = false;
redirectUrl: string = '';
// Remeber me indicator
rememberMe: boolean = false;
rememberedName: string = '';
customLoginBgImg: string;
customAppTitle: string;
// Form reference
signInForm: NgForm;
@ViewChild('signInForm', { static: true }) currentForm: NgForm;
@ViewChild('signupDialog') signUpDialog: SignUpComponent;
@ViewChild('forgotPwdDialog') forgotPwdDialog: ForgotPasswordComponent;
@ViewChild(AboutDialogComponent) aboutDialog: AboutDialogComponent;
// Status flag
signInStatus: number = signInStatusNormal;
// Initialize sign in credential
@Input() signInCredential: SignInCredential = {
principal: '',
password: '',
};
isCoreServiceAvailable: boolean = true;
steps: number = 1;
hasLoadedAppConfig: boolean = false;
constructor(
private router: Router,
private session: SessionService,
private route: ActivatedRoute,
private appConfigService: AppConfigService,
private cookie: CookieService,
private skinableConfig: SkinableConfig,
private userPermissionService: UserPermissionService
) {}
ngOnInit(): void {
// custom skin
let customSkinObj = this.skinableConfig.getSkinConfig();
if (customSkinObj) {
if (customSkinObj.loginBgImg) {
this.customLoginBgImg = customSkinObj.loginBgImg;
}
if (customSkinObj.loginTitle) {
this.customAppTitle = customSkinObj.loginTitle;
}
}
this.route.queryParams.subscribe(params => {
this.redirectUrl = params['redirect_url'] || '';
let isSignUp = params['sign_up'] || '';
if (isSignUp !== '') {
this.signUp(); // Open sign up
}
});
let remUsername = this.cookie.get(remCookieKey);
remUsername = remUsername ? remUsername.trim() : '';
if (remUsername) {
this.signInCredential.principal = remUsername;
this.rememberMe = true;
this.rememberedName = remUsername;
}
}
// App title
public get appTitle(): string {
return 'APP_TITLE.VMW_HARBOR';
}
// For template accessing
public get isError(): boolean {
return this.signInStatus === signInStatusError;
}
public get isOnGoing(): boolean {
return this.signInStatus === signInStatusOnGoing;
}
// Validate the related fields
public get isValid(): boolean {
return this.currentForm.form.valid;
}
// Whether show the 'sign up' link
public get selfSignUp(): boolean {
return (
this.appConfigService.getConfig() &&
this.appConfigService.getConfig().auth_mode ===
CONFIG_AUTH_MODE.DB_AUTH &&
this.appConfigService.getConfig().self_registration
);
}
public get isOidcLoginMode(): boolean {
return (
this.appConfigService.getConfig() &&
this.appConfigService.getConfig().auth_mode ===
CONFIG_AUTH_MODE.OIDC_AUTH
);
}
clickRememberMe($event: any): void {
if ($event && $event.target) {
this.rememberMe = $event.target.checked;
if (!this.rememberMe) {
// Remove cookie data
this.cookie.remove(remCookieKey);
this.rememberedName = '';
}
}
}
remeberMe(): void {
if (this.rememberMe) {
if (this.rememberedName !== this.signInCredential.principal) {
// Set expire time
let expires: number = expireDays * 3600 * 24 * 1000;
let date = new Date(Date.now() + expires);
let cookieptions: CookieOptions = {
path: '/',
expires: date,
};
this.cookie.put(
remCookieKey,
this.signInCredential.principal,
cookieptions
);
}
}
}
// General error handler
handleError(error: any) {
// Set error status
this.signInStatus = signInStatusError;
let message = error.status
? error.status + ':' + error.statusText
: error;
console.error('An error occurred when signing in:', message);
}
// Hande form values changes
formChanged() {
if (this.currentForm === this.signInForm) {
return;
}
this.signInForm = this.currentForm;
if (this.signInForm) {
this.signInForm.valueChanges.subscribe(data => {
this.updateState();
});
}
}
// Fill the new user info into the sign in form
handleUserCreation(user: User): void {
if (user) {
this.currentForm.setValue({
login_username: user.username,
login_password: '',
});
}
}
// Implement interface
// Watch the view change only when view is in error state
ngAfterViewChecked() {
if (this.signInStatus === signInStatusError) {
this.formChanged();
}
}
// Update the status if we have done some changes
updateState(): void {
if (this.signInStatus === signInStatusError) {
this.signInStatus = signInStatusNormal; // reset
}
}
// Trigger the signin action
signIn(): void {
// Should validate input firstly
if (!this.isValid) {
// Set error status
this.signInStatus = signInStatusError;
return;
}
if (this.isOnGoing) {
// Ongoing, directly return
return;
}
// Start signing in progress
this.signInStatus = signInStatusOnGoing;
// Call the service to send out the http request
this.session.signIn(this.signInCredential).subscribe(
() => {
// Set status
// Keep it ongoing to keep the button 'disabled'
// this.signInStatus = signInStatusNormal;
// clear permissions cache
this.userPermissionService.clearPermissionCache();
// Remeber me
this.remeberMe();
// Redirect to the right router-guard
if (
!this.redirectUrl ||
this.redirectUrl.startsWith(CommonRoutes.EMBEDDED_SIGN_IN)
) {
// Routing to the default location
this.router.navigateByUrl(CommonRoutes.HARBOR_DEFAULT);
} else {
this.router.navigateByUrl(this.redirectUrl);
}
this.isCoreServiceAvailable = true;
// after login successfully: Make sure the updated configuration can be loaded
this.appConfigService.load().subscribe();
},
error => {
// 403 oidc login no body;
if (this.isOidcLoginMode && error && error.status === 403) {
try {
let redirect_location = '';
redirect_location =
error.error && error.error.redirect_location
? error.error.redirect_location
: JSON.parse(error.error).redirect_location;
window.location.href = redirect_location;
return;
} catch (error) {}
}
// core service is not available for error code 5xx
if (error && /5[0-9][0-9]/.test(error.status)) {
this.isCoreServiceAvailable = false;
}
this.handleError(error);
}
);
}
// Open sign up dialog
signUp(): void {
this.signUpDialog.open();
}
// Open forgot password dialog
forgotPassword(): void {
this.forgotPwdDialog.open();
}
// Open modal dialog
openModal(event: ModalEvent): void {
switch (event.modalName) {
case modalEvents.ABOUT:
this.aboutDialog.open();
break;
default:
break;
}
}
}