fix: restrict languages in console (#6964)

* feat: return 404 or 409 if org reg disallowed

* fix: system limit permissions

* feat: add iam limits api

* feat: disallow public org registrations on default instance

* add integration test

* test: integration

* fix test

* docs: describe public org registrations

* avoid updating docs deps

* fix system limits integration test

* silence integration tests

* fix linting

* ignore strange linter complaints

* review

* improve reset properties naming

* redefine the api

* use restrictions aggregate

* test query

* simplify and test projection

* test commands

* fix unit tests

* move integration test

* support restrictions on default instance

* also test GetRestrictions

* self review

* lint

* abstract away resource owner

* fix tests

* configure supported languages

* fix allowed languages

* fix tests

* default lang must not be restricted

* preferred language must be allowed

* change preferred languages

* check languages everywhere

* lint

* test command side

* lint

* add integration test

* add integration test

* restrict supported ui locales

* lint

* lint

* cleanup

* lint

* allow undefined preferred language

* fix integration tests

* update main

* fix env var

* ignore linter

* ignore linter

* improve integration test config

* reduce cognitive complexity

* compile

* fix(console): switch back to saved language

* feat(API): get allowed languages

* fix(console): only make allowed languages selectable

* warn when editing not allowed languages

* check for duplicates

* remove useless restriction checks

* review

* revert restriction renaming

* fix language restrictions

* lint

* generate

* allow custom texts for supported langs for now

* fix tests

* cleanup

* cleanup

* cleanup

* lint

* unsupported preferred lang is allowed

* fix integration test

* allow unsupported preferred languages

* lint

* load languages for tests

* cleanup

* lint

* cleanup

* get allowed only on admin

* cleanup

* reduce flakiness on very limited postgres

* simplify langSvc

* refactor according to suggestions in pr

* lint

* set first allowed language as default

* selectionchange for language in msg texts

* initialize login texts

* init message texts

* lint

---------

Co-authored-by: peintnermax <[email protected]>
This commit is contained in:
Elio Bischof
2023-12-07 08:43:23 +00:00
committed by GitHub
co-authored by peintnermax
parent f09fbf8709
commit 8c85318fbd
46 changed files with 479 additions and 312 deletions
@@ -88,7 +88,7 @@
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
<mat-select formControlName="preferredLanguage">
<mat-option *ngFor="let language of languages" [value]="language">
<mat-option *ngFor="let language of langSvc.supported$ | async" [value]="language">
{{ 'LANGUAGES.' + language | translate }}
</mat-option>
</mat-select>
@@ -20,7 +20,7 @@ import { AdminService } from 'src/app/services/admin.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { supportedLanguages } from 'src/app/utils/language';
import { LanguagesService } from '../../services/languages.service';
@Component({
selector: 'cnsl-org-create',
@@ -46,7 +46,6 @@ export class OrgCreateComponent {
public pwdForm?: UntypedFormGroup;
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = supportedLanguages;
public policy?: PasswordComplexityPolicy.AsObject;
public usePassword: boolean = false;
@@ -60,6 +59,7 @@ export class OrgCreateComponent {
private _location: Location,
private fb: UntypedFormBuilder,
private mgmtService: ManagementService,
public langSvc: LanguagesService,
breadcrumbService: BreadcrumbService,
) {
const instanceBread = new Breadcrumb({
@@ -70,10 +70,6 @@ export class OrgCreateComponent {
breadcrumbService.setBreadcrumb([instanceBread]);
this.initForm();
this.adminService.getSupportedLanguages().then((supportedResp) => {
this.languages = supportedResp.languagesList;
});
}
public createSteps: number = 2;
@@ -91,7 +91,7 @@
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
<mat-select formControlName="preferredLanguage">
<mat-option *ngFor="let language of languages" [value]="language">
<mat-option *ngFor="let language of langSvc.supported$ | async" [value]="language">
{{ 'LANGUAGES.' + language | translate }}
</mat-option>
</mat-select>
@@ -2,7 +2,7 @@ import { Location } from '@angular/common';
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Subject, debounceTime } from 'rxjs';
import { Subject, debounceTime, Observable } from 'rxjs';
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Domain } from 'src/app/proto/generated/zitadel/org_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
@@ -13,7 +13,6 @@ import { ToastService } from 'src/app/services/toast.service';
import { CountryCallingCodesService, CountryPhoneCode } from 'src/app/services/country-calling-codes.service';
import { formatPhone } from 'src/app/utils/formatPhone';
import { supportedLanguages } from 'src/app/utils/language';
import {
containsLowerCaseValidator,
containsNumberValidator,
@@ -25,6 +24,7 @@ import {
phoneValidator,
requiredValidator,
} from '../../../modules/form-field/validators/validators';
import { LanguagesService } from '../../../services/languages.service';
@Component({
selector: 'cnsl-user-create',
@@ -34,7 +34,6 @@ import {
export class UserCreateComponent implements OnInit, OnDestroy {
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = supportedLanguages;
public selected: CountryPhoneCode | undefined = {
countryCallingCode: '1',
countryCode: 'US',
@@ -61,6 +60,7 @@ export class UserCreateComponent implements OnInit, OnDestroy {
private changeDetRef: ChangeDetectorRef,
private _location: Location,
private countryCallingCodesService: CountryCallingCodesService,
public langSvc: LanguagesService,
breadcrumbService: BreadcrumbService,
) {
breadcrumbService.setBreadcrumb([
@@ -69,7 +69,6 @@ export class UserCreateComponent implements OnInit, OnDestroy {
routerLink: ['/org'],
}),
]);
this.loading = true;
this.loadOrg();
this.mgmtService
@@ -88,10 +87,6 @@ export class UserCreateComponent implements OnInit, OnDestroy {
this.loading = false;
this.changeDetRef.detectChanges();
});
this.mgmtService.getSupportedLanguages().then((lang) => {
this.languages = lang.languagesList;
});
}
public close(): void {
@@ -19,7 +19,12 @@
<div class="max-width-container">
<cnsl-meta-layout>
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList" queryParam="id">
<cnsl-sidenav
[(ngModel)]="currentSetting"
[settingsList]="settingsList"
queryParam="id"
(ngModelChange)="settingChanged()"
>
<ng-container *ngIf="currentSetting === 'general'">
<cnsl-card
*ngIf="user && user.human && user.human.profile"
@@ -30,7 +35,7 @@
[showEditImage]="true"
[preferredLoginName]="user.preferredLoginName"
[genders]="genders"
[languages]="languages"
[languages]="(langSvc.supported$ | async) || []"
[username]="user.userName"
[user]="user.human"
[disabled]="false"
@@ -6,7 +6,7 @@ import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Buffer } from 'buffer';
import { Subscription, take } from 'rxjs';
import { from, Observable, Subscription, take } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { phoneValidator, requiredValidator } from 'src/app/modules/form-field/validators/validators';
import { InfoDialogComponent } from 'src/app/modules/info-dialog/info-dialog.component';
@@ -24,8 +24,8 @@ 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 { formatPhone } from 'src/app/utils/formatPhone';
import { supportedLanguages } from 'src/app/utils/language';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
import { LanguagesService } from '../../../../services/languages.service';
@Component({
selector: 'cnsl-auth-user-detail',
@@ -35,7 +35,6 @@ import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.c
export class AuthUserDetailComponent implements OnDestroy {
public user?: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = supportedLanguages;
private subscription: Subscription = new Subscription();
@@ -65,6 +64,7 @@ export class AuthUserDetailComponent implements OnDestroy {
];
public currentSetting: string | undefined = this.settingsList[0].id;
public loginPolicy?: LoginPolicy.AsObject;
private savedLanguage?: string;
constructor(
public translate: TranslateService,
@@ -77,10 +77,12 @@ export class AuthUserDetailComponent implements OnDestroy {
private mediaMatcher: MediaMatcher,
private _location: Location,
activatedRoute: ActivatedRoute,
public langSvc: LanguagesService,
) {
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.cleanupTranslation();
this.currentSetting = id;
}
});
@@ -97,10 +99,6 @@ export class AuthUserDetailComponent implements OnDestroy {
this.loading = true;
this.refreshUser();
this.userService.getSupportedLanguages().then((lang) => {
this.languages = lang.languagesList;
});
this.userService.getMyLoginPolicy().then((policy) => {
if (policy.policy) {
this.loginPolicy = policy.policy;
@@ -109,6 +107,7 @@ export class AuthUserDetailComponent implements OnDestroy {
}
private changeSelection(small: boolean): void {
this.cleanupTranslation();
if (small) {
this.currentSetting = undefined;
} else {
@@ -138,6 +137,7 @@ export class AuthUserDetailComponent implements OnDestroy {
}),
]);
}
this.savedLanguage = resp.user?.human?.profile?.preferredLanguage;
this.loading = false;
})
.catch((error) => {
@@ -147,9 +147,22 @@ export class AuthUserDetailComponent implements OnDestroy {
}
public ngOnDestroy(): void {
this.cleanupTranslation();
this.subscription.unsubscribe();
}
public settingChanged(): void {
this.cleanupTranslation();
}
private cleanupTranslation(): void {
if (this?.savedLanguage) {
this.translate.use(this?.savedLanguage);
} else {
this.translate.use(this.translate.defaultLang);
}
}
public changeUsername(): void {
const dialogRef = this.dialog.open(EditDialogComponent, {
data: {
@@ -193,6 +206,7 @@ export class AuthUserDetailComponent implements OnDestroy {
)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.savedLanguage = this.user?.human?.profile?.preferredLanguage;
this.refreshChanges$.emit();
})
.catch((error) => {
@@ -83,7 +83,7 @@
[preferredLoginName]="user.preferredLoginName"
[disabled]="(canWrite$ | async) === false"
[genders]="genders"
[languages]="languages"
[languages]="(langSvc.supported$ | async) || []"
[username]="user.userName"
[user]="user.human"
(submitData)="saveProfile($event)"
@@ -22,10 +22,11 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { formatPhone } from 'src/app/utils/formatPhone';
import { supportedLanguages } from 'src/app/utils/language';
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 { MachineSecretDialogComponent } from './machine-secret-dialog/machine-secret-dialog.component';
import { Observable } from 'rxjs';
import { LanguagesService } from '../../../../services/languages.service';
const GENERAL: SidenavSetting = { id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' };
const GRANTS: SidenavSetting = { id: 'grants', i18nKey: 'USER.SETTINGS.USERGRANTS' };
@@ -45,7 +46,6 @@ export class UserDetailComponent implements OnInit {
public user!: User.AsObject;
public metadata: Metadata.AsObject[] = [];
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = supportedLanguages;
public ChangeType: any = ChangeType;
@@ -76,6 +76,7 @@ export class UserDetailComponent implements OnInit {
private router: Router,
activatedRoute: ActivatedRoute,
private mediaMatcher: MediaMatcher,
public langSvc: LanguagesService,
breadcrumbService: BreadcrumbService,
) {
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
@@ -100,10 +101,6 @@ export class UserDetailComponent implements OnInit {
this.mediaMatcher.matchMedia(mediaq).onchange = (small) => {
this.changeSelection(small.matches);
};
this.mgmtUserService.getSupportedLanguages().then((lang) => {
this.languages = lang.languagesList;
});
}
private changeSelection(small: boolean): void {