mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
fix(console): u2f, mfa, loginpolicy, auth and mgmt passwordless, clockskew, userinfo within idtoken (#1108)
* fix 2fa,mfa config, self management * u2f enable when otp * passwordless grpc auth * clockskew, passwordless container, util class * passwordless, i18n * passwordless auth and mgmt * lint ts * chore(deps-dev): bump ts-node from 9.1.0 to 9.1.1 in /console (#1089) Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 9.1.0 to 9.1.1. - [Release notes](https://github.com/TypeStrong/ts-node/releases) - [Commits](https://github.com/TypeStrong/ts-node/compare/v9.1.0...v9.1.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/cli from 11.0.3 to 11.0.4 in /console (#1094) Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.0.3 to 11.0.4. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.0.3...v11.0.4) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * chore(deps): bump uuid from 8.3.1 to 8.3.2 in /console (#1098) Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.1 to 8.3.2. - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v8.3.1...v8.3.2) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * chore(deps-dev): bump @angular/language-service in /console (#1099) Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.0.3 to 11.0.4. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.0.4/packages/language-service) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/node from 14.14.10 to 14.14.13 in /console (#1100) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.10 to 14.14.13. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular-devkit/build-angular in /console (#1088) Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1100.3 to 0.1100.4. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * fix: replace regex check for projectid (#1064) * update lock * fix app detail * logs * fix login policy update * fix error message * decode excluded cred id Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
parent
71df1bcd0e
commit
6aa0588fe0
779
console/package-lock.json
generated
779
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,17 +40,17 @@
|
|||||||
"rxjs": "~6.6.3",
|
"rxjs": "~6.6.3",
|
||||||
"ts-protoc-gen": "^0.13.0",
|
"ts-protoc-gen": "^0.13.0",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"uuid": "^8.3.1",
|
"uuid": "^8.3.2",
|
||||||
"zone.js": "~0.11.3"
|
"zone.js": "~0.11.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.1100.3",
|
"@angular-devkit/build-angular": "~0.1100.4",
|
||||||
"@angular/cli": "~11.0.3",
|
"@angular/cli": "~11.0.4",
|
||||||
"@angular/compiler-cli": "~11.0.0",
|
"@angular/compiler-cli": "~11.0.0",
|
||||||
"@types/jasmine": "~3.6.2",
|
"@types/jasmine": "~3.6.2",
|
||||||
"@angular/language-service": "~11.0.3",
|
"@angular/language-service": "~11.0.4",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/node": "^14.14.10",
|
"@types/node": "^14.14.13",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~6.0.0",
|
"jasmine-spec-reporter": "~6.0.0",
|
||||||
@ -64,7 +64,7 @@
|
|||||||
"stylelint": "^13.8.0",
|
"stylelint": "^13.8.0",
|
||||||
"stylelint-config-standard": "^20.0.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"stylelint-scss": "^3.18.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
"ts-node": "~9.1.0",
|
"ts-node": "~9.1.1",
|
||||||
"tslint": "~6.1.3",
|
"tslint": "~6.1.3",
|
||||||
"typescript": "^4.0.5"
|
"typescript": "^4.0.5"
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions class="action">
|
<div mat-dialog-actions class="action">
|
||||||
<button mat-button (click)="closeDialog()"><span translate>ACTIONS.CLOSE</span></button>
|
<button mat-button (click)="closeDialog()"><span>{{'ACTIONS.CLOSE' | translate}}</span></button>
|
||||||
<button mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()"><span
|
<button [disabled]="newMfaType == undefined" mat-raised-button class="ok-button" color="primary"
|
||||||
translate>ACTIONS.OK</span>
|
(click)="closeDialogWithCode()"><span>{{'ACTIONS.OK' | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@ -4,12 +4,16 @@ import { MatPaginator } from '@angular/material/paginator';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
MultiFactor as AdminMultiFactor, MultiFactorType as AdminMultiFactorType,
|
MultiFactor as AdminMultiFactor,
|
||||||
SecondFactor as AdminSecondFactor, SecondFactorType as AdminSecondFactorType,
|
MultiFactorType as AdminMultiFactorType,
|
||||||
|
SecondFactor as AdminSecondFactor,
|
||||||
|
SecondFactorType as AdminSecondFactorType,
|
||||||
} from 'src/app/proto/generated/admin_pb';
|
} from 'src/app/proto/generated/admin_pb';
|
||||||
import {
|
import {
|
||||||
MultiFactor as MgmtMultiFactor, MultiFactorType as MgmtMultiFactorType,
|
MultiFactor as MgmtMultiFactor,
|
||||||
SecondFactor as MgmtSecondFactor, SecondFactorType as MgmtSecondFactorType,
|
MultiFactorType as MgmtMultiFactorType,
|
||||||
|
SecondFactor as MgmtSecondFactor,
|
||||||
|
SecondFactorType as MgmtSecondFactorType,
|
||||||
} from 'src/app/proto/generated/management_pb';
|
} from 'src/app/proto/generated/management_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
@ -111,11 +115,19 @@ export class MfaTableComponent implements OnInit {
|
|||||||
[];
|
[];
|
||||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||||
selection = this.serviceType === PolicyComponentServiceType.MGMT ?
|
selection = this.serviceType === PolicyComponentServiceType.MGMT ?
|
||||||
[MgmtSecondFactorType.SECONDFACTORTYPE_U2F, MgmtSecondFactorType.SECONDFACTORTYPE_U2F] :
|
[MgmtSecondFactorType.SECONDFACTORTYPE_U2F, MgmtSecondFactorType.SECONDFACTORTYPE_OTP] :
|
||||||
this.serviceType === PolicyComponentServiceType.ADMIN ?
|
this.serviceType === PolicyComponentServiceType.ADMIN ?
|
||||||
[AdminSecondFactorType.SECONDFACTORTYPE_OTP, AdminSecondFactorType.SECONDFACTORTYPE_U2F] :
|
[AdminSecondFactorType.SECONDFACTORTYPE_OTP, AdminSecondFactorType.SECONDFACTORTYPE_U2F] :
|
||||||
[];
|
[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.mfas.forEach(mfa => {
|
||||||
|
const index = selection.findIndex(sel => sel === mfa);
|
||||||
|
if (index > -1) {
|
||||||
|
selection.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
|
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
|
||||||
data: {
|
data: {
|
||||||
title: 'MFA.CREATE.TITLE',
|
title: 'MFA.CREATE.TITLE',
|
||||||
|
@ -51,6 +51,16 @@
|
|||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
<p> {{'POLICY.DATA.FORCEMFA_DESC' | translate}} </p>
|
<p> {{'POLICY.DATA.FORCEMFA_DESC' | translate}} </p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<cnsl-form-field class="form-field" label="Access Code" required="true">
|
||||||
|
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
|
||||||
|
<mat-select [(ngModel)]="loginData.passwordlessType">
|
||||||
|
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
|
||||||
|
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</cnsl-form-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
|
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
|
||||||
@ -58,19 +68,21 @@
|
|||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<h3 class="subheader">{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}</h3>
|
<ng-container *ngIf="!isDefault">
|
||||||
<p class="subdesc">{{ 'MFA.LIST.MULTIFACTORDESCRIPTION' | translate }}</p>
|
<h3 class="subheader">{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}</h3>
|
||||||
<app-mfa-table [service]="service" [serviceType]="serviceType"
|
<p class="subdesc">{{ 'MFA.LIST.MULTIFACTORDESCRIPTION' | translate }}</p>
|
||||||
[componentType]="LoginMethodComponentType.MultiFactor"
|
<app-mfa-table [service]="service" [serviceType]="serviceType"
|
||||||
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.policy.write' : ''] | hasRole | async) == false">
|
[componentType]="LoginMethodComponentType.MultiFactor"
|
||||||
</app-mfa-table>
|
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'iam.policy.write' : ''] | hasRole | async) == false">
|
||||||
|
</app-mfa-table>
|
||||||
|
|
||||||
<h3 class="subheader">{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}</h3>
|
<h3 class="subheader">{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}</h3>
|
||||||
<p class="subdesc">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
|
<p class="subdesc">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
|
||||||
<app-mfa-table [service]="service" [serviceType]="serviceType"
|
<app-mfa-table [service]="service" [serviceType]="serviceType"
|
||||||
[componentType]="LoginMethodComponentType.SecondFactor"
|
[componentType]="LoginMethodComponentType.SecondFactor"
|
||||||
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.policy.write' : ''] | hasRole | async) == false">
|
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'iam.policy.write' : ''] | hasRole | async) == false">
|
||||||
</app-mfa-table>
|
</app-mfa-table>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<h3 class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</h3>
|
<h3 class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</h3>
|
||||||
|
|
||||||
@ -96,4 +108,4 @@
|
|||||||
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false">
|
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false">
|
||||||
</app-idp-table>
|
</app-idp-table>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-detail-layout>
|
</app-detail-layout>
|
@ -3,18 +3,23 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.component';
|
||||||
import {
|
import {
|
||||||
DefaultLoginPolicy,
|
DefaultLoginPolicy,
|
||||||
|
DefaultLoginPolicyRequest,
|
||||||
DefaultLoginPolicyView,
|
DefaultLoginPolicyView,
|
||||||
IdpProviderView as AdminIdpProviderView,
|
IdpProviderView as AdminIdpProviderView,
|
||||||
IdpView as AdminIdpView,
|
IdpView as AdminIdpView,
|
||||||
|
PasswordlessType as AdminPasswordlessType,
|
||||||
} from 'src/app/proto/generated/admin_pb';
|
} from 'src/app/proto/generated/admin_pb';
|
||||||
import {
|
import {
|
||||||
IdpProviderType,
|
IdpProviderType,
|
||||||
IdpProviderView as MgmtIdpProviderView,
|
IdpProviderView as MgmtIdpProviderView,
|
||||||
IdpView as MgmtIdpView,
|
IdpView as MgmtIdpView,
|
||||||
LoginPolicy,
|
LoginPolicy,
|
||||||
|
LoginPolicyRequest,
|
||||||
LoginPolicyView,
|
LoginPolicyView,
|
||||||
|
PasswordlessType as MgmtPasswordlessType,
|
||||||
} from 'src/app/proto/generated/management_pb';
|
} from 'src/app/proto/generated/management_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
@ -22,7 +27,6 @@ import { ToastService } from 'src/app/services/toast.service';
|
|||||||
|
|
||||||
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
|
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
|
||||||
import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login-policy',
|
selector: 'app-login-policy',
|
||||||
@ -31,6 +35,7 @@ import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.co
|
|||||||
})
|
})
|
||||||
export class LoginPolicyComponent implements OnDestroy {
|
export class LoginPolicyComponent implements OnDestroy {
|
||||||
public LoginMethodComponentType: any = LoginMethodComponentType;
|
public LoginMethodComponentType: any = LoginMethodComponentType;
|
||||||
|
public passwordlessTypes: Array<AdminPasswordlessType | MgmtPasswordlessType> = [];
|
||||||
public loginData!: LoginPolicyView.AsObject | DefaultLoginPolicyView.AsObject;
|
public loginData!: LoginPolicyView.AsObject | DefaultLoginPolicyView.AsObject;
|
||||||
|
|
||||||
private sub: Subscription = new Subscription();
|
private sub: Subscription = new Subscription();
|
||||||
@ -52,9 +57,13 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
|
this.passwordlessTypes = [MgmtPasswordlessType.PASSWORDLESSTYPE_ALLOWED,
|
||||||
|
MgmtPasswordlessType.PASSWORDLESSTYPE_NOT_ALLOWED];
|
||||||
break;
|
break;
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
this.service = this.injector.get(AdminService as Type<AdminService>);
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
|
this.passwordlessTypes = [AdminPasswordlessType.PASSWORDLESSTYPE_ALLOWED,
|
||||||
|
AdminPasswordlessType.PASSWORDLESSTYPE_NOT_ALLOWED];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,11 +119,12 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
Promise<LoginPolicy | DefaultLoginPolicy> {
|
Promise<LoginPolicy | DefaultLoginPolicy> {
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
const mgmtreq = new LoginPolicy();
|
const mgmtreq = new LoginPolicyRequest();
|
||||||
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
||||||
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
||||||
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||||
mgmtreq.setForceMfa(this.loginData.forceMfa);
|
mgmtreq.setForceMfa(this.loginData.forceMfa);
|
||||||
|
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
|
||||||
// console.log(mgmtreq.toObject());
|
// console.log(mgmtreq.toObject());
|
||||||
if ((this.loginData as LoginPolicyView.AsObject).pb_default) {
|
if ((this.loginData as LoginPolicyView.AsObject).pb_default) {
|
||||||
return (this.service as ManagementService).CreateLoginPolicy(mgmtreq);
|
return (this.service as ManagementService).CreateLoginPolicy(mgmtreq);
|
||||||
@ -122,11 +132,13 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
return (this.service as ManagementService).UpdateLoginPolicy(mgmtreq);
|
return (this.service as ManagementService).UpdateLoginPolicy(mgmtreq);
|
||||||
}
|
}
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
const adminreq = new DefaultLoginPolicy();
|
const adminreq = new DefaultLoginPolicyRequest();
|
||||||
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
||||||
adminreq.setAllowRegister(this.loginData.allowRegister);
|
adminreq.setAllowRegister(this.loginData.allowRegister);
|
||||||
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||||
adminreq.setForceMfa(this.loginData.forceMfa);
|
adminreq.setForceMfa(this.loginData.forceMfa);
|
||||||
|
adminreq.setPasswordlessType(this.loginData.passwordlessType);
|
||||||
|
|
||||||
// console.log(adminreq.toObject());
|
// console.log(adminreq.toObject());
|
||||||
|
|
||||||
return (this.service as AdminService).UpdateDefaultLoginPolicy(adminreq);
|
return (this.service as AdminService).UpdateDefaultLoginPolicy(adminreq);
|
||||||
|
@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -12,12 +13,12 @@ import { CardModule } from 'src/app/modules/card/card.module';
|
|||||||
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||||
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
|
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
|
||||||
import { InputModule } from 'src/app/modules/input/input.module';
|
import { InputModule } from 'src/app/modules/input/input.module';
|
||||||
|
import { MfaTableModule } from 'src/app/modules/mfa-table/mfa-table.module';
|
||||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
|
||||||
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
|
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
|
||||||
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
|
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
|
||||||
import { LoginPolicyComponent } from './login-policy.component';
|
import { LoginPolicyComponent } from './login-policy.component';
|
||||||
import { MfaTableModule } from 'src/app/modules/mfa-table/mfa-table.module';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [LoginPolicyComponent],
|
declarations: [LoginPolicyComponent],
|
||||||
@ -39,6 +40,7 @@ import { MfaTableModule } from 'src/app/modules/mfa-table/mfa-table.module';
|
|||||||
IdpTableModule,
|
IdpTableModule,
|
||||||
MfaTableModule,
|
MfaTableModule,
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
|
MatSelectModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class LoginPolicyModule { }
|
export class LoginPolicyModule { }
|
||||||
|
@ -127,6 +127,22 @@
|
|||||||
<span>{{'APP.OIDC.IDTOKENROLEASSERTION_DESCRIPTION' | translate}}</span>
|
<span>{{'APP.OIDC.IDTOKENROLEASSERTION_DESCRIPTION' | translate}}</span>
|
||||||
</cnsl-info-section>
|
</cnsl-info-section>
|
||||||
|
|
||||||
|
<mat-checkbox class="full-width" style="margin-bottom: .5rem"
|
||||||
|
formControlName="idTokenUserinfoAssertion" color="primary">
|
||||||
|
{{'APP.OIDC.IDTOKENUSERINFOASSERTION' | translate}}</mat-checkbox>
|
||||||
|
<cnsl-info-section class="full-width desc">
|
||||||
|
<span>{{'APP.OIDC.IDTOKENUSERINFOASSERTION_DESCRIPTION' | translate}}</span>
|
||||||
|
</cnsl-info-section>
|
||||||
|
|
||||||
|
<p class="clockskew-title">ClockSkew</p>
|
||||||
|
<mat-slider color="primary" formControlName="clockSkewSeconds" class="clockskew-slider" thumbLabel
|
||||||
|
[displayWith]="formatClockSkewLabel" tickInterval=".1" min="0" [step]="1" max="5">
|
||||||
|
</mat-slider>
|
||||||
|
<cnsl-info-section class="full-width desc">
|
||||||
|
<span>{{'APP.OIDC.CLOCKSKEW' | translate}}</span>
|
||||||
|
</cnsl-info-section>
|
||||||
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<p class="full-width section-title">{{'APP.OIDC.REDIRECTSECTIONTITLE' | translate}}</p>
|
<p class="full-width section-title">{{'APP.OIDC.REDIRECTSECTIONTITLE' | translate}}</p>
|
||||||
|
@ -128,6 +128,17 @@
|
|||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clockskew-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--grey);
|
||||||
|
margin: 1rem .5rem 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clockskew-slider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
color: var(--grey);
|
color: var(--grey);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -6,6 +6,7 @@ import { MatButtonToggleChange } from '@angular/material/button-toggle';
|
|||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
OIDCApplicationType,
|
OIDCApplicationType,
|
||||||
OIDCAuthMethodType,
|
OIDCAuthMethodType,
|
||||||
OIDCConfig,
|
OIDCConfig,
|
||||||
|
OIDCConfigUpdate,
|
||||||
OIDCGrantType,
|
OIDCGrantType,
|
||||||
OIDCResponseType,
|
OIDCResponseType,
|
||||||
OIDCTokenType,
|
OIDCTokenType,
|
||||||
@ -116,9 +118,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
accessTokenType: [{ value: '', disabled: true }],
|
accessTokenType: [{ value: '', disabled: true }],
|
||||||
accessTokenRoleAssertion: [{ value: false, disabled: true }],
|
accessTokenRoleAssertion: [{ value: false, disabled: true }],
|
||||||
idTokenRoleAssertion: [{ value: false, disabled: true }],
|
idTokenRoleAssertion: [{ value: false, disabled: true }],
|
||||||
|
idTokenUserinfoAssertion: [{ value: false, disabled: true }],
|
||||||
|
clockSkewSeconds: [{ value: 0, disabled: true }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public formatClockSkewLabel(seconds: number): string {
|
||||||
|
return seconds + 's';
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
this.subscription = this.route.params.subscribe(params => this.getData(params));
|
this.subscription = this.route.params.subscribe(params => this.getData(params));
|
||||||
}
|
}
|
||||||
@ -132,11 +140,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.mgmtService.GetIam().then(iam => {
|
this.mgmtService.GetIam().then(iam => {
|
||||||
this.isZitadel = iam.toObject().iamProjectId === this.projectId;
|
this.isZitadel = iam.toObject().iamProjectId === this.projectId;
|
||||||
});
|
});
|
||||||
this.authService.isAllowed(['project.app.write$', 'project.app.write:' + id]).pipe(take(1)).subscribe((allowed) => {
|
this.authService.isAllowed(['project.app.write$', 'project.app.write:' + projectid]).pipe(take(1)).subscribe((allowed) => {
|
||||||
this.canWrite = allowed;
|
this.canWrite = allowed;
|
||||||
this.mgmtService.GetApplicationById(projectid, id).then(app => {
|
this.mgmtService.GetApplicationById(projectid, id).then(app => {
|
||||||
this.app = app.toObject();
|
this.app = app.toObject();
|
||||||
this.appNameForm.patchValue(this.app);
|
this.appNameForm.patchValue(this.app);
|
||||||
|
console.log(this.app);
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
this.appNameForm.enable();
|
this.appNameForm.enable();
|
||||||
this.appForm.enable();
|
this.appForm.enable();
|
||||||
@ -150,6 +159,11 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
if (this.app.oidcConfig?.postLogoutRedirectUrisList) {
|
if (this.app.oidcConfig?.postLogoutRedirectUrisList) {
|
||||||
this.postLogoutRedirectUrisList = this.app.oidcConfig.postLogoutRedirectUrisList;
|
this.postLogoutRedirectUrisList = this.app.oidcConfig.postLogoutRedirectUrisList;
|
||||||
}
|
}
|
||||||
|
if (this.app.oidcConfig?.clockSkew) {
|
||||||
|
const inSecs = this.app.oidcConfig?.clockSkew.seconds + this.app.oidcConfig?.clockSkew.nanos / 100000;
|
||||||
|
console.log(inSecs);
|
||||||
|
this.appForm.controls['clockSkewSeconds'].setValue(inSecs);
|
||||||
|
}
|
||||||
if (this.app.oidcConfig) {
|
if (this.app.oidcConfig) {
|
||||||
this.appForm.patchValue(this.app.oidcConfig);
|
this.appForm.patchValue(this.app.oidcConfig);
|
||||||
}
|
}
|
||||||
@ -159,8 +173,6 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.errorMessage = error.message;
|
this.errorMessage = error.message;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.docs = (await this.mgmtService.GetZitadelDocs()).toObject();
|
this.docs = (await this.mgmtService.GetZitadelDocs()).toObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,9 +259,32 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.app.oidcConfig.accessTokenType = this.accessTokenType?.value;
|
this.app.oidcConfig.accessTokenType = this.accessTokenType?.value;
|
||||||
this.app.oidcConfig.accessTokenRoleAssertion = this.accessTokenRoleAssertion?.value;
|
this.app.oidcConfig.accessTokenRoleAssertion = this.accessTokenRoleAssertion?.value;
|
||||||
this.app.oidcConfig.idTokenRoleAssertion = this.idTokenRoleAssertion?.value;
|
this.app.oidcConfig.idTokenRoleAssertion = this.idTokenRoleAssertion?.value;
|
||||||
|
this.app.oidcConfig.idTokenUserinfoAssertion = this.idTokenUserinfoAssertion?.value;
|
||||||
|
|
||||||
|
|
||||||
|
const req = new OIDCConfigUpdate();
|
||||||
|
req.setProjectId(this.projectId);
|
||||||
|
req.setApplicationId(this.app.id);
|
||||||
|
req.setRedirectUrisList(this.app.oidcConfig.redirectUrisList);
|
||||||
|
req.setResponseTypesList(this.app.oidcConfig.responseTypesList);
|
||||||
|
req.setAuthMethodType(this.app.oidcConfig.authMethodType);
|
||||||
|
req.setPostLogoutRedirectUrisList(this.app.oidcConfig.postLogoutRedirectUrisList);
|
||||||
|
req.setGrantTypesList(this.app.oidcConfig.grantTypesList);
|
||||||
|
req.setApplicationType(this.app.oidcConfig.applicationType);
|
||||||
|
req.setDevMode(this.app.oidcConfig.devMode);
|
||||||
|
req.setAccessTokenType(this.app.oidcConfig.accessTokenType);
|
||||||
|
req.setAccessTokenRoleAssertion(this.app.oidcConfig.accessTokenRoleAssertion);
|
||||||
|
req.setIdTokenRoleAssertion(this.app.oidcConfig.idTokenRoleAssertion);
|
||||||
|
req.setIdTokenUserinfoAssertion(this.app.oidcConfig.idTokenUserinfoAssertion);
|
||||||
|
if (this.clockSkewSeconds?.value) {
|
||||||
|
const dur = new Duration();
|
||||||
|
dur.setSeconds(Math.floor(this.clockSkewSeconds?.value));
|
||||||
|
dur.setNanos((Math.floor(this.clockSkewSeconds?.value % 1) * 10000));
|
||||||
|
req.setClockSkew(dur);
|
||||||
|
}
|
||||||
|
console.log(req.toObject());
|
||||||
this.mgmtService
|
this.mgmtService
|
||||||
.UpdateOIDCAppConfig(this.projectId, this.app.id, this.app.oidcConfig)
|
.UpdateOIDCAppConfig(req)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
|
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
|
||||||
})
|
})
|
||||||
@ -319,4 +354,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
public get accessTokenRoleAssertion(): AbstractControl | null {
|
public get accessTokenRoleAssertion(): AbstractControl | null {
|
||||||
return this.appForm.get('accessTokenRoleAssertion');
|
return this.appForm.get('accessTokenRoleAssertion');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get idTokenUserinfoAssertion(): AbstractControl | null {
|
||||||
|
return this.appForm.get('idTokenUserinfoAssertion');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get clockSkewSeconds(): AbstractControl | null {
|
||||||
|
return this.appForm.get('clockSkewSeconds');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|||||||
import { MatRadioModule } from '@angular/material/radio';
|
import { MatRadioModule } from '@angular/material/radio';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
|
import { MatSliderModule } from '@angular/material/slider';
|
||||||
import { MatStepperModule } from '@angular/material/stepper';
|
import { MatStepperModule } from '@angular/material/stepper';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -61,6 +62,7 @@ import { AppsRoutingModule } from './apps-routing.module';
|
|||||||
MatSlideToggleModule,
|
MatSlideToggleModule,
|
||||||
InputModule,
|
InputModule,
|
||||||
MetaLayoutModule,
|
MetaLayoutModule,
|
||||||
|
MatSliderModule,
|
||||||
ChangesModule,
|
ChangesModule,
|
||||||
InfoSectionModule,
|
InfoSectionModule,
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
|
||||||
|
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
|
||||||
|
<app-refresh-table [loading]="loading$ | async" (refreshed)="getPasswordless()"
|
||||||
|
[dataSize]="dataSource?.data?.length">
|
||||||
|
<table class="table" mat-table [dataSource]="dataSource">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.name" class="centered">
|
||||||
|
{{ mfa?.name }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="state">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLESTATE' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let mfa"><span class="centered">
|
||||||
|
{{'USER.PASSWORDLESS.STATE.'+ mfa.state | translate}}
|
||||||
|
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
|
||||||
|
class="verified las la-check-circle"></i>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLEACTIONS' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let mfa">
|
||||||
|
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
|
||||||
|
(click)="deletePasswordless(mfa.id)">
|
||||||
|
<i class="las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
</table>
|
||||||
|
</app-refresh-table>
|
||||||
|
<div class="add-row">
|
||||||
|
<button class="button" (click)="addPasswordless()" mat-stroked-button color="primary"
|
||||||
|
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||||
|
<i class="las la-fingerprint"></i>
|
||||||
|
{{'USER.PASSWORDLESS.U2F' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="spinner-container" *ngIf="loading$ | async">
|
||||||
|
<mat-spinner diameter="50"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</app-card>
|
@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
.add-row {
|
||||||
|
display: flex;
|
||||||
|
margin: -.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: .5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 1rem;
|
||||||
|
color: var(--color-main);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthPasswordlessComponent } from './auth-passwordless.component';
|
||||||
|
|
||||||
|
describe('AuthPasswordlessComponent', () => {
|
||||||
|
let component: AuthPasswordlessComponent;
|
||||||
|
let fixture: ComponentFixture<AuthPasswordlessComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [AuthPasswordlessComponent],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AuthPasswordlessComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,114 @@
|
|||||||
|
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { MatTable, MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||||
|
import { MFAState, WebAuthNResponse, WebAuthNToken } from 'src/app/proto/generated/auth_pb';
|
||||||
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { _base64ToArrayBuffer } from '../../u2f-util';
|
||||||
|
import { DialogU2FComponent, U2FComponentDestination } from '../dialog-u2f/dialog-u2f.component';
|
||||||
|
|
||||||
|
export interface WebAuthNOptions {
|
||||||
|
challenge: string;
|
||||||
|
rp: { name: string, id: string; };
|
||||||
|
user: { name: string, id: string, displayName: string; };
|
||||||
|
pubKeyCredParams: any;
|
||||||
|
authenticatorSelection: { userVerification: string; };
|
||||||
|
timeout: number;
|
||||||
|
attestation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-auth-passwordless',
|
||||||
|
templateUrl: './auth-passwordless.component.html',
|
||||||
|
styleUrls: ['./auth-passwordless.component.scss'],
|
||||||
|
})
|
||||||
|
export class AuthPasswordlessComponent implements OnInit, OnDestroy {
|
||||||
|
public displayedColumns: string[] = ['name', 'state', 'actions'];
|
||||||
|
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||||
|
|
||||||
|
@ViewChild(MatTable) public table!: MatTable<WebAuthNToken.AsObject>;
|
||||||
|
@ViewChild(MatSort) public sort!: MatSort;
|
||||||
|
public dataSource!: MatTableDataSource<WebAuthNToken.AsObject>;
|
||||||
|
|
||||||
|
public MFAState: any = MFAState;
|
||||||
|
public error: string = '';
|
||||||
|
|
||||||
|
constructor(private service: GrpcAuthService,
|
||||||
|
private toast: ToastService,
|
||||||
|
private dialog: MatDialog) { }
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.getPasswordless();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
this.loadingSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addPasswordless(): void {
|
||||||
|
this.service.AddMyPasswordless().then((u2fresp) => {
|
||||||
|
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
|
||||||
|
const credOptions: CredentialCreationOptions = JSON.parse(atob(webauthn.publicKey as string));
|
||||||
|
|
||||||
|
if (credOptions.publicKey?.challenge) {
|
||||||
|
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||||
|
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
||||||
|
const dialogRef = this.dialog.open(DialogU2FComponent, {
|
||||||
|
width: '400px',
|
||||||
|
data: {
|
||||||
|
credOptions,
|
||||||
|
type: U2FComponentDestination.PASSWORDLESS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(done => {
|
||||||
|
if (done) {
|
||||||
|
this.getPasswordless();
|
||||||
|
} else {
|
||||||
|
this.getPasswordless();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}, error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPasswordless(): void {
|
||||||
|
this.service.GetMyPasswordless().then(passwordless => {
|
||||||
|
this.dataSource = new MatTableDataSource(passwordless.toObject().tokensList);
|
||||||
|
this.dataSource.sort = this.sort;
|
||||||
|
}).catch(error => {
|
||||||
|
this.error = error.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public deletePasswordless(id?: string): void {
|
||||||
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'USER.PASSWORDLESS.DIALOG.DELETE_TITLE',
|
||||||
|
descriptionKey: 'USER.PASSWORDLESS.DIALOG.DELETE_DESCRIPTION',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
|
if (resp && id) {
|
||||||
|
this.service.RemoveMyPasswordless(id).then(() => {
|
||||||
|
this.toast.showInfo('USER.TOAST.PASSWORDLESSREMOVED', true);
|
||||||
|
this.getPasswordless();
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,8 @@
|
|||||||
</app-contact>
|
</app-contact>
|
||||||
</app-card>
|
</app-card>
|
||||||
|
|
||||||
|
<app-auth-passwordless *ngIf="user" #mfaComponent></app-auth-passwordless>
|
||||||
|
|
||||||
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
||||||
|
|
||||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="attr">
|
<ng-container matColumnDef="attr">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
|
||||||
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attr" class="centered">
|
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attribute" class="centered">
|
||||||
{{ mfa.attr }}
|
{{ mfa?.attribute }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -43,7 +43,7 @@
|
|||||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||||
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>{{'USER.MFA.OTP' | translate}}
|
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>{{'USER.MFA.OTP' | translate}}
|
||||||
</button>
|
</button>
|
||||||
<button class="button" *ngIf="otpAvailable" (click)="addU2F()" mat-stroked-button color="primary"
|
<button class="button" (click)="addU2F()" mat-stroked-button color="primary"
|
||||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||||
<i class="las la-fingerprint"></i>
|
<i class="las la-fingerprint"></i>
|
||||||
{{'USER.MFA.U2F' | translate}}
|
{{'USER.MFA.U2F' | translate}}
|
||||||
|
@ -8,19 +8,10 @@ import { MfaOtpResponse, MFAState, MfaType, MultiFactor, WebAuthNResponse } from
|
|||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { _base64ToArrayBuffer } from '../../u2f-util';
|
||||||
import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
|
import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
|
||||||
import { DialogU2FComponent } from '../dialog-u2f/dialog-u2f.component';
|
import { DialogU2FComponent } from '../dialog-u2f/dialog-u2f.component';
|
||||||
|
|
||||||
export function _base64ToArrayBuffer(base64: string): any {
|
|
||||||
const binaryString = atob(base64);
|
|
||||||
const len = binaryString.length;
|
|
||||||
const bytes = new Uint8Array(len);
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
bytes[i] = binaryString.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return bytes.buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebAuthNOptions {
|
export interface WebAuthNOptions {
|
||||||
challenge: string;
|
challenge: string;
|
||||||
rp: { name: string, id: string; };
|
rp: { name: string, id: string; };
|
||||||
@ -50,6 +41,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
public error: string = '';
|
public error: string = '';
|
||||||
public otpAvailable: boolean = false;
|
public otpAvailable: boolean = false;
|
||||||
|
|
||||||
constructor(private service: GrpcAuthService,
|
constructor(private service: GrpcAuthService,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private dialog: MatDialog) { }
|
private dialog: MatDialog) { }
|
||||||
@ -82,10 +74,6 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public verifyU2f(): void {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public addU2F(): void {
|
public addU2F(): void {
|
||||||
this.service.AddMyMfaU2F().then((u2fresp) => {
|
this.service.AddMyMfaU2F().then((u2fresp) => {
|
||||||
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
|
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
|
||||||
@ -94,6 +82,9 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
if (credOptions.publicKey?.challenge) {
|
if (credOptions.publicKey?.challenge) {
|
||||||
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||||
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
||||||
|
if (credOptions.publicKey.excludeCredentials) {
|
||||||
|
credOptions.publicKey.excludeCredentials.map(cred => cred.id = _base64ToArrayBuffer(cred.id as any));
|
||||||
|
}
|
||||||
const dialogRef = this.dialog.open(DialogU2FComponent, {
|
const dialogRef = this.dialog.open(DialogU2FComponent, {
|
||||||
width: '400px',
|
width: '400px',
|
||||||
data: {
|
data: {
|
||||||
|
@ -17,19 +17,27 @@ export function _arrayBufferToBase64(buffer: any): string {
|
|||||||
.replace(/=/g, '');
|
.replace(/=/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum U2FComponentDestination {
|
||||||
|
MFA = 'mfa',
|
||||||
|
PASSWORDLESS = 'passwordless',
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dialog-u2f',
|
selector: 'app-dialog-u2f',
|
||||||
templateUrl: './dialog-u2f.component.html',
|
templateUrl: './dialog-u2f.component.html',
|
||||||
styleUrls: ['./dialog-u2f.component.scss'],
|
styleUrls: ['./dialog-u2f.component.scss'],
|
||||||
})
|
})
|
||||||
export class DialogU2FComponent {
|
export class DialogU2FComponent {
|
||||||
|
private type!: U2FComponentDestination;
|
||||||
public name: string = '';
|
public name: string = '';
|
||||||
public error: string = '';
|
public error: string = '';
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
|
|
||||||
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
|
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; },
|
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; type: U2FComponentDestination; },
|
||||||
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) { }
|
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) {
|
||||||
|
this.type = data.type;
|
||||||
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
@ -61,16 +69,29 @@ export class DialogU2FComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const base64 = btoa(data);
|
const base64 = btoa(data);
|
||||||
this.service.VerifyMyMfaU2F(base64, this.name).then(() => {
|
if (this.type === U2FComponentDestination.MFA) {
|
||||||
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
this.service.VerifyMyMfaU2F(base64, this.name).then(() => {
|
||||||
this.toast.showInfo(msg);
|
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||||
|
this.toast.showInfo(msg);
|
||||||
|
});
|
||||||
|
this.dialogRef.close(true);
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
this.loading = false;
|
||||||
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
this.dialogRef.close(true);
|
} else if (this.type === U2FComponentDestination.PASSWORDLESS) {
|
||||||
this.loading = false;
|
this.service.verifyMyPasswordless(base64, this.name).then(() => {
|
||||||
}).catch(error => {
|
this.translate.get('USER.PASSWORDLESS.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||||
this.loading = false;
|
this.toast.showInfo(msg);
|
||||||
this.toast.showError(error);
|
});
|
||||||
});
|
this.dialogRef.close(true);
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
this.loading = false;
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
||||||
|
10
console/src/app/pages/users/user-detail/u2f-util.ts
Normal file
10
console/src/app/pages/users/user-detail/u2f-util.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
export function _base64ToArrayBuffer(base64: string): any {
|
||||||
|
const binaryString = atob(base64);
|
||||||
|
const len = binaryString.length;
|
||||||
|
const bytes = new Uint8Array(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes.buffer;
|
||||||
|
}
|
@ -29,6 +29,7 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
|
|||||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||||
|
|
||||||
|
import { AuthPasswordlessComponent } from './auth-user-detail/auth-passwordless/auth-passwordless.component';
|
||||||
import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component';
|
import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component';
|
||||||
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
|
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
|
||||||
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
|
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
|
||||||
@ -47,6 +48,7 @@ import { ShowKeyDialogModule } from './machine-keys/show-key-dialog/show-key-dia
|
|||||||
import { MembershipsComponent } from './memberships/memberships.component';
|
import { MembershipsComponent } from './memberships/memberships.component';
|
||||||
import { PasswordComponent } from './password/password.component';
|
import { PasswordComponent } from './password/password.component';
|
||||||
import { UserDetailRoutingModule } from './user-detail-routing.module';
|
import { UserDetailRoutingModule } from './user-detail-routing.module';
|
||||||
|
import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component';
|
||||||
import { UserDetailComponent } from './user-detail/user-detail.component';
|
import { UserDetailComponent } from './user-detail/user-detail.component';
|
||||||
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||||
|
|
||||||
@ -57,7 +59,9 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
|||||||
DialogOtpComponent,
|
DialogOtpComponent,
|
||||||
EditDialogComponent,
|
EditDialogComponent,
|
||||||
AuthUserMfaComponent,
|
AuthUserMfaComponent,
|
||||||
|
AuthPasswordlessComponent,
|
||||||
UserMfaComponent,
|
UserMfaComponent,
|
||||||
|
PasswordlessComponent,
|
||||||
ThemeSettingComponent,
|
ThemeSettingComponent,
|
||||||
PasswordComponent,
|
PasswordComponent,
|
||||||
CodeDialogComponent,
|
CodeDialogComponent,
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
|
||||||
|
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
|
||||||
|
<app-refresh-table [loading]="loading$ | async" (refreshed)="getPasswordless()"
|
||||||
|
[dataSize]="dataSource?.data?.length">
|
||||||
|
<table class="table" mat-table [dataSource]="dataSource">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.name" class="centered">
|
||||||
|
{{ mfa?.name }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="state">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLESTATE' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let mfa"><span class="centered">
|
||||||
|
{{'USER.PASSWORDLESS.STATE.'+ mfa.state | translate}}
|
||||||
|
<i matTooltip="verified" *ngIf="mfa.state === MFAState.MFASTATE_READY"
|
||||||
|
class="verified las la-check-circle"></i>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.TABLEACTIONS' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let mfa">
|
||||||
|
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
|
||||||
|
(click)="deletePasswordless(mfa.id)">
|
||||||
|
<i class="las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
</table>
|
||||||
|
</app-refresh-table>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="spinner-container" *ngIf="loading$ | async">
|
||||||
|
<mat-spinner diameter="50"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</app-card>
|
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
.centered {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 1rem;
|
||||||
|
color: var(--color-main);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthPasswordlessComponent } from './auth-passwordless.component';
|
||||||
|
|
||||||
|
describe('AuthPasswordlessComponent', () => {
|
||||||
|
let component: AuthPasswordlessComponent;
|
||||||
|
let fixture: ComponentFixture<AuthPasswordlessComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [AuthPasswordlessComponent],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AuthPasswordlessComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,84 @@
|
|||||||
|
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { MatTable, MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||||
|
import { MFAState, WebAuthNToken } from 'src/app/proto/generated/auth_pb';
|
||||||
|
import { UserView } from 'src/app/proto/generated/management_pb';
|
||||||
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
export interface WebAuthNOptions {
|
||||||
|
challenge: string;
|
||||||
|
rp: { name: string, id: string; };
|
||||||
|
user: { name: string, id: string, displayName: string; };
|
||||||
|
pubKeyCredParams: any;
|
||||||
|
authenticatorSelection: { userVerification: string; };
|
||||||
|
timeout: number;
|
||||||
|
attestation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-passwordless',
|
||||||
|
templateUrl: './passwordless.component.html',
|
||||||
|
styleUrls: ['./passwordless.component.scss'],
|
||||||
|
})
|
||||||
|
export class PasswordlessComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() private user!: UserView.AsObject;
|
||||||
|
public displayedColumns: string[] = ['name', 'state', 'actions'];
|
||||||
|
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||||
|
|
||||||
|
@ViewChild(MatTable) public table!: MatTable<WebAuthNToken.AsObject>;
|
||||||
|
@ViewChild(MatSort) public sort!: MatSort;
|
||||||
|
public dataSource!: MatTableDataSource<WebAuthNToken.AsObject>;
|
||||||
|
|
||||||
|
public MFAState: any = MFAState;
|
||||||
|
public error: string = '';
|
||||||
|
|
||||||
|
constructor(private service: ManagementService,
|
||||||
|
private toast: ToastService,
|
||||||
|
private dialog: MatDialog) { }
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.getPasswordless();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
this.loadingSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPasswordless(): void {
|
||||||
|
this.service.GetPasswordless(this.user.id).then(passwordless => {
|
||||||
|
console.log(passwordless.toObject().tokensList);
|
||||||
|
this.dataSource = new MatTableDataSource(passwordless.toObject().tokensList);
|
||||||
|
this.dataSource.sort = this.sort;
|
||||||
|
}).catch(error => {
|
||||||
|
this.error = error.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public deletePasswordless(id?: string): void {
|
||||||
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'USER.PASSWORDLESS.DIALOG.DELETE_TITLE',
|
||||||
|
descriptionKey: 'USER.PASSWORDLESS.DIALOG.DELETE_DESCRIPTION',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
|
if (resp && id) {
|
||||||
|
this.service.RemovePasswordless(id, this.user.id).then(() => {
|
||||||
|
this.toast.showInfo('USER.TOAST.PASSWORDLESSREMOVED', true);
|
||||||
|
this.getPasswordless();
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -84,6 +84,8 @@
|
|||||||
</app-contact>
|
</app-contact>
|
||||||
</app-card>
|
</app-card>
|
||||||
|
|
||||||
|
<app-passwordless *ngIf="user && user.human" [user]="user"></app-passwordless>
|
||||||
|
|
||||||
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
|
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
|
||||||
|
|
||||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="attr">
|
<ng-container matColumnDef="attr">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
|
||||||
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attr" class="centered">
|
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attribute" class="centered">
|
||||||
{{ mfa.attr }}
|
{{ mfa.attribute }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -47,6 +47,7 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
public getMFAs(): void {
|
public getMFAs(): void {
|
||||||
this.mgmtUserService.getUserMfas(this.user.id).then(mfas => {
|
this.mgmtUserService.getUserMfas(this.user.id).then(mfas => {
|
||||||
|
console.log(mfas.toObject().mfasList);
|
||||||
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
|
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource.sort = this.sort;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@ -81,7 +82,8 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
|||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
} else if (type === MfaType.MFATYPE_U2F && id) {
|
} else if (type === MfaType.MFATYPE_U2F && id) {
|
||||||
this.mgmtUserService.RemoveMfaU2F(id).then(() => {
|
console.log(this.user.id, id);
|
||||||
|
this.mgmtUserService.RemoveMfaU2F(this.user.id, id).then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||||
|
|
||||||
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
|
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
DefaultLabelPolicyUpdate,
|
DefaultLabelPolicyUpdate,
|
||||||
DefaultLabelPolicyView,
|
DefaultLabelPolicyView,
|
||||||
DefaultLoginPolicy,
|
DefaultLoginPolicy,
|
||||||
|
DefaultLoginPolicyRequest,
|
||||||
DefaultLoginPolicyView,
|
DefaultLoginPolicyView,
|
||||||
DefaultPasswordAgePolicyRequest,
|
DefaultPasswordAgePolicyRequest,
|
||||||
DefaultPasswordAgePolicyView,
|
DefaultPasswordAgePolicyView,
|
||||||
@ -214,7 +215,7 @@ export class AdminService {
|
|||||||
return this.grpcService.admin.getDefaultLoginPolicy(req);
|
return this.grpcService.admin.getDefaultLoginPolicy(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdateDefaultLoginPolicy(req: DefaultLoginPolicy): Promise<DefaultLoginPolicy> {
|
public UpdateDefaultLoginPolicy(req: DefaultLoginPolicyRequest): Promise<DefaultLoginPolicy> {
|
||||||
return this.grpcService.admin.updateDefaultLoginPolicy(req);
|
return this.grpcService.admin.updateDefaultLoginPolicy(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,37 +5,38 @@ import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
|
|||||||
import { catchError, filter, finalize, first, map, mergeMap, switchMap, take, timeout } from 'rxjs/operators';
|
import { catchError, filter, finalize, first, map, mergeMap, switchMap, take, timeout } from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Changes,
|
Changes,
|
||||||
ChangesRequest,
|
ChangesRequest,
|
||||||
ExternalIDPRemoveRequest,
|
ExternalIDPRemoveRequest,
|
||||||
ExternalIDPSearchRequest,
|
ExternalIDPSearchRequest,
|
||||||
ExternalIDPSearchResponse,
|
ExternalIDPSearchResponse,
|
||||||
Gender,
|
Gender,
|
||||||
MfaOtpResponse,
|
MfaOtpResponse,
|
||||||
MultiFactors,
|
MultiFactors,
|
||||||
MyPermissions,
|
MyPermissions,
|
||||||
MyProjectOrgSearchQuery,
|
MyProjectOrgSearchQuery,
|
||||||
MyProjectOrgSearchRequest,
|
MyProjectOrgSearchRequest,
|
||||||
MyProjectOrgSearchResponse,
|
MyProjectOrgSearchResponse,
|
||||||
Org,
|
Org,
|
||||||
PasswordChange,
|
PasswordChange,
|
||||||
PasswordComplexityPolicy,
|
PasswordComplexityPolicy,
|
||||||
UpdateUserAddressRequest,
|
UpdateUserAddressRequest,
|
||||||
UpdateUserEmailRequest,
|
UpdateUserEmailRequest,
|
||||||
UpdateUserPhoneRequest,
|
UpdateUserPhoneRequest,
|
||||||
UpdateUserProfileRequest,
|
UpdateUserProfileRequest,
|
||||||
UserAddress,
|
UserAddress,
|
||||||
UserEmail,
|
UserEmail,
|
||||||
UserPhone,
|
UserPhone,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
UserProfileView,
|
UserProfileView,
|
||||||
UserSessionViews,
|
UserSessionViews,
|
||||||
UserView,
|
UserView,
|
||||||
VerifyMfaOtp,
|
VerifyMfaOtp,
|
||||||
VerifyUserPhoneRequest,
|
VerifyUserPhoneRequest,
|
||||||
VerifyWebAuthN,
|
VerifyWebAuthN,
|
||||||
WebAuthNResponse,
|
WebAuthNResponse,
|
||||||
WebAuthNTokenID,
|
WebAuthNTokenID,
|
||||||
|
WebAuthNTokens,
|
||||||
} from '../proto/generated/auth_pb';
|
} from '../proto/generated/auth_pb';
|
||||||
import { GrpcService } from './grpc.service';
|
import { GrpcService } from './grpc.service';
|
||||||
import { StorageKey, StorageService } from './storage.service';
|
import { StorageKey, StorageService } from './storage.service';
|
||||||
@ -338,9 +339,9 @@ export class GrpcAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public RemoveMyMfaU2F(id: string): Promise<Empty> {
|
public RemoveMyMfaU2F(id: string): Promise<Empty> {
|
||||||
const req = new WebAuthNTokenID();
|
const req = new WebAuthNTokenID();
|
||||||
req.setId(id);
|
req.setId(id);
|
||||||
return this.grpcService.auth.removeMyMfaU2F(req);
|
return this.grpcService.auth.removeMyMfaU2F(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VerifyMyMfaU2F(credential: string, tokenname: string): Promise<Empty> {
|
public VerifyMyMfaU2F(credential: string, tokenname: string): Promise<Empty> {
|
||||||
@ -353,9 +354,37 @@ export class GrpcAuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetMyPasswordless(): Promise<WebAuthNTokens> {
|
||||||
|
return this.grpcService.auth.getMyPasswordless(
|
||||||
|
new Empty(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddMyPasswordless(): Promise<WebAuthNResponse> {
|
||||||
|
return this.grpcService.auth.addMyPasswordless(
|
||||||
|
new Empty(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoveMyPasswordless(id: string): Promise<Empty> {
|
||||||
|
const req = new WebAuthNTokenID();
|
||||||
|
req.setId(id);
|
||||||
|
return this.grpcService.auth.removeMyPasswordless(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public verifyMyPasswordless(credential: string, tokenname: string): Promise<Empty> {
|
||||||
|
const req = new VerifyWebAuthN();
|
||||||
|
req.setPublicKeyCredential(credential);
|
||||||
|
req.setTokenName(tokenname);
|
||||||
|
|
||||||
|
return this.grpcService.auth.verifyMyPasswordless(
|
||||||
|
req,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public RemoveMfaOTP(): Promise<Empty> {
|
public RemoveMfaOTP(): Promise<Empty> {
|
||||||
return this.grpcService.auth.removeMfaOTP(
|
return this.grpcService.auth.removeMfaOTP(
|
||||||
new Empty(),
|
new Empty(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
||||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { MultiFactorsResult } from '../proto/generated/admin_pb';
|
|
||||||
|
|
||||||
|
import { MultiFactorsResult } from '../proto/generated/admin_pb';
|
||||||
import {
|
import {
|
||||||
AddMachineKeyRequest,
|
AddMachineKeyRequest,
|
||||||
AddMachineKeyResponse,
|
AddMachineKeyResponse,
|
||||||
@ -157,6 +157,7 @@ import {
|
|||||||
UserView,
|
UserView,
|
||||||
ValidateOrgDomainRequest,
|
ValidateOrgDomainRequest,
|
||||||
WebAuthNTokenID,
|
WebAuthNTokenID,
|
||||||
|
WebAuthNTokens,
|
||||||
ZitadelDocs,
|
ZitadelDocs,
|
||||||
} from '../proto/generated/management_pb';
|
} from '../proto/generated/management_pb';
|
||||||
import { GrpcService } from './grpc.service';
|
import { GrpcService } from './grpc.service';
|
||||||
@ -190,6 +191,19 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.searchIdps(req);
|
return this.grpcService.mgmt.searchIdps(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetPasswordless(userId: string): Promise<WebAuthNTokens> {
|
||||||
|
const req = new UserID();
|
||||||
|
req.setId(userId);
|
||||||
|
return this.grpcService.mgmt.getPasswordless(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemovePasswordless(id: string, userId: string): Promise<Empty> {
|
||||||
|
const req = new WebAuthNTokenID();
|
||||||
|
req.setId(id);
|
||||||
|
req.setUserId(userId);
|
||||||
|
return this.grpcService.mgmt.removePasswordless(req);
|
||||||
|
}
|
||||||
|
|
||||||
public GetLoginPolicyMultiFactors(): Promise<MultiFactorsResult> {
|
public GetLoginPolicyMultiFactors(): Promise<MultiFactorsResult> {
|
||||||
const req = new Empty();
|
const req = new Empty();
|
||||||
return this.grpcService.mgmt.getLoginPolicyMultiFactors(req);
|
return this.grpcService.mgmt.getLoginPolicyMultiFactors(req);
|
||||||
@ -714,9 +728,10 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.removeMfaOTP(req);
|
return this.grpcService.mgmt.removeMfaOTP(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoveMfaU2F(id: string): Promise<Empty> {
|
public RemoveMfaU2F(userid: string, id: string): Promise<Empty> {
|
||||||
const req = new WebAuthNTokenID();
|
const req = new WebAuthNTokenID();
|
||||||
req.setId(id);
|
req.setId(id);
|
||||||
|
req.setUserId(userid);
|
||||||
return this.grpcService.mgmt.removeMfaU2F(req);
|
return this.grpcService.mgmt.removeMfaU2F(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1344,21 +1359,7 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.updateApplication(req);
|
return this.grpcService.mgmt.updateApplication(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdateOIDCAppConfig(projectId: string,
|
public UpdateOIDCAppConfig(req: OIDCConfigUpdate): Promise<OIDCConfig> {
|
||||||
appId: string, oidcConfig: OIDCConfig.AsObject): Promise<OIDCConfig> {
|
|
||||||
const req = new OIDCConfigUpdate();
|
|
||||||
req.setProjectId(projectId);
|
|
||||||
req.setApplicationId(appId);
|
|
||||||
req.setRedirectUrisList(oidcConfig.redirectUrisList);
|
|
||||||
req.setResponseTypesList(oidcConfig.responseTypesList);
|
|
||||||
req.setAuthMethodType(oidcConfig.authMethodType);
|
|
||||||
req.setPostLogoutRedirectUrisList(oidcConfig.postLogoutRedirectUrisList);
|
|
||||||
req.setGrantTypesList(oidcConfig.grantTypesList);
|
|
||||||
req.setApplicationType(oidcConfig.applicationType);
|
|
||||||
req.setDevMode(oidcConfig.devMode);
|
|
||||||
req.setAccessTokenType(oidcConfig.accessTokenType);
|
|
||||||
req.setAccessTokenRoleAssertion(oidcConfig.accessTokenRoleAssertion);
|
|
||||||
req.setIdTokenRoleAssertion(oidcConfig.idTokenRoleAssertion);
|
|
||||||
return this.grpcService.mgmt.updateApplicationOIDCConfig(req);
|
return this.grpcService.mgmt.updateApplicationOIDCConfig(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,36 @@
|
|||||||
},
|
},
|
||||||
"EMPTY":"Keine Einträge"
|
"EMPTY":"Keine Einträge"
|
||||||
},
|
},
|
||||||
|
"PASSWORDLESS": {
|
||||||
|
"TABLETYPE":"Typ",
|
||||||
|
"TABLESTATE":"Status",
|
||||||
|
"NAME":"Name",
|
||||||
|
"TABLEACTIONS":"Aktionen",
|
||||||
|
"TITLE": "Passwortlose Authentifizierungsmethoden",
|
||||||
|
"DESCRIPTION": "Füge WebAuthn kompatible Authentifikatoren hinzu um dich passwortlos anzumelden.",
|
||||||
|
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
|
||||||
|
"U2F":"U2F hinzufügen",
|
||||||
|
"U2F_DIALOG_TITLE": "U2F hinzufügen",
|
||||||
|
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Login an.",
|
||||||
|
"U2F_SUCCESS":"Passwordless erfolgreich erstellt!",
|
||||||
|
"U2F_ERROR":"Ein Fehler ist aufgetreten!",
|
||||||
|
"U2F_NAME":"U2F Name",
|
||||||
|
"TYPE": {
|
||||||
|
"0":"Keine MFA definiert",
|
||||||
|
"1":"OTP",
|
||||||
|
"2":"U2F"
|
||||||
|
},
|
||||||
|
"STATE": {
|
||||||
|
"0": "Kein Status",
|
||||||
|
"1": "Nicht bereit",
|
||||||
|
"2": "Bereit",
|
||||||
|
"3": "Gelöscht"
|
||||||
|
},
|
||||||
|
"DIALOG": {
|
||||||
|
"DELETE_TITLE":"Passwordless entfernen",
|
||||||
|
"DELETE_DESCRIPTION":"Sie sind im Begriff eine Passwortlose Authentifizierungsmethode zu entfernen. Sind sie sicher?"
|
||||||
|
}
|
||||||
|
},
|
||||||
"MFA": {
|
"MFA": {
|
||||||
"TABLETYPE":"Typ",
|
"TABLETYPE":"Typ",
|
||||||
"TABLESTATE":"Status",
|
"TABLESTATE":"Status",
|
||||||
@ -353,6 +383,7 @@
|
|||||||
"EMAILVERIFICATIONSENT":"Bestätigungscode per E-Mail gesendet.",
|
"EMAILVERIFICATIONSENT":"Bestätigungscode per E-Mail gesendet.",
|
||||||
"OTPREMOVED":"OTP entfernt.",
|
"OTPREMOVED":"OTP entfernt.",
|
||||||
"U2FREMOVED":"U2F entfernt.",
|
"U2FREMOVED":"U2F entfernt.",
|
||||||
|
"PASSWORDLESSREMOVED":"Passwordless entfernt.",
|
||||||
"INITIALPASSWORDSET":"Initiales Passwort gesetzt.",
|
"INITIALPASSWORDSET":"Initiales Passwort gesetzt.",
|
||||||
"PASSWORDNOTIFICATIONSENT":"Passwortänderung mittgeteilt.",
|
"PASSWORDNOTIFICATIONSENT":"Passwortänderung mittgeteilt.",
|
||||||
"PASSWORDCHANGED":"Passwort geändert.",
|
"PASSWORDCHANGED":"Passwort geändert.",
|
||||||
@ -877,6 +908,10 @@
|
|||||||
"TITLE":"Identity Provider hinzufügen",
|
"TITLE":"Identity Provider hinzufügen",
|
||||||
"DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen",
|
"DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen",
|
||||||
"SELECTIDPS":"Identity Provider"
|
"SELECTIDPS":"Identity Provider"
|
||||||
|
},
|
||||||
|
"PASSWORDLESSTYPE": {
|
||||||
|
"0":"Nicht erlaubt",
|
||||||
|
"1":"Erlaubt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"APP": {
|
"APP": {
|
||||||
@ -951,8 +986,11 @@
|
|||||||
"OVERVIEWTITLE":"Deine Konfiguration ist bereit. Du kannst sie hier nochmals prüfen.",
|
"OVERVIEWTITLE":"Deine Konfiguration ist bereit. Du kannst sie hier nochmals prüfen.",
|
||||||
"ACCESSTOKENROLEASSERTION":"Benutzerrollen dem Access Token hinzufügen",
|
"ACCESSTOKENROLEASSERTION":"Benutzerrollen dem Access Token hinzufügen",
|
||||||
"ACCESSTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Access Token die Rollen des Authentifizierten Benutzers hinzugefügt.",
|
"ACCESSTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Access Token die Rollen des Authentifizierten Benutzers hinzugefügt.",
|
||||||
"IDTOKENROLEASSERTION":"Benutzerrollen dem Id Token hinzufügen",
|
"IDTOKENROLEASSERTION":"Benutzerrollen im ID Token",
|
||||||
"IDTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Id Token die Rollen des Authentifizierten Benutzers hinzugefügt."
|
"IDTOKENROLEASSERTION_DESCRIPTION":"Bei Auswahl werden dem Id Token die Rollen des Authentifizierten Benutzers hinzugefügt.",
|
||||||
|
"IDTOKENUSERINFOASSERTION":"User Info im ID Token",
|
||||||
|
"IDTOKENUSERINFOASSERTION_DESCRIPTION":"Ermöglich OIDC clients claims von profile, email, phone und address direkt vom ID Token zu beziehen.",
|
||||||
|
"CLOCKSKEW":"ermöglicht Clients, den Taktversatz von OP und Client zu verarbeiten. Die Dauer (0-5s) wird der exp addiert und von iats, auth_time und nbf abgezogen."
|
||||||
},
|
},
|
||||||
"TOAST": {
|
"TOAST": {
|
||||||
"REACTIVATED":"Anwendung reaktiviert.",
|
"REACTIVATED":"Anwendung reaktiviert.",
|
||||||
|
@ -152,6 +152,36 @@
|
|||||||
},
|
},
|
||||||
"EMPTY":"No entries"
|
"EMPTY":"No entries"
|
||||||
},
|
},
|
||||||
|
"PASSWORDLESS": {
|
||||||
|
"TABLETYPE":"Type",
|
||||||
|
"TABLESTATE":"Status",
|
||||||
|
"NAME":"Name",
|
||||||
|
"TABLEACTIONS":"Actions",
|
||||||
|
"TITLE": "Passwordless Authentication",
|
||||||
|
"DESCRIPTION": "Add WebAuthn based Authentication Methods to log onto ZITADEL passwordless.",
|
||||||
|
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
|
||||||
|
"U2F":"Add U2F",
|
||||||
|
"U2F_DIALOG_TITLE": "Verify U2F",
|
||||||
|
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used passwordless Login",
|
||||||
|
"U2F_SUCCESS":"Passwordless Auth created successfully!",
|
||||||
|
"U2F_ERROR":"An error during U2F setup occurred!",
|
||||||
|
"U2F_NAME":"U2F Name",
|
||||||
|
"TYPE": {
|
||||||
|
"0": "No MFA defined",
|
||||||
|
"1": "OTP",
|
||||||
|
"2": "U2F"
|
||||||
|
},
|
||||||
|
"STATE": {
|
||||||
|
"0": "No State",
|
||||||
|
"1": "Not Ready",
|
||||||
|
"2": "Ready",
|
||||||
|
"3": "Deleted"
|
||||||
|
},
|
||||||
|
"DIALOG": {
|
||||||
|
"DELETE_TITLE":"Remove Passwordless Authentication Method",
|
||||||
|
"DELETE_DESCRIPTION":"You are about to delete a passwordless Authentication method. Are you sure?"
|
||||||
|
}
|
||||||
|
},
|
||||||
"MFA": {
|
"MFA": {
|
||||||
"TABLETYPE":"Type",
|
"TABLETYPE":"Type",
|
||||||
"TABLESTATE":"Status",
|
"TABLESTATE":"Status",
|
||||||
@ -353,6 +383,7 @@
|
|||||||
"EMAILVERIFICATIONSENT":"E-mail verification code sent.",
|
"EMAILVERIFICATIONSENT":"E-mail verification code sent.",
|
||||||
"OTPREMOVED":"OTP removed.",
|
"OTPREMOVED":"OTP removed.",
|
||||||
"U2FREMOVED":"U2F removed.",
|
"U2FREMOVED":"U2F removed.",
|
||||||
|
"PASSWORDLESSREMOVED":"Passwordless removed.",
|
||||||
"INITIALPASSWORDSET":"Initial password set.",
|
"INITIALPASSWORDSET":"Initial password set.",
|
||||||
"PASSWORDNOTIFICATIONSENT":"Password change notification sent.",
|
"PASSWORDNOTIFICATIONSENT":"Password change notification sent.",
|
||||||
"PASSWORDCHANGED":"Password changed successfully.",
|
"PASSWORDCHANGED":"Password changed successfully.",
|
||||||
@ -877,6 +908,10 @@
|
|||||||
"TITLE":"Add Identity Provider",
|
"TITLE":"Add Identity Provider",
|
||||||
"DESCRIPTION":"You can select predefined or selfcreated providers for authentication.",
|
"DESCRIPTION":"You can select predefined or selfcreated providers for authentication.",
|
||||||
"SELECTIDPS":"Identity providers"
|
"SELECTIDPS":"Identity providers"
|
||||||
|
},
|
||||||
|
"PASSWORDLESSTYPE": {
|
||||||
|
"0":"Not allowed",
|
||||||
|
"1":"Allowed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"APP": {
|
"APP": {
|
||||||
@ -951,8 +986,11 @@
|
|||||||
"OVERVIEWTITLE":"You are now done. Review your configuration.",
|
"OVERVIEWTITLE":"You are now done. Review your configuration.",
|
||||||
"ACCESSTOKENROLEASSERTION":"Add user roles to the access token",
|
"ACCESSTOKENROLEASSERTION":"Add user roles to the access token",
|
||||||
"ACCESSTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the access token.",
|
"ACCESSTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the access token.",
|
||||||
"IDTOKENROLEASSERTION":"Add user roles to the id token",
|
"IDTOKENROLEASSERTION":"User roles inside ID Token",
|
||||||
"IDTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the id token."
|
"IDTOKENROLEASSERTION_DESCRIPTION":"If selected, the roles of the authenticated user are added to the ID token.",
|
||||||
|
"IDTOKENUSERINFOASSERTION":"User Info inside ID Token",
|
||||||
|
"IDTOKENUSERINFOASSERTION_DESCRIPTION":"Enables clients to retrieve profile, email, phone and address claims from ID token.",
|
||||||
|
"CLOCKSKEW":"Enables clients to handle clock skew of OP and client. The duration (0-5s) will be added to exp claim and subtracted from iats, auth_time and nbf."
|
||||||
},
|
},
|
||||||
"TOAST": {
|
"TOAST": {
|
||||||
"REACTIVATED":"Application reactivated.",
|
"REACTIVATED":"Application reactivated.",
|
||||||
|
@ -1353,7 +1353,7 @@ func (es *UserEventstore) RemoveU2FToken(ctx context.Context, userID, webAuthNTo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, token := user.Human.GetU2F(webAuthNTokenID); token == nil {
|
if _, token := user.Human.GetU2F(webAuthNTokenID); token == nil {
|
||||||
return errors.ThrowPreconditionFailed(nil, "EVENT-2M9ds", "Errors.User.NotHuman")
|
return errors.ThrowPreconditionFailed(nil, "EVENT-2M9ds", "Errors.User.MFA.U2F.NotExisting")
|
||||||
}
|
}
|
||||||
repoUser := model.UserFromModel(user)
|
repoUser := model.UserFromModel(user)
|
||||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FRemoveAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNTokenID{webAuthNTokenID}))
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FRemoveAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNTokenID{webAuthNTokenID}))
|
||||||
|
Loading…
Reference in New Issue
Block a user