fix: merge back origin/main

This commit is contained in:
Stefan Benz 2022-10-05 16:33:44 +02:00
commit 9cbe4650d8
No known key found for this signature in database
GPG Key ID: 9D2FE4EA50BEFE68
141 changed files with 3229 additions and 2489 deletions

View File

@ -37,6 +37,16 @@ jobs:
- name: Test ${{ matrix.browser }}
run: docker compose run e2e --browser ${{ matrix.browser }}
working-directory: e2e
- name: Ensure Artifacts Directory Exists
run: mdkir -p ./.artifacts
- name: Save ZITADEL Logs
if: always()
run: docker compose logs zitadel > ../.artifacts/e2e-compose-zitadel.log
working-directory: e2e
- name: Save Prepare Logs
if: always()
run: docker compose logs prepare > ../.artifacts/e2e-compose-prepare.log
working-directory: e2e
- name: Archive production tests ${{ matrix.browser }}
if: always()
uses: actions/upload-artifact@v2
@ -46,4 +56,6 @@ jobs:
e2e/cypress/results
e2e/cypress/videos
e2e/cypress/screenshots
.artifacts/e2e-compose-zitadel.log
.artifacts/e2e-compose-prepare.log
retention-days: 30

View File

@ -50,6 +50,10 @@ jobs:
if: always()
run: docker compose logs zitadel > ../.artifacts/e2e-compose-zitadel.log
working-directory: e2e
- name: Save Prepare Logs
if: always()
run: docker compose logs prepare > ../.artifacts/e2e-compose-prepare.log
working-directory: e2e
- name: Archive Test Results
if: always()
uses: actions/upload-artifact@v2
@ -60,4 +64,5 @@ jobs:
e2e/cypress/videos
e2e/cypress/screenshots
.artifacts/e2e-compose-zitadel.log
.artifacts/e2e-compose-prepare.log
retention-days: 30

View File

@ -125,4 +125,4 @@ brews:
announce:
discord:
enabled: true
message_template: 'ZITADEL {{ .Tag }} is ready! Check the notes: https://github.com/zitadel/zitadel/releases/tag/{{ .Tag }}'
message_template: 'ZITADEL {{ .Tag }} is ready! Check the notes: https://github.com/zitadel/zitadel/releases/tag/{{ .Tag }}'

View File

@ -290,7 +290,7 @@ You may edit the texts in these files or create a new file for additional langua
## Want to start ZITADEL?
You can find an installation guide for all the different environments here:
[https://docs.zitadel.com/docs/guides/installation](https://docs.zitadel.com/docs/guides/installation)
[https://docs.zitadel.com/docs/guides/deploy/overview](https://docs.zitadel.com/docs/guides/deploy/overview)
## **Did you find a security flaw?**

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@ import (
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
crypto_db "github.com/zitadel/zitadel/internal/crypto/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
)
@ -83,9 +84,17 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
mig.instanceSetup.CustomDomain = mig.externalDomain
mig.instanceSetup.DefaultLanguage = mig.DefaultLanguage
mig.instanceSetup.Org = mig.Org
// check if username is email style or else append @<orgname>.<custom-domain>
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
}
mig.instanceSetup.Org.Human.Email.Address = strings.TrimSpace(mig.instanceSetup.Org.Human.Email.Address)
if mig.instanceSetup.Org.Human.Email.Address == "" {
mig.instanceSetup.Org.Human.Email.Address = "admin@" + mig.instanceSetup.CustomDomain
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Username
if !strings.Contains(mig.instanceSetup.Org.Human.Email.Address, "@") {
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
}
}
_, _, _, _, err = cmd.SetUpInstance(ctx, &mig.instanceSetup)

View File

@ -4,13 +4,16 @@ FirstInstance:
Org:
Name: ZITADEL
Human:
# in case that UserLoginMustBeDomain is false (default) and you don't overwrite the username with an email,
# it will be suffixed by the org domain (org-name + domain from config).
# for example: zitadel-admin in org ZITADEL on domain.tld -> zitadel-admin@zitadel.domain.tld
UserName: zitadel-admin
FirstName: ZITADEL
LastName: Admin
NickName:
DisplayName:
Email:
Address: #autogenerated if empty. uses domain from config and prefixes admin@. for example: admin@domain.tdl
Address: #uses the username if empty
Verified: true
PreferredLanguage: en
Gender:

View File

@ -181,7 +181,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil {
return fmt.Errorf("error starting admin repo: %w", err)
}
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.Database.Database(), config.DefaultInstance)); err != nil {
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.Database.Database(), config.DefaultInstance, config.ExternalDomain)); err != nil {
return err
}
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.Database(), commands, queries, config.SystemDefaults, adminRepo, config.ExternalSecure, keys.User)); err != nil {

40
console/.eslintrc.js Normal file
View File

@ -0,0 +1,40 @@
module.exports = {
root: true,
ignorePatterns: ['projects/**/*'],
overrides: [
{
files: ['*.ts'],
parserOptions: {
project: ['tsconfig.json', 'e2e/tsconfig.json'],
createDefaultProgram: true,
tsconfigRootDir: __dirname,
},
extends: ['plugin:@angular-eslint/recommended', 'plugin:@angular-eslint/template/process-inline-templates'],
rules: {
'@angular-eslint/no-conflicting-lifecycle': 'off',
'@angular-eslint/no-host-metadata-property': 'off',
'@angular-eslint/component-selector': [
'error',
{
prefix: 'cnsl',
style: 'kebab-case',
type: 'element',
},
],
'@angular-eslint/directive-selector': [
'error',
{
prefix: 'cnsl',
style: 'camelCase',
type: 'attribute',
},
],
},
},
{
files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'],
rules: {},
},
],
};

View File

@ -1,53 +0,0 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/no-conflicting-lifecycle": "off",
"@angular-eslint/no-host-metadata-property": "off",
"@angular-eslint/component-selector": [
"error",
{
"prefix": "cnsl",
"style": "kebab-case",
"type": "element"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"prefix": "cnsl",
"style": "camelCase",
"type": "attribute"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@ -32,6 +32,7 @@
"grpc-web",
"@angular/common/locales/de",
"codemirror/mode/javascript/javascript",
"codemirror/mode/xml/xml",
"src/app/proto/generated/zitadel/admin_pb",
"src/app/proto/generated/zitadel/org_pb",
"src/app/proto/generated/zitadel/management_pb",

2325
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,22 +12,21 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.2",
"@angular/cdk": "^14.2.2",
"@angular/common": "^14.2.2",
"@angular/compiler": "^14.2.2",
"@angular/core": "^14.2.2",
"@angular/forms": "^14.2.2",
"@angular/material": "^14.2.2",
"@angular/material-moment-adapter": "^14.2.2",
"@angular/platform-browser": "^14.2.2",
"@angular/platform-browser-dynamic": "^14.2.2",
"@angular/router": "^14.2.2",
"@angular/service-worker": "^14.2.2",
"@angular/animations": "^14.2.4",
"@angular/cdk": "^14.2.3",
"@angular/common": "^14.2.4",
"@angular/compiler": "^14.2.4",
"@angular/core": "^14.2.4",
"@angular/forms": "^14.2.4",
"@angular/material": "^14.2.3",
"@angular/material-moment-adapter": "^14.2.3",
"@angular/platform-browser": "^14.2.4",
"@angular/platform-browser-dynamic": "^14.2.4",
"@angular/router": "^14.2.4",
"@angular/service-worker": "^14.2.4",
"@ctrl/ngx-codemirror": "^5.1.1",
"@grpc/grpc-js": "^1.7.0",
"@grpc/grpc-js": "^1.7.1",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@types/file-saver": "^2.0.2",
"@types/google-protobuf": "^3.15.3",
"@types/uuid": "^8.3.0",
@ -38,7 +37,7 @@
"file-saver": "^2.0.5",
"google-proto-files": "^3.0.0",
"google-protobuf": "^3.19.4",
"grpc-web": "^1.3.0",
"grpc-web": "^1.4.1",
"libphonenumber-js": "^1.10.6",
"material-design-icons-iconfont": "^6.1.1",
"moment": "^2.29.4",
@ -48,27 +47,27 @@
"rxjs": "~7.5.2",
"tinycolor2": "^1.4.2",
"tslib": "^2.2.0",
"uuid": "^8.3.2",
"uuid": "^9.0.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.2",
"@angular-eslint/builder": "^14.0.4",
"@angular-eslint/eslint-plugin": "^14.0.4",
"@angular-eslint/eslint-plugin-template": "^14.0.4",
"@angular-eslint/schematics": "^14.0.4",
"@angular-eslint/template-parser": "^14.0.4",
"@angular/cli": "^14.2.2",
"@angular/compiler-cli": "^14.2.2",
"@angular/language-service": "^14.2.2",
"@angular-devkit/build-angular": "^14.2.4",
"@angular-eslint/builder": "^14.1.2",
"@angular-eslint/eslint-plugin": "^14.1.2",
"@angular-eslint/eslint-plugin-template": "^14.1.2",
"@angular-eslint/schematics": "^14.1.2",
"@angular-eslint/template-parser": "^14.1.2",
"@angular/cli": "^14.2.4",
"@angular/compiler-cli": "^14.2.4",
"@angular/language-service": "^14.2.4",
"@types/jasmine": "~4.3.0",
"@types/jasminewd2": "~2.0.10",
"@types/jsonwebtoken": "^8.5.5",
"@types/node": "^18.7.16",
"@typescript-eslint/eslint-plugin": "5.36.1",
"@typescript-eslint/eslint-plugin": "5.38.1",
"@typescript-eslint/parser": "5.36.1",
"codelyzer": "^6.0.0",
"eslint": "^8.18.0",
"eslint": "^8.24.0",
"jasmine-core": "~4.4.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.0",
@ -78,6 +77,6 @@
"karma-jasmine-html-reporter": "^2.0.0",
"prettier": "^2.7.1",
"protractor": "~7.0.0",
"typescript": "^4.4.4"
"typescript": "^4.8.4"
}
}
}

View File

@ -11,7 +11,10 @@ const routes: Routes = [
{
path: '',
loadChildren: () => import('./pages/home/home.module').then((m) => m.HomeModule),
canActivate: [AuthGuard],
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['.'],
},
},
{
path: 'signedout',

View File

@ -47,7 +47,7 @@ export class AppComponent implements OnDestroy {
public showProjectSection: boolean = false;
private destroy$: Subject<void> = new Subject();
public labelpolicy!: LabelPolicy.AsObject;
public labelpolicy: LabelPolicy.AsObject | undefined = undefined;
public language: string = 'en';
public privacyPolicy!: PrivacyPolicy.AsObject;
@ -195,10 +195,12 @@ export class AppComponent implements OnDestroy {
if (authenticated) {
this.authService
.getActiveOrg()
.then((org) => {
.then(async (org) => {
this.org = org;
this.themeService.loadPrivateLabelling();
const policy = await this.themeService.loadPrivateLabelling();
if (policy) {
this.labelpolicy = policy;
}
// TODO add when console storage is implemented
// this.startIntroWorkflow();
})

View File

@ -51,7 +51,6 @@ export class AccountsCardComponent implements OnInit {
login_hint: loginHint,
},
};
(configWithPrompt as any).customQueryParams['login_hint'] = loginHint;
this.authService.authenticate(configWithPrompt);
}

View File

@ -1,11 +1,11 @@
<div class="footer-wrapper">
<div class="footer-row">
<div class="footer-links">
<a target="_blank" *ngIf="policy?.tosLink" rel="noreferrer" [href]="policy.tosLink" external>
<a target="_blank" *ngIf="policy?.tosLink" rel="noreferrer" [href]="policy?.tosLink" external>
<span>{{ 'FOOTER.LINKS.TOS' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
<a target="_blank" *ngIf="policy?.privacyLink" rel="noreferrer" [href]="policy.privacyLink" external>
<a target="_blank" *ngIf="policy?.privacyLink" rel="noreferrer" [href]="policy?.privacyLink" external>
<span>{{ 'FOOTER.LINKS.PP' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>

View File

@ -8,8 +8,8 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
styleUrls: ['./footer.component.scss'],
})
export class FooterComponent {
public policy!: PrivacyPolicy.AsObject;
@Input() public privateLabelPolicy!: LabelPolicy.AsObject;
public policy?: PrivacyPolicy.AsObject;
@Input() public privateLabelPolicy?: LabelPolicy.AsObject;
constructor(authService: GrpcAuthService) {
authService.getMyPrivacyPolicy().then((policyResp) => {
if (policyResp.policy) {

View File

@ -23,7 +23,7 @@
</a>
<ng-template #defaultHome>
<a class="title" [routerLink]="['/']">
<a class="title" [routerLink]="authService.zitadelPermissions.getValue().length === 0 ? ['/users', 'me'] : ['/']">
<img
class="logo"
alt="zitadel logo"

View File

@ -45,7 +45,7 @@ export class HeaderComponent implements OnDestroy {
];
constructor(
public authenticationService: AuthenticationService,
private authService: GrpcAuthService,
public authService: GrpcAuthService,
public mgmtService: ManagementService,
public breadcrumbService: BreadcrumbService,
public router: Router,

View File

@ -1,22 +1,19 @@
<div class="title-row">
<h1 class="metadata-title">{{ 'USER.METADATA.TITLE' | translate }}</h1>
<h1 class="metadata-title">{{ 'METADATA.TITLE' | translate }}</h1>
<span class="fill-space"></span>
<p *ngIf="ts" class="ts cnsl-secondary-text">{{ ts | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</p>
<mat-spinner *ngIf="loading" diameter="20"></mat-spinner>
<button class="icon-button" mat-icon-button (click)="load()">
<mat-icon class="icon">refresh</mat-icon>
</button>
</div>
<p class="desc">{{ 'USER.METADATA.DESCRIPTION' | translate }}</p>
<p class="desc">{{ 'METADATA.DESCRIPTION' | translate }}</p>
<div mat-dialog-content class="metadata-dialog-content">
<form *ngFor="let md of metadata; index as i" (ngSubmit)="saveElement(i)">
<div class="content">
<cnsl-form-field #key id="key{{ i }}" class="formfield">
<cnsl-label>{{ 'USER.METADATA.KEY' | translate }}</cnsl-label>
<cnsl-label>{{ 'METADATA.KEY' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="md.key" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>
<cnsl-form-field #value id="value{{ i }}" class="formfield">
<cnsl-label>{{ 'USER.METADATA.VALUE' | translate }}</cnsl-label>
<cnsl-label>{{ 'METADATA.VALUE' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="md.value" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>

View File

@ -3,7 +3,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Buffer } from 'buffer';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@ -14,61 +13,15 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class MetadataDialogComponent {
public metadata: Partial<Metadata.AsObject>[] = [];
public injData: any = {};
public loading: boolean = true;
public loading: boolean = false;
public ts!: Timestamp.AsObject | undefined;
constructor(
private managementService: ManagementService,
private authService: GrpcAuthService,
private toast: ToastService,
public dialogRef: MatDialogRef<MetadataDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.injData = data;
this.load();
}
public load(): void {
this.loadMetadata()
.then(() => {
this.loading = false;
if (this.metadata.length === 0) {
this.addEntry();
}
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
if (this.metadata.length === 0) {
this.addEntry();
}
});
}
public loadMetadata(): Promise<void> {
this.loading = true;
if (this.injData.userId) {
return this.managementService.listUserMetadata(this.injData.userId).then((resp) => {
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: Buffer.from(md.value as string, 'base64'),
};
});
this.ts = resp.details?.viewTimestamp;
});
} else {
return this.authService.listMyMetadata().then((resp) => {
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: Buffer.from(md.value as string, 'base64'),
};
});
this.ts = resp.details?.viewTimestamp;
});
}
this.metadata = data.metadata;
}
public addEntry(): void {
@ -104,24 +57,24 @@ export class MetadataDialogComponent {
public setMetadata(key: string, value: string): void {
if (key && value) {
this.managementService
.setUserMetadata(key, btoa(value), this.injData.userId)
this.data
.setFcn(key, value)
.then(() => {
this.toast.showInfo('USER.METADATA.SETSUCCESS', true);
this.toast.showInfo('METADATA.SETSUCCESS', true);
})
.catch((error) => {
.catch((error: any) => {
this.toast.showError(error);
});
}
}
public removeMetadata(key: string): Promise<void> {
return this.managementService
.removeUserMetadata(key, this.injData.userId)
.then((resp) => {
this.toast.showInfo('USER.METADATA.REMOVESUCCESS', true);
return this.data
.removeFcn(key)
.then((resp: any) => {
this.toast.showInfo('METADATA.REMOVESUCCESS', true);
})
.catch((error) => {
.catch((error: any) => {
this.toast.showError(error);
});
}

View File

@ -0,0 +1,40 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
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 { CardModule } from '../card/card.module';
import { InputModule } from '../input/input.module';
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
import { MetadataDialogComponent } from './metadata-dialog/metadata-dialog.component';
import { MetadataComponent } from './metadata/metadata.component';
@NgModule({
declarations: [MetadataComponent, MetadataDialogComponent],
imports: [
CommonModule,
MatDialogModule,
MatProgressSpinnerModule,
CardModule,
MatButtonModule,
TranslateModule,
InputModule,
MatIconModule,
MatTooltipModule,
FormsModule,
LocalizedDatePipeModule,
TimestampToDatePipeModule,
RefreshTableModule,
MatTableModule,
],
exports: [MetadataComponent, MetadataDialogComponent],
})
export class MetadataModule {}

View File

@ -0,0 +1,36 @@
<cnsl-card class="metadata-details" title="{{ 'METADATA.TITLE' | translate }}">
<mat-spinner card-actions class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refresh.emit()" [dataSize]="dataSource.data.length">
<button actions [disabled]="disabled" mat-raised-button color="primary" class="edit" (click)="editClicked.emit()">
{{ 'ACTIONS.EDIT' | translate }}
</button>
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="key">
<th mat-header-cell *matHeaderCellDef>{{ 'METADATA.KEY' | translate }}</th>
<td mat-cell *matCellDef="let metadata">
<span *ngIf="metadata?.key" class="centered">
{{ metadata.key }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef>{{ 'METADATA.VALUE' | translate }}</th>
<td mat-cell *matCellDef="let metadata">
<span *ngIf="metadata?.value" class="centered">
{{ metadata.value }}
</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{ 'USER.MFA.EMPTY' | translate }}</span>
</div>
</cnsl-refresh-table>
</cnsl-card>

View File

@ -1,16 +1,19 @@
.metadata-details {
padding-bottom: 1rem;
.metadata-actions {
display: flex;
align-items: center;
justify-content: flex-end;
.refresh {
margin-left: 0.5rem;
font-size: 1.2rem;
.edit {
font-size: 14px;
i {
font-size: 1.2rem;
}
}
.edit {
font-size: 14px;
}
.meta-row {
display: flex;
margin-bottom: 0.5rem;

View File

@ -0,0 +1,34 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
@Component({
selector: 'cnsl-metadata',
templateUrl: './metadata.component.html',
styleUrls: ['./metadata.component.scss'],
})
export class MetadataComponent implements OnChanges {
@Input() public metadata: Metadata.AsObject[] = [];
@Input() public disabled: boolean = false;
@Input() public loading: boolean = false;
@Output() public editClicked: EventEmitter<void> = new EventEmitter();
@Output() public refresh: EventEmitter<void> = new EventEmitter();
public displayedColumns: string[] = ['key', 'value'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<Metadata.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
public dataSource: MatTableDataSource<Metadata.AsObject> = new MatTableDataSource<Metadata.AsObject>([]);
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.metadata?.currentValue) {
this.dataSource = new MatTableDataSource<Metadata.AsObject>(changes.metadata.currentValue);
}
}
}

View File

@ -73,7 +73,7 @@ export class NavComponent implements OnDestroy {
@Input() public isDarkTheme: boolean = true;
@Input() public user!: User.AsObject;
@Input() public labelpolicy!: LabelPolicy.AsObject;
@Input() public labelpolicy?: LabelPolicy.AsObject;
public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
map((result) => {
return result.matches;

View File

@ -46,6 +46,7 @@
color="primary"
type="submit"
mat-raised-button
data-e2e="save-button"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>

View File

@ -2,7 +2,12 @@ import { Component, OnInit } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { take } from 'rxjs';
import { SetDefaultLanguageResponse, UpdateOIDCSettingsRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddOIDCSettingsRequest,
AddOIDCSettingsResponse,
UpdateOIDCSettingsRequest,
UpdateOIDCSettingsResponse,
} from 'src/app/proto/generated/zitadel/admin_pb';
import { OIDCSettings } from 'src/app/proto/generated/zitadel/settings_pb';
import { AdminService } from 'src/app/services/admin.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@ -15,6 +20,7 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class OIDCConfigurationComponent implements OnInit {
public oidcSettings!: OIDCSettings.AsObject;
private settingsSet: boolean = false;
public loading: boolean = false;
public form!: UntypedFormGroup;
@ -25,10 +31,10 @@ export class OIDCConfigurationComponent implements OnInit {
private authService: GrpcAuthService,
) {
this.form = this.fb.group({
accessTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
idTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
refreshTokenExpiration: [{ disabled: true, value: 30 }, [Validators.required]],
refreshTokenIdleExpiration: [{ disabled: true, value: 90 }, [Validators.required]],
accessTokenLifetime: [{ disabled: true }, [Validators.required]],
idTokenLifetime: [{ disabled: true }, [Validators.required]],
refreshTokenExpiration: [{ disabled: true }, [Validators.required]],
refreshTokenIdleExpiration: [{ disabled: true }, [Validators.required]],
});
}
@ -50,26 +56,27 @@ export class OIDCConfigurationComponent implements OnInit {
.then((oidcConfiguration) => {
if (oidcConfiguration.settings) {
this.oidcSettings = oidcConfiguration.settings;
this.settingsSet = true;
this.accessTokenLifetime?.setValue(
oidcConfiguration.settings.accessTokenLifetime?.seconds
? oidcConfiguration.settings.accessTokenLifetime?.seconds / 60 / 60
: 12,
: 0,
);
this.idTokenLifetime?.setValue(
oidcConfiguration.settings.idTokenLifetime?.seconds
? oidcConfiguration.settings.idTokenLifetime?.seconds / 60 / 60
: 12,
: 0,
);
this.refreshTokenExpiration?.setValue(
oidcConfiguration.settings.refreshTokenExpiration?.seconds
? oidcConfiguration.settings.refreshTokenExpiration?.seconds / 60 / 60 / 24
: 30,
: 0,
);
this.refreshTokenIdleExpiration?.setValue(
oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds
? oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds / 60 / 60 / 24
: 90,
: 0,
);
}
})
@ -78,31 +85,58 @@ export class OIDCConfigurationComponent implements OnInit {
});
}
private updateData(): Promise<SetDefaultLanguageResponse.AsObject> {
private updateData(): Promise<UpdateOIDCSettingsResponse.AsObject> {
const req = new UpdateOIDCSettingsRequest();
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 12) * 60 * 60);
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60);
req.setAccessTokenLifetime(accessToken);
const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 12) * 60 * 60);
const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 0) * 60 * 60);
req.setIdTokenLifetime(idToken);
const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 30) * 60 * 60 * 24);
const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 0) * 60 * 60 * 24);
req.setRefreshTokenExpiration(refreshToken);
const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 90) * 60 * 60 * 24);
const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 0) * 60 * 60 * 24);
req.setRefreshTokenIdleExpiration(refreshIdleToken);
return (this.service as AdminService).updateOIDCSettings(req);
}
private addData(): Promise<AddOIDCSettingsResponse.AsObject> {
const req = new AddOIDCSettingsRequest();
const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60);
req.setAccessTokenLifetime(accessToken);
const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 0) * 60 * 60);
req.setIdTokenLifetime(idToken);
const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 0) * 60 * 60 * 24);
req.setRefreshTokenExpiration(refreshToken);
const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 0) * 60 * 60 * 24);
req.setRefreshTokenIdleExpiration(refreshIdleToken);
return (this.service as AdminService).addOIDCSettings(req);
}
public savePolicy(): void {
const prom = this.updateData();
if (prom) {
prom
if (this.settingsSet) {
this.updateData()
.then(() => {
this.toast.showInfo('SETTING.SMTP.SAVED', true);
setTimeout(() => {
this.fetchData();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
} else {
this.addData()
.then(() => {
this.toast.showInfo('SETTING.SMTP.SAVED', true);
this.loading = true;
setTimeout(() => {
this.fetchData();
}, 2000);

View File

@ -162,7 +162,7 @@
"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
>
<i class="las la-ban"></i>
<i class="las la-trash"></i>
</button>
</ng-container>
<ng-template #addLogoButton>
@ -233,7 +233,7 @@
"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
>
<i class="las la-ban"></i>
<i class="las la-trash"></i>
</button>
</ng-container>
<ng-template #addIconButton>
@ -514,7 +514,7 @@
(click)="deleteFont()"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
>
<i class="las la-ban"></i>
<i class="las la-trash"></i>
</button>
</div>
@ -568,7 +568,7 @@
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="adv-container" *ngIf="previewData">
<div class="adv-container">
<mat-slide-toggle
class="toggle"
color="primary"

View File

@ -60,8 +60,8 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
@Input() public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public previewData!: LabelPolicy.AsObject;
public data!: LabelPolicy.AsObject;
public previewData?: LabelPolicy.AsObject;
public data?: LabelPolicy.AsObject;
public panelOpenState: boolean = false;
public isHoveringOverDarkLogo: boolean = false;
@ -447,6 +447,9 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
return (this.service as ManagementService)
.addCustomLabelPolicy(req0)
.then(() => {
if (this.previewData) {
this.previewData.isDefault = false;
}
this.toast.showInfo('POLICY.TOAST.SET', true);
reloadPolicy();
@ -493,60 +496,78 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
}
public setDarkBackgroundColorAndSave($event: string): void {
this.previewData.backgroundColorDark = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.backgroundColorDark = $event;
this.savePolicy();
}
}
public setDarkPrimaryColorAndSave($event: string): void {
this.previewData.primaryColorDark = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.primaryColorDark = $event;
this.savePolicy();
}
}
public setDarkWarnColorAndSave($event: string): void {
this.previewData.warnColorDark = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.warnColorDark = $event;
this.savePolicy();
}
}
public setDarkFontColorAndSave($event: string): void {
this.previewData.fontColorDark = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.fontColorDark = $event;
this.savePolicy();
}
}
public setBackgroundColorAndSave($event: string): void {
this.previewData.backgroundColor = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.backgroundColor = $event;
this.savePolicy();
}
}
public setPrimaryColorAndSave($event: string): void {
this.previewData.primaryColor = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.primaryColor = $event;
this.savePolicy();
}
}
public setWarnColorAndSave($event: string): void {
this.previewData.warnColor = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.warnColor = $event;
this.savePolicy();
}
}
public setFontColorAndSave($event: string): void {
this.previewData.fontColor = $event;
this.savePolicy();
if (this.previewData) {
this.previewData.fontColor = $event;
this.savePolicy();
}
}
public overwriteValues(req: AddCustomLabelPolicyRequest | UpdateCustomLabelPolicyRequest): void {
req.setBackgroundColorDark(this.previewData.backgroundColorDark);
req.setBackgroundColor(this.previewData.backgroundColor);
if (this.previewData) {
req.setBackgroundColorDark(this.previewData.backgroundColorDark);
req.setBackgroundColor(this.previewData.backgroundColor);
req.setFontColorDark(this.previewData.fontColorDark);
req.setFontColor(this.previewData.fontColor);
req.setFontColorDark(this.previewData.fontColorDark);
req.setFontColor(this.previewData.fontColor);
req.setPrimaryColorDark(this.previewData.primaryColorDark);
req.setPrimaryColor(this.previewData.primaryColor);
req.setPrimaryColorDark(this.previewData.primaryColorDark);
req.setPrimaryColor(this.previewData.primaryColor);
req.setWarnColorDark(this.previewData.warnColorDark);
req.setWarnColor(this.previewData.warnColor);
req.setWarnColorDark(this.previewData.warnColorDark);
req.setWarnColor(this.previewData.warnColor);
req.setDisableWatermark(this.previewData.disableWatermark);
req.setHideLoginNameSuffix(this.previewData.hideLoginNameSuffix);
req.setDisableWatermark(this.previewData.disableWatermark);
req.setHideLoginNameSuffix(this.previewData.hideLoginNameSuffix);
}
}
public activatePolicy(): Promise<any> {

View File

@ -5,7 +5,7 @@
>
<p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}</span>
<a mat-icon-button href="https://docs.zitadel.com/docs/manuals/admin-managers" target="_blank">
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/managers" target="_blank">
<i class="las la-info-circle"></i>
</a>
</p>

View File

@ -84,7 +84,8 @@ export class ActionTableComponent implements OnInit {
.deleteAction(action.id)
.then(() => {
this.toast.showInfo('FLOWS.DIALOG.DELETEACTION.DELETE_SUCCESS', true);
this.getData(20, 0);
this.refreshPage();
})
.catch((error: any) => {
this.toast.showError(error);
@ -152,7 +153,9 @@ export class ActionTableComponent implements OnInit {
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
setTimeout(() => {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}, 1000);
}
public deactivateSelection(): Promise<void> {

View File

@ -6,7 +6,7 @@
<h1>{{ 'ORG.DOMAINS.TITLE' | translate }}</h1>
<a
mat-icon-button
href="https://docs.zitadel.com/docs/guides/basics/organizations#how-zitadel-handles-usernames"
href="https://docs.zitadel.com/docs/guides/manage/console/organizations#how-zitadel-handles-usernames"
rel="noreferrer"
target="_blank"
>

View File

@ -18,7 +18,7 @@
</a>
<a
href="https://docs.zitadel.com/docs/guides/basics/get-started/"
href="https://docs.zitadel.com/docs/guides/start/quickstart"
target="_blank"
rel="noreferrer"
class="grid-item green"
@ -29,12 +29,7 @@
<span>{{ 'HOME.GETSTARTED.TITLE' | translate }}</span>
</a>
<a
href="https://docs.zitadel.com/docs/quickstarts/introduction"
target="_blank"
rel="noreferrer"
class="grid-item green"
>
<a href="https://docs.zitadel.com/docs/examples/introduction" target="_blank" rel="noreferrer" class="grid-item green">
<div class="grid-item-avatar green">
<i class="icon las la-play"></i>
</div>

View File

@ -1,7 +1,7 @@
<cnsl-detail-layout [hasBackButton]="true" title="{{ 'IAM.MEMBER.TITLE' | translate }}">
<p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</span>
<a mat-icon-button href="https://docs.zitadel.com/docs/manuals/admin-managers" target="_blank">
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/managers" target="_blank">
<i class="las la-info-circle"></i>
</a>
</p>

View File

@ -46,6 +46,13 @@
<cnsl-settings-grid [type]="PolicyComponentServiceType.MGMT"></cnsl-settings-grid>
</ng-container>
<cnsl-metadata
[metadata]="metadata"
[disabled]="(['org.write'] | hasRole | async) === false"
(editClicked)="editMetadata()"
(refresh)="loadMetadata()"
></cnsl-metadata>
<ng-template #nopolicyreadpermission>
<div class="no-permission-warn-wrapper">
<cnsl-info-section class="info-section-warn" [fitWidth]="true" [type]="InfoSectionType.ALERT">{{

View File

@ -1,20 +1,23 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { BehaviorSubject, from, Observable, of, Subject, takeUntil } from 'rxjs';
import { catchError, finalize, map, take } from 'rxjs/operators';
import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { MetadataDialogComponent } from 'src/app/modules/metadata/metadata-dialog/metadata-dialog.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { Buffer } from 'buffer';
@Component({
selector: 'cnsl-org-detail',
@ -28,6 +31,9 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
public OrgState: any = OrgState;
public ChangeType: any = ChangeType;
public metadata: Metadata.AsObject[] = [];
public loadingMetadata: boolean = true;
// members
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ -36,6 +42,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject();
public InfoSectionType: any = InfoSectionType;
constructor(
auth: GrpcAuthService,
private dialog: MatDialog,
@ -52,11 +59,13 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
auth.activeOrgChanged.pipe(takeUntil(this.destroy$)).subscribe((org) => {
this.getData();
this.loadMetadata();
});
}
public ngOnInit(): void {
this.getData();
this.loadMetadata();
}
public ngOnDestroy(): void {
@ -188,4 +197,40 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.membersSubject.next(members);
});
}
public loadMetadata(): Promise<any> | void {
this.loadingMetadata = true;
return this.mgmtService
.listOrgMetadata()
.then((resp) => {
this.loadingMetadata = false;
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: Buffer.from(md.value as string, 'base64').toString('ascii'),
};
});
})
.catch((error) => {
this.loadingMetadata = false;
this.toast.showError(error);
});
}
public editMetadata(): void {
const setFcn = (key: string, value: string): Promise<any> => this.mgmtService.setOrgMetadata(key, btoa(value));
const removeFcn = (key: string): Promise<any> => this.mgmtService.removeOrgMetadata(key);
const dialogRef = this.dialog.open(MetadataDialogComponent, {
data: {
metadata: this.metadata,
setFcn: setFcn,
removeFcn: removeFcn,
},
});
dialogRef.afterClosed().subscribe(() => {
this.loadMetadata();
});
}
}

View File

@ -1,7 +1,7 @@
<cnsl-detail-layout title="{{ org?.name }} {{ 'ORG.MEMBER.TITLE' | translate }}">
<p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'ORG.MEMBER.DESCRIPTION' | translate }}</span>
<a mat-icon-button href="https://docs.zitadel.com/docs/manuals/admin-managers" target="_blank">
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/managers" target="_blank">
<i class="las la-info-circle"></i>
</a>
</p>

View File

@ -18,6 +18,7 @@ import { InfoRowModule } from 'src/app/modules/info-row/info-row.module';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { MetadataModule } from 'src/app/modules/metadata/metadata.module';
import { SettingsGridModule } from 'src/app/modules/settings-grid/settings-grid.module';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { TopViewModule } from 'src/app/modules/top-view/top-view.module';
@ -53,6 +54,7 @@ import { OrgRoutingModule } from './org-routing.module';
MatMenuModule,
ChangesModule,
MatProgressSpinnerModule,
MetadataModule,
TranslateModule,
SharedModule,
SettingsGridModule,

View File

@ -1,7 +1,7 @@
<cnsl-top-view
title="{{ app?.name }}"
[hasActions]="isZitadel === false && (['project.app.write:' + projectId, 'project.app.write'] | hasRole | async)"
docLink="https://docs.zitadel.com/docs/guides/basics/projects"
docLink="https://docs.zitadel.com/docs/guides/manage/console/projects"
[sub]="app?.oidcConfig ? ('APP.OIDC.APPTYPE.' + app?.oidcConfig?.appType | translate) : app?.apiConfig ? 'API' : 'SAML'"
[isActive]="app?.state === AppState.APP_STATE_ACTIVE"
[isInactive]="app?.state === AppState.APP_STATE_INACTIVE"

View File

@ -1,7 +1,7 @@
<cnsl-top-view
title="{{ project?.projectName }}"
[hasActions]="false"
docLink="https://docs.zitadel.com/docs/guides/basics/projects#what-is-a-granted-project"
docLink="https://docs.zitadel.com/docs/guides/manage/console/projects#what-is-a-granted-project"
sub="{{ 'PROJECT.PAGES.TYPE.GRANTED_SINGULAR' | translate }} {{ 'ACTIONS.OF' | translate }} <strong>{{
project?.projectOwnerName
}}</strong>"

View File

@ -1,6 +1,6 @@
<cnsl-top-view
title="{{ project?.name }}"
docLink="https://docs.zitadel.com/docs/guides/basics/projects"
docLink="https://docs.zitadel.com/docs/guides/manage/console/projects"
sub="{{ 'PROJECT.PAGES.TYPE.OWNED_SINGULAR' | translate }}"
[isActive]="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
[isInactive]="project?.state === ProjectState.PROJECT_STATE_INACTIVE"

View File

@ -2,7 +2,12 @@
<div class="enlarged-container">
<div class="project-title-row">
<h1>{{ 'PROJECT.PAGES.LIST' | translate }}</h1>
<a mat-icon-button href="https://docs.zitadel.com/docs/guides/basics/projects" rel="noreferrer" target="_blank">
<a
mat-icon-button
href="https://docs.zitadel.com/docs/guides/manage/console/projects"
rel="noreferrer"
target="_blank"
>
<i class="las la-info-circle"></i>
</a>
</div>

View File

@ -25,7 +25,7 @@
color="primary"
matTooltip="{{ 'ACTIONS.NEW' | translate }}"
>
<i class="icon las la-fingerprint"></i>
<mat-icon>add</mat-icon>
{{ 'USER.PASSWORDLESS.U2F' | translate }}
</button>
<table class="table" mat-table [dataSource]="dataSource">

View File

@ -73,7 +73,9 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy {
});
dialogRef.afterClosed().subscribe((done) => {
this.getPasswordless();
setTimeout(() => {
this.getPasswordless();
}, 1000);
});
}
}

View File

@ -3,7 +3,7 @@
<div *ngIf="!showSent && !showQR">
<p>{{ 'USER.PASSWORDLESS.DIALOG.ADD_DESCRIPTION' | translate }}</p>
<div class="desc">
<div class="passwordless-desc">
<i class="icon las la-plus-circle"></i>
<p class="cnsl-secondary-text">{{ 'USER.PASSWORDLESS.DIALOG.NEW_DESCRIPTION' | translate }}</p>
</div>
@ -19,7 +19,7 @@
<p class="error">{{ error }}</p>
<div class="desc">
<div class="passwordless-desc">
<i class="icon las la-paper-plane"></i>
<p class="cnsl-secondary-text">{{ 'USER.PASSWORDLESS.DIALOG.SEND_DESCRIPTION' | translate }}</p>
</div>
@ -28,7 +28,7 @@
{{ 'USER.PASSWORDLESS.DIALOG.SEND' | translate }}
</button>
<div class="desc">
<div class="passwordless-desc">
<i class="icon las la-qrcode"></i>
<p class="cnsl-secondary-text">{{ 'USER.PASSWORDLESS.DIALOG.QRCODE_DESCRIPTION' | translate }}</p>
</div>

View File

@ -12,7 +12,7 @@
margin: 0;
}
.desc {
.passwordless-desc {
display: flex;
align-items: center;
padding-top: 1rem;

View File

@ -130,7 +130,13 @@
</ng-container>
<ng-container *ngIf="currentSetting === 'metadata'">
<cnsl-metadata *ngIf="user && user.id" [userId]="user.id"></cnsl-metadata>
<cnsl-metadata
[metadata]="metadata"
[disabled]="(['user.write:' + user.id, 'user.write'] | hasRole | async) === false"
*ngIf="user && user.id"
(editClicked)="editMetadata()"
(refresh)="loadMetadata()"
></cnsl-metadata>
</ng-container>
</cnsl-sidenav>

View File

@ -6,15 +6,18 @@ import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Subscription, take } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { MetadataDialogComponent } from 'src/app/modules/metadata/metadata-dialog/metadata-dialog.component';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { Email, Gender, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { AuthenticationService } from 'src/app/services/authentication.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { Buffer } from 'buffer';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
@Component({
@ -30,6 +33,7 @@ export class AuthUserDetailComponent implements OnDestroy {
private subscription: Subscription = new Subscription();
public loading: boolean = false;
public loadingMetadata: boolean = false;
public ChangeType: any = ChangeType;
public userLoginMustBeDomain: boolean = false;
@ -38,6 +42,8 @@ export class AuthUserDetailComponent implements OnDestroy {
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
public metadata: Metadata.AsObject[] = [];
public settingsList: SidenavSetting[] = [
{ id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' },
{ id: 'idp', i18nKey: 'USER.SETTINGS.IDP' },
@ -55,6 +61,7 @@ export class AuthUserDetailComponent implements OnDestroy {
public userService: GrpcAuthService,
private dialog: MatDialog,
private auth: AuthenticationService,
private mgmt: ManagementService,
private breadcrumbService: BreadcrumbService,
private mediaMatcher: MediaMatcher,
private _location: Location,
@ -104,6 +111,8 @@ export class AuthUserDetailComponent implements OnDestroy {
if (resp.user) {
this.user = resp.user;
this.loadMetadata();
this.breadcrumbService.setBreadcrumb([
new Breadcrumb({
type: BreadcrumbType.AUTHUSER,
@ -337,4 +346,45 @@ export class AuthUserDetailComponent implements OnDestroy {
}
});
}
public loadMetadata(): Promise<any> | void {
if (this.user) {
this.loadingMetadata = true;
return this.mgmt
.listUserMetadata(this.user.id)
.then((resp) => {
this.loadingMetadata = false;
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: Buffer.from(md.value as string, 'base64').toString('ascii'),
};
});
})
.catch((error) => {
this.loadingMetadata = false;
this.toast.showError(error);
});
}
}
public editMetadata(): void {
if (this.user && this.user.id) {
const setFcn = (key: string, value: string): Promise<any> =>
this.mgmt.setUserMetadata(key, Buffer.from(value).toString('base64'), this.user?.id ?? '');
const removeFcn = (key: string): Promise<any> => this.mgmt.removeUserMetadata(key, this.user?.id ?? '');
const dialogRef = this.dialog.open(MetadataDialogComponent, {
data: {
metadata: this.metadata,
setFcn: setFcn,
removeFcn: removeFcn,
},
});
dialogRef.afterClosed().subscribe(() => {
this.loadMetadata();
});
}
}
}

View File

@ -1,25 +0,0 @@
<cnsl-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
<div class="metadata-actions">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<button
[disabled]="(['user.write:' + userId, 'user.write'] | hasRole | async) === false"
mat-raised-button
color="primary"
class="edit"
(click)="editMetadata()"
>
{{ 'ACTIONS.EDIT' | translate }}
</button>
</div>
<ng-container *ngIf="metadata?.length; else emptyList">
<div class="metadata-set" *ngFor="let md of metadata">
<span class="first cnsl-secondary-text">{{ md.key }}</span>
<span class="second">{{ md.value }}</span>
</div>
</ng-container>
<ng-template #emptyList>
<p class="empty-desc cnsl-secondary-text">{{ 'USER.METADATA.EMPTY' | translate }}</p>
</ng-template>
</cnsl-card>

View File

@ -1,55 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { MetadataDialogComponent } from '../metadata-dialog/metadata-dialog.component';
@Component({
selector: 'cnsl-metadata',
templateUrl: './metadata.component.html',
styleUrls: ['./metadata.component.scss'],
})
export class MetadataComponent implements OnInit {
@Input() userId: string = '';
public metadata: Metadata.AsObject[] = [];
public loading: boolean = false;
constructor(private dialog: MatDialog, private service: ManagementService, private toast: ToastService) {}
ngOnInit(): void {
this.loadMetadata();
}
public editMetadata(): void {
const dialogRef = this.dialog.open(MetadataDialogComponent, {
data: {
userId: this.userId,
},
});
dialogRef.afterClosed().subscribe(() => {
this.loadMetadata();
});
}
public loadMetadata(): Promise<any> {
this.loading = true;
return (this.service as ManagementService)
.listUserMetadata(this.userId)
.then((resp) => {
this.loading = false;
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: atob(md.value as string),
};
});
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}
}

View File

@ -52,12 +52,11 @@ import { ContactComponent } from './contact/contact.component';
import { DetailFormMachineModule } from './detail-form-machine/detail-form-machine.module';
import { DetailFormModule } from './detail-form/detail-form.module';
import { ExternalIdpsComponent } from './external-idps/external-idps.component';
import { MetadataDialogComponent } from './metadata-dialog/metadata-dialog.component';
import { MetadataComponent } from './metadata/metadata.component';
import { PasswordComponent } from './password/password.component';
import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
import { MetadataModule } from 'src/app/modules/metadata/metadata.module';
@NgModule({
declarations: [
@ -76,8 +75,6 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
DialogU2FComponent,
DialogPasswordlessComponent,
AuthFactorDialogComponent,
MetadataDialogComponent,
MetadataComponent,
],
imports: [
ChangesModule,
@ -95,6 +92,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
ShowTokenDialogModule,
MetaLayoutModule,
MatCheckboxModule,
MetadataModule,
TopViewModule,
HasRolePipeModule,
UserGrantsModule,

View File

@ -1,7 +1,7 @@
<cnsl-top-view
*ngIf="user"
title="{{ user.human ? user.human.profile?.displayName : user.machine?.name }}"
docLink="https://docs.zitadel.com/docs/guides/basics/projects"
docLink="https://docs.zitadel.com/docs/guides/manage/console/projects"
sub="{{ user.preferredLoginName }}"
[isActive]="user.state === UserState.USER_STATE_ACTIVE"
[isInactive]="user.state === UserState.USER_STATE_INACTIVE"
@ -206,7 +206,13 @@
</ng-container>
<ng-container *ngIf="currentSetting && currentSetting === 'metadata'">
<cnsl-metadata *ngIf="user" [userId]="user.id"></cnsl-metadata>
<cnsl-metadata
[metadata]="metadata"
[disabled]="(['user.write:' + user.id, 'user.write'] | hasRole | async) === false"
*ngIf="user && user.id"
(editClicked)="editMetadata()"
(refresh)="loadMetadata(user.id)"
></cnsl-metadata>
</ng-container>
</div>
</cnsl-sidenav>

View File

@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { MetadataDialogComponent } from 'src/app/modules/metadata/metadata-dialog/metadata-dialog.component';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
@ -16,7 +17,7 @@ import { Email, Gender, Machine, Phone, Profile, User, UserState } from 'src/app
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { Buffer } from 'buffer';
import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
@ -42,7 +43,9 @@ export class UserDetailComponent implements OnInit {
public languages: string[] = ['de', 'en', 'it', 'fr'];
public ChangeType: any = ChangeType;
public loading: boolean = true;
public loadingMetadata: boolean = true;
public UserState: any = UserState;
public copied: string = '';
@ -113,6 +116,7 @@ export class UserDetailComponent implements OnInit {
this.mgmtUserService
.getUserByID(id)
.then((resp) => {
this.loadMetadata(id);
this.loading = false;
if (resp.user) {
this.user = resp.user;
@ -129,17 +133,6 @@ export class UserDetailComponent implements OnInit {
this.loading = false;
this.toast.showError(err);
});
this.mgmtUserService
.listUserMetadata(id, 0, 100, [])
.then((resp) => {
if (resp.resultList) {
this.metadata = resp.resultList;
}
})
.catch((err) => {
console.error(err);
});
});
}
@ -448,4 +441,43 @@ export class UserDetailComponent implements OnInit {
break;
}
}
public loadMetadata(id: string): Promise<any> | void {
this.loadingMetadata = true;
return this.mgmtUserService
.listUserMetadata(id)
.then((resp) => {
this.loadingMetadata = false;
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: Buffer.from(md.value as string, 'base64').toString('ascii'),
};
});
})
.catch((error) => {
this.loadingMetadata = false;
this.toast.showError(error);
});
}
public editMetadata(): void {
if (this.user) {
const setFcn = (key: string, value: string): Promise<any> =>
this.mgmtUserService.setUserMetadata(key, Buffer.from(value).toString('base64'), this.user.id);
const removeFcn = (key: string): Promise<any> => this.mgmtUserService.removeUserMetadata(key, this.user.id);
const dialogRef = this.dialog.open(MetadataDialogComponent, {
data: {
metadata: this.metadata,
setFcn: setFcn,
removeFcn: removeFcn,
},
});
dialogRef.afterClosed().subscribe(() => {
this.loadMetadata(this.user.id);
});
}
}
}

View File

@ -180,6 +180,8 @@ import {
UpdateLockoutPolicyResponse,
UpdateLoginPolicyRequest,
UpdateLoginPolicyResponse,
AddOIDCSettingsRequest,
AddOIDCSettingsResponse,
UpdateOIDCSettingsRequest,
UpdateOIDCSettingsResponse,
UpdatePasswordAgePolicyRequest,
@ -623,6 +625,10 @@ export class AdminService {
return this.grpcService.admin.updateOIDCSettings(req, null).then((resp) => resp.toObject());
}
public addOIDCSettings(req: AddOIDCSettingsRequest): Promise<AddOIDCSettingsResponse.AsObject> {
return this.grpcService.admin.addOIDCSettings(req, null).then((resp) => resp.toObject());
}
/* LOG and FILE Notifications */
public getLogNotificationProvider(): Promise<GetLogNotificationProviderResponse.AsObject> {

View File

@ -132,7 +132,7 @@ export class GrpcAuthService {
),
),
);
private zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
public zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private cachedOrgs: Org.AsObject[] = [];

View File

@ -219,6 +219,8 @@ import {
ListOrgMemberRolesResponse,
ListOrgMembersRequest,
ListOrgMembersResponse,
ListOrgMetadataRequest,
ListOrgMetadataResponse,
ListPersonalAccessTokensRequest,
ListPersonalAccessTokensResponse,
ListProjectChangesRequest,
@ -303,6 +305,8 @@ import {
RemoveOrgIDPResponse,
RemoveOrgMemberRequest,
RemoveOrgMemberResponse,
RemoveOrgMetadataRequest,
RemoveOrgMetadataResponse,
RemovePersonalAccessTokenRequest,
RemovePersonalAccessTokenResponse,
RemoveProjectGrantMemberRequest,
@ -371,6 +375,8 @@ import {
SetCustomVerifyPhoneMessageTextRequest,
SetCustomVerifyPhoneMessageTextResponse,
SetHumanInitialPasswordRequest,
SetOrgMetadataRequest,
SetOrgMetadataResponse,
SetPrimaryOrgDomainRequest,
SetPrimaryOrgDomainResponse,
SetTriggerActionsRequest,
@ -1374,6 +1380,26 @@ export class ManagementService {
return this.grpcService.mgmt.listUserMetadata(req, null).then((resp) => resp.toObject());
}
public listOrgMetadata(
offset?: number,
limit?: number,
queryList?: MetadataQuery[],
): Promise<ListOrgMetadataResponse.AsObject> {
const req = new ListOrgMetadataRequest();
const metadata = new ListQuery();
if (offset) {
metadata.setOffset(offset);
}
if (limit) {
metadata.setLimit(limit);
}
if (queryList) {
req.setQueriesList(queryList);
}
return this.grpcService.mgmt.listOrgMetadata(req, null).then((resp) => resp.toObject());
}
public getUserMetadata(userId: string, key: string): Promise<GetUserMetadataResponse.AsObject> {
const req = new GetUserMetadataRequest();
req.setId(userId);
@ -1389,6 +1415,13 @@ export class ManagementService {
return this.grpcService.mgmt.setUserMetadata(req, null).then((resp) => resp.toObject());
}
public setOrgMetadata(key: string, value: string): Promise<SetOrgMetadataResponse.AsObject> {
const req = new SetOrgMetadataRequest();
req.setKey(key);
req.setValue(value);
return this.grpcService.mgmt.setOrgMetadata(req, null).then((resp) => resp.toObject());
}
public bulkSetUserMetadata(
list: BulkSetUserMetadataRequest.Metadata[],
userId: string,
@ -1406,6 +1439,12 @@ export class ManagementService {
return this.grpcService.mgmt.removeUserMetadata(req, null).then((resp) => resp.toObject());
}
public removeOrgMetadata(key: string): Promise<RemoveOrgMetadataResponse.AsObject> {
const req = new RemoveOrgMetadataRequest();
req.setKey(key);
return this.grpcService.mgmt.removeOrgMetadata(req, null).then((resp) => resp.toObject());
}
public removeUser(id: string): Promise<RemoveUserResponse.AsObject> {
const req = new RemoveUserRequest();
req.setId(id);

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { LabelPolicy } from '../proto/generated/zitadel/policy_pb';
import { GrpcAuthService } from './grpc-auth.service';
@ -140,14 +141,15 @@ export class ThemeService {
this.saveTextColor(lightText, false);
};
public loadPrivateLabelling(forceDefault: boolean = false): void {
public loadPrivateLabelling(forceDefault: boolean = false): Promise<LabelPolicy.AsObject | undefined> {
if (forceDefault) {
this.setDefaultColors();
return Promise.resolve(undefined);
} else {
const isDark = (color: string) => this.isDark(color);
const isLight = (color: string) => this.isLight(color);
this.authService
return this.authService
.getMyLabelPolicy()
.then((lpresp) => {
const labelpolicy = lpresp.policy;
@ -201,10 +203,17 @@ export class ThemeService {
lightText = '#000000';
}
this.saveTextColor(lightText || '#000000', false);
if (labelpolicy) {
return labelpolicy;
} else {
return Promise.reject();
}
})
.catch((error) => {
console.error('could not load private labelling policy!', error);
this.setDefaultColors();
return Promise.reject();
});
}
}

View File

@ -294,7 +294,7 @@
"TITLE": "Passwortlose Authentifizierungsmethoden",
"DESCRIPTION": "Füge WebAuthn kompatible Authentifikatoren hinzu um dich passwortlos anzumelden.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"U2F": "Authentifikator hinzufügen",
"U2F": "Methode hinzufügen",
"U2F_DIALOG_TITLE": "Authentifikator hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Login an.",
"U2F_SUCCESS": "Passwortlos erfolgreich erstellt!",
@ -326,17 +326,6 @@
"NEW": "Hinzufügen"
}
},
"METADATA": {
"TITLE": "Metadata",
"DESCRIPTION": "",
"KEY": "Schlüssel",
"VALUE": "Wert",
"ADD": "Neues Element",
"SAVE": "Speichern",
"EMPTY": "Keine Metadaten",
"SETSUCCESS": "Element erfolgreich gespeichert",
"REMOVESUCCESS": "Element erfolgreich gelöscht"
},
"MFA": {
"TABLETYPE": "Typ",
"TABLESTATE": "Status",
@ -346,7 +335,7 @@
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"ADD": "Faktor hinzufügen",
"OTP": "OTP (One-Time Password)",
"OTP": "Authentikator App für OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "OTP hinzufügen",
"OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.",
"U2F": "Fingerabdruck, Security Key, Face ID oder andere",
@ -639,6 +628,17 @@
"DELETED": "Personal Access Token gelöscht."
}
},
"METADATA": {
"TITLE": "Metadata",
"DESCRIPTION": "",
"KEY": "Schlüssel",
"VALUE": "Wert",
"ADD": "Neues Element",
"SAVE": "Speichern",
"EMPTY": "Keine Metadaten",
"SETSUCCESS": "Element erfolgreich gespeichert",
"REMOVESUCCESS": "Element erfolgreich gelöscht"
},
"FLOWS": {
"TITLE": "Aktionen und Abläufe",
"DESCRIPTION": "Hinterlege scripts die bei einem bestimmten Event ausgeführt werden.",

View File

@ -294,7 +294,7 @@
"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 authenticator",
"U2F": "Add method",
"U2F_DIALOG_TITLE": "Verify authenticator",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used passwordless Login",
"U2F_SUCCESS": "Passwordless Auth created successfully!",
@ -326,17 +326,6 @@
"NEW": "Add New"
}
},
"METADATA": {
"TITLE": "Metadata",
"DESCRIPTION": "",
"KEY": "Key",
"VALUE": "Value",
"ADD": "New Entry",
"SAVE": "Save",
"EMPTY": "No metadata",
"SETSUCCESS": "Element saved successfully",
"REMOVESUCCESS": "Element deleted successfully"
},
"MFA": {
"TABLETYPE": "Type",
"TABLESTATE": "Status",
@ -346,7 +335,7 @@
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
"ADD": "Add Factor",
"OTP": "OTP (One-Time Password)",
"OTP": "Authenticator App for OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Add OTP",
"OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.",
"U2F": "Fingerprint, Security Keys, Face ID and other",
@ -639,6 +628,17 @@
"DELETED": "Token deleted with success."
}
},
"METADATA": {
"TITLE": "Metadata",
"DESCRIPTION": "",
"KEY": "Key",
"VALUE": "Value",
"ADD": "New Entry",
"SAVE": "Save",
"EMPTY": "No metadata",
"SETSUCCESS": "Element saved successfully",
"REMOVESUCCESS": "Element deleted successfully"
},
"FLOWS": {
"TITLE": "Actions and Flows",
"DESCRIPTION": "Define scripts to execute on a certain event.",

View File

@ -294,7 +294,7 @@
"TITLE": "Authentification sans mot de passe",
"DESCRIPTION": "Ajoutez des méthodes d'authentification basées sur WebAuthn pour vous connecter à ZITADEL sans mot de passe.",
"MANAGE_DESCRIPTION": "Gérez les méthodes de second facteur de vos utilisateurs.",
"U2F": "Ajouter un authentifiant",
"U2F": "Ajouter une méthode",
"U2F_DIALOG_TITLE": "Vérifier l'authentifiant",
"U2F_DIALOG_DESCRIPTION": "Entrez un nom pour votre connexion sans mot de passe utilisée",
"U2F_SUCCESS": "Auth sans mot de passe créé avec succès !",
@ -326,17 +326,6 @@
"NEW": "Ajouter un nouveau"
}
},
"METADATA": {
"TITLE": "Métadonnées",
"DESCRIPTION": "",
"KEY": "Clé",
"VALUE": "Valeur",
"ADD": "Nouvelle entrée",
"SAVE": "Enregistrer",
"EMPTY": "Pas de métadonnées",
"SETSUCCESS": "Élément sauvegardé avec succès",
"REMOVESUCCESS": "Élément supprimé avec succès"
},
"MFA": {
"TABLETYPE": "Type",
"TABLESTATE": "Statut",
@ -346,7 +335,7 @@
"DESCRIPTION": "Ajoutez un second facteur pour garantir une sécurité optimale de votre compte.",
"MANAGE_DESCRIPTION": "Gérez les méthodes de second facteur de vos utilisateurs.",
"ADD": "Ajouter un facteur",
"OTP": "OTP (mot de passe à usage unique)",
"OTP": "Application d'authentification pour OTP (One-time password)",
"OTP_DIALOG_TITLE": "Ajouter un OTP",
"OTP_DIALOG_DESCRIPTION": "Scannez le code QR avec une application d'authentification et saisissez le code ci-dessous pour vérifier et activer la méthode OTP.",
"U2F": "Empreinte digitale, clés de sécurité, Face ID et autres",
@ -639,6 +628,17 @@
"DELETED": "Jeton supprimé avec succès."
}
},
"METADATA": {
"TITLE": "Métadonnées",
"DESCRIPTION": "",
"KEY": "Clé",
"VALUE": "Valeur",
"ADD": "Nouvelle entrée",
"SAVE": "Enregistrer",
"EMPTY": "Pas de métadonnées",
"SETSUCCESS": "Élément sauvegardé avec succès",
"REMOVESUCCESS": "Élément supprimé avec succès"
},
"FLOWS": {
"TITLE": "Actions et flux",
"DESCRIPTION": "Définissez des scripts à exécuter lors d'un certain événement.",

View File

@ -294,7 +294,7 @@
"TITLE": "Autenticazione passwordless",
"DESCRIPTION": "Aggiungi i metodi di autenticazione basati su WebAuthn per accedere a ZITADEL senza password.",
"MANAGE_DESCRIPTION": "Gestisci i metodi del secondo fattore dei vostri utenti.",
"U2F": "Aggiungi autenticatore",
"U2F": "Aggiungi metodo",
"U2F_DIALOG_TITLE": "Verifica autenticatore",
"U2F_DIALOG_DESCRIPTION": "Inserisci un nome per il tuo authenticatore o dispositivo usato.",
"U2F_SUCCESS": "Autorizzazione passwordless creata con successo!",
@ -326,17 +326,6 @@
"NEW": "Aggiungi nuovo"
}
},
"METADATA": {
"TITLE": "Metadati",
"DESCRIPTION": "",
"KEY": "Chiave",
"VALUE": "Valore",
"ADD": "Nuova voce",
"SAVE": "Salva",
"EMPTY": "Nessun metadato",
"SETSUCCESS": "Salvato con successo",
"REMOVESUCCESS": "Rimosso con successo"
},
"MFA": {
"TABLETYPE": "Tipo",
"TABLESTATE": "Stato",
@ -346,7 +335,7 @@
"DESCRIPTION": "Aggiungi un secondo fattore per garantire la sicurezza ottimale del tuo account.",
"MANAGE_DESCRIPTION": "Gestite i metodi del secondo fattore dei vostri utenti.",
"ADD": "Aggiungi fattore",
"OTP": "OTP (One-Time Password)",
"OTP": "App di autenticazione per OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Aggiungi OTP",
"OTP_DIALOG_DESCRIPTION": "Scansiona il codice QR con un'app di autenticazione e inserisci il codice nel campo sottostante per verificare e attivare il metodo OTP.",
"U2F": "Impronta digitale, chiave di sicurezza, Face ID e altri",
@ -639,6 +628,17 @@
"DELETED": "Token eliminato con successo."
}
},
"METADATA": {
"TITLE": "Metadati",
"DESCRIPTION": "",
"KEY": "Chiave",
"VALUE": "Valore",
"ADD": "Nuova voce",
"SAVE": "Salva",
"EMPTY": "Nessun metadato",
"SETSUCCESS": "Salvato con successo",
"REMOVESUCCESS": "Rimosso con successo"
},
"FLOWS": {
"TITLE": "Azioni e Processi",
"DESCRIPTION": "Esegui processi su certi eventi.",

View File

@ -294,7 +294,7 @@
"TITLE": "无密码身份验证",
"DESCRIPTION": "添加基于 WebAuthn 的身份验证方法以无密码登录 ZITADEL。",
"MANAGE_DESCRIPTION": "管理用户的第二因素认证方式。",
"U2F": "添加验证器",
"U2F": "添加方法",
"U2F_DIALOG_TITLE": "身份验证器",
"U2F_DIALOG_DESCRIPTION": "输入您使用的无密码登录名",
"U2F_SUCCESS": "无密码验证创建成功!",
@ -326,17 +326,6 @@
"NEW": "添加"
}
},
"METADATA": {
"TITLE": "元数据",
"DESCRIPTION": "",
"KEY": "键",
"VALUE": "值",
"ADD": "新条目",
"SAVE": "保存",
"EMPTY": "暂无元数据",
"SETSUCCESS": "键值对保存成功",
"REMOVESUCCESS": "键值对删除成功"
},
"MFA": {
"TABLETYPE": "类型",
"TABLESTATE": "状态",
@ -346,7 +335,7 @@
"DESCRIPTION": "添加第二个因素以确保您帐户的最佳安全性。",
"MANAGE_DESCRIPTION": "管理用户的第二因素身份认证方式。",
"ADD": "添加因子",
"OTP": "一次性密码 (OTP)",
"OTP": "用于 OTP 的身份验证器应用程序",
"OTP_DIALOG_TITLE": "添加 OTP",
"OTP_DIALOG_DESCRIPTION": "使用验证器应用程序扫描二维码并输入下面的代码以验证并激活 OTP 方法。",
"U2F": "指纹、安全密钥、Face ID 等",
@ -639,6 +628,17 @@
"DELETED": "成功删除令牌。"
}
},
"METADATA": {
"TITLE": "元数据",
"DESCRIPTION": "",
"KEY": "键",
"VALUE": "值",
"ADD": "新条目",
"SAVE": "保存",
"EMPTY": "暂无元数据",
"SETSUCCESS": "键值对保存成功",
"REMOVESUCCESS": "键值对删除成功"
},
"FLOWS": {
"TITLE": "动作和流程",
"DESCRIPTION": "定义要在特定事件上需要执行的脚本。",

View File

@ -25,6 +25,7 @@ In addition to the standard compliant scopes we utilize the following scopes.
| Scopes | Example | Description |
|:--------------------------------------------------|:-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `urn:zitadel:iam:org:project:role:{rolekey}` | `urn:zitadel:iam:org:project:role:user` | By using this scope a client can request the claim urn:zitadel:iam:roles to be asserted when possible. As an alternative approach you can enable all roles to be asserted from the [project](../../guides/manage/console/projects) a client belongs to. |
| `urn:zitadel:iam:org:id:{id}` | `urn:zitadel:iam:org:id:178204173316174381` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed. It will assert the `urn:zitadel:iam:user:resourceowner` claims. |
| `urn:zitadel:iam:org:domain:primary:{domainname}` | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed |
| `urn:zitadel:iam:role:{rolename}` | | |
| `urn:zitadel:iam:org:project:id:{projectid}:aud` | `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access token |

View File

@ -284,6 +284,18 @@ Get OIDC settings (e.g token lifetimes, etc.)
GET: /settings/oidc
### AddOIDCSettings
> **rpc** AddOIDCSettings([AddOIDCSettingsRequest](#addoidcsettingsrequest))
[AddOIDCSettingsResponse](#addoidcsettingsresponse)
Add oidc settings (e.g token lifetimes, etc)
POST: /settings/oidc
### UpdateOIDCSettings
> **rpc** UpdateOIDCSettings([UpdateOIDCSettingsRequest](#updateoidcsettingsrequest))
@ -1739,6 +1751,31 @@ This is an empty request
### AddOIDCSettingsRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| access_token_lifetime | google.protobuf.Duration | - | |
| id_token_lifetime | google.protobuf.Duration | - | |
| refresh_token_idle_expiration | google.protobuf.Duration | - | |
| refresh_token_expiration | google.protobuf.Duration | - | |
### AddOIDCSettingsResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### AddSMSProviderTwilioRequest

View File

@ -57,6 +57,18 @@ This might take some time
POST: /instances
### UpdateInstance
> **rpc** UpdateInstance([UpdateInstanceRequest](#updateinstancerequest))
[UpdateInstanceResponse](#updateinstanceresponse)
Updates name of an existing instance
PUT: /instances/{instance_id}
### CreateInstance
> **rpc** CreateInstance([CreateInstanceRequest](#createinstancerequest))
@ -729,6 +741,29 @@ This is an empty response
### UpdateInstanceRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| instance_id | string | - | |
| instance_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### UpdateInstanceResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### View

View File

@ -0,0 +1,55 @@
---
title: JWT IDP
---
# JWT IDP
JSON Web Token Identity Provider (JWT IDP) gives you the possibility to use an (existing) JWT as federated identity.
Imagine you have a Web Application Firewall (WAF) which handles your session for an existing application.
You're now creating a new application which uses ZITADEL as its IDP / Authentication Server.
The new app might even be opened from within the existing application and you want to reuse the session information for the new application.
This is where JWT IDP comes into place.
All you need to provide is an endpoint where ZITADEL can receive a JWT and some information for its signature verification.
## Authentication using JWT IDP
The authentication process then might look like the following:
![JWT IDP Architecture](/img/concepts/objects/jwt_idp.png)
1. The user is logged into the existing application and the WAF holds the session information. It might even send a JWT to the application.
The new application is opened by clicking on a link in the existing application.
2. The application bootstraps and since it cannot find a session, it will create an OIDC Authorization Request to ZITADEL.
In this request it provides a scope to directly request the JWT IDP.
3. ZITADEL will do so and redirect to the preconfigured JWT Endpoint. While the endpoint is behind the WAF, ZITADEL is able to receive a JWT from a defined http header.
It will then validate its signature, which might require to call the configured Keys Endpoint.
If the signature is valid and token is not expired, ZITADEL will then use the token and the enclosed `sub` claim as if it was an id_token returned from an OIDC IDP:
It will try to match a user's external identity and if not possible, create a new user with the information of the provided token.
Prerequisite for this is that the IDP setting `autoregister` is set to `true`.
4. ZITADEL will then redirect to its main instance and the login flow will proceed.
5. The user will be redirected to the Callback Endpoint of the new Application, where the application will exchange the code for tokens.
The user is finally logged in the new application, without any direct interaction.
### Terms and example values
To further explain and illustrate how a JWT IDP works, we will assume the following:
- the **Existing Application** is deployed under `apps.test.com/existing/`
- the **New Application** is deployed under `new.test.com`
- the **Login UI of ZITADEL** is deployed under `accounts.test.com`
The **JWT IDP Configuration** might then be:
- **JWT Endpoint** (Endpoint where ZITADEL will redirect to):<br/>`https://apps.test.com/existing/auth-new`
- **Issuer** (of the JWT):<br/>`https://issuer.test.internal`
- **Keys Endpoint** (where keys of the JWT Signature can be gathered):<br/>`https://issuer.test.internal/keys`
- **Header Name** (of the JWT, Authorization if omitted):<br/>`x-custom-tkn`
Therefore, if the user is redirected from ZITADEL to the JWT Endpoint on the WAF (`https://apps.test.com/existing/auth-new`),
the session cookies previously issued by the WAF, will be sent along by the browser due to the path being on the same domain as the exiting application.
The WAF will reuse the session and send the JWT in the HTTP header `x-custom-tkn` to its upstream, the ZITADEL JWT Endpoint (`https://accounts.test.com/ui/login/login/jwt/authorize`).
For the signature validation, ZITADEL must be able to connect to Keys Endpoint (`https://issuer.test.internal/keys`)
and it will check if the token was signed (claim `iss`) by the defined Issuer (`https://issuer.test.internal`).

View File

@ -7,7 +7,7 @@ It demonstrates how to fetch some data from the ZITADEL management API.
At the end of the guide you should have an application able to read the details of your organization.
If you need any other information about the .NET SDK go to the [documentation](https://caos.github.io/zitadel-net/) of the SDK itself.
If you need any other information about the .NET SDK go to the [documentation](https://zitadel.github.io/zitadel-net/) of the SDK itself.
## Prerequisites
The client [SDK](https://github.com/zitadel/zitadel-net) will handle all necessary OAuth 2.0 requests and send the required headers to the ZITADEL API.

View File

@ -301,6 +301,7 @@ module.exports = {
"concepts/structure/granted_projects",
"concepts/structure/users",
"concepts/structure/managers",
"concepts/structure/jwt_idp",
],
},
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -21,7 +21,7 @@ describe('humans', () => {
cy.url().should('contain', 'users/create');
cy.get('[formcontrolname="email"]').type(loginname('e2ehuman', Cypress.env('ORGANIZATION')));
//force needed due to the prefilled username prefix
cy.get('[formcontrolname="userName"]').type(testHumanUserNameAdd);
cy.get('[formcontrolname="userName"]').type(loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION')));
cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname');
cy.get('[formcontrolname="lastName"]').type('e2ehumanlastname');
cy.get('[formcontrolname="phone"]').type('+41 123456789');
@ -35,7 +35,7 @@ describe('humans', () => {
describe('remove', () => {
before('ensure it exists', () => {
apiAuth().then((api) => {
ensureHumanUserExists(api, testHumanUserNameRemove).then(() => {
ensureHumanUserExists(api, loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION'))).then(() => {
cy.visit(humansPath);
});
});

View File

@ -45,9 +45,7 @@ describe('machines', () => {
// .trigger('mouseover')
.find('[data-e2e="enabled-delete-button"]')
.click({ force: true });
cy.get('[data-e2e="confirm-dialog-input"]')
.focus()
.type(loginname(testMachineUserNameRemove, Cypress.env('ORGANIZATION')));
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testMachineUserNameRemove);
cy.get('[data-e2e="confirm-dialog-button"]').click();
cy.get('.data-e2e-success');
cy.wait(200);

View File

@ -0,0 +1,38 @@
import { apiAuth } from '../../support/api/apiauth';
import { ensureOIDCSettingsSet } from '../../support/api/oidc-settings';
describe('oidc settings', () => {
const oidcSettingsPath = `/settings?id=oidc`;
const accessTokenPrecondition = 1;
const idTokenPrecondition = 2;
const refreshTokenExpirationPrecondition = 7;
const refreshTokenIdleExpirationPrecondition = 2;
before(`ensure they are set`, () => {
apiAuth().then((apiCallProperties) => {
ensureOIDCSettingsSet(
apiCallProperties,
accessTokenPrecondition,
idTokenPrecondition,
refreshTokenExpirationPrecondition,
refreshTokenIdleExpirationPrecondition,
);
cy.visit(oidcSettingsPath);
});
});
it(`should update oidc settings`, () => {
cy.get('[formcontrolname="accessTokenLifetime"]').should('value', accessTokenPrecondition).clear().type('2');
cy.get('[formcontrolname="idTokenLifetime"]').should('value', idTokenPrecondition).clear().type('24');
cy.get('[formcontrolname="refreshTokenExpiration"]')
.should('value', refreshTokenExpirationPrecondition)
.clear()
.type('30');
cy.get('[formcontrolname="refreshTokenIdleExpiration"]')
.should('value', refreshTokenIdleExpirationPrecondition)
.clear()
.type('7');
cy.get('[data-e2e="save-button"]').click();
cy.get('.data-e2e-success');
});
});

View File

@ -3,6 +3,7 @@ import { login, User } from 'support/login/users';
export interface apiCallProperties {
authHeader: string;
mgntBaseURL: string;
adminBaseURL: string;
}
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
@ -10,6 +11,7 @@ export function apiAuth(): Cypress.Chainable<apiCallProperties> {
return <apiCallProperties>{
authHeader: `Bearer ${token}`,
mgntBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1/`,
adminBaseURL: `${Cypress.env('BACKEND_URL')}/admin/v1/`,
};
});
}

View File

@ -40,6 +40,46 @@ export function ensureSomethingExists(
});
}
export function ensureSomethingIsSet(
api: apiCallProperties,
path: string,
find: (entity: any) => SearchResult,
createPath: string,
body: any,
): Cypress.Chainable<number> {
return getSomething(api, path, find)
.then((sRes) => {
if (sRes.entity) {
return cy.wrap({
id: sRes.entity.id,
initialSequence: 0,
});
}
return cy
.request({
method: 'PUT',
url: createPath,
headers: {
Authorization: api.authHeader,
},
body: body,
failOnStatusCode: false,
followRedirect: false,
})
.then((cRes) => {
expect(cRes.status).to.equal(200);
return {
id: cRes.body.id,
initialSequence: sRes.sequence,
};
});
})
.then((data) => {
awaitDesiredById(90, (entity) => !!entity, data.initialSequence, api, path, find);
return cy.wrap<number>(data.id);
});
}
export function ensureSomethingDoesntExist(
api: apiCallProperties,
searchPath: string,
@ -97,6 +137,24 @@ function searchSomething(
});
}
function getSomething(
api: apiCallProperties,
searchPath: string,
find: (entity: any) => SearchResult,
): Cypress.Chainable<SearchResult> {
return cy
.request({
method: 'GET',
url: searchPath,
headers: {
Authorization: api.authHeader,
},
})
.then((res) => {
return find(res.body);
});
}
function awaitDesired(
trials: number,
expectEntity: (entity: any) => boolean,
@ -116,3 +174,23 @@ function awaitDesired(
}
});
}
function awaitDesiredById(
trials: number,
expectEntity: (entity: any) => boolean,
initialSequence: number,
api: apiCallProperties,
path: string,
find: (entity: any) => SearchResult,
) {
getSomething(api, path, find).then((resp) => {
const foundExpectedEntity = expectEntity(resp.entity);
const foundExpectedSequence = resp.sequence > initialSequence;
if (!foundExpectedEntity || !foundExpectedSequence) {
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
cy.wait(1000);
awaitDesiredById(trials - 1, expectEntity, initialSequence, api, path, find);
}
});
}

View File

@ -0,0 +1,44 @@
import { apiCallProperties } from './apiauth';
import { ensureSomethingIsSet } from './ensure';
export function ensureOIDCSettingsSet(
api: apiCallProperties,
accessTokenLifetime,
idTokenLifetime,
refreshTokenExpiration,
refreshTokenIdleExpiration: number,
): Cypress.Chainable<number> {
return ensureSomethingIsSet(
api,
`${api.adminBaseURL}settings/oidc`,
(settings: any) => {
let entity = null;
if (
settings.settings?.accessTokenLifetime === hoursToDuration(accessTokenLifetime) &&
settings.settings?.idTokenLifetime === hoursToDuration(idTokenLifetime) &&
settings.settings?.refreshTokenExpiration === daysToDuration(refreshTokenExpiration) &&
settings.settings?.refreshTokenIdleExpiration === daysToDuration(refreshTokenIdleExpiration)
) {
entity = settings.settings;
}
return {
entity: entity,
sequence: settings.settings?.details?.sequence,
};
},
`${api.adminBaseURL}settings/oidc`,
{
accessTokenLifetime: hoursToDuration(accessTokenLifetime),
idTokenLifetime: hoursToDuration(idTokenLifetime),
refreshTokenExpiration: daysToDuration(refreshTokenExpiration),
refreshTokenIdleExpiration: daysToDuration(refreshTokenIdleExpiration),
},
);
}
function hoursToDuration(hours: number): string {
return (hours * 3600).toString() + 's';
}
function daysToDuration(days: number): string {
return hoursToDuration(24 * days);
}

View File

@ -71,7 +71,7 @@ export function login(
cy.get('#submit-button').click();
cy.wait('@password').then((interception) => {
if (interception.response.body.indexOf('Multifactor Setup') === -1) {
if (interception.response.body.indexOf('/ui/login/mfa/prompt') === -1) {
return;
}

View File

@ -18,6 +18,16 @@ func (s *Server) GetOIDCSettings(ctx context.Context, _ *admin_pb.GetOIDCSetting
}, nil
}
func (s *Server) AddOIDCSettings(ctx context.Context, req *admin_pb.AddOIDCSettingsRequest) (*admin_pb.AddOIDCSettingsResponse, error) {
result, err := s.command.AddOIDCSettings(ctx, AddOIDCConfigToConfig(req))
if err != nil {
return nil, err
}
return &admin_pb.AddOIDCSettingsResponse{
Details: object.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) UpdateOIDCSettings(ctx context.Context, req *admin_pb.UpdateOIDCSettingsRequest) (*admin_pb.UpdateOIDCSettingsResponse, error) {
result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req))
if err != nil {

View File

@ -1,12 +1,13 @@
package admin
import (
"google.golang.org/protobuf/types/known/durationpb"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
"google.golang.org/protobuf/types/known/durationpb"
)
func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings {
@ -19,6 +20,15 @@ func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings {
}
}
func AddOIDCConfigToConfig(req *admin_pb.AddOIDCSettingsRequest) *domain.OIDCSettings {
return &domain.OIDCSettings{
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(),
IdTokenLifetime: req.IdTokenLifetime.AsDuration(),
RefreshTokenIdleExpiration: req.RefreshTokenIdleExpiration.AsDuration(),
RefreshTokenExpiration: req.RefreshTokenExpiration.AsDuration(),
}
}
func UpdateOIDCConfigToConfig(req *admin_pb.UpdateOIDCSettingsRequest) *domain.OIDCSettings {
return &domain.OIDCSettings{
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(),

View File

@ -41,7 +41,7 @@ func (s *Server) GetInstance(ctx context.Context, req *system_pb.GetInstanceRequ
}
func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequest) (*system_pb.AddInstanceResponse, error) {
id, _, _, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.DefaultInstance))
id, _, _, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
if err != nil {
return nil, err
}
@ -51,8 +51,19 @@ func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequ
}, nil
}
func (s *Server) UpdateInstance(ctx context.Context, req *system_pb.UpdateInstanceRequest) (*system_pb.UpdateInstanceResponse, error) {
ctx = authz.WithInstanceID(ctx, req.InstanceId)
details, err := s.command.UpdateInstance(ctx, req.InstanceName)
if err != nil {
return nil, err
}
return &system_pb.UpdateInstanceResponse{
Details: object.AddToDetailsPb(details.Sequence, details.EventDate, details.ResourceOwner),
}, nil
}
func (s *Server) CreateInstance(ctx context.Context, req *system_pb.CreateInstanceRequest) (*system_pb.CreateInstanceResponse, error) {
id, pat, key, details, err := s.command.SetUpInstance(ctx, CreateInstancePbToSetupInstance(req, s.DefaultInstance))
id, pat, key, details, err := s.command.SetUpInstance(ctx, CreateInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
if err != nil {
return nil, err
}

View File

@ -1,6 +1,8 @@
package system
import (
"strings"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language"
@ -15,7 +17,7 @@ import (
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
)
func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defaultInstance command.InstanceSetup) *command.InstanceSetup {
func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defaultInstance command.InstanceSetup, externalDomain string) *command.InstanceSetup {
if req.InstanceName != "" {
defaultInstance.InstanceName = req.InstanceName
defaultInstance.Org.Name = req.InstanceName
@ -72,6 +74,11 @@ func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defau
}
}
}
// check if default username is email style or else append @<orgname>.<custom-domain>
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
if !defaultInstance.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(defaultInstance.Org.Human.Username, "@") {
defaultInstance.Org.Human.Username = defaultInstance.Org.Human.Username + "@" + domain.NewIAMDomainName(defaultInstance.Org.Name, externalDomain)
}
if user.UserName != "" {
defaultInstance.Org.Human.Username = user.UserName
}
@ -89,7 +96,7 @@ func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defau
return &defaultInstance
}
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup) *command.InstanceSetup {
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup, externalDomain string) *command.InstanceSetup {
if req.InstanceName != "" {
defaultInstance.InstanceName = req.InstanceName
defaultInstance.Org.Name = req.InstanceName
@ -118,6 +125,11 @@ func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInst
}
}
}
// check if default username is email style or else append @<orgname>.<custom-domain>
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
if !defaultInstance.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(defaultInstance.Org.Human.Username, "@") {
defaultInstance.Org.Human.Username = defaultInstance.Org.Human.Username + "@" + domain.NewIAMDomainName(defaultInstance.Org.Name, externalDomain)
}
if req.OwnerUserName != "" {
defaultInstance.Org.Human.Username = req.OwnerUserName
}

View File

@ -25,25 +25,29 @@ type Server struct {
command *command.Commands
query *query.Queries
administrator repository.AdministratorRepository
DefaultInstance command.InstanceSetup
defaultInstance command.InstanceSetup
externalDomain string
}
type Config struct {
Repository eventsourcing.Config
}
func CreateServer(command *command.Commands,
func CreateServer(
command *command.Commands,
query *query.Queries,
repo repository.Repository,
database string,
defaultInstance command.InstanceSetup,
externalDomain string,
) *Server {
return &Server{
command: command,
query: query,
administrator: repo,
database: database,
DefaultInstance: defaultInstance,
defaultInstance: defaultInstance,
externalDomain: externalDomain,
}
}

View File

@ -88,7 +88,13 @@ func (o *OPStorage) CreateAccessToken(ctx context.Context, req op.TokenRequest)
applicationID = authReq.ApplicationID
userOrgID = authReq.UserOrgID
}
resp, err := o.command.AddUserToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), o.defaultAccessTokenLifetime) //PLANNED: lifetime from client
accessTokenLifetime, _, _, _, err := o.getOIDCSettings(ctx)
if err != nil {
return "", time.Time{}, err
}
resp, err := o.command.AddUserToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), accessTokenLifetime) //PLANNED: lifetime from client
if err != nil {
return "", time.Time{}, err
}
@ -106,9 +112,15 @@ func (o *OPStorage) CreateAccessAndRefreshTokens(ctx context.Context, req op.Tok
if request, ok := req.(op.RefreshTokenRequest); ok {
request.SetCurrentScopes(scopes)
}
accessTokenLifetime, _, refreshTokenIdleExpiration, refreshTokenExpiration, err := o.getOIDCSettings(ctx)
if err != nil {
return "", "", time.Time{}, err
}
resp, token, err := o.command.AddAccessAndRefreshToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(),
refreshToken, req.GetAudience(), scopes, authMethodsReferences, o.defaultAccessTokenLifetime,
o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, authTime) //PLANNED: lifetime from client
refreshToken, req.GetAudience(), scopes, authMethodsReferences, accessTokenLifetime,
refreshTokenIdleExpiration, refreshTokenExpiration, authTime) //PLANNED: lifetime from client
if err != nil {
if errors.IsErrorInvalidArgument(err) {
err = oidc.ErrInvalidGrant().WithParent(err)
@ -248,3 +260,15 @@ func setContextUserSystem(ctx context.Context) context.Context {
}
return authz.SetCtxData(ctx, data)
}
func (o *OPStorage) getOIDCSettings(ctx context.Context) (accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration, _ error) {
oidcSettings, err := o.query.OIDCSettingsByAggID(ctx, authz.GetInstance(ctx).InstanceID())
if err != nil && !errors.IsNotFound(err) {
return time.Duration(0), time.Duration(0), time.Duration(0), time.Duration(0), err
}
if oidcSettings != nil {
return oidcSettings.AccessTokenLifetime, oidcSettings.IdTokenLifetime, oidcSettings.RefreshTokenIdleExpiration, oidcSettings.RefreshTokenExpiration, nil
}
return o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, nil
}

View File

@ -51,7 +51,13 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
for i, role := range projectRoles.ProjectRoles {
allowedScopes[i] = ScopeProjectRolePrefix + role.Key
}
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, allowedScopes)
accessTokenLifetime, idTokenLifetime, _, _, err := o.getOIDCSettings(ctx)
if err != nil {
return nil, err
}
return ClientFromBusiness(client, o.defaultLoginURL, accessTokenLifetime, idTokenLifetime, allowedScopes)
}
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
@ -95,6 +101,13 @@ func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string
scopes = scopes[:len(scopes)-1]
}
}
if strings.HasPrefix(scope, domain.OrgIDScope) {
if strings.TrimPrefix(scope, domain.OrgIDScope) != user.ResourceOwner {
scopes[i] = scopes[len(scopes)-1]
scopes[len(scopes)-1] = ""
scopes = scopes[:len(scopes)-1]
}
}
}
return scopes, nil
}
@ -251,6 +264,16 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
userInfo.AppendClaims(domain.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
}
if strings.HasPrefix(scope, domain.OrgIDScope) {
userInfo.AppendClaims(domain.OrgIDClaim, strings.TrimPrefix(scope, domain.OrgIDScope))
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
if err != nil {
return err
}
for claim, value := range resourceOwnerClaims {
userInfo.AppendClaims(claim, value)
}
}
}
}
if len(roles) == 0 || applicationID == "" {
@ -289,9 +312,20 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
}
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
} else if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
}
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
claims = appendClaim(claims, domain.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
}
if strings.HasPrefix(scope, domain.OrgIDScope) {
claims = appendClaim(claims, domain.OrgIDClaim, strings.TrimPrefix(scope, domain.OrgIDScope))
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
if err != nil {
return nil, err
}
for claim, value := range resourceOwnerClaims {
claims = appendClaim(claims, claim, value)
}
}
}
if len(roles) == 0 || clientID == "" {
return claims, nil

View File

@ -103,6 +103,9 @@ func (c *Client) IsScopeAllowed(scope string) bool {
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
return true
}
if strings.HasPrefix(scope, domain.OrgIDScope) {
return true
}
if strings.HasPrefix(scope, domain.ProjectIDScope) {
return true
}

View File

@ -507,7 +507,7 @@ func (l *Login) isDisplayLoginNameSuffix(authReq *domain.AuthRequest) bool {
if authReq == nil {
return false
}
if authReq.RequestedOrgID == "" {
if authReq.RequestedOrgID == "" || !authReq.RequestedOrgDomain {
return false
}
return authReq.LabelPolicy != nil && !authReq.LabelPolicy.HideLoginNameSuffix

View File

@ -53,7 +53,7 @@ InitPassword:
CodeLabel: Code
NewPasswordLabel: Neues Passwort
NewPasswordConfirmLabel: Passwort bestätigen
ResendButtonText: erneut senden
ResendButtonText: Code erneut senden
NextButtonText: weiter
InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser:
Title: User aktivieren
Description: Du hast einen Code erhalten, welcher im untenstehenden Formular eingegeben werden muss um deine E-Mail zu verifizieren und ein neues Passwort zu setzen.
Description: Bestätige deine E-Mail mit dem unten stehenden Code und legen dein Passwort fest.
CodeLabel: Code
NewPasswordLabel: Neues Passwort
NewPasswordConfirmLabel: Passwort bestätigen
NewPasswordConfirm: Passwort bestätigen
NextButtonText: weiter
ResendButtonText: erneut senden
ResendButtonText: Code erneut senden
InitUserDone:
Title: User aktiviert
@ -78,65 +78,65 @@ InitUserDone:
CancelButtonText: abbrechen
InitMFAPrompt:
Title: Multifaktor hinzufügen
Description: Möchtest du einen Mulitfaktor hinzufügen?
Provider0: OTP (One Time Password)
Provider1: U2F (Universal 2nd Factor)
Title: 2-Faktor hinzufügen
Description: 2-Faktor-Authentifizierung gibt dir eine zusätzliche Sicherheit für dein Benutzerkonto. Damit stellst du sicher, dass nur du Zugriff auf deinen Account hast.
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
NextButtonText: weiter
SkipButtonText: überspringen
InitMFAOTP:
Title: Multifaktor Verifizierung
Description: Verifiziere deinen Multifaktor
OTPDescription: Scanne den Code mit einem Authentifizierungs-App (z.B Google Authenticator) oder kopiere das Secret und gib anschliessend den Code ein.
Title: 2-Faktor Verifizierung
Description: Erstelle deinen 2-Faktor. Lade eine Authenticator-App herunter, wenn du noch keines hast.
OTPDescription: Scanne den Code mit einem Authentifizierungs-App (z.B Google/Mircorsoft Authenticator, Authy) oder kopiere das Secret und gib anschliessend den Code ein.
SecretLabel: Secret
CodeLabel: Code
NextButtonText: weiter
CancelButtonText: abbrechen
InitMFAU2F:
Title: Multifaktor U2F / WebAuthN hinzufügen
Description: Füge dein Token hinzu, indem du einen Namen eingibst und den 'Token registrieren' Button drückst.
TokenNameLabel: Name des Tokens / Geräts
NotSupported: WebAuthN wird durch deinen Browser nicht unterstützt. Stelle sicher, dass du die aktuelle Version installiert hast oder nutze einen anderen (z.B. Chrome, Safari, Firefox)
RegisterTokenButtonText: Token registrieren
Title: Sicherheitsschlüssel hinzufügen
Description: Ein Sicherheitsschlüssel ist eine Verifizierungsmethode, die in Ihrem Telefon integriert sein kann, Bluetooth verwenden oder direkt an den USB-Anschluss Ihres Computers angeschlossen werden.
TokenNameLabel: Name des Geräts
NotSupported: WebAuthN wird durch deinen Browser nicht unterstützt. Stelle sicher, dass du die aktuelle Version installiert hast oder nutze eine anderen (z.B. Chrome, Safari, Firefox)
RegisterTokenButtonText: Sicherschlüssel hinzufügen
ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode.
InitMFADone:
Title: Multifaktor Verifizierung erstellt
Description: Multifikator Verifizierung erfolgreich abgeschlossen. Der Multifaktor muss bei jeder Anmeldung eingegeben werden.
Title: Sicherheitsschlüssel eingerichtet
Description: Großartig! Du hast gerade erfolgreich deinen 2-Faktor eingerichtet und dein Konto viel sicherer gemacht. Der 2-Faktor muss bei jeder Anmeldung verwendet werden.
NextButtonText: weiter
CancelButtonText: abbrechen
MFAProvider:
Provider0: OTP (One Time Password)
Provider1: U2F (Universal 2nd Factor)
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
ChooseOther: oder wähle eine andere Option aus
VerifyMFAOTP:
Title: Multifaktor verifizieren
Description: Verifiziere deinen Multifaktor
Title: 2-Faktor verifizieren
Description: Verifiziere deinen Zweitfaktor
CodeLabel: Code
NextButtonText: next
VerifyMFAU2F:
Title: Multifaktor Verifizierung
Title: 2-Faktor Verifizierung
Description: Verifiziere deinen Multifaktor U2F / WebAuthN Token
NotSupported: WebAuthN wird durch deinen Browser nicht unterstützt. Stelle sicher, dass du die aktuelle Version installiert hast oder nutze einen anderen (z.B. Chrome, Safari, Firefox)
ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode.
ValidateTokenButtonText: Token validieren
ValidateTokenButtonText: 2-Faktor verifizieren
Passwordless:
Title: Passwortlos einloggen
Description: Verifiziere dein Token
Description: Melden Sie sich mit den von Ihrem Gerät bereitgestellten Authentifizierungsmethoden wie FaceID, Windows Hello oder Fingerabdruck an.
NotSupported: WebAuthN wird durch deinen Browser nicht unterstützt. Stelle sicher, dass du die aktuelle Version installiert hast oder nutze einen anderen (z.B. Chrome, Safari, Firefox)
ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode.
LoginWithPwButtonText: Mit Passwort anmelden
ValidateTokenButtonText: Token validieren
ValidateTokenButtonText: Passwortlos anmelden
PasswordlessPrompt:
Title: Passwortloser Login hinzufügen
Description: Möchtest du einen passwortlosen Login hinzufügen?
Description: Möchtest du einen passwortlosen Login hinzufügen? (Authentifizierungsmethoden deines Gerätes wie FaceID, Windows Hello oder Fingerprint)
DescriptionInit: Du musst zuerst den Passwortlosen Login hinzufügen. Nutze dazu den Link, den du erhalten hast um dein Gerät zu registrieren.
PasswordlessButtonText: Werde Passwortlos
NextButtonText: weiter
@ -144,15 +144,15 @@ PasswordlessPrompt:
PasswordlessRegistration:
Title: Passwortloser Login hinzufügen
Description: Füge dein Token hinzu, indem du einen Namen eingibst und den 'Token registrieren' Button drückst.
TokenNameLabel: Name des Tokens / Geräts
Description: Füge dein Authentifizierung hinzu, indem du einen Namen eingibst (eg. MyPhone, MacBook, etc) und den 'Passwortlos registrieren' Button drückst.
TokenNameLabel: Name des Geräts
NotSupported: WebAuthN wird durch deinen Browser nicht unterstützt. Stelle sicher, dass du die aktuelle Version installiert hast oder nutze einen anderen (z.B. Chrome, Safari, Firefox)
RegisterTokenButtonText: Token registrieren
RegisterTokenButtonText: Passwortlos registrieren
ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle eine andere Methode.
PasswordlessRegistrationDone:
Title: Passwortloser Login erstellt
Description: Token für passwortlosen Login erfolgreich hinzugefügt.
Description: Gerät für passwortlosen Login erfolgreich hinzugefügt.
DescriptionClose: Du kannst das Fenster nun schliessen.
NextButtonText: weiter
CancelButtonText: abbrechen
@ -181,7 +181,7 @@ EmailVerification:
Description: Du hast ein E-Mail zur Verifizierung deiner E-Mail Adresse bekommen. Gib den Code im untenstehenden Formular ein. Mit erneut versenden, wird dir ein neues E-Mail zugestellt.
CodeLabel: Code
NextButtonText: weiter
ResendButtonText: erneut senden
ResendButtonText: Code erneut senden
EmailVerificationDone:
Title: E-Mail Verifizierung
@ -239,6 +239,7 @@ ExternalRegistrationUserOverview:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
TosConfirm: Ich akzeptiere die
TosLinkText: AGBs
@ -282,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: abbrechen
NextButtonText: weiter
ExternalNotFoundOption:
ExternalNotFound:
Title: Externer Benutzer
Description: Externer Benutzer konnte nicht gefunden werden. Willst du deinen Benutzer mit einem bestehenden verlinken oder diesen als neuen Benutzer registrieren.
LinkButtonText: Verlinken
@ -296,6 +297,7 @@ ExternalNotFoundOption:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Footer:
PoweredBy: Powered By

View File

@ -47,13 +47,13 @@ UsernameChangeDone:
Description: Your username was changed successfully.
NextButtonText: next
InitPassword:
InitPassword:
Title: Set Password
Description: You have received a code, which you have to enter in the form below, to set your new password.
CodeLabel: Code
NewPasswordLabel: New Password
NewPasswordConfirmLabel: Confirm Password
ResendButtonText: resend
ResendButtonText: resend code
NextButtonText: next
InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser:
Title: Activate User
Description: You have received a code, which you have to enter in the form below, to verify your email and set your new password.
Description: Verify your e-mail with the code below and set your password.
CodeLabel: Code
NewPasswordLabel: New Password
NewPasswordConfirmLabel: Confirm Password
NewPasswordConfirm: Confirm Password
NextButtonText: next
ResendButtonText: resend
ResendButtonText: resend code
InitUserDone:
Title: User activated
@ -78,65 +78,65 @@ InitUserDone:
CancelButtonText: cancel
InitMFAPrompt:
Title: Multifactor Setup
Description: Would you like to setup multifactor authentication?
Provider0: OTP (One Time Password)
Provider1: U2F (Universal 2nd Factor)
Title: 2-Factor Setup
Description: 2-factor authentication gives you an additional security for your user account. This ensures that only you have access to your account.
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
NextButtonText: next
SkipButtonText: skip
InitMFAOTP:
Title: Multifactor Verification
Description: Verify your multifactor.
OTPDescription: Scan the code with your authenticator app (e.g Google Authenticator) or copy the secret and insert the generated code below.
Title: 2-Factor Verification
Description: Create your 2-factor. Download an authenticator app if you do not already have one.
OTPDescription: Scan the code with your authenticator app (e.g Google/Microsoft Authenticator, Authy) or copy the secret and insert the generated code below.
SecretLabel: Secret
CodeLabel: Code
NextButtonText: next
CancelButtonText: cancel
InitMFAU2F:
Title: Multifactor Setup U2F / WebAuthN
Description: Add your Token by providing a name and then clicking on the 'Register Token' button below.
TokenNameLabel: Name of the token / machine
Title: Add security key
Description: A security key is a verification method that can be built into your phone, use Bluetooth, or plug directly into your computer's USB port.
TokenNameLabel: Name of the security key / device
NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox)
RegisterTokenButtonText: Register Token
RegisterTokenButtonText: Add security key
ErrorRetry: Retry, create a new challenge or choose a different method.
InitMFADone:
Title: Multifactor Verification done
Description: Multifactor verification successfully done. The multifactor has to be entered on each login.
Title: Security key verified
Description: Awesome! You just successfully set up your 2-factor and made your account way more secure. The Factor has to be entered on each login.
NextButtonText: next
CancelButtonText: cancel
MFAProvider:
Provider0: OTP (One Time Password)
Provider1: U2F (Universal 2nd Factor)
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
ChooseOther: or choose an other option
VerifyMFAOTP:
Title: Verify Multifactor
Description: Verify your multifactor
Title: Verify 2-Factor
Description: Verify your second factor
CodeLabel: Code
NextButtonText: next
VerifyMFAU2F:
Title: Multifactor Verification
Description: Verify your multifactor U2F / WebAuthN token
Title: 2-Factor Verification
Description: Verify your 2-Factor with the registered device (e.g FaceID, Windows Hello, Fingerprint)
NotSupported: WebAuthN is not supported by your browser. Make sure you are using the newest version or change your browser to a supported one (Chrome, Safari, Firefox)
ErrorRetry: Retry, create a new request or choose a other method.
ValidateTokenButtonText: Validate Token
ValidateTokenButtonText: Verify 2-Factor
Passwordless:
Title: Login passwordless
Description: Verify your token
Description: Login with authentication methods provided by your device like FaceID, Windows Hello or Fingerprint.
NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox)
ErrorRetry: Retry, create a new challenge or choose a different method.
LoginWithPwButtonText: Login with password
ValidateTokenButtonText: Validate Token
ValidateTokenButtonText: Login with passwordless
PasswordlessPrompt:
Title: Passwordless setup
Description: Would you like to setup passwordless login?
Description: Would you like to setup passwordless login? (Authenticationmethods of your device like FaceID, Windows Hello or Fingerprint)
DescriptionInit: You need to set up passwordless login. Use the link you were given to register your device.
PasswordlessButtonText: Go passwordless
NextButtonText: next
@ -144,15 +144,15 @@ PasswordlessPrompt:
PasswordlessRegistration:
Title: Passwordless setup
Description: Add your Token by providing a name and then clicking on the 'Register Token' button below.
TokenNameLabel: Name of the token / machine
Description: Add your authentication by providing a name (e.g MyMobilePhone, MacBook, etc) and then clicking on the 'Register passwordless' button below.
TokenNameLabel: Name of the device
NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox)
RegisterTokenButtonText: Register Token
RegisterTokenButtonText: Register passwordless
ErrorRetry: Retry, create a new challenge or choose a different method.
PasswordlessRegistrationDone:
Title: Passwordless set up
Description: Token for passwordless successfully added.
Description: Device for passwordless successfully added.
DescriptionClose: You can now close this window.
NextButtonText: next
CancelButtonText: cancel
@ -172,7 +172,7 @@ PasswordChangeDone:
NextButtonText: next
PasswordResetDone:
Title: Reset link set
Title: Password reset link sent
Description: Check your email to reset your password.
NextButtonText: next
@ -181,7 +181,7 @@ EmailVerification:
Description: We have sent you an email to verify your address. Please enter the code in the form below.
CodeLabel: Code
NextButtonText: next
ResendButtonText: resend
ResendButtonText: resend code
EmailVerificationDone:
Title: E-Mail Verification
@ -202,8 +202,8 @@ RegistrationUser:
DescriptionOrgRegister: Enter your Userdata.
EmailLabel: E-Mail
UsernameLabel: Username
FirstnameLabel: Firstname
LastnameLabel: Lastname
FirstnameLabel: First name
LastnameLabel: Surname
LanguageLabel: Language
German: Deutsch
English: English
@ -230,8 +230,8 @@ ExternalRegistrationUserOverview:
Description: We have taken your user details from the selected provider. You can now change or complete them.
EmailLabel: E-Mail
UsernameLabel: Username
FirstnameLabel: Firstname
LastnameLabel: Lastname
FirstnameLabel: First name
LastnameLabel: Surname
NicknameLabel: Nickname
PhoneLabel: Phonenumber
LanguageLabel: Language
@ -239,6 +239,7 @@ ExternalRegistrationUserOverview:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
TosAndPrivacyLabel: Terms and conditions
TosConfirm: I accept the
TosLinkText: TOS
@ -254,8 +255,8 @@ RegistrationOrg:
OrgNameLabel: Organisationname
EmailLabel: E-Mail
UsernameLabel: Username
FirstnameLabel: Firstname
LastnameLabel: Lastname
FirstnameLabel: First name
LastnameLabel: Surname
PasswordLabel: Password
PasswordConfirmLabel: Password confirmation
TosAndPrivacyLabel: Terms and conditions
@ -282,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: cancel
NextButtonText: next
ExternalNotFoundOption:
ExternalNotFound:
Title: External User
Description: External user not found. Do you want to link your user or auto register a new one.
LinkButtonText: Link
@ -296,6 +297,7 @@ ExternalNotFoundOption:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Footer:
PoweredBy: Powered By

View File

@ -53,7 +53,7 @@ InitPassword:
CodeLabel: Code
NewPasswordLabel: Nouveau mot de passe
NewPasswordConfirmLabel: Confirmer le mot de passe
ResendButtonText: envoyer à nouveau
ResendButtonText: code de réexpédition
NextButtonText: suivant
InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser:
Title: Activer l'utilisateur
Description: Vous avez reçu un code, que vous devez entrer dans le formulaire ci-dessous, pour vérifier votre e-mail et définir votre nouveau mot de passe.
Description: Vérifiez votre e-mail avec le code ci-dessous et définissez votre mot de passe.
CodeLabel: Code
NewPasswordLabel: Nouveau mot de passe
NewPasswordConfirmLabel: Confirmer le mot de passe
NewPasswordConfirm: Confirmer le mot de passe
NextButtonText: Suivant
ResendButtonText: envoyer à nouveau
ResendButtonText: code de réexpédition
InitUserDone:
Title: User Utilisateur activé
@ -78,16 +78,16 @@ InitUserDone:
CancelButtonText: annuler
InitMFAPrompt:
Title: Configuration multifactorielle
Description: Voulez-vous configurer l'authentification multifactorielle ?
Provider0: OTP (mot de passe à usage unique)
Provider1: U2F (2e facteur universel)
Title: Configuration à 2 facteurs
Description: L'authentification à deux facteurs vous offre une sécurité supplémentaire pour votre compte d'utilisateur. Vous êtes ainsi assuré d'être le seul à avoir accès à votre compte.
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
NextButtonText: Suivant
SkipButtonText: Passer
InitMFAOTP:
Title: Vérification multifactorielle
Description: Vérifier votre multifacteur.
Title: Vérification à deux facteurs
Description: Créez votre 2-facteurs. Téléchargez une application d'authentification si vous n'en avez pas déjà une.
OTPDescription: Scannez le code avec votre application d'authentification (par exemple Google Authenticator) ou copiez le secret et insérez le code généré ci-dessous.
SecretLabel: Secret
CodeLabel: Code
@ -95,48 +95,48 @@ InitMFAOTP:
CancelButtonText: Annuler
InitMFAU2F:
Title: Configuration multifactorielle U2F / WebAuthN
Description: Ajoutez votre Token en fournissant un nom et en cliquant sur le bouton "Enregistrer un Token" ci-dessous.
TokenNameLabel: Nom du jeton / de la machine
Title: Ajouter une clé de sécurité
Description: Une clé de sécurité est une méthode de vérification qui peut être intégrée à votre téléphone, utiliser Bluetooth ou se brancher directement sur le port USB de votre ordinateur.
TokenNameLabel: Nom de la clé de sécurité / de l'appareil
NotSupported: WebAuthN n'est pas pris en charge par votre navigateur. Veuillez vous assurer qu'il est à jour ou utiliser un autre navigateur (par exemple Chrome, Safari, Firefox).
RegisterTokenButtonText: Enregistrer le jeton
RegisterTokenButtonText: Enregistrez 2-facteurs
ErrorRetry: Réessayez, créez un nouveau défi ou choisissez une autre méthode.
InitMFADone:
Title: Vérification multifactorielle effectuée
Description: La vérification multifactorielle a été effectuée avec succès. Le multifacteur doit être saisi à chaque connexion.
Title: Clé de sécurité ajoutée
Description: Génial! Vous venez de configurer avec succès votre facteur 2 et de rendre votre compte beaucoup plus sûr. Le facteur doit être saisi à chaque connexion.
NextButtonText: Suivant
CancelButtonText: Annuler
MFAProvider:
Provider0: OTP (Mot de passe à usage unique)
Provider1: U2F (2ne facteur universel)
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
ChooseOther: ou choisissez une autre option
VerifyMFAOTP:
Title: Vérifier le multifacteur
Description: Vérifier votre multifacteur
Title: Vérifier 2-Facteurs
Description: Vérifiez votre second facteur
CodeLabel: Code
NextButtonText: Suivant
VerifyMFAU2F:
Title: Vérification multifactorielle
Description: Vérifiez votre jeton multifactoriel U2F / WebAuthN.
Title: Vérifier 2-Facteurs
Description: Vérifiez votre facteur 2 avec l'appareil enregistré (par exemple FaceID, Windows Hello, empreinte digitale).
NotSupported: WebAuthN n'est pas pris en charge par votre navigateur. Assurez-vous que vous utilisez la dernière version ou changez votre navigateur pour un navigateur pris en charge (Chrome, Safari, Firefox).
ErrorRetry: Réessayer, créer une nouvelle demande ou choisir une autre méthode.
ValidateTokenButtonText: Valider le jeton
ValidateTokenButtonText: Vérifier 2-Facteurs
Passwordless:
Title: Connexion sans mot de passe
Description: Vérifiez votre jeton
Description: Connectez-vous avec les méthodes d'authentification fournies par votre appareil, comme FaceID, Windows Hello ou les empreintes digitales.
NotSupported: WebAuthN n'est pas pris en charge par votre navigateur. Veuillez vous assurer qu'il est à jour ou utiliser un autre navigateur (par exemple Chrome, Safari, Firefox).
ErrorRetry: Réessayez, créez un nouveau défi ou choisissez une autre méthode.
LoginWithPwButtonText: Connectez-vous avec le mot de passe
ValidateTokenButtonText: Valider le jeton
ValidateTokenButtonText: Connexion sans mot de passe
PasswordlessPrompt:
Title: Configuration sans mot de passe
Description: Souhaitez-vous configurer une connexion sans mot de passe ?
Description: Souhaitez-vous configurer une connexion sans mot de passe? Méthodes d'authentification de votre appareil comme FaceID, Windows Hello ou Fingerprint.
DescriptionInit: Vous devez configurer la connexion sans mot de passe. Utilisez le lien qui vous a été donné pour enregistrer votre appareil.
PasswordlessButtonText: Aller sans mot de passe
NextButtonText: suivant
@ -144,15 +144,15 @@ PasswordlessPrompt:
PasswordlessRegistration:
Title: Configuration sans mot de passe
Description: Ajoutez votre Token en fournissant un nom et en cliquant sur le bouton 'Enregistrer le jeton' ci-dessous.
TokenNameLabel: Nom du jeton / de la machine
Description: Ajoutez votre authentification en fournissant un nom (par exemple MyMobilePhone, MacBook, etc.) et cliquez ensuite sur le bouton "Register passwordless" ci-dessous.
TokenNameLabel: Nom de l'appareil
NotSupported: WebAuthN n'est pas pris en charge par votre navigateur. Veuillez vous assurer qu'il est à jour ou utiliser un autre navigateur (par exemple Chrome, Safari, Firefox).
RegisterTokenButtonText: Register Token
RegisterTokenButtonText: Enregistrement sans mot de passe
ErrorRetry: Réessayer, créer un nouveau défi ou choisir une autre méthode.
PasswordlessRegistrationDone:
Title: Configuration sans mot de passe
Description: Le jeton pour le système sans mot de passe a été ajouté avec succès.
Description: Le dispositif sans mot de passe a été ajouté avec succès.
DescriptionClose: Vous pouvez maintenant fermer cette fenêtre.
NextButtonText: suivant
CancelButtonText: annuler
@ -172,7 +172,7 @@ PasswordChangeDone:
NextButtonText: suivant
PasswordResetDone:
Title: Réinitialisation du jeu de liens
Title: Lien de réinitialisation du mot de passe envoyé
Description: Vérifiez votre e-mail pour réinitialiser votre mot de passe.
NextButtonText: suivant
@ -181,7 +181,7 @@ EmailVerification:
Description: Nous vous avons envoyé un e-mail pour vérifier votre adresse. Veuillez saisir le code dans le formulaire ci-dessous.
CodeLabel: Code
NextButtonText: suivant
ResendButtonText: envoyer à nouveau
ResendButtonText: code de réexpédition
EmailVerificationDone:
Title: E-Mail Verification
@ -239,6 +239,7 @@ ExternalRegistrationUserOverview:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
TosAndPrivacyLabel: Termes et conditions
TosConfirm: J'accepte les
TosLinkText: TOS
@ -282,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: annuler
NextButtonText: suivant
ExternalNotFoundOption:
ExternalNotFound:
Title: Utilisateur externe
Description: Utilisateur externe non trouvé. Voulez-vous lier votre utilisateur ou enregistrer automatiquement un nouvel utilisateur.
LinkButtonText: Lier
@ -296,6 +297,7 @@ ExternalNotFoundOption:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Footer:
PoweredBy: Promulgué par

View File

@ -7,7 +7,7 @@ Login:
UsernamePlaceHolder: nome utente
LoginnamePlaceHolder: nomeutente@dominio
ExternalUserDescription: Accedi con un utente esterno.
MustBeMemberOfOrg: 'L''utente deve essere membro dell''organizzazione {{.OrgName}}.'
MustBeMemberOfOrg: "L'utente deve essere membro dell'organizzazione {{.OrgName}}."
RegisterButtonText: registrare
NextButtonText: Avanti
@ -19,7 +19,7 @@ SelectAccount:
OtherUser: Altro utente
SessionState0: attivo
SessionState1: inattivo
MustBeMemberOfOrg: 'L''utente deve essere membro dell''organizzazione {{.OrgName}}.'
MustBeMemberOfOrg: "L'utente deve essere membro dell'organizzazione {{.OrgName}}."
Password:
Title: Password
@ -53,7 +53,7 @@ InitPassword:
CodeLabel: Codice
NewPasswordLabel: Nuova password
NewPasswordConfirmLabel: Conferma la password
ResendButtonText: rispedisci
ResendButtonText: codice di reinvio
NextButtonText: Avanti
InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser:
Title: Attivare l'utente
Description: Hai ricevuto un codice, che devi inserire nel modulo sottostante, per verificare la tua email e impostare la tua nuova password.
Description: Verifica la tua e-mail con il codice sottostante e imposta la tua nuova password.
CodeLabel: Codice
NewPasswordLabel: Nuova password
NewPasswordConfirmLabel: Conferma la password
NewPasswordConfirm: Conferma la password
NextButtonText: Avanti
ResendButtonText: rispedisci
ResendButtonText: Reinvia codice
InitUserDone:
Title: Utente attivato
@ -78,16 +78,16 @@ InitUserDone:
CancelButtonText: annulla
InitMFAPrompt:
Title: Configurazione a più fattori
Description: Vuoi impostare l'autenticazione a più fattori?
Provider0: OTP (One Time Password)
Provider1: U2F (2° fattore universale)
Title: Impostazione a 2 fattori
Description: L'autenticazione a due fattori offre un'ulteriore sicurezza al vostro account utente. Questo garantisce che solo voi possiate accedere al vostro account.
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
NextButtonText: Avanti
SkipButtonText: salta
InitMFAOTP:
Title: Verifica a più fattori
Description: Verifica il tuo multifattore.
Title: Verificazione a due fattori
Description: Scarica un'app di autenticazione e aggiungi la tua chiave
OTPDescription: Scannerizza il codice con la tua app di autenticazione (ad esempio Google Authenticator) o copia la chiave segreta e inserisci il codice generato nel campo sottostante.
SecretLabel: Chiave
CodeLabel: Codice
@ -95,64 +95,64 @@ InitMFAOTP:
CancelButtonText: annulla
InitMFAU2F:
Title: Configurazione a più fattori U2F / WebAuthN
Description: Aggiungi il tuo Token fornendo un nome e cliccando sul pulsante 'Registra'.
TokenNameLabel: Nome del token / dispositivo
Title: Aggiungi chiave di sicurezza
Description: Una chiave di sicurezza è un metodo di verifica che può essere integrato nel telefono, utilizzare il Bluetooth o collegarlo direttamente alla porta USB del computer.
TokenNameLabel: Nome del dispositivo a due fattori
NotSupported: WebAuthN non è supportato dal tuo browser. Assicurati che sia aggiornato o usane uno diverso (ad esempio Chrome, Safari, Firefox)
RegisterTokenButtonText: Registra
RegisterTokenButtonText: Aggiungi chiave
ErrorRetry: Riprova, crea una nuova richiesta o scegli un metodo diverso.
InitMFADone:
Title: Verificazione a più fattori effettuata
Description: La verificazione del multifattore eseguita con successo. Il multifattore è richiesto ad ogni login.
Title: Chiave aggiunta con successo
Description: Fantastico! Hai appena impostato un secondo fattore e quindi reso il tuo account molto più sicuro. Il secondo fattore deve essere inserito a ogni accesso.
NextButtonText: Avanti
CancelButtonText: annulla
MFAProvider:
Provider0: OTP (One Time Password)
Provider1: U2F (2° fattore universale)
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
ChooseOther: o scegli un'altra opzione
VerifyMFAOTP:
Title: Verificazione del Multificator
Description: Verifica il tuo multifattore
Title: Verificazione fattore
Description: Verifica il tuo secondo fattore con la tua app
CodeLabel: Codice
NextButtonText: Avanti
VerifyMFAU2F:
Title: Verificazione a più fattori
Description: Verifica il tuo token U2F / WebAuthN
Title: Verificazione fattore
Description: Verifica il tuo fattore con il dispositivo registrato (ad es. FaceID, Windows Hello, impronta digitale).
NotSupported: WebAuthN non è supportato dal tuo browser. Assicurati di avere l'ultima versione installata o usane una diversa (per esempio Chrome, Safari, Firefox).
ErrorRetry: Prova di nuovo, crea una nuova richiesta o scegli un metodo diverso.
ValidateTokenButtonText: Verifica
Passwordless:
Title: Accesso senza password
Description: Verifica il tuo token
Description: Accedi con il metodo di autenticazione del tuo dispositivo registrato (ad es. FaceID, Windows Hello o impronta digitale).
NotSupported: WebAuthN non è supportato dal tuo browser. Assicurati che sia aggiornato o usane uno diverso (ad esempio Chrome, Safari, Firefox)
ErrorRetry: Riprova, crea una nuova richiesta o scegli un metodo diverso.
LoginWithPwButtonText: Accedi con password
ValidateTokenButtonText: Verifica
ValidateTokenButtonText: Accedi
PasswordlessPrompt:
Title: Autenticazione passwordless
Description: Vuoi impostare il login senza password?
Description: Vuoi impostare il login senza password? Scegli tra metodi di autenticazione di un dispositivo (ad es. FaceID, Windows Hello o impronte digitali).
DescriptionInit: Devi impostare il login senza password. Usa il link che ti è stato inviato per registrare il tuo dispositivo.
PasswordlessButtonText: Continua
NextButtonText: Avanti
SkipButtonText: salta
PasswordlessRegistration:
Title: Configurazione dell'autenticazione senza password
Description: Aggiungi il tuo Token fornendo un nome e poi cliccando sul pulsante 'Registra'.
TokenNameLabel: Nome del token / dispositivo
Title: Registrazione dell'autenticazione passwordless
Description: Aggiungi il tuo metodo fornendo un nome (ad es. Cellulare, MacBook, etc) e poi cliccando sul pulsante 'Registra'.
TokenNameLabel: Nome del dispositivo
NotSupported: WebAuthN non è supportato dal tuo browser. Assicurati che sia aggiornato o usane uno diverso (ad esempio Chrome, Safari, Firefox)
RegisterTokenButtonText: Registra
ErrorRetry: Riprova, crea una nuova richiesta o scegli un metodo diverso.
PasswordlessRegistrationDone:
Title: Configurazione dell'autenticazione senza password
Description: Token per lautenticazione passwordless aggiunto con successo.
Description: Dispositivo per l'autenticazione passwordless aggiunto con successo.
DescriptionClose: Ora puoi chiudere questa finestra.
NextButtonText: Avanti
CancelButtonText: annulla
@ -172,8 +172,8 @@ PasswordChangeDone:
NextButtonText: Avanti
PasswordResetDone:
Title: Link per il cambiamento inviato
Description: Controlla la tua email per reimpostare la tua password.
Title: Link per la reimpostazione della password è stato inviato
Description: Controlla la tua email per continuare e reimpostare la tua password.
NextButtonText: Avanti
EmailVerification:
@ -181,7 +181,7 @@ EmailVerification:
Description: Ti abbiamo inviato un'e-mail per verificare il tuo indirizzo. Inserisci il codice nel campo sottostante.
CodeLabel: Codice
NextButtonText: Avanti
ResendButtonText: rispedisci
ResendButtonText: codice di reinvio
EmailVerificationDone:
Title: Verificazione email effettuata
@ -189,7 +189,7 @@ EmailVerificationDone:
NextButtonText: Avanti
CancelButtonText: annulla
LoginButtonText: Accedi
RegisterOption:
Title: Opzioni di registrazione
Description: Scegli come vuoi registrarti
@ -209,9 +209,9 @@ RegistrationUser:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
GenderLabel: Genere
Female: Femminile
Chinese: 简体中文
Male: Maschile
Diverse: diverso / X
PasswordLabel: Password
@ -219,7 +219,7 @@ RegistrationUser:
TosAndPrivacyLabel: Termini di servizio
TosConfirm: Accetto i
TosLinkText: Termini di servizio
TosConfirmAnd: e
TosConfirmAnd: e
PrivacyLinkText: l'informativa sulla privacy
ExternalLogin: o registrati con un utente esterno
BackButtonText: indietro
@ -239,6 +239,7 @@ ExternalRegistrationUserOverview:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
TosAndPrivacyLabel: Termini di servizio
TosConfirm: Accetto i
TosLinkText: Termini di servizio
@ -282,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: annulla
NextButtonText: Avanti
ExternalNotFoundOption:
ExternalNotFound:
Title: Utente esterno
Description: Utente esterno non trovato. Vuoi collegare il tuo utente o registrarne uno nuovo automaticamente.
LinkButtonText: Link
@ -296,6 +297,7 @@ ExternalNotFoundOption:
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Footer:
PoweredBy: Alimentato da

View File

@ -47,13 +47,13 @@ UsernameChangeDone:
Description: 您的用户名已成功更改。
NextButtonText: 继续
InitPassword:
InitPassword:
Title: 设置密码
Description: 您将到一个验证码,您必须在下面输入该验证码以设置您的新密码。
CodeLabel: 验证码
NewPasswordLabel: 新密码
NewPasswordConfirmLabel: 确认密码
ResendButtonText: 新发送
ResendButtonText: 发代码
NextButtonText: 继续
InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser:
Title: 激活用户
Description: 您将收到一个验证码,您必须在下面输入该验证码,以验证您的电子邮件并设置您的密码。
Description: 请在下面输入您收到的验证码,以验证您的电子邮件并设置您的密码。
CodeLabel: 验证码
NewPasswordLabel: 新密码
NewPasswordConfirmLabel: 确认密码
NewPasswordConfirm: 确认密码
NextButtonText: 继续
ResendButtonText: 新发送
ResendButtonText: 发代码
InitUserDone:
Title: 用户已激活
@ -78,16 +78,16 @@ InitUserDone:
CancelButtonText: 取消
InitMFAPrompt:
Title: 多因素身份认证设置
Description: 您想设置多因素身份认证吗?
Provider0: OTP一次性密码
Provider1: U2F通用两步验证
Title: 两步验证设置
Description: 两步验证为您的账户提供了额外的安全保障。这确保只有你能访问你的账户。
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹
NextButtonText: 继续
SkipButtonText: 跳过
InitMFAOTP:
Title: 因素验证
Description: 验证您的多因素身份认证
Title: 因素验证
Description: 创建你的双因素。如果你还没有,请下载一个认证器应用程序
OTPDescription: 使用您的身份验证器应用程序(例如 Google Authenticator扫描代码或复制密码并在下方插入生成的代码。
SecretLabel: 秘钥
CodeLabel: 验证码
@ -95,48 +95,48 @@ InitMFAOTP:
CancelButtonText: 取消
InitMFAU2F:
Title: 多因素身份认证设置 U2F / WebAuthN
Description: 通过提供名称添加您的令牌,然后单击下面的“注册令牌”按钮
TokenNameLabel: 令牌/机器的名称
Title: 添加安全密钥
Description: 安全密钥是一种验证方法,可以内置在手机中、使用蓝牙或直接插入计算机的 USB 端口
TokenNameLabel: 双因素/设备的名称
NotSupported: 您的浏览器不支持 WebAuthN。请确保它是最新的或使用其他版本例如 Chrome、Safari、Firefox
RegisterTokenButtonText: 注册令牌
RegisterTokenButtonText: 注册双因素
ErrorRetry: 重试、创建新挑战码或选择不同的方法。
InitMFADone:
Title: 多因素身份认证验证完成
Description: 多因素验证成功完成。必须在每次登录时输入多因素身份认证
Title: 2-Factor设置完成
Description: 真棒!你刚刚成功地设置了你的双因素,使你的账户更加安全。你刚刚成功地设置了你的双因素,使你的账户更加安全。第二次因素必须在每次登录时输入
NextButtonText: 继续
CancelButtonText: 取消
MFAProvider:
Provider0: OTP一次性密码
Provider1: U2F通用第二因素身份认证
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹
ChooseOther: 或选择其他选项
VerifyMFAOTP:
Title: 验证多因素
Description: 验证您的多因素
Title: 验证2-Factor
Description: 验证你的第二个因素
CodeLabel: 验证码
NextButtonText: 继续
VerifyMFAU2F:
Title: 多因素验证
Description: 验证您的多因素 U2F / WebAuthN 令牌
Title: 验证2-Factor
Description: 用注册的设备验证你的2-Factor如FaceID、Windows Hello、Fingerprint
NotSupported: 您的浏览器不支持 WebAuthN。确保您使用的是最新版本或将您的浏览器更改为受支持的版本Chrome、Safari、Firefox
ErrorRetry: 重试、创建新请求或选择其他方法。
ValidateTokenButtonText: 验证令牌
ValidateTokenButtonText: 验证2-Factor
Passwordless:
Title: 无密码登录
Description: 验证您的令牌
Description: 用你的设备提供的认证方法登录如FaceID、Windows Hello或指纹。
NotSupported: 您的浏览器不支持 WebAuthN。请确保它是最新的或使用其他版本例如 Chrome、Safari、Firefox
ErrorRetry: 重试、创建新挑战码或选择不同的方法。
LoginWithPwButtonText: 使用密码登录
ValidateTokenButtonText: 验证令牌
ValidateTokenButtonText: 使用无密码登录
PasswordlessPrompt:
Title: 无密码登录设置
Description: 您想设置无密码登录吗?
Description: 您想设置无密码登录吗?你的设备的认证方法如FaceID、Windows Hello或指纹。
DescriptionInit: 您需要设置无密码登录,使用您提供的链接来注册您的设备。
PasswordlessButtonText: 去设置无密码登录
NextButtonText: 继续
@ -144,15 +144,15 @@ PasswordlessPrompt:
PasswordlessRegistration:
Title: 无密码设置
Description: 通过提供名称添加您的令牌,然后单击下面的“注册令牌”按钮
TokenNameLabel: 令牌/机器的名称
Description: 通过提供一个名称(如 MyMobilePhone、MacBook等然后点击下面的 "注册无密码" 按钮,添加你的认证
TokenNameLabel: 设备的名称
NotSupported: 您的浏览器不支持 WebAuthN。请确保它是最新的或使用其他版本例如 Chrome、Safari、Firefox
RegisterTokenButtonText: 注册令牌
RegisterTokenButtonText: 注册无密码
ErrorRetry: 重试、创建新挑战码或选择不同的方法。
PasswordlessRegistrationDone:
Title: 无密码登录设置完成
Description: 已成功添加无密码令牌
Description: 成功添加无密码的设备
DescriptionClose: 您现在可以关闭此窗口
NextButtonText: 继续
CancelButtonText: 取消
@ -172,7 +172,7 @@ PasswordChangeDone:
NextButtonText: 继续
PasswordResetDone:
Title: 重置链
Title: 发送密码重置链
Description: 请检查您的电子邮件以重置您的密码。
NextButtonText: 继续
@ -181,7 +181,7 @@ EmailVerification:
Description: 我们已向您发送一封电子邮件以验证您的地址。请在下面的表格中输入验证码。
CodeLabel: 验证码
NextButtonText: 继续
ResendButtonText: 新发送
ResendButtonText: 发代码
EmailVerificationDone:
Title: 电子邮件验证
@ -283,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: 取消
NextButtonText: 继续
ExternalNotFoundOption:
ExternalNotFound:
Title: 外部用户
Description: 未找到外部用户。你想绑定你已存在的用户还是自动注册一个新用户。
LinkButtonText: 绑定
@ -293,10 +293,10 @@ ExternalNotFoundOption:
TosLinkText: 服务条款
TosConfirmAnd:
PrivacyLinkText: 隐私政策
German: 德语
English: 英语
Italian: 意大利语
French: 法语
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Footer:

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 118 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<rect id="mfa01" x="0" y="0" width="117.576" height="117.576" style="fill:none;"/>
<g id="mfa011" serif:id="mfa01">
<g transform="matrix(1,0,0,1,0,2.49899)">
<g transform="matrix(0.0210574,0,0,0.0210574,61.0927,55.0915)">
<path d="M2500,1249.98C2500,1940.39 1940.37,2500 1249.97,2500C559.609,2500 0,1940.39 0,1249.98C0,559.619 559.609,0 1249.97,0C1940.37,0 2500,559.619 2500,1249.98Z" style="fill:rgb(236,28,36);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.0210574,0,0,0.0210574,61.0927,55.0915)">
<path d="M1142.81,985.254L1442.53,1284.86C1485.79,1328.18 1556.04,1328.18 1599.4,1284.86C1642.68,1241.55 1642.77,1171.37 1599.4,1128.05L1299.67,828.32C1085.2,613.809 738.584,610.352 519.57,817.422C517.525,819.121 515.554,820.906 513.662,822.773C512.705,823.73 511.924,824.746 510.977,825.605C510,826.543 509.004,827.383 508.076,828.32C506.182,830.205 504.453,832.188 502.803,834.15C295.625,1053.26 299.189,1399.89 513.672,1614.39L813.398,1914.06C856.758,1957.35 926.904,1957.35 970.254,1914.06C1013.63,1870.73 1013.63,1800.46 970.361,1757.23L670.625,1457.51C539.785,1326.72 538.906,1114.45 667.91,982.539C799.805,853.535 1011.95,854.473 1142.81,985.264L1142.81,985.254ZM1529.65,586.104C1486.34,629.434 1486.34,699.609 1529.77,742.93L1829.38,1042.67C1960.15,1173.45 1961,1385.68 1832,1517.63C1700.07,1646.53 1487.99,1645.67 1357.17,1514.87L1057.44,1215.18C1014.1,1171.85 943.838,1171.85 900.547,1215.18C857.158,1258.47 857.158,1328.87 900.547,1372.06L1200.18,1671.81C1414.71,1886.31 1761.29,1889.8 1980.34,1682.7C1982.37,1680.91 1984.29,1679.24 1986.3,1677.34C1987.22,1676.42 1988.08,1675.47 1988.97,1674.51C1989.94,1673.58 1990.89,1672.73 1991.8,1671.69C1993.72,1669.9 1995.36,1667.94 1997.07,1665.89C2204.25,1446.87 2200.78,1100.35 1986.31,885.723L1686.58,586.094C1643.26,542.705 1572.93,542.705 1529.65,586.104Z" style="fill:white;fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,-1,2.49899)">
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M105.787,10.628C122.837,8.633 140.367,10.679 156.457,16.701C170.98,22.133 184.367,30.683 195.275,41.718C185.945,50.969 176.702,60.305 167.384,69.568C161.378,63.678 154.379,58.771 146.676,55.364C136.442,50.777 125.061,48.759 113.876,49.532C101.239,50.346 88.854,54.737 78.55,62.099C65.765,71.114 56.19,84.6 51.98,99.674C48.823,110.733 48.562,122.58 51.11,133.789C53.158,142.957 57.148,151.678 62.694,159.255C67.841,166.303 74.336,172.364 81.723,177.009C90.238,182.388 99.952,185.855 109.95,187.073C118.519,188.149 127.286,187.589 135.652,185.452C136.571,185.01 137.097,186.057 137.673,186.577C147.102,196.055 156.577,205.486 166.009,214.959C149.042,223.199 129.906,226.962 111.087,225.634C95.337,224.59 79.825,220.061 66.022,212.4C60.266,209.252 54.859,205.498 49.745,201.397C45.704,197.969 41.711,194.451 38.237,190.438C32.356,184.099 27.311,176.989 23.154,169.406C22.228,167.62 21.154,165.907 20.4,164.037C19.365,161.942 18.462,159.788 17.564,157.633C16.416,154.561 15.19,151.513 14.352,148.34C14.252,147.921 13.998,147.57 13.769,147.218C11.848,140.585 10.771,133.718 10.195,126.841C9.589,118.194 9.887,109.471 11.301,100.912C14.12,83.443 21.349,66.719 32.099,52.67C39.07,43.528 47.497,35.494 56.97,28.982C71.422,19.004 88.338,12.621 105.787,10.628M117.169,22.311C115.85,22.54 114.633,23.182 113.61,24.031C111.203,26.098 110.337,29.773 111.719,32.659C113.184,36.136 117.542,38.006 121.06,36.594C124.631,35.381 126.795,31.153 125.629,27.554C124.68,23.982 120.787,21.585 117.169,22.311M55.151,48.231C53.917,48.296 52.7,48.675 51.642,49.314C48.988,50.941 47.504,54.346 48.319,57.382C49.034,60.641 52.202,63.182 55.554,63.078C59.032,63.173 62.284,60.442 62.854,57.021C63.372,54.63 62.481,52.034 60.738,50.344C59.294,48.879 57.197,48.117 55.151,48.231M28.313,111.184C26.781,111.469 25.341,112.244 24.253,113.36C22.066,115.609 21.541,119.278 23.091,122.02C24.748,125.251 28.989,126.848 32.344,125.404C35.821,124.129 37.897,119.984 36.756,116.445C35.821,112.878 31.928,110.436 28.313,111.184M54.23,174.149C51.571,174.6 49.259,176.63 48.481,179.213C47.585,181.904 48.451,185.036 50.541,186.934C51.964,188.202 53.874,188.947 55.788,188.859C58.371,188.802 60.875,187.269 62.064,184.971C63.205,182.927 63.256,180.338 62.229,178.236C60.882,175.278 57.412,173.531 54.23,174.149M117.197,200.078C114.475,200.545 112.124,202.64 111.39,205.308C110.494,208.217 111.652,211.615 114.147,213.36C115.966,214.7 118.41,215.135 120.574,214.485C122.798,213.844 124.67,212.115 125.45,209.93C126.601,206.982 125.534,203.376 123.002,201.492C121.377,200.23 119.217,199.716 117.197,200.078Z" style="fill:rgb(160,160,160);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M117.169,22.311C120.787,21.585 124.68,23.982 125.629,27.554C126.795,31.153 124.631,35.381 121.06,36.594C117.542,38.006 113.184,36.136 111.719,32.659C110.337,29.773 111.203,26.098 113.61,24.031C114.633,23.182 115.85,22.54 117.169,22.311Z" style="fill:rgb(193,193,193);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M28.313,111.184C31.928,110.436 35.821,112.878 36.756,116.445C37.897,119.984 35.821,124.129 32.344,125.404C28.989,126.848 24.748,125.251 23.091,122.02C21.541,119.278 22.066,115.609 24.253,113.36C25.341,112.244 26.781,111.469 28.313,111.184Z" style="fill:rgb(193,193,193);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M195.275,41.718L195.3,41.693C201.003,47.506 206.169,53.867 210.486,60.78C218.82,74.009 224.229,89.067 226.194,104.581C227.506,114.968 227.414,125.55 225.719,135.888C222.78,154.406 214.855,172.089 203.061,186.658C193.271,198.805 180.872,208.886 166.86,215.765L166.715,215.705C166.481,215.45 166.245,215.205 166.009,214.959C156.577,205.486 147.102,196.055 137.673,186.577C137.097,186.057 136.571,185.01 135.652,185.452C127.286,187.589 118.519,188.149 109.95,187.073C99.952,185.855 90.238,182.388 81.723,177.009C74.336,172.364 67.841,166.303 62.694,159.255C57.148,151.678 53.158,142.957 51.11,133.789C48.562,122.58 48.823,110.733 51.98,99.674C56.19,84.6 65.765,71.114 78.55,62.099C88.854,54.737 101.239,50.346 113.876,49.532C125.061,48.759 136.442,50.777 146.676,55.364C154.379,58.771 161.378,63.678 167.384,69.568C176.702,60.305 185.945,50.969 195.275,41.718M113.864,84.297C108.951,84.957 104.169,86.676 100.005,89.375C94.663,92.719 90.34,97.61 87.53,103.241C84.156,109.999 83.216,117.87 84.637,125.267C85.547,129.862 87.422,134.266 90.09,138.115C91.925,140.781 94.175,143.128 96.49,145.378C109.758,158.665 123.015,171.962 136.296,185.235C147.657,182.21 158.207,176.222 166.627,168.015C177.067,157.985 184.138,144.538 186.603,130.276C186.658,130.035 186.654,129.602 187.031,129.674C193.889,129.683 200.746,129.672 207.606,129.686C212.013,129.616 216.14,126.267 217.04,121.942C217.091,121.662 217.04,121.405 216.885,121.166C217.167,119.845 217.366,118.486 217.204,117.132L217.149,117.225C216.674,112.517 212.326,108.633 207.602,108.622C188.938,108.606 170.274,108.62 151.608,108.606C150.118,103.495 147.327,98.809 143.732,94.897C137.761,88.532 129.322,84.48 120.583,84.061C118.343,84.066 116.084,83.936 113.864,84.297M180.143,174.17C177.218,174.646 174.734,177.083 174.195,179.997C173.852,181.881 174.163,183.937 175.29,185.517C176.623,187.647 179.178,188.933 181.68,188.815C183.606,188.822 185.448,187.901 186.809,186.58C187.936,185.284 188.764,183.647 188.815,181.904C189.014,179.273 187.644,176.558 185.374,175.202C183.865,174.17 181.928,173.871 180.143,174.17Z" style="fill:rgb(104,104,104);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M55.151,48.231C57.197,48.117 59.294,48.879 60.738,50.344C62.481,52.034 63.372,54.63 62.854,57.021C62.284,60.442 59.032,63.173 55.554,63.078C52.202,63.182 49.034,60.641 48.319,57.382C47.504,54.346 48.988,50.941 51.642,49.314C52.7,48.675 53.917,48.296 55.151,48.231Z" style="fill:rgb(195,195,195);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M113.864,84.297C116.084,83.936 118.343,84.066 120.583,84.061C129.322,84.48 137.761,88.532 143.732,94.897C147.327,98.809 150.118,103.495 151.608,108.606C140.575,108.603 129.54,108.603 118.505,108.603C115.683,108.578 112.897,109.858 111.05,111.985C109.659,113.57 108.795,115.607 108.633,117.711C108.358,120.99 109.874,124.353 112.496,126.339C114.26,127.707 116.496,128.422 118.727,128.376C148.049,128.367 177.373,128.362 206.695,128.383C207.796,128.392 208.914,128.348 209.981,128.04C213.27,127.156 216.003,124.46 216.885,121.166C217.04,121.405 217.091,121.662 217.04,121.942C216.14,126.267 212.013,129.616 207.606,129.686C200.746,129.672 193.889,129.683 187.031,129.674C186.654,129.602 186.658,130.035 186.603,130.276C184.138,144.538 177.067,157.985 166.627,168.015C158.207,176.222 147.657,182.21 136.296,185.235C123.015,171.962 109.758,158.665 96.49,145.378C94.175,143.128 91.925,140.781 90.09,138.115C87.422,134.266 85.547,129.862 84.637,125.267C83.216,117.87 84.156,109.999 87.53,103.241C90.34,97.61 94.663,92.719 100.005,89.375C104.169,86.676 108.951,84.957 113.864,84.297Z" style="fill:rgb(71,71,71);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M111.05,111.985C112.897,109.858 115.683,108.578 118.505,108.603C129.54,108.603 140.575,108.603 151.608,108.606C170.274,108.62 188.938,108.606 207.602,108.622C212.326,108.633 216.674,112.517 217.149,117.225C217.105,117.301 217.015,117.449 216.968,117.523C216.117,113.239 211.976,109.849 207.602,109.874C178.678,109.897 149.754,109.902 120.831,109.913C119.042,109.943 117.188,109.728 115.473,110.367C113.376,111.024 111.601,112.473 110.318,114.232C109.545,115.285 109.302,116.602 108.633,117.711C108.795,115.607 109.659,113.57 111.05,111.985Z" style="fill:rgb(191,191,191);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M54.23,174.149C57.412,173.531 60.882,175.278 62.229,178.236C63.256,180.338 63.205,182.927 62.064,184.971C60.875,187.269 58.371,188.802 55.788,188.859C53.874,188.947 51.964,188.202 50.541,186.934C48.451,185.036 47.585,181.904 48.481,179.213C49.259,176.63 51.571,174.6 54.23,174.149Z" style="fill:rgb(191,191,191);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M115.473,110.367C117.188,109.728 119.042,109.943 120.831,109.913C149.754,109.902 178.678,109.897 207.602,109.874C211.976,109.849 216.117,113.239 216.968,117.523C217.015,117.449 217.105,117.301 217.149,117.225L217.204,117.132C217.366,118.486 217.167,119.845 216.885,121.166C216.003,124.46 213.27,127.156 209.981,128.04C208.914,128.348 207.796,128.392 206.695,128.383C177.373,128.362 148.049,128.367 118.727,128.376C116.496,128.422 114.26,127.707 112.496,126.339C109.874,124.353 108.358,120.99 108.633,117.711C109.302,116.602 109.545,115.285 110.318,114.232C111.601,112.473 113.376,111.024 115.473,110.367Z" style="fill:rgb(176,176,176);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M13.769,147.218C13.998,147.57 14.252,147.921 14.352,148.34C15.19,151.513 16.416,154.561 17.564,157.633C18.462,159.788 19.365,161.942 20.4,164.037C21.154,165.907 22.228,167.62 23.154,169.406C27.311,176.989 32.356,184.099 38.237,190.438C41.711,194.451 45.704,197.969 49.745,201.397C54.859,205.498 60.266,209.252 66.022,212.4C79.825,220.061 95.337,224.59 111.087,225.634C129.906,226.962 149.042,223.199 166.009,214.959C166.245,215.205 166.481,215.45 166.715,215.705L166.722,215.846C156.927,220.713 146.382,224.069 135.578,225.777C122.407,227.837 108.856,227.511 95.811,224.736C80.099,221.414 65.163,214.504 52.434,204.713C37.733,193.458 25.975,178.391 18.675,161.371C16.727,156.772 14.988,152.062 13.769,147.218Z" style="fill:rgb(147,147,147);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M180.143,174.17C181.928,173.871 183.865,174.17 185.374,175.202C187.644,176.558 189.014,179.273 188.815,181.904C188.764,183.647 187.936,185.284 186.809,186.58C185.448,187.901 183.606,188.822 181.68,188.815C179.178,188.933 176.623,187.647 175.29,185.517C174.163,183.937 173.852,181.881 174.195,179.997C174.734,177.083 177.218,174.646 180.143,174.17Z" style="fill:rgb(118,118,118);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.246717,0,0,0.246717,1.81554,51.8412)">
<path d="M117.197,200.078C119.217,199.716 121.377,200.23 123.002,201.492C125.534,203.376 126.601,206.982 125.45,209.93C124.67,212.115 122.798,213.844 120.574,214.485C118.41,215.135 115.966,214.7 114.147,213.36C111.652,211.615 110.494,208.217 111.39,205.308C112.124,202.64 114.475,200.545 117.197,200.078Z" style="fill:rgb(190,190,190);fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(0.0188103,0,0,0.0188103,35.2752,7.20512)">
<path d="M2345.33,2497L154.666,2497C69.791,2497 1.5,2428.22 1.5,2343.83L1.5,153.166C1.5,68.291 70.278,0 154.666,0L2345.33,0C2430.21,0 2498.5,68.778 2498.5,153.166L2498.5,2343.83C2498.5,2428.71 2430.21,2497 2345.33,2497Z" style="fill:rgb(0,120,215);fill-rule:nonzero;"/>
<path d="M1719.01,779.977C1719.01,787.294 1719.01,794.123 1718.52,801.44C1661.94,789.245 1602.92,779.489 1541.94,771.685C1537.55,614.128 1408.78,487.791 1250.24,487.791C1091.71,487.791 962.447,614.128 958.545,771.685C897.571,779.489 838.549,789.245 781.965,801.44C781.477,794.123 781.477,786.806 781.477,779.489C781.477,520.473 991.227,310.235 1250.73,310.235C1508.77,311.21 1719.01,520.96 1719.01,779.977ZM1252.19,2186.76C2080.46,1838.48 1954.13,951.679 1954.13,951.679C1760.96,879.486 1517.07,836.561 1251.22,836.561C985.374,836.561 741.478,879.486 548.313,951.679C548.313,951.679 421.975,1838.48 1250.24,2186.76L1252.19,2186.76Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M1480.48,1628.25C1480.48,1638.49 1479.99,1648.24 1478.53,1658L1023.42,1658C1021.96,1648.24 1021.47,1638.49 1021.47,1628.25C1021.47,1524.83 1089.76,1437.52 1183.9,1408.74C1120.49,1382.4 1075.62,1319.96 1075.62,1246.79C1075.62,1149.72 1154.15,1071.68 1250.73,1071.68C1347.31,1071.68 1425.85,1150.21 1425.85,1246.79C1425.85,1319.96 1380.97,1382.4 1317.56,1408.74C1412.19,1437.03 1480.48,1524.83 1480.48,1628.25Z" style="fill:rgb(0,120,215);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 118 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-125,0)">
<g id="mfa02" transform="matrix(1,0,0,1,-58.8999,0)">
<rect x="184.243" y="0" width="117.576" height="117.576" style="fill:none;"/>
<g id="ic_fingerprint_24px" transform="matrix(2.30074,0,0,2.30074,214.226,0.504338)">
<path d="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12.01,3C10.03,3 8.15,3.47 6.44,4.41C6.198,4.537 5.895,4.448 5.76,4.21C5.631,3.968 5.721,3.663 5.96,3.53C7.82,2.52 9.86,2 12.01,2C14.14,2 16,2.47 18.04,3.52C18.29,3.65 18.38,3.95 18.25,4.19C18.169,4.36 17.998,4.469 17.81,4.47ZM3.5,9.72C3.499,9.72 3.498,9.72 3.497,9.72C3.223,9.72 2.997,9.494 2.997,9.22C2.997,9.116 3.03,9.015 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C9.98,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.51 20.9,8.9C20.961,8.985 20.993,9.086 20.993,9.191C20.993,9.353 20.913,9.507 20.78,9.6C20.695,9.661 20.594,9.693 20.489,9.693C20.327,9.693 20.173,9.613 20.08,9.48C19.204,8.24 18.041,7.232 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.51C3.81,9.65 3.66,9.72 3.5,9.72ZM9.75,21.79C9.617,21.792 9.49,21.737 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66C17.66,14.94 17.44,15.16 17.16,15.16C16.88,15.16 16.66,14.94 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.51C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10.01,21.74 9.88,21.79 9.75,21.79ZM16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66C11.44,14.38 11.66,14.16 11.94,14.16C12.22,14.16 12.44,14.38 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.49,18.96 18.54,19.24C18.59,19.51 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94ZM14.91,22C14.87,22 14.82,21.99 14.78,21.98C13.19,21.54 12.15,20.95 11.06,19.88C9.665,18.503 8.883,16.62 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 15.98,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.52,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19.01 4.59,18.87 4.5,18.62C4.018,17.355 3.771,16.013 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.51,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21.01C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.145369,0.145369,-0.145369,0.145369,124.248,-755.444)">
<g transform="matrix(0.455964,0,0,0.542435,3234,1523.19)">
<path d="M408.347,1287.73L297.347,1287.73L297.347,1370.87L408.347,1370.87L408.347,1287.73ZM388.248,1304.63L388.248,1353.97C388.248,1353.97 317.446,1353.97 317.446,1353.97C317.446,1353.97 317.446,1304.63 317.446,1304.63L388.248,1304.63Z" style="fill:rgb(175,175,175);"/>
</g>
<g transform="matrix(1,0,0,1,0.742563,-124.975)">
<path d="M3469.55,2402.21C3469.55,2393.88 3462.79,2387.13 3454.47,2387.13L3333.82,2387.13C3325.5,2387.13 3318.74,2393.88 3318.74,2402.21L3318.74,2602.61C3318.74,2623.42 3335.64,2640.31 3356.44,2640.31L3431.85,2640.31C3452.65,2640.31 3469.55,2623.42 3469.55,2602.61L3469.55,2402.21ZM3460.38,2402.21L3460.38,2602.61C3460.38,2618.36 3447.59,2631.14 3431.85,2631.14L3356.44,2631.14C3340.69,2631.14 3327.91,2618.36 3327.91,2602.61L3327.91,2402.21C3327.91,2398.94 3330.56,2396.29 3333.82,2396.29C3333.82,2396.29 3454.47,2396.29 3454.47,2396.29C3457.73,2396.29 3460.38,2398.94 3460.38,2402.21ZM3394.14,2578.68C3380.41,2578.68 3369.26,2589.83 3369.26,2603.56C3369.26,2617.3 3380.41,2628.45 3394.14,2628.45C3407.88,2628.45 3419.03,2617.3 3419.03,2603.56C3419.03,2589.83 3407.88,2578.68 3394.14,2578.68ZM3394.14,2587.84C3402.82,2587.84 3409.86,2594.89 3409.86,2603.56C3409.86,2612.24 3402.82,2619.28 3394.14,2619.28C3385.47,2619.28 3378.43,2612.24 3378.43,2603.56C3378.43,2594.89 3385.47,2587.84 3394.14,2587.84Z" style="fill:rgb(175,175,175);"/>
</g>
<g transform="matrix(1.18897,0,0,1.18897,2954.16,178.595)">
<path d="M370.675,1826.57C388.504,1826.57 402.979,1841.05 402.979,1858.88C402.979,1876.71 388.504,1891.18 370.675,1891.18C352.846,1891.18 338.371,1876.71 338.371,1858.88C338.371,1841.05 352.846,1826.57 370.675,1826.57ZM370.675,1834.28C384.25,1834.28 395.271,1845.3 395.271,1858.88C395.271,1872.45 384.25,1883.47 370.675,1883.47C357.1,1883.47 346.079,1872.45 346.079,1858.88C346.079,1845.3 357.1,1834.28 370.675,1834.28Z" style="fill:rgb(175,175,175);"/>
</g>
</g>
<g id="Face-ID" transform="matrix(0.63043,0,0,0.63043,187.473,59.1379)">
<g>
<g>
<g id="Corners">
<g id="Corner">
<path d="M4.114,21.943L4.114,13.029C4.114,7.993 7.993,4.114 13.029,4.114L21.943,4.114C23.079,4.114 24,3.193 24,2.057C24,0.921 23.079,0 21.943,0L13.029,0C5.721,0 0,5.721 0,13.029L0,21.943C0,23.079 0.921,24 2.057,24C3.193,24 4.114,23.079 4.114,21.943Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
<g id="Corner1" serif:id="Corner" transform="matrix(-1,0,0,1,80,0)">
<path d="M4.114,21.943L4.114,13.029C4.114,7.993 7.993,4.114 13.029,4.114L21.943,4.114C23.079,4.114 24,3.193 24,2.057C24,0.921 23.079,0 21.943,0L13.029,0C5.721,0 0,5.721 0,13.029L0,21.943C0,23.079 0.921,24 2.057,24C3.193,24 4.114,23.079 4.114,21.943Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
<g id="Corner2" serif:id="Corner" transform="matrix(1,0,0,-1,0,80)">
<path d="M4.114,21.943L4.114,13.029C4.114,7.993 7.993,4.114 13.029,4.114L21.943,4.114C23.079,4.114 24,3.193 24,2.057C24,0.921 23.079,0 21.943,0L13.029,0C5.721,0 0,5.721 0,13.029L0,21.943C0,23.079 0.921,24 2.057,24C3.193,24 4.114,23.079 4.114,21.943Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
<g id="Corner3" serif:id="Corner" transform="matrix(-1,0,0,-1,80,80)">
<path d="M4.114,21.943L4.114,13.029C4.114,7.993 7.993,4.114 13.029,4.114L21.943,4.114C23.079,4.114 24,3.193 24,2.057C24,0.921 23.079,0 21.943,0L13.029,0C5.721,0 0,5.721 0,13.029L0,21.943C0,23.079 0.921,24 2.057,24C3.193,24 4.114,23.079 4.114,21.943Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
</g>
<g id="Eye" transform="matrix(1,0,0,1,21.7544,28.0702)">
<path id="Path" d="M0,2.143L0,7.86C0,9.044 0.895,10.003 2,10.003C3.105,10.003 4,9.044 4,7.86L4,2.143C4,0.959 3.105,0 2,0C0.895,0 0,0.959 0,2.143Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
<g id="Eye1" serif:id="Eye" transform="matrix(1,0,0,1,54.7368,28.0702)">
<path id="Path1" serif:id="Path" d="M0,2.143L0,7.86C0,9.044 0.895,10.003 2,10.003C3.105,10.003 4,9.044 4,7.86L4,2.143C4,0.959 3.105,0 2,0C0.895,0 0,0.959 0,2.143Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
<path id="Mouth" d="M25.932,59.083C29.833,62.724 34.558,64.561 40,64.561C45.442,64.561 50.167,62.724 54.068,59.083C54.918,58.29 54.964,56.957 54.171,56.107C53.377,55.257 52.045,55.211 51.195,56.005C48.079,58.913 44.382,60.351 40,60.351C35.618,60.351 31.921,58.913 28.805,56.005C27.955,55.211 26.623,55.257 25.829,56.107C25.036,56.957 25.082,58.29 25.932,59.083Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
<path id="Nose" d="M40,30.175L40,44.912C40,45.855 39.539,46.316 38.591,46.316L37.193,46.316C36.03,46.316 35.088,47.258 35.088,48.421C35.088,49.584 36.03,50.526 37.193,50.526L38.591,50.526C41.863,50.526 44.211,48.182 44.211,44.912L44.211,30.175C44.211,29.013 43.268,28.07 42.105,28.07C40.943,28.07 40,29.013 40,30.175Z" style="fill:rgb(175,175,175);fill-rule:nonzero;"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,23 +1,27 @@
@import 'input_base';
@import "input_base";
input:not([type=radio]):not([type=checkbox]),
input:not([type="radio"]):not([type="checkbox"]),
.lgn-input {
@include lgn-input-base;
}
// use seme base styling for select as input
select, .lgn-select {
@include lgn-input-base;
select,
.lgn-select {
@include lgn-input-base;
}
.lgn-suffix-wrapper {
position: relative;
position: relative;
[lgnSuffix] {
position: absolute;
right: .5rem;
top: 9px;
height: inherit;
vertical-align: middle;
}
[lgnSuffix] {
position: absolute;
right: 0.5rem;
top: 9px;
height: inherit;
vertical-align: middle;
max-width: 150px;
overflow-x: hidden;
text-overflow: ellipsis;
}
}

View File

@ -0,0 +1,5 @@
@import "mfa_base";
.lgn-mfa-options {
@include lgn-mfa-base;
}

View File

@ -0,0 +1,49 @@
@mixin lgn-mfa-base {
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin: 2rem 0;
.mfa {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
padding: 0 0.5rem;
input[type="radio"] + label {
display: flex;
flex-direction: column;
align-items: center;
span {
text-align: center;
}
.mfa-img {
border: 1px solid var(--zitadel-color-input-border);
border-radius: 0.5rem;
padding: 1rem;
display: flex;
margin-bottom: 0.5rem;
transition: all 0.2s ease;
&:hover {
border: 1px solid var(--zitadel-color-input-border-active);
}
}
}
input[type="radio"] {
display: none;
&:checked {
& + label {
.mfa-img {
border: 1px solid var(--zitadel-color-primary);
}
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More