UI: Add 2FA login page
This commit is contained in:
parent
866ad82187
commit
1ab9ed2467
@ -45,7 +45,8 @@ import { ActionNotificationShow } from '@core/notification/notification.actions'
|
||||
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
|
||||
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
|
||||
import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models';
|
||||
import { isDefinedAndNotNull, isMobileApp } from '@core/utils';
|
||||
import { isMobileApp } from '@core/utils';
|
||||
import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -70,6 +71,7 @@ export class AuthService {
|
||||
|
||||
redirectUrl: string;
|
||||
oauth2Clients: Array<OAuth2ClientInfo> = null;
|
||||
twoFactorAuthProviders: Array<TwoFaProviderInfo> = null;
|
||||
|
||||
private refreshTokenSubject: ReplaySubject<LoginResponse> = null;
|
||||
private jwtHelper = new JwtHelperService();
|
||||
@ -114,6 +116,18 @@ export class AuthService {
|
||||
|
||||
public login(loginRequest: LoginRequest): Observable<LoginResponse> {
|
||||
return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe(
|
||||
tap((loginResponse: LoginResponse) => {
|
||||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true);
|
||||
if (loginResponse.scope === Authority.PRE_VERIFICATION_TOKEN) {
|
||||
this.router.navigateByUrl(`login/mfa`);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
public checkTwoFaVerificationCode(providerType: TwoFactorAuthProviderType, verificationCode: number): Observable<LoginResponse> {
|
||||
return this.http.post<LoginResponse>(`/api/auth/2fa/verification/check?providerType=${providerType}&verificationCode=${verificationCode}`,
|
||||
null, defaultHttpOptions()).pipe(
|
||||
tap((loginResponse: LoginResponse) => {
|
||||
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true);
|
||||
}
|
||||
@ -215,6 +229,15 @@ export class AuthService {
|
||||
);
|
||||
}
|
||||
|
||||
public getAvailableTwoFaLoginProviders(): Observable<Array<TwoFaProviderInfo>> {
|
||||
return this.http.get<Array<TwoFaProviderInfo>>(`/api/auth/2fa/providers`, defaultHttpOptions()).pipe(
|
||||
catchError(() => of([])),
|
||||
tap((providers) => {
|
||||
this.twoFactorAuthProviders = providers;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean {
|
||||
if (authState && authState.authUser) {
|
||||
if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) {
|
||||
@ -382,6 +405,9 @@ export class AuthService {
|
||||
loadUserSubject.error(err);
|
||||
}
|
||||
);
|
||||
} else if (authPayload.authUser.authority === Authority.PRE_VERIFICATION_TOKEN) {
|
||||
loadUserSubject.next(authPayload);
|
||||
loadUserSubject.complete();
|
||||
} else if (authPayload.authUser.userId) {
|
||||
this.userService.getUser(authPayload.authUser.userId).subscribe(
|
||||
(user) => {
|
||||
@ -594,8 +620,8 @@ export class AuthService {
|
||||
private updateAndValidateToken(token, prefix, notify) {
|
||||
let valid = false;
|
||||
const tokenData = this.jwtHelper.decodeToken(token);
|
||||
const issuedAt = tokenData.iat;
|
||||
const expTime = tokenData.exp;
|
||||
const issuedAt = tokenData?.iat;
|
||||
const expTime = tokenData?.exp;
|
||||
if (issuedAt && expTime) {
|
||||
const ttl = expTime - issuedAt;
|
||||
if (ttl > 0) {
|
||||
|
||||
@ -94,6 +94,8 @@ export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
return true;
|
||||
})
|
||||
);
|
||||
} else if (path === 'login.mfa') {
|
||||
return of(this.router.parseUrl('/login'));
|
||||
} else {
|
||||
return of(true);
|
||||
}
|
||||
@ -114,6 +116,17 @@ export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
this.mobileService.handleMobileNavigation(path, params);
|
||||
return of(false);
|
||||
}
|
||||
if (authState.authUser.authority === Authority.PRE_VERIFICATION_TOKEN) {
|
||||
if (path === 'login.mfa') {
|
||||
return this.authService.getAvailableTwoFaLoginProviders().pipe(
|
||||
map(() => {
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
this.authService.logout();
|
||||
return of(false);
|
||||
}
|
||||
const defaultUrl = this.authService.defaultUrl(true, authState, path, params);
|
||||
if (defaultUrl) {
|
||||
// this.authService.gotoDefaultPlace(true);
|
||||
|
||||
@ -37,9 +37,9 @@
|
||||
<form [formGroup]="emailConfigForm" (ngSubmit)="nextStep()">
|
||||
<p class="mat-body step-description input" translate>security.2fa.dialog.email-step-description</p>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always">
|
||||
<mat-form-field fxFlex class="mat-block input-container" hideRequiredMarker floatLabel="always">
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="email"
|
||||
<input matInput formControlName="email" required
|
||||
placeholder="{{ 'security.2fa.dialog.email-step-label' | translate }}" />
|
||||
<mat-error *ngIf="emailConfigForm.get('email').hasError('required')">
|
||||
{{ 'user.email-required' | translate }}
|
||||
@ -64,11 +64,11 @@
|
||||
{{ 'security.2fa.dialog.verification-step-description' | translate : {address: emailConfigForm.get('email').value} }}
|
||||
</p>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always">
|
||||
<mat-form-field fxFlex class="mat-block code-container" hideRequiredMarker floatLabel="always">
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="verificationCode"
|
||||
maxlength="6" type="text"
|
||||
pattern="\d*"
|
||||
pattern="\d*" required
|
||||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}">
|
||||
<mat-error *ngIf="emailVerificationForm.get('verificationCode').invalid">
|
||||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }}
|
||||
|
||||
@ -37,9 +37,9 @@
|
||||
<form [formGroup]="smsConfigForm" (ngSubmit)="nextStep()">
|
||||
<p class="mat-body step-description input" translate>security.2fa.dialog.sms-step-description</p>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always">
|
||||
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always" hideRequiredMarker>
|
||||
<mat-label></mat-label>
|
||||
<input type="tel"
|
||||
<input type="tel" required
|
||||
[pattern]="phoneNumberPattern"
|
||||
matInput formControlName="phone"
|
||||
placeholder="{{ 'security.2fa.dialog.sms-step-label' | translate }}">
|
||||
@ -67,11 +67,11 @@
|
||||
{{ 'security.2fa.dialog.verification-step-description' | translate : {address: smsConfigForm.get('phone').value} }}
|
||||
</p>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always">
|
||||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always" hideRequiredMarker>
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="verificationCode"
|
||||
maxlength="6" type="text"
|
||||
pattern="\d*"
|
||||
pattern="\d*" required
|
||||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}">
|
||||
<mat-error *ngIf="smsVerificationForm.get('verificationCode').invalid">
|
||||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }}
|
||||
|
||||
@ -53,11 +53,11 @@
|
||||
<p class="mat-body qr-code-description" translate>security.2fa.dialog.scan-qr-code</p>
|
||||
<canvas fxFlex #canvas [ngStyle]="{display: totpAuthURL ? 'block' : 'none'}"></canvas>
|
||||
<p class="mat-body qr-code-description" style="margin-top: 30px;" translate>security.2fa.dialog.enter-verification-code</p>
|
||||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always">
|
||||
<mat-form-field fxFlex class="mat-block code-container" floatLabel="always" hideRequiredMarker>
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="verificationCode"
|
||||
maxlength="6" type="text"
|
||||
pattern="\d*"
|
||||
pattern="\d*" required
|
||||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}">
|
||||
<mat-error *ngIf="totpConfigForm.get('verificationCode').invalid">
|
||||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }}
|
||||
|
||||
@ -22,6 +22,8 @@ import { AuthGuard } from '@core/guards/auth.guard';
|
||||
import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component';
|
||||
import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component';
|
||||
import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component';
|
||||
import { TwoFactorAuthLoginComponent } from '@modules/login/pages/login/two-factor-auth-login.component';
|
||||
import { Authority } from '@shared/models/authority.enum';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -69,6 +71,16 @@ const routes: Routes = [
|
||||
module: 'public'
|
||||
},
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'login/mfa',
|
||||
component: TwoFactorAuthLoginComponent,
|
||||
data: {
|
||||
title: 'login.create-password',
|
||||
auth: [Authority.PRE_VERIFICATION_TOKEN],
|
||||
module: 'public'
|
||||
},
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -23,13 +23,15 @@ import { SharedModule } from '@app/shared/shared.module';
|
||||
import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component';
|
||||
import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component';
|
||||
import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component';
|
||||
import { TwoFactorAuthLoginComponent } from '@modules/login/pages/login/two-factor-auth-login.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
LoginComponent,
|
||||
ResetPasswordRequestComponent,
|
||||
ResetPasswordComponent,
|
||||
CreatePasswordComponent
|
||||
CreatePasswordComponent,
|
||||
TwoFactorAuthLoginComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2022 The Thingsboard 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.
|
||||
|
||||
-->
|
||||
<div class="tb-two-factor-auth-login-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center"
|
||||
style="width: 100%;">
|
||||
<mat-card fxFlex="initial" class="tb-two-factor-auth-login-card" >
|
||||
<mat-card-title class="mat-headline" fxLayout="row" fxLayoutAlign="start center">
|
||||
<button mat-icon-button type="button" (click)="cancelLogin()">
|
||||
<mat-icon>chevron_left</mat-icon>
|
||||
</button>
|
||||
{{ 'login.verify-your-identity' | translate }}
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
<div class="providers-container tb-default" fxLayout="column" fxLayoutGap="8px" *ngIf="!selectedProvider">
|
||||
<p class="mat-body" translate>login.select-way-to-verify</p>
|
||||
<ng-container *ngFor="let provider of allowProviders">
|
||||
<button type="button" mat-stroked-button class="provider" (click)="selectProvider(provider)">
|
||||
<mat-icon class="icon" svgIcon="{{ providersData.get(provider).icon }}"></mat-icon>
|
||||
{{ providersData.get(provider).name | translate }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<form [formGroup]="verificationForm" (ngSubmit)="sendVerificationCode()" *ngIf="selectedProvider">
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<div tb-toast fxLayout="column">
|
||||
<p class="mat-body">{{ providerDescription }}</p>
|
||||
<div fxLayout="row" class="code-block" fxLayoutGap="8px">
|
||||
<mat-form-field class="mat-block" fxFlex floatLabel="always" hideRequiredMarker>
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="verificationCode"
|
||||
required maxlength="6" type="text" pattern="\d*"
|
||||
placeholder="{{ 'security.2fa.dialog.verification-code' | translate }}"/>
|
||||
<mat-error *ngIf="verificationForm.get('verificationCode').invalid">
|
||||
{{ 'security.2fa.dialog.verification-code-invalid' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxLayoutAlign="start center" *ngIf="selectedProvider !== twoFactorAuthProvider.TOTP">
|
||||
<button
|
||||
mat-button
|
||||
(click)="sendCode()"
|
||||
type="button">
|
||||
{{ 'login.resend-code' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span style="height: 40px;"></span>
|
||||
<div fxLayout="column" fxLayoutGap="8px">
|
||||
<button mat-raised-button
|
||||
color="accent"
|
||||
[disabled]="(isLoading$ | async) || verificationForm.invalid"
|
||||
type="submit">
|
||||
{{ 'action.continue' | translate }}
|
||||
</button>
|
||||
<button mat-button
|
||||
type="button"
|
||||
(click)="selectProvider(null)">
|
||||
{{ 'login.try-another-way' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright © 2016-2022 The Thingsboard 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 '../../../../../scss/constants';
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
.tb-two-factor-auth-login-content {
|
||||
background-color: #eee;
|
||||
.tb-two-factor-auth-login-card {
|
||||
padding: 48px 48px 48px 16px;
|
||||
|
||||
@media #{$mat-gt-xs} {
|
||||
width: 450px !important;
|
||||
}
|
||||
|
||||
.mat-card-title{
|
||||
font: 500 28px / 36px Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.mat-card-content {
|
||||
margin-top: 44px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.mat-body {
|
||||
letter-spacing: 0.25px;
|
||||
line-height: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.providers-container{
|
||||
padding: 0;
|
||||
|
||||
.mat-body {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
::ng-deep{
|
||||
button.provider {
|
||||
text-align: start;
|
||||
&:not(.mat-button-disabled) {
|
||||
border-color: rgba(255, 255, 255, .8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
///
|
||||
/// Copyright © 2016-2022 The Thingsboard 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 { Component, OnInit } from '@angular/core';
|
||||
import { AuthService } from '@core/auth/auth.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service';
|
||||
import {
|
||||
twoFactorAuthProvidersLoginData,
|
||||
TwoFactorAuthProviderType,
|
||||
TwoFaProviderInfo
|
||||
} from '@shared/models/two-factor-auth.models';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-two-factor-auth-login',
|
||||
templateUrl: './two-factor-auth-login.component.html',
|
||||
styleUrls: ['./two-factor-auth-login.component.scss']
|
||||
})
|
||||
export class TwoFactorAuthLoginComponent extends PageComponent implements OnInit {
|
||||
|
||||
private providersInfo: TwoFaProviderInfo[];
|
||||
|
||||
selectedProvider: TwoFactorAuthProviderType;
|
||||
twoFactorAuthProvider = TwoFactorAuthProviderType;
|
||||
allowProviders: TwoFactorAuthProviderType[] = [];
|
||||
|
||||
providersData = twoFactorAuthProvidersLoginData;
|
||||
providerDescription = '';
|
||||
|
||||
verificationForm = this.fb.group({
|
||||
verificationCode: ['', [
|
||||
Validators.required,
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(6),
|
||||
Validators.pattern(/^\d*$/)
|
||||
]]
|
||||
});
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private twoFactorAuthService: TwoFactorAuthenticationService,
|
||||
private authService: AuthService,
|
||||
private translate: TranslateService,
|
||||
private fb: FormBuilder) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.providersInfo = this.authService.twoFactorAuthProviders;
|
||||
Object.values(TwoFactorAuthProviderType).forEach(provider => {
|
||||
const providerConfig = this.providersInfo.find(config => config.type === provider);
|
||||
if (providerConfig) {
|
||||
if (providerConfig.default) {
|
||||
this.selectedProvider = providerConfig.type;
|
||||
this.providerDescription = this.translate.instant(this.providersData.get(providerConfig.type).description, {
|
||||
contact: providerConfig.contact
|
||||
});
|
||||
}
|
||||
this.allowProviders.push(providerConfig.type);
|
||||
}
|
||||
});
|
||||
if (this.selectedProvider !== TwoFactorAuthProviderType.TOTP) {
|
||||
this.sendCode();
|
||||
}
|
||||
}
|
||||
|
||||
sendVerificationCode() {
|
||||
if (this.verificationForm.valid && this.selectedProvider) {
|
||||
this.authService.checkTwoFaVerificationCode(this.selectedProvider, this.verificationForm.get('verificationCode').value).subscribe(
|
||||
() => {}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
selectProvider(type: TwoFactorAuthProviderType) {
|
||||
this.selectedProvider = type;
|
||||
const providerConfig = this.providersInfo.find(config => config.type === type);
|
||||
this.providerDescription = this.translate.instant(this.providersData.get(providerConfig.type).description, {
|
||||
contact: providerConfig.contact
|
||||
});
|
||||
if (type !== TwoFactorAuthProviderType.TOTP && type !== null) {
|
||||
this.sendCode();
|
||||
}
|
||||
}
|
||||
|
||||
sendCode() {
|
||||
this.twoFactorAuthService.requestTwoFaVerificationCodeSend(this.selectedProvider).subscribe(() => {});
|
||||
}
|
||||
|
||||
cancelLogin() {
|
||||
this.authService.logout();
|
||||
}
|
||||
}
|
||||
@ -19,5 +19,6 @@ export enum Authority {
|
||||
TENANT_ADMIN = 'TENANT_ADMIN',
|
||||
CUSTOMER_USER = 'CUSTOMER_USER',
|
||||
REFRESH_TOKEN = 'REFRESH_TOKEN',
|
||||
ANONYMOUS = 'ANONYMOUS'
|
||||
ANONYMOUS = 'ANONYMOUS',
|
||||
PRE_VERIFICATION_TOKEN = 'PRE_VERIFICATION_TOKEN'
|
||||
}
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Authority } from '@shared/models/authority.enum';
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
@ -26,4 +28,5 @@ export interface PublicLoginRequest {
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
scope?: Authority;
|
||||
}
|
||||
|
||||
@ -92,6 +92,7 @@ export interface AccountTwoFaSettings {
|
||||
export interface TwoFaProviderInfo {
|
||||
type: TwoFactorAuthProviderType;
|
||||
default: boolean;
|
||||
contact?: string;
|
||||
}
|
||||
|
||||
export interface TwoFactorAuthProviderData {
|
||||
@ -99,6 +100,10 @@ export interface TwoFactorAuthProviderData {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface TwoFactorAuthProviderLoginData extends TwoFactorAuthProviderData {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export const twoFactorAuthProvidersData = new Map<TwoFactorAuthProviderType, TwoFactorAuthProviderData>(
|
||||
[
|
||||
[
|
||||
@ -121,3 +126,29 @@ export const twoFactorAuthProvidersData = new Map<TwoFactorAuthProviderType, Two
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
export const twoFactorAuthProvidersLoginData = new Map<TwoFactorAuthProviderType, TwoFactorAuthProviderLoginData>(
|
||||
[
|
||||
[
|
||||
TwoFactorAuthProviderType.TOTP, {
|
||||
name: 'security.2fa.provider.totp',
|
||||
description: 'login.totp-auth-description',
|
||||
icon: 'mdi:cellphone-key'
|
||||
}
|
||||
],
|
||||
[
|
||||
TwoFactorAuthProviderType.SMS, {
|
||||
name: 'security.2fa.provider.sms',
|
||||
description: 'login.sms-auth-description',
|
||||
icon: 'mdi:message-reply-text-outline'
|
||||
}
|
||||
],
|
||||
[
|
||||
TwoFactorAuthProviderType.EMAIL, {
|
||||
name: 'security.2fa.provider.email',
|
||||
description: 'login.email-auth-description',
|
||||
icon: 'mdi:email-outline'
|
||||
}
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
@ -2480,7 +2480,14 @@
|
||||
"email": "Email",
|
||||
"login-with": "Login with {{name}}",
|
||||
"or": "or",
|
||||
"error": "Login error"
|
||||
"error": "Login error",
|
||||
"verify-your-identity": "Verify your identity",
|
||||
"select-way-to-verify": "Select a way to verify",
|
||||
"resend-code": "Resend code",
|
||||
"try-another-way": "Try another way",
|
||||
"totp-auth-description": "Please enter the security code from your authenticator app.",
|
||||
"sms-auth-description": "A security code has been sent to your phone at {{contact}}.",
|
||||
"email-auth-description": "A security code has been sent to your email address at {{contact}}."
|
||||
},
|
||||
"markdown": {
|
||||
"copy-code": "Click to copy",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user