Improve login page for OIDC (#15214)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
孙世军 2021-07-02 09:42:04 +08:00 committed by GitHub
parent 29ccdff766
commit bb57264f11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 137 additions and 98 deletions

View File

@ -2,55 +2,80 @@
<global-message [isAppLevel]="true"></global-message>
<navigator (showDialogModalAction)="openModal($event)"></navigator>
<search-result></search-result>
<div class="login-wrapper" [ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
<div class="login-wrapper"
[ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
<form #signInForm="ngForm" class="login">
<label class="title"> {{customAppTitle? customAppTitle:(appTitle | translate)}}
<span *ngIf="isOidcLoginMode && steps===2" (click)="steps=1" class="back-icon">
<clr-icon shape="angle left"></clr-icon>
<div class="margin-left-3">{{'SEARCH.BACK' | translate }}</div>
</span>
<label class="title"> {{customAppTitle ? customAppTitle : (appTitle | translate)}}
</label>
<a href="/c/oidc/login" class="login-oidc" *ngIf="isOidcLoginMode">
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
</button>
</a>
<div class="login-group">
<clr-input-container>
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal" name="login_username" id="login_username"
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
</clr-input-container>
<div class="clr-form-control">
<div class="clr-control-container" [class.clr-error] ="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
<div class="clr-input-wrapper">
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required [(ngModel)]="signInCredential.password" name="login_password" id="login_password"
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye" (click)="showPwd =!showPwd"></clr-icon>
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye" (click)="showPwd =!showPwd"></clr-icon>
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
</clr-control-error>
<ng-container *ngIf="isOidcLoginMode && steps ===1">
<a href="/c/oidc/login">
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
</button>
</a>
<div class="divider-container mt-1 mb-1">
<hr class="divider" size="1" noshade/>
<h4 class="m-0"><span>{{'SIGN_IN.OR' | translate }}</span></h4>
<hr class="divider" size="1" noshade/>
</div>
<a href="javascript:void(0)" (click)="steps=2">
<button type="button" id="login-db" class="btn btn-primary btn-block mt-0">
<span>{{'SIGN_IN.VIA_LOCAL_DB' | translate }}</span>
</button>
</a>
</ng-container>
<ng-container *ngIf="(!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)">
<clr-input-container class="mt-3">
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal"
name="login_username" id="login_username"
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
</clr-input-container>
<div class="clr-form-control">
<div class="clr-control-container"
[class.clr-error]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
<div class="clr-input-wrapper">
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required
[(ngModel)]="signInCredential.password" name="login_password" id="login_password"
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye"
(click)="showPwd =!showPwd"></clr-icon>
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye"
(click)="showPwd =!showPwd"></clr-icon>
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error
*ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
</clr-control-error>
</div>
</div>
<div class="display-flex">
<clr-checkbox-wrapper>
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox
(click)="clickRememberMe($event)" [checked]="rememberMe">
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
</clr-checkbox-wrapper>
</div>
<div *ngIf="isError" class="error active">
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
</div>
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary mt-2" (click)="signIn()"
id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
<a href="javascript:void(0)" class="signup" (click)="signUp()"
*ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
</ng-container>
<div class="more-info" [ngClass]="{'pt-1': (!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)}">
<a href="https://github.com/goharbor/harbor" target="_blank">{{ 'BUTTON.MORE_INFO' | translate }}</a>
</div>
<div class="display-flex">
<clr-checkbox-wrapper>
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox (click)="clickRememberMe($event)" [checked]="rememberMe">
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
</clr-checkbox-wrapper>
</div>
<div [class.visibility-hidden]="!isError" class="error active">
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
</div>
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()" id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
<a href="javascript:void(0)" class="signup" (click)="signUp()" *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
</div>
<div>
<a href="https://github.com/goharbor/harbor" target="_blank" class="more-info-link">{{ 'BUTTON.MORE_INFO' | translate }}</a>
</div>
</form>
<div *ngIf="appConfig.show_popular_repo" id="pop_repo" class="popular-repo-wrapper">
<top-repo class="repo-container"></top-repo>
</div>
</div>
</clr-main-container>
<sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up>

View File

@ -14,31 +14,6 @@
top: -5px;
}
.popular-repo-wrapper {
background-color: white;
min-height: 100vh;
display: flex;
flex-grow: 1;
margin-top: 24px;
justify-content: center;
flex-direction: column;
height: auto;
position: relative;
border-left: 1px solid #eee;
}
.repo-container {
width: 100%;
margin-top: -210px;
}
.more-info-link {
position: relative;
top: 80px;
left: 294px;
padding-right: 36px;
}
.tm-font {
font-size: 14px !important;
position: relative;
@ -59,10 +34,6 @@
background:transparent;
}
}
.login-oidc{
margin-top: 50px;
}
.reset-label {
font-size:inherit;
color:inherit;
@ -81,4 +52,42 @@
}
.pwd-input {
padding-right: 26px;
}
}
.divider-container {
display: flex;
align-items: center;
justify-content: space-between;
}
.divider {
margin: 0;
width: 8.5rem;
}
.login {
padding-bottom: 180px;
}
.back-icon {
position: absolute;
top: 1rem;
display: flex;
align-items: center;
color: #007cbb;
font-size: 16px;
cursor: pointer;
}
.margin-left-3 {
margin-left: 3px;
}
.title {
position: absolute;
top: 10rem
}
.login-group {
width: 70%;
position: absolute;
top: 10rem
}
.more-info {
text-align: right;
padding-top: 2rem;
}

View File

@ -28,6 +28,7 @@ import { AboutDialogComponent } from "../../shared/components/about-dialog/about
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;
@ -45,7 +46,6 @@ const expireDays = 10;
export class SignInComponent implements AfterViewChecked, OnInit {
showPwd: boolean = false;
redirectUrl: string = "";
appConfig: AppConfig = new AppConfig();
// Remeber me indicator
rememberMe: boolean = false;
rememberedName: string = "";
@ -68,6 +68,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
password: ""
};
isCoreServiceAvailable: boolean = true;
steps: number = 1;
hasLoadedAppConfig: boolean = false;
constructor(
private router: Router,
private session: SessionService,
@ -88,15 +90,6 @@ export class SignInComponent implements AfterViewChecked, OnInit {
this.customAppTitle = customSkinObj.loginTitle;
}
}
// Before login: Make sure the updated configuration can be loaded
this.appConfigService.load()
.subscribe(updatedConfig => this.appConfig = updatedConfig
, error => {
// Catch the error
console.error("Failed to load bootstrap options with error: ", error);
});
this.route.queryParams
.subscribe(params => {
this.redirectUrl = params["redirect_url"] || "";
@ -117,7 +110,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
// App title
public get appTitle(): string {
if (this.appConfig && this.appConfig.with_admiral) {
if (this.appConfigService.getConfig() && this.appConfigService.getConfig().with_admiral) {
return "APP_TITLE.VIC";
}
@ -140,11 +133,11 @@ export class SignInComponent implements AfterViewChecked, OnInit {
// Whether show the 'sign up' link
public get selfSignUp(): boolean {
return this.appConfig.auth_mode === CONFIG_AUTH_MODE.DB_AUTH
&& this.appConfig.self_registration;
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.appConfig.auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
}
clickRememberMe($event: any): void {
if ($event && $event.target) {
@ -261,11 +254,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
// after login successfully: Make sure the updated configuration can be loaded
this.appConfigService.load()
.subscribe(updatedConfig => this.appConfig = updatedConfig
, error => {
// Catch the error
console.error("Failed to load bootstrap options with error: ", error);
});
.subscribe();
}, error => {
// 403 oidc login no body;
if (this.isOidcLoginMode && error && error.status === 403) {

View File

@ -14,6 +14,8 @@
"INVALID_MSG": "Falscher Nutzername oder Passwort.",
"FORGOT_PWD": "Passwort vergessen",
"HEADER_LINK": "Einloggen",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB",
"CORE_SERVICE_NOT_AVAILABLE": "Core Service ist nicht verfügbar"
},
"SIGN_UP": {

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "Invalid user name or password.",
"FORGOT_PWD": "Forgot password",
"HEADER_LINK": "Sign In",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
},
"SIGN_UP": {
"TITLE": "Sign Up"

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "Nombre o contraseña no válidos.",
"FORGOT_PWD": "Olvidé mi contraseña",
"HEADER_LINK": "Identificarse",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
},
"SIGN_UP": {
"TITLE": "Registrarse"

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "Nom d'utilisateur ou mot de passe invalide.",
"FORGOT_PWD": "Mot de passe oublié",
"HEADER_LINK": "S'identifier",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
},
"SIGN_UP": {
"TITLE": "S'inscrire"

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "Usuário ou senha inválidos",
"FORGOT_PWD": "Esqueci a senha",
"HEADER_LINK": "Logar-se",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
},
"SIGN_UP": {
"TITLE": "Registrar-se"

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "Geçersiz kullanıcı adı veya şifre.",
"FORGOT_PWD": "Parolamı Unuttum",
"HEADER_LINK": "Oturum aç",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
},
"SIGN_UP": {
"TITLE": "Kayıt ol"

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "用户名或者密码不正确。",
"FORGOT_PWD": "忘记密码",
"HEADER_LINK": "登录",
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。"
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。",
"OR": "或",
"VIA_LOCAL_DB": "通过本地数据库登录"
},
"SIGN_UP": {
"TITLE": "注册"

View File

@ -14,7 +14,9 @@
"INVALID_MSG": "用戶名或者密碼不正確。",
"FORGOT_PWD": "忘記密碼",
"HEADER_LINK": "登錄",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available.",
"OR": "OR",
"VIA_LOCAL_DB": "LOGIN VIA LOCAL DB"
},
"SIGN_UP": {
"TITLE": "註冊"