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 }} - name: Test ${{ matrix.browser }}
run: docker compose run e2e --browser ${{ matrix.browser }} run: docker compose run e2e --browser ${{ matrix.browser }}
working-directory: e2e 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 }} - name: Archive production tests ${{ matrix.browser }}
if: always() if: always()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
@ -46,4 +56,6 @@ jobs:
e2e/cypress/results e2e/cypress/results
e2e/cypress/videos e2e/cypress/videos
e2e/cypress/screenshots e2e/cypress/screenshots
.artifacts/e2e-compose-zitadel.log
.artifacts/e2e-compose-prepare.log
retention-days: 30 retention-days: 30

View File

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

View File

@ -125,4 +125,4 @@ brews:
announce: announce:
discord: discord:
enabled: true 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? ## Want to start ZITADEL?
You can find an installation guide for all the different environments here: 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?** ## **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/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
crypto_db "github.com/zitadel/zitadel/internal/crypto/database" crypto_db "github.com/zitadel/zitadel/internal/crypto/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "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.CustomDomain = mig.externalDomain
mig.instanceSetup.DefaultLanguage = mig.DefaultLanguage mig.instanceSetup.DefaultLanguage = mig.DefaultLanguage
mig.instanceSetup.Org = mig.Org 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) mig.instanceSetup.Org.Human.Email.Address = strings.TrimSpace(mig.instanceSetup.Org.Human.Email.Address)
if 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) _, _, _, _, err = cmd.SetUpInstance(ctx, &mig.instanceSetup)

View File

@ -4,13 +4,16 @@ FirstInstance:
Org: Org:
Name: ZITADEL Name: ZITADEL
Human: 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 UserName: zitadel-admin
FirstName: ZITADEL FirstName: ZITADEL
LastName: Admin LastName: Admin
NickName: NickName:
DisplayName: DisplayName:
Email: 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 Verified: true
PreferredLanguage: en PreferredLanguage: en
Gender: Gender:

View File

@ -181,7 +181,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil { if err != nil {
return fmt.Errorf("error starting admin repo: %w", err) 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 return err
} }
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.Database(), commands, queries, config.SystemDefaults, adminRepo, config.ExternalSecure, keys.User)); err != nil { 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", "grpc-web",
"@angular/common/locales/de", "@angular/common/locales/de",
"codemirror/mode/javascript/javascript", "codemirror/mode/javascript/javascript",
"codemirror/mode/xml/xml",
"src/app/proto/generated/zitadel/admin_pb", "src/app/proto/generated/zitadel/admin_pb",
"src/app/proto/generated/zitadel/org_pb", "src/app/proto/generated/zitadel/org_pb",
"src/app/proto/generated/zitadel/management_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, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^14.2.2", "@angular/animations": "^14.2.4",
"@angular/cdk": "^14.2.2", "@angular/cdk": "^14.2.3",
"@angular/common": "^14.2.2", "@angular/common": "^14.2.4",
"@angular/compiler": "^14.2.2", "@angular/compiler": "^14.2.4",
"@angular/core": "^14.2.2", "@angular/core": "^14.2.4",
"@angular/forms": "^14.2.2", "@angular/forms": "^14.2.4",
"@angular/material": "^14.2.2", "@angular/material": "^14.2.3",
"@angular/material-moment-adapter": "^14.2.2", "@angular/material-moment-adapter": "^14.2.3",
"@angular/platform-browser": "^14.2.2", "@angular/platform-browser": "^14.2.4",
"@angular/platform-browser-dynamic": "^14.2.2", "@angular/platform-browser-dynamic": "^14.2.4",
"@angular/router": "^14.2.2", "@angular/router": "^14.2.4",
"@angular/service-worker": "^14.2.2", "@angular/service-worker": "^14.2.4",
"@ctrl/ngx-codemirror": "^5.1.1", "@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/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@types/file-saver": "^2.0.2", "@types/file-saver": "^2.0.2",
"@types/google-protobuf": "^3.15.3", "@types/google-protobuf": "^3.15.3",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
@ -38,7 +37,7 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"google-proto-files": "^3.0.0", "google-proto-files": "^3.0.0",
"google-protobuf": "^3.19.4", "google-protobuf": "^3.19.4",
"grpc-web": "^1.3.0", "grpc-web": "^1.4.1",
"libphonenumber-js": "^1.10.6", "libphonenumber-js": "^1.10.6",
"material-design-icons-iconfont": "^6.1.1", "material-design-icons-iconfont": "^6.1.1",
"moment": "^2.29.4", "moment": "^2.29.4",
@ -48,27 +47,27 @@
"rxjs": "~7.5.2", "rxjs": "~7.5.2",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
"tslib": "^2.2.0", "tslib": "^2.2.0",
"uuid": "^8.3.2", "uuid": "^9.0.0",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^14.2.2", "@angular-devkit/build-angular": "^14.2.4",
"@angular-eslint/builder": "^14.0.4", "@angular-eslint/builder": "^14.1.2",
"@angular-eslint/eslint-plugin": "^14.0.4", "@angular-eslint/eslint-plugin": "^14.1.2",
"@angular-eslint/eslint-plugin-template": "^14.0.4", "@angular-eslint/eslint-plugin-template": "^14.1.2",
"@angular-eslint/schematics": "^14.0.4", "@angular-eslint/schematics": "^14.1.2",
"@angular-eslint/template-parser": "^14.0.4", "@angular-eslint/template-parser": "^14.1.2",
"@angular/cli": "^14.2.2", "@angular/cli": "^14.2.4",
"@angular/compiler-cli": "^14.2.2", "@angular/compiler-cli": "^14.2.4",
"@angular/language-service": "^14.2.2", "@angular/language-service": "^14.2.4",
"@types/jasmine": "~4.3.0", "@types/jasmine": "~4.3.0",
"@types/jasminewd2": "~2.0.10", "@types/jasminewd2": "~2.0.10",
"@types/jsonwebtoken": "^8.5.5", "@types/jsonwebtoken": "^8.5.5",
"@types/node": "^18.7.16", "@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", "@typescript-eslint/parser": "5.36.1",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"eslint": "^8.18.0", "eslint": "^8.24.0",
"jasmine-core": "~4.4.0", "jasmine-core": "~4.4.0",
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.0", "karma": "~6.4.0",
@ -78,6 +77,6 @@
"karma-jasmine-html-reporter": "^2.0.0", "karma-jasmine-html-reporter": "^2.0.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"typescript": "^4.4.4" "typescript": "^4.8.4"
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
<div class="footer-wrapper"> <div class="footer-wrapper">
<div class="footer-row"> <div class="footer-row">
<div class="footer-links"> <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> <span>{{ 'FOOTER.LINKS.TOS' | translate }}</span>
<i class="las la-external-link-alt"></i> <i class="las la-external-link-alt"></i>
</a> </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> <span>{{ 'FOOTER.LINKS.PP' | translate }}</span>
<i class="las la-external-link-alt"></i> <i class="las la-external-link-alt"></i>
</a> </a>

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb'; import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_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 { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -14,61 +13,15 @@ import { ToastService } from 'src/app/services/toast.service';
}) })
export class MetadataDialogComponent { export class MetadataDialogComponent {
public metadata: Partial<Metadata.AsObject>[] = []; public metadata: Partial<Metadata.AsObject>[] = [];
public injData: any = {}; public loading: boolean = false;
public loading: boolean = true;
public ts!: Timestamp.AsObject | undefined; public ts!: Timestamp.AsObject | undefined;
constructor( constructor(
private managementService: ManagementService,
private authService: GrpcAuthService,
private toast: ToastService, private toast: ToastService,
public dialogRef: MatDialogRef<MetadataDialogComponent>, public dialogRef: MatDialogRef<MetadataDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
) { ) {
this.injData = data; this.metadata = data.metadata;
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;
});
}
} }
public addEntry(): void { public addEntry(): void {
@ -104,24 +57,24 @@ export class MetadataDialogComponent {
public setMetadata(key: string, value: string): void { public setMetadata(key: string, value: string): void {
if (key && value) { if (key && value) {
this.managementService this.data
.setUserMetadata(key, btoa(value), this.injData.userId) .setFcn(key, value)
.then(() => { .then(() => {
this.toast.showInfo('USER.METADATA.SETSUCCESS', true); this.toast.showInfo('METADATA.SETSUCCESS', true);
}) })
.catch((error) => { .catch((error: any) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
} }
public removeMetadata(key: string): Promise<void> { public removeMetadata(key: string): Promise<void> {
return this.managementService return this.data
.removeUserMetadata(key, this.injData.userId) .removeFcn(key)
.then((resp) => { .then((resp: any) => {
this.toast.showInfo('USER.METADATA.REMOVESUCCESS', true); this.toast.showInfo('METADATA.REMOVESUCCESS', true);
}) })
.catch((error) => { .catch((error: any) => {
this.toast.showError(error); 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 { .metadata-details {
padding-bottom: 1rem; padding-bottom: 1rem;
.metadata-actions { .refresh {
display: flex; margin-left: 0.5rem;
align-items: center; font-size: 1.2rem;
justify-content: flex-end;
.edit { i {
font-size: 14px; font-size: 1.2rem;
} }
} }
.edit {
font-size: 14px;
}
.meta-row { .meta-row {
display: flex; display: flex;
margin-bottom: 0.5rem; 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 isDarkTheme: boolean = true;
@Input() public user!: User.AsObject; @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( public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
map((result) => { map((result) => {
return result.matches; return result.matches;

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
> >
<p class="subinfo" sub> <p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}</span> <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> <i class="las la-info-circle"></i>
</a> </a>
</p> </p>

View File

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

View File

@ -6,7 +6,7 @@
<h1>{{ 'ORG.DOMAINS.TITLE' | translate }}</h1> <h1>{{ 'ORG.DOMAINS.TITLE' | translate }}</h1>
<a <a
mat-icon-button 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" rel="noreferrer"
target="_blank" target="_blank"
> >

View File

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

View File

@ -1,7 +1,7 @@
<cnsl-detail-layout [hasBackButton]="true" title="{{ 'IAM.MEMBER.TITLE' | translate }}"> <cnsl-detail-layout [hasBackButton]="true" title="{{ 'IAM.MEMBER.TITLE' | translate }}">
<p class="subinfo" sub> <p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</span> <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> <i class="las la-info-circle"></i>
</a> </a>
</p> </p>

View File

@ -46,6 +46,13 @@
<cnsl-settings-grid [type]="PolicyComponentServiceType.MGMT"></cnsl-settings-grid> <cnsl-settings-grid [type]="PolicyComponentServiceType.MGMT"></cnsl-settings-grid>
</ng-container> </ng-container>
<cnsl-metadata
[metadata]="metadata"
[disabled]="(['org.write'] | hasRole | async) === false"
(editClicked)="editMetadata()"
(refresh)="loadMetadata()"
></cnsl-metadata>
<ng-template #nopolicyreadpermission> <ng-template #nopolicyreadpermission>
<div class="no-permission-warn-wrapper"> <div class="no-permission-warn-wrapper">
<cnsl-info-section class="info-section-warn" [fitWidth]="true" [type]="InfoSectionType.ALERT">{{ <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 { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; 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 { 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 { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.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 { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Member } from 'src/app/proto/generated/zitadel/member_pb'; 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 { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb'; import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { Buffer } from 'buffer';
@Component({ @Component({
selector: 'cnsl-org-detail', selector: 'cnsl-org-detail',
@ -28,6 +31,9 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
public OrgState: any = OrgState; public OrgState: any = OrgState;
public ChangeType: any = ChangeType; public ChangeType: any = ChangeType;
public metadata: Metadata.AsObject[] = [];
public loadingMetadata: boolean = true;
// members // members
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ -36,6 +42,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject(); private destroy$: Subject<void> = new Subject();
public InfoSectionType: any = InfoSectionType; public InfoSectionType: any = InfoSectionType;
constructor( constructor(
auth: GrpcAuthService, auth: GrpcAuthService,
private dialog: MatDialog, private dialog: MatDialog,
@ -52,11 +59,13 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
auth.activeOrgChanged.pipe(takeUntil(this.destroy$)).subscribe((org) => { auth.activeOrgChanged.pipe(takeUntil(this.destroy$)).subscribe((org) => {
this.getData(); this.getData();
this.loadMetadata();
}); });
} }
public ngOnInit(): void { public ngOnInit(): void {
this.getData(); this.getData();
this.loadMetadata();
} }
public ngOnDestroy(): void { public ngOnDestroy(): void {
@ -188,4 +197,40 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.membersSubject.next(members); 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 }}"> <cnsl-detail-layout title="{{ org?.name }} {{ 'ORG.MEMBER.TITLE' | translate }}">
<p class="subinfo" sub> <p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'ORG.MEMBER.DESCRIPTION' | translate }}</span> <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> <i class="las la-info-circle"></i>
</a> </a>
</p> </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 { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { InputModule } from 'src/app/modules/input/input.module'; import { InputModule } from 'src/app/modules/input/input.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.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 { SettingsGridModule } from 'src/app/modules/settings-grid/settings-grid.module';
import { SharedModule } from 'src/app/modules/shared/shared.module'; import { SharedModule } from 'src/app/modules/shared/shared.module';
import { TopViewModule } from 'src/app/modules/top-view/top-view.module'; import { TopViewModule } from 'src/app/modules/top-view/top-view.module';
@ -53,6 +54,7 @@ import { OrgRoutingModule } from './org-routing.module';
MatMenuModule, MatMenuModule,
ChangesModule, ChangesModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MetadataModule,
TranslateModule, TranslateModule,
SharedModule, SharedModule,
SettingsGridModule, SettingsGridModule,

View File

@ -1,7 +1,7 @@
<cnsl-top-view <cnsl-top-view
title="{{ app?.name }}" title="{{ app?.name }}"
[hasActions]="isZitadel === false && (['project.app.write:' + projectId, 'project.app.write'] | hasRole | async)" [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'" [sub]="app?.oidcConfig ? ('APP.OIDC.APPTYPE.' + app?.oidcConfig?.appType | translate) : app?.apiConfig ? 'API' : 'SAML'"
[isActive]="app?.state === AppState.APP_STATE_ACTIVE" [isActive]="app?.state === AppState.APP_STATE_ACTIVE"
[isInactive]="app?.state === AppState.APP_STATE_INACTIVE" [isInactive]="app?.state === AppState.APP_STATE_INACTIVE"

View File

@ -1,7 +1,7 @@
<cnsl-top-view <cnsl-top-view
title="{{ project?.projectName }}" title="{{ project?.projectName }}"
[hasActions]="false" [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>{{ sub="{{ 'PROJECT.PAGES.TYPE.GRANTED_SINGULAR' | translate }} {{ 'ACTIONS.OF' | translate }} <strong>{{
project?.projectOwnerName project?.projectOwnerName
}}</strong>" }}</strong>"

View File

@ -1,6 +1,6 @@
<cnsl-top-view <cnsl-top-view
title="{{ project?.name }}" 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 }}" sub="{{ 'PROJECT.PAGES.TYPE.OWNED_SINGULAR' | translate }}"
[isActive]="project?.state === ProjectState.PROJECT_STATE_ACTIVE" [isActive]="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
[isInactive]="project?.state === ProjectState.PROJECT_STATE_INACTIVE" [isInactive]="project?.state === ProjectState.PROJECT_STATE_INACTIVE"

View File

@ -2,7 +2,12 @@
<div class="enlarged-container"> <div class="enlarged-container">
<div class="project-title-row"> <div class="project-title-row">
<h1>{{ 'PROJECT.PAGES.LIST' | translate }}</h1> <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> <i class="las la-info-circle"></i>
</a> </a>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

@ -130,7 +130,13 @@
</ng-container> </ng-container>
<ng-container *ngIf="currentSetting === 'metadata'"> <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> </ng-container>
</cnsl-sidenav> </cnsl-sidenav>

View File

@ -6,15 +6,18 @@ import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription, take } from 'rxjs'; import { Subscription, take } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; 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 { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource'; import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; 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 { Email, Gender, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { AuthenticationService } from 'src/app/services/authentication.service'; import { AuthenticationService } from 'src/app/services/authentication.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.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 { ToastService } from 'src/app/services/toast.service';
import { Buffer } from 'buffer';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component'; import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
@Component({ @Component({
@ -30,6 +33,7 @@ export class AuthUserDetailComponent implements OnDestroy {
private subscription: Subscription = new Subscription(); private subscription: Subscription = new Subscription();
public loading: boolean = false; public loading: boolean = false;
public loadingMetadata: boolean = false;
public ChangeType: any = ChangeType; public ChangeType: any = ChangeType;
public userLoginMustBeDomain: boolean = false; public userLoginMustBeDomain: boolean = false;
@ -38,6 +42,8 @@ export class AuthUserDetailComponent implements OnDestroy {
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER; public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public refreshChanges$: EventEmitter<void> = new EventEmitter(); public refreshChanges$: EventEmitter<void> = new EventEmitter();
public metadata: Metadata.AsObject[] = [];
public settingsList: SidenavSetting[] = [ public settingsList: SidenavSetting[] = [
{ id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' }, { id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' },
{ id: 'idp', i18nKey: 'USER.SETTINGS.IDP' }, { id: 'idp', i18nKey: 'USER.SETTINGS.IDP' },
@ -55,6 +61,7 @@ export class AuthUserDetailComponent implements OnDestroy {
public userService: GrpcAuthService, public userService: GrpcAuthService,
private dialog: MatDialog, private dialog: MatDialog,
private auth: AuthenticationService, private auth: AuthenticationService,
private mgmt: ManagementService,
private breadcrumbService: BreadcrumbService, private breadcrumbService: BreadcrumbService,
private mediaMatcher: MediaMatcher, private mediaMatcher: MediaMatcher,
private _location: Location, private _location: Location,
@ -104,6 +111,8 @@ export class AuthUserDetailComponent implements OnDestroy {
if (resp.user) { if (resp.user) {
this.user = resp.user; this.user = resp.user;
this.loadMetadata();
this.breadcrumbService.setBreadcrumb([ this.breadcrumbService.setBreadcrumb([
new Breadcrumb({ new Breadcrumb({
type: BreadcrumbType.AUTHUSER, 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 { DetailFormMachineModule } from './detail-form-machine/detail-form-machine.module';
import { DetailFormModule } from './detail-form/detail-form.module'; import { DetailFormModule } from './detail-form/detail-form.module';
import { ExternalIdpsComponent } from './external-idps/external-idps.component'; 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 { PasswordComponent } from './password/password.component';
import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component'; import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component';
import { UserDetailComponent } from './user-detail/user-detail.component'; import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component'; import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
import { MetadataModule } from 'src/app/modules/metadata/metadata.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -76,8 +75,6 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
DialogU2FComponent, DialogU2FComponent,
DialogPasswordlessComponent, DialogPasswordlessComponent,
AuthFactorDialogComponent, AuthFactorDialogComponent,
MetadataDialogComponent,
MetadataComponent,
], ],
imports: [ imports: [
ChangesModule, ChangesModule,
@ -95,6 +92,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
ShowTokenDialogModule, ShowTokenDialogModule,
MetaLayoutModule, MetaLayoutModule,
MatCheckboxModule, MatCheckboxModule,
MetadataModule,
TopViewModule, TopViewModule,
HasRolePipeModule, HasRolePipeModule,
UserGrantsModule, UserGrantsModule,

View File

@ -1,7 +1,7 @@
<cnsl-top-view <cnsl-top-view
*ngIf="user" *ngIf="user"
title="{{ user.human ? user.human.profile?.displayName : user.machine?.name }}" 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 }}" sub="{{ user.preferredLoginName }}"
[isActive]="user.state === UserState.USER_STATE_ACTIVE" [isActive]="user.state === UserState.USER_STATE_ACTIVE"
[isInactive]="user.state === UserState.USER_STATE_INACTIVE" [isInactive]="user.state === UserState.USER_STATE_INACTIVE"
@ -206,7 +206,13 @@
</ng-container> </ng-container>
<ng-container *ngIf="currentSetting && currentSetting === 'metadata'"> <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> </ng-container>
</div> </div>
</cnsl-sidenav> </cnsl-sidenav>

View File

@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.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 { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource'; import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; 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 { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.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 { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-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 languages: string[] = ['de', 'en', 'it', 'fr'];
public ChangeType: any = ChangeType; public ChangeType: any = ChangeType;
public loading: boolean = true; public loading: boolean = true;
public loadingMetadata: boolean = true;
public UserState: any = UserState; public UserState: any = UserState;
public copied: string = ''; public copied: string = '';
@ -113,6 +116,7 @@ export class UserDetailComponent implements OnInit {
this.mgmtUserService this.mgmtUserService
.getUserByID(id) .getUserByID(id)
.then((resp) => { .then((resp) => {
this.loadMetadata(id);
this.loading = false; this.loading = false;
if (resp.user) { if (resp.user) {
this.user = resp.user; this.user = resp.user;
@ -129,17 +133,6 @@ export class UserDetailComponent implements OnInit {
this.loading = false; this.loading = false;
this.toast.showError(err); 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; 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, UpdateLockoutPolicyResponse,
UpdateLoginPolicyRequest, UpdateLoginPolicyRequest,
UpdateLoginPolicyResponse, UpdateLoginPolicyResponse,
AddOIDCSettingsRequest,
AddOIDCSettingsResponse,
UpdateOIDCSettingsRequest, UpdateOIDCSettingsRequest,
UpdateOIDCSettingsResponse, UpdateOIDCSettingsResponse,
UpdatePasswordAgePolicyRequest, UpdatePasswordAgePolicyRequest,
@ -623,6 +625,10 @@ export class AdminService {
return this.grpcService.admin.updateOIDCSettings(req, null).then((resp) => resp.toObject()); 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 */ /* LOG and FILE Notifications */
public getLogNotificationProvider(): Promise<GetLogNotificationProviderResponse.AsObject> { 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); public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private cachedOrgs: Org.AsObject[] = []; private cachedOrgs: Org.AsObject[] = [];

View File

@ -219,6 +219,8 @@ import {
ListOrgMemberRolesResponse, ListOrgMemberRolesResponse,
ListOrgMembersRequest, ListOrgMembersRequest,
ListOrgMembersResponse, ListOrgMembersResponse,
ListOrgMetadataRequest,
ListOrgMetadataResponse,
ListPersonalAccessTokensRequest, ListPersonalAccessTokensRequest,
ListPersonalAccessTokensResponse, ListPersonalAccessTokensResponse,
ListProjectChangesRequest, ListProjectChangesRequest,
@ -303,6 +305,8 @@ import {
RemoveOrgIDPResponse, RemoveOrgIDPResponse,
RemoveOrgMemberRequest, RemoveOrgMemberRequest,
RemoveOrgMemberResponse, RemoveOrgMemberResponse,
RemoveOrgMetadataRequest,
RemoveOrgMetadataResponse,
RemovePersonalAccessTokenRequest, RemovePersonalAccessTokenRequest,
RemovePersonalAccessTokenResponse, RemovePersonalAccessTokenResponse,
RemoveProjectGrantMemberRequest, RemoveProjectGrantMemberRequest,
@ -371,6 +375,8 @@ import {
SetCustomVerifyPhoneMessageTextRequest, SetCustomVerifyPhoneMessageTextRequest,
SetCustomVerifyPhoneMessageTextResponse, SetCustomVerifyPhoneMessageTextResponse,
SetHumanInitialPasswordRequest, SetHumanInitialPasswordRequest,
SetOrgMetadataRequest,
SetOrgMetadataResponse,
SetPrimaryOrgDomainRequest, SetPrimaryOrgDomainRequest,
SetPrimaryOrgDomainResponse, SetPrimaryOrgDomainResponse,
SetTriggerActionsRequest, SetTriggerActionsRequest,
@ -1374,6 +1380,26 @@ export class ManagementService {
return this.grpcService.mgmt.listUserMetadata(req, null).then((resp) => resp.toObject()); 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> { public getUserMetadata(userId: string, key: string): Promise<GetUserMetadataResponse.AsObject> {
const req = new GetUserMetadataRequest(); const req = new GetUserMetadataRequest();
req.setId(userId); req.setId(userId);
@ -1389,6 +1415,13 @@ export class ManagementService {
return this.grpcService.mgmt.setUserMetadata(req, null).then((resp) => resp.toObject()); 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( public bulkSetUserMetadata(
list: BulkSetUserMetadataRequest.Metadata[], list: BulkSetUserMetadataRequest.Metadata[],
userId: string, userId: string,
@ -1406,6 +1439,12 @@ export class ManagementService {
return this.grpcService.mgmt.removeUserMetadata(req, null).then((resp) => resp.toObject()); 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> { public removeUser(id: string): Promise<RemoveUserResponse.AsObject> {
const req = new RemoveUserRequest(); const req = new RemoveUserRequest();
req.setId(id); req.setId(id);

View File

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

View File

@ -294,7 +294,7 @@
"TITLE": "Passwortlose Authentifizierungsmethoden", "TITLE": "Passwortlose Authentifizierungsmethoden",
"DESCRIPTION": "Füge WebAuthn kompatible Authentifikatoren hinzu um dich passwortlos anzumelden.", "DESCRIPTION": "Füge WebAuthn kompatible Authentifikatoren hinzu um dich passwortlos anzumelden.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.", "MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"U2F": "Authentifikator hinzufügen", "U2F": "Methode hinzufügen",
"U2F_DIALOG_TITLE": "Authentifikator hinzufügen", "U2F_DIALOG_TITLE": "Authentifikator hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Login an.", "U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Login an.",
"U2F_SUCCESS": "Passwortlos erfolgreich erstellt!", "U2F_SUCCESS": "Passwortlos erfolgreich erstellt!",
@ -326,17 +326,6 @@
"NEW": "Hinzufügen" "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": { "MFA": {
"TABLETYPE": "Typ", "TABLETYPE": "Typ",
"TABLESTATE": "Status", "TABLESTATE": "Status",
@ -346,7 +335,7 @@
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.", "DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
"MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.", "MANAGE_DESCRIPTION": "Verwalte die Multifaktor-Merkmale Deiner Benutzer.",
"ADD": "Faktor hinzufügen", "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_TITLE": "OTP hinzufügen",
"OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.", "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", "U2F": "Fingerabdruck, Security Key, Face ID oder andere",
@ -639,6 +628,17 @@
"DELETED": "Personal Access Token gelöscht." "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": { "FLOWS": {
"TITLE": "Aktionen und Abläufe", "TITLE": "Aktionen und Abläufe",
"DESCRIPTION": "Hinterlege scripts die bei einem bestimmten Event ausgeführt werden.", "DESCRIPTION": "Hinterlege scripts die bei einem bestimmten Event ausgeführt werden.",

View File

@ -294,7 +294,7 @@
"TITLE": "Passwordless Authentication", "TITLE": "Passwordless Authentication",
"DESCRIPTION": "Add WebAuthn based Authentication Methods to log onto ZITADEL passwordless.", "DESCRIPTION": "Add WebAuthn based Authentication Methods to log onto ZITADEL passwordless.",
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.", "MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
"U2F": "Add authenticator", "U2F": "Add method",
"U2F_DIALOG_TITLE": "Verify authenticator", "U2F_DIALOG_TITLE": "Verify authenticator",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used passwordless Login", "U2F_DIALOG_DESCRIPTION": "Enter a name for your used passwordless Login",
"U2F_SUCCESS": "Passwordless Auth created successfully!", "U2F_SUCCESS": "Passwordless Auth created successfully!",
@ -326,17 +326,6 @@
"NEW": "Add New" "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": { "MFA": {
"TABLETYPE": "Type", "TABLETYPE": "Type",
"TABLESTATE": "Status", "TABLESTATE": "Status",
@ -346,7 +335,7 @@
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.", "DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
"MANAGE_DESCRIPTION": "Manage the second factor methods of your users.", "MANAGE_DESCRIPTION": "Manage the second factor methods of your users.",
"ADD": "Add Factor", "ADD": "Add Factor",
"OTP": "OTP (One-Time Password)", "OTP": "Authenticator App for OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Add OTP", "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.", "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", "U2F": "Fingerprint, Security Keys, Face ID and other",
@ -639,6 +628,17 @@
"DELETED": "Token deleted with success." "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": { "FLOWS": {
"TITLE": "Actions and Flows", "TITLE": "Actions and Flows",
"DESCRIPTION": "Define scripts to execute on a certain event.", "DESCRIPTION": "Define scripts to execute on a certain event.",

View File

@ -294,7 +294,7 @@
"TITLE": "Authentification sans mot de passe", "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.", "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.", "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_TITLE": "Vérifier l'authentifiant",
"U2F_DIALOG_DESCRIPTION": "Entrez un nom pour votre connexion sans mot de passe utilisée", "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 !", "U2F_SUCCESS": "Auth sans mot de passe créé avec succès !",
@ -326,17 +326,6 @@
"NEW": "Ajouter un nouveau" "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": { "MFA": {
"TABLETYPE": "Type", "TABLETYPE": "Type",
"TABLESTATE": "Statut", "TABLESTATE": "Statut",
@ -346,7 +335,7 @@
"DESCRIPTION": "Ajoutez un second facteur pour garantir une sécurité optimale de votre compte.", "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.", "MANAGE_DESCRIPTION": "Gérez les méthodes de second facteur de vos utilisateurs.",
"ADD": "Ajouter un facteur", "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_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.", "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", "U2F": "Empreinte digitale, clés de sécurité, Face ID et autres",
@ -639,6 +628,17 @@
"DELETED": "Jeton supprimé avec succès." "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": { "FLOWS": {
"TITLE": "Actions et flux", "TITLE": "Actions et flux",
"DESCRIPTION": "Définissez des scripts à exécuter lors d'un certain événement.", "DESCRIPTION": "Définissez des scripts à exécuter lors d'un certain événement.",

View File

@ -294,7 +294,7 @@
"TITLE": "Autenticazione passwordless", "TITLE": "Autenticazione passwordless",
"DESCRIPTION": "Aggiungi i metodi di autenticazione basati su WebAuthn per accedere a ZITADEL senza password.", "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.", "MANAGE_DESCRIPTION": "Gestisci i metodi del secondo fattore dei vostri utenti.",
"U2F": "Aggiungi autenticatore", "U2F": "Aggiungi metodo",
"U2F_DIALOG_TITLE": "Verifica autenticatore", "U2F_DIALOG_TITLE": "Verifica autenticatore",
"U2F_DIALOG_DESCRIPTION": "Inserisci un nome per il tuo authenticatore o dispositivo usato.", "U2F_DIALOG_DESCRIPTION": "Inserisci un nome per il tuo authenticatore o dispositivo usato.",
"U2F_SUCCESS": "Autorizzazione passwordless creata con successo!", "U2F_SUCCESS": "Autorizzazione passwordless creata con successo!",
@ -326,17 +326,6 @@
"NEW": "Aggiungi nuovo" "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": { "MFA": {
"TABLETYPE": "Tipo", "TABLETYPE": "Tipo",
"TABLESTATE": "Stato", "TABLESTATE": "Stato",
@ -346,7 +335,7 @@
"DESCRIPTION": "Aggiungi un secondo fattore per garantire la sicurezza ottimale del tuo account.", "DESCRIPTION": "Aggiungi un secondo fattore per garantire la sicurezza ottimale del tuo account.",
"MANAGE_DESCRIPTION": "Gestite i metodi del secondo fattore dei vostri utenti.", "MANAGE_DESCRIPTION": "Gestite i metodi del secondo fattore dei vostri utenti.",
"ADD": "Aggiungi fattore", "ADD": "Aggiungi fattore",
"OTP": "OTP (One-Time Password)", "OTP": "App di autenticazione per OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Aggiungi OTP", "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.", "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", "U2F": "Impronta digitale, chiave di sicurezza, Face ID e altri",
@ -639,6 +628,17 @@
"DELETED": "Token eliminato con successo." "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": { "FLOWS": {
"TITLE": "Azioni e Processi", "TITLE": "Azioni e Processi",
"DESCRIPTION": "Esegui processi su certi eventi.", "DESCRIPTION": "Esegui processi su certi eventi.",

View File

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

View File

@ -25,6 +25,7 @@ In addition to the standard compliant scopes we utilize the following scopes.
| Scopes | Example | Description | | 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: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: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: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 | | `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 GET: /settings/oidc
### AddOIDCSettings
> **rpc** AddOIDCSettings([AddOIDCSettingsRequest](#addoidcsettingsrequest))
[AddOIDCSettingsResponse](#addoidcsettingsresponse)
Add oidc settings (e.g token lifetimes, etc)
POST: /settings/oidc
### UpdateOIDCSettings ### UpdateOIDCSettings
> **rpc** UpdateOIDCSettings([UpdateOIDCSettingsRequest](#updateoidcsettingsrequest)) > **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 ### AddSMSProviderTwilioRequest

View File

@ -57,6 +57,18 @@ This might take some time
POST: /instances POST: /instances
### UpdateInstance
> **rpc** UpdateInstance([UpdateInstanceRequest](#updateinstancerequest))
[UpdateInstanceResponse](#updateinstanceresponse)
Updates name of an existing instance
PUT: /instances/{instance_id}
### CreateInstance ### CreateInstance
> **rpc** CreateInstance([CreateInstanceRequest](#createinstancerequest)) > **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

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

View File

@ -45,9 +45,7 @@ describe('machines', () => {
// .trigger('mouseover') // .trigger('mouseover')
.find('[data-e2e="enabled-delete-button"]') .find('[data-e2e="enabled-delete-button"]')
.click({ force: true }); .click({ force: true });
cy.get('[data-e2e="confirm-dialog-input"]') cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testMachineUserNameRemove);
.focus()
.type(loginname(testMachineUserNameRemove, Cypress.env('ORGANIZATION')));
cy.get('[data-e2e="confirm-dialog-button"]').click(); cy.get('[data-e2e="confirm-dialog-button"]').click();
cy.get('.data-e2e-success'); cy.get('.data-e2e-success');
cy.wait(200); 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 { export interface apiCallProperties {
authHeader: string; authHeader: string;
mgntBaseURL: string; mgntBaseURL: string;
adminBaseURL: string;
} }
export function apiAuth(): Cypress.Chainable<apiCallProperties> { export function apiAuth(): Cypress.Chainable<apiCallProperties> {
@ -10,6 +11,7 @@ export function apiAuth(): Cypress.Chainable<apiCallProperties> {
return <apiCallProperties>{ return <apiCallProperties>{
authHeader: `Bearer ${token}`, authHeader: `Bearer ${token}`,
mgntBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1/`, 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( export function ensureSomethingDoesntExist(
api: apiCallProperties, api: apiCallProperties,
searchPath: string, 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( function awaitDesired(
trials: number, trials: number,
expectEntity: (entity: any) => boolean, 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.get('#submit-button').click();
cy.wait('@password').then((interception) => { cy.wait('@password').then((interception) => {
if (interception.response.body.indexOf('Multifactor Setup') === -1) { if (interception.response.body.indexOf('/ui/login/mfa/prompt') === -1) {
return; return;
} }

View File

@ -18,6 +18,16 @@ func (s *Server) GetOIDCSettings(ctx context.Context, _ *admin_pb.GetOIDCSetting
}, nil }, 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) { func (s *Server) UpdateOIDCSettings(ctx context.Context, req *admin_pb.UpdateOIDCSettingsRequest) (*admin_pb.UpdateOIDCSettingsResponse, error) {
result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req)) result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req))
if err != nil { if err != nil {

View File

@ -1,12 +1,13 @@
package admin package admin
import ( import (
"google.golang.org/protobuf/types/known/durationpb"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings" settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
"google.golang.org/protobuf/types/known/durationpb"
) )
func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings { 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 { func UpdateOIDCConfigToConfig(req *admin_pb.UpdateOIDCSettingsRequest) *domain.OIDCSettings {
return &domain.OIDCSettings{ return &domain.OIDCSettings{
AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(), 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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -51,8 +51,19 @@ func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequ
}, nil }, 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,8 @@
package system package system
import ( import (
"strings"
"github.com/zitadel/oidc/v2/pkg/oidc" "github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language" "golang.org/x/text/language"
@ -15,7 +17,7 @@ import (
system_pb "github.com/zitadel/zitadel/pkg/grpc/system" 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 != "" { if req.InstanceName != "" {
defaultInstance.InstanceName = req.InstanceName defaultInstance.InstanceName = req.InstanceName
defaultInstance.Org.Name = 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 != "" { if user.UserName != "" {
defaultInstance.Org.Human.Username = user.UserName defaultInstance.Org.Human.Username = user.UserName
} }
@ -89,7 +96,7 @@ func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defau
return &defaultInstance 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 != "" { if req.InstanceName != "" {
defaultInstance.InstanceName = req.InstanceName defaultInstance.InstanceName = req.InstanceName
defaultInstance.Org.Name = 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 != "" { if req.OwnerUserName != "" {
defaultInstance.Org.Human.Username = req.OwnerUserName defaultInstance.Org.Human.Username = req.OwnerUserName
} }

View File

@ -25,25 +25,29 @@ type Server struct {
command *command.Commands command *command.Commands
query *query.Queries query *query.Queries
administrator repository.AdministratorRepository administrator repository.AdministratorRepository
DefaultInstance command.InstanceSetup defaultInstance command.InstanceSetup
externalDomain string
} }
type Config struct { type Config struct {
Repository eventsourcing.Config Repository eventsourcing.Config
} }
func CreateServer(command *command.Commands, func CreateServer(
command *command.Commands,
query *query.Queries, query *query.Queries,
repo repository.Repository, repo repository.Repository,
database string, database string,
defaultInstance command.InstanceSetup, defaultInstance command.InstanceSetup,
externalDomain string,
) *Server { ) *Server {
return &Server{ return &Server{
command: command, command: command,
query: query, query: query,
administrator: repo, administrator: repo,
database: database, 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 applicationID = authReq.ApplicationID
userOrgID = authReq.UserOrgID 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 { if err != nil {
return "", time.Time{}, err 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 { if request, ok := req.(op.RefreshTokenRequest); ok {
request.SetCurrentScopes(scopes) 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(), resp, token, err := o.command.AddAccessAndRefreshToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(),
refreshToken, req.GetAudience(), scopes, authMethodsReferences, o.defaultAccessTokenLifetime, refreshToken, req.GetAudience(), scopes, authMethodsReferences, accessTokenLifetime,
o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, authTime) //PLANNED: lifetime from client refreshTokenIdleExpiration, refreshTokenExpiration, authTime) //PLANNED: lifetime from client
if err != nil { if err != nil {
if errors.IsErrorInvalidArgument(err) { if errors.IsErrorInvalidArgument(err) {
err = oidc.ErrInvalidGrant().WithParent(err) err = oidc.ErrInvalidGrant().WithParent(err)
@ -248,3 +260,15 @@ func setContextUserSystem(ctx context.Context) context.Context {
} }
return authz.SetCtxData(ctx, data) 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 { for i, role := range projectRoles.ProjectRoles {
allowedScopes[i] = ScopeProjectRolePrefix + role.Key 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) { 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] 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 return scopes, nil
} }
@ -251,6 +264,16 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) { if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
userInfo.AppendClaims(domain.OrgDomainPrimaryClaim, strings.TrimPrefix(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 == "" { if len(roles) == 0 || applicationID == "" {
@ -289,9 +312,20 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
} }
if strings.HasPrefix(scope, ScopeProjectRolePrefix) { if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
roles = append(roles, strings.TrimPrefix(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)) 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 == "" { if len(roles) == 0 || clientID == "" {
return claims, nil return claims, nil

View File

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

View File

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

View File

@ -53,7 +53,7 @@ InitPassword:
CodeLabel: Code CodeLabel: Code
NewPasswordLabel: Neues Passwort NewPasswordLabel: Neues Passwort
NewPasswordConfirmLabel: Passwort bestätigen NewPasswordConfirmLabel: Passwort bestätigen
ResendButtonText: erneut senden ResendButtonText: Code erneut senden
NextButtonText: weiter NextButtonText: weiter
InitPasswordDone: InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser: InitUser:
Title: User aktivieren 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 CodeLabel: Code
NewPasswordLabel: Neues Passwort NewPasswordLabel: Neues Passwort
NewPasswordConfirmLabel: Passwort bestätigen NewPasswordConfirm: Passwort bestätigen
NextButtonText: weiter NextButtonText: weiter
ResendButtonText: erneut senden ResendButtonText: Code erneut senden
InitUserDone: InitUserDone:
Title: User aktiviert Title: User aktiviert
@ -78,65 +78,65 @@ InitUserDone:
CancelButtonText: abbrechen CancelButtonText: abbrechen
InitMFAPrompt: InitMFAPrompt:
Title: Multifaktor hinzufügen Title: 2-Faktor hinzufügen
Description: Möchtest du einen Mulitfaktor 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: OTP (One Time Password) Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: U2F (Universal 2nd Factor) Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
NextButtonText: weiter NextButtonText: weiter
SkipButtonText: überspringen SkipButtonText: überspringen
InitMFAOTP: InitMFAOTP:
Title: Multifaktor Verifizierung Title: 2-Faktor Verifizierung
Description: Verifiziere deinen Multifaktor 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 Authenticator) oder kopiere das Secret und gib anschliessend den Code ein. 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 SecretLabel: Secret
CodeLabel: Code CodeLabel: Code
NextButtonText: weiter NextButtonText: weiter
CancelButtonText: abbrechen CancelButtonText: abbrechen
InitMFAU2F: InitMFAU2F:
Title: Multifaktor U2F / WebAuthN hinzufügen Title: Sicherheitsschlüssel hinzufügen
Description: Füge dein Token hinzu, indem du einen Namen eingibst und den 'Token registrieren' Button drückst. 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 Tokens / Geräts 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) 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: Token registrieren RegisterTokenButtonText: Sicherschlüssel hinzufügen
ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode. ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode.
InitMFADone: InitMFADone:
Title: Multifaktor Verifizierung erstellt Title: Sicherheitsschlüssel eingerichtet
Description: Multifikator Verifizierung erfolgreich abgeschlossen. Der Multifaktor muss bei jeder Anmeldung eingegeben werden. 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 NextButtonText: weiter
CancelButtonText: abbrechen CancelButtonText: abbrechen
MFAProvider: MFAProvider:
Provider0: OTP (One Time Password) Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
Provider1: U2F (Universal 2nd Factor) Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
ChooseOther: oder wähle eine andere Option aus ChooseOther: oder wähle eine andere Option aus
VerifyMFAOTP: VerifyMFAOTP:
Title: Multifaktor verifizieren Title: 2-Faktor verifizieren
Description: Verifiziere deinen Multifaktor Description: Verifiziere deinen Zweitfaktor
CodeLabel: Code CodeLabel: Code
NextButtonText: next NextButtonText: next
VerifyMFAU2F: VerifyMFAU2F:
Title: Multifaktor Verifizierung Title: 2-Faktor Verifizierung
Description: Verifiziere deinen Multifaktor U2F / WebAuthN Token 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) 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. ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode.
ValidateTokenButtonText: Token validieren ValidateTokenButtonText: 2-Faktor verifizieren
Passwordless: Passwordless:
Title: Passwortlos einloggen 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) 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. ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode.
LoginWithPwButtonText: Mit Passwort anmelden LoginWithPwButtonText: Mit Passwort anmelden
ValidateTokenButtonText: Token validieren ValidateTokenButtonText: Passwortlos anmelden
PasswordlessPrompt: PasswordlessPrompt:
Title: Passwortloser Login hinzufügen 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. 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 PasswordlessButtonText: Werde Passwortlos
NextButtonText: weiter NextButtonText: weiter
@ -144,15 +144,15 @@ PasswordlessPrompt:
PasswordlessRegistration: PasswordlessRegistration:
Title: Passwortloser Login hinzufügen Title: Passwortloser Login hinzufügen
Description: Füge dein Token hinzu, indem du einen Namen eingibst und den 'Token registrieren' Button drückst. 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 Tokens / Geräts 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) 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. ErrorRetry: Versuche es erneut, erstelle eine neue Abfrage oder wähle eine andere Methode.
PasswordlessRegistrationDone: PasswordlessRegistrationDone:
Title: Passwortloser Login erstellt 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. DescriptionClose: Du kannst das Fenster nun schliessen.
NextButtonText: weiter NextButtonText: weiter
CancelButtonText: abbrechen 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. 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 CodeLabel: Code
NextButtonText: weiter NextButtonText: weiter
ResendButtonText: erneut senden ResendButtonText: Code erneut senden
EmailVerificationDone: EmailVerificationDone:
Title: E-Mail Verifizierung Title: E-Mail Verifizierung
@ -239,6 +239,7 @@ ExternalRegistrationUserOverview:
English: English English: English
Italian: Italiano Italian: Italiano
French: Français French: Français
Chinese: 简体中文
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
TosConfirm: Ich akzeptiere die TosConfirm: Ich akzeptiere die
TosLinkText: AGBs TosLinkText: AGBs
@ -282,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: abbrechen CancelButtonText: abbrechen
NextButtonText: weiter NextButtonText: weiter
ExternalNotFoundOption: ExternalNotFound:
Title: Externer Benutzer Title: Externer Benutzer
Description: Externer Benutzer konnte nicht gefunden werden. Willst du deinen Benutzer mit einem bestehenden verlinken oder diesen als neuen Benutzer registrieren. Description: Externer Benutzer konnte nicht gefunden werden. Willst du deinen Benutzer mit einem bestehenden verlinken oder diesen als neuen Benutzer registrieren.
LinkButtonText: Verlinken LinkButtonText: Verlinken
@ -296,6 +297,7 @@ ExternalNotFoundOption:
English: English English: English
Italian: Italiano Italian: Italiano
French: Français French: Français
Chinese: 简体中文
Footer: Footer:
PoweredBy: Powered By PoweredBy: Powered By

View File

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

View File

@ -53,7 +53,7 @@ InitPassword:
CodeLabel: Code CodeLabel: Code
NewPasswordLabel: Nouveau mot de passe NewPasswordLabel: Nouveau mot de passe
NewPasswordConfirmLabel: Confirmer le mot de passe NewPasswordConfirmLabel: Confirmer le mot de passe
ResendButtonText: envoyer à nouveau ResendButtonText: code de réexpédition
NextButtonText: suivant NextButtonText: suivant
InitPasswordDone: InitPasswordDone:
@ -64,12 +64,12 @@ InitPasswordDone:
InitUser: InitUser:
Title: Activer l'utilisateur 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 CodeLabel: Code
NewPasswordLabel: Nouveau mot de passe NewPasswordLabel: Nouveau mot de passe
NewPasswordConfirmLabel: Confirmer le mot de passe NewPasswordConfirm: Confirmer le mot de passe
NextButtonText: Suivant NextButtonText: Suivant
ResendButtonText: envoyer à nouveau ResendButtonText: code de réexpédition
InitUserDone: InitUserDone:
Title: User Utilisateur activé Title: User Utilisateur activé
@ -78,16 +78,16 @@ InitUserDone:
CancelButtonText: annuler CancelButtonText: annuler
InitMFAPrompt: InitMFAPrompt:
Title: Configuration multifactorielle Title: Configuration à 2 facteurs
Description: Voulez-vous configurer l'authentification multifactorielle ? 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: OTP (mot de passe à usage unique) Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
Provider1: U2F (2e facteur universel) Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
NextButtonText: Suivant NextButtonText: Suivant
SkipButtonText: Passer SkipButtonText: Passer
InitMFAOTP: InitMFAOTP:
Title: Vérification multifactorielle Title: Vérification à deux facteurs
Description: Vérifier votre multifacteur. 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. 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 SecretLabel: Secret
CodeLabel: Code CodeLabel: Code
@ -95,48 +95,48 @@ InitMFAOTP:
CancelButtonText: Annuler CancelButtonText: Annuler
InitMFAU2F: InitMFAU2F:
Title: Configuration multifactorielle U2F / WebAuthN Title: Ajouter une clé de sécurité
Description: Ajoutez votre Token en fournissant un nom et en cliquant sur le bouton "Enregistrer un Token" ci-dessous. 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 du jeton / de la machine 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). 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. ErrorRetry: Réessayez, créez un nouveau défi ou choisissez une autre méthode.
InitMFADone: InitMFADone:
Title: Vérification multifactorielle effectuée Title: Clé de sécurité ajoutée
Description: La vérification multifactorielle a été effectuée avec succès. Le multifacteur doit être saisi à chaque connexion. 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 NextButtonText: Suivant
CancelButtonText: Annuler CancelButtonText: Annuler
MFAProvider: MFAProvider:
Provider0: OTP (Mot de passe à usage unique) Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
Provider1: U2F (2ne facteur universel) Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
ChooseOther: ou choisissez une autre option ChooseOther: ou choisissez une autre option
VerifyMFAOTP: VerifyMFAOTP:
Title: Vérifier le multifacteur Title: Vérifier 2-Facteurs
Description: Vérifier votre multifacteur Description: Vérifiez votre second facteur
CodeLabel: Code CodeLabel: Code
NextButtonText: Suivant NextButtonText: Suivant
VerifyMFAU2F: VerifyMFAU2F:
Title: Vérification multifactorielle Title: Vérifier 2-Facteurs
Description: Vérifiez votre jeton multifactoriel U2F / WebAuthN. 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). 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. ErrorRetry: Réessayer, créer une nouvelle demande ou choisir une autre méthode.
ValidateTokenButtonText: Valider le jeton ValidateTokenButtonText: Vérifier 2-Facteurs
Passwordless: Passwordless:
Title: Connexion sans mot de passe 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). 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. ErrorRetry: Réessayez, créez un nouveau défi ou choisissez une autre méthode.
LoginWithPwButtonText: Connectez-vous avec le mot de passe LoginWithPwButtonText: Connectez-vous avec le mot de passe
ValidateTokenButtonText: Valider le jeton ValidateTokenButtonText: Connexion sans mot de passe
PasswordlessPrompt: PasswordlessPrompt:
Title: Configuration sans mot de passe 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. 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 PasswordlessButtonText: Aller sans mot de passe
NextButtonText: suivant NextButtonText: suivant
@ -144,15 +144,15 @@ PasswordlessPrompt:
PasswordlessRegistration: PasswordlessRegistration:
Title: Configuration sans mot de passe 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. 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 du jeton / de la machine 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). 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. ErrorRetry: Réessayer, créer un nouveau défi ou choisir une autre méthode.
PasswordlessRegistrationDone: PasswordlessRegistrationDone:
Title: Configuration sans mot de passe 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. DescriptionClose: Vous pouvez maintenant fermer cette fenêtre.
NextButtonText: suivant NextButtonText: suivant
CancelButtonText: annuler CancelButtonText: annuler
@ -172,7 +172,7 @@ PasswordChangeDone:
NextButtonText: suivant NextButtonText: suivant
PasswordResetDone: 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. Description: Vérifiez votre e-mail pour réinitialiser votre mot de passe.
NextButtonText: suivant 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. Description: Nous vous avons envoyé un e-mail pour vérifier votre adresse. Veuillez saisir le code dans le formulaire ci-dessous.
CodeLabel: Code CodeLabel: Code
NextButtonText: suivant NextButtonText: suivant
ResendButtonText: envoyer à nouveau ResendButtonText: code de réexpédition
EmailVerificationDone: EmailVerificationDone:
Title: E-Mail Verification Title: E-Mail Verification
@ -239,6 +239,7 @@ ExternalRegistrationUserOverview:
English: English English: English
Italian: Italiano Italian: Italiano
French: Français French: Français
Chinese: 简体中文
TosAndPrivacyLabel: Termes et conditions TosAndPrivacyLabel: Termes et conditions
TosConfirm: J'accepte les TosConfirm: J'accepte les
TosLinkText: TOS TosLinkText: TOS
@ -282,7 +283,7 @@ LinkingUsersDone:
CancelButtonText: annuler CancelButtonText: annuler
NextButtonText: suivant NextButtonText: suivant
ExternalNotFoundOption: ExternalNotFound:
Title: Utilisateur externe Title: Utilisateur externe
Description: Utilisateur externe non trouvé. Voulez-vous lier votre utilisateur ou enregistrer automatiquement un nouvel utilisateur. Description: Utilisateur externe non trouvé. Voulez-vous lier votre utilisateur ou enregistrer automatiquement un nouvel utilisateur.
LinkButtonText: Lier LinkButtonText: Lier
@ -296,6 +297,7 @@ ExternalNotFoundOption:
English: English English: English
Italian: Italiano Italian: Italiano
French: Français French: Français
Chinese: 简体中文
Footer: Footer:
PoweredBy: Promulgué par PoweredBy: Promulgué par

View File

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

View File

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