feat(console): update deps, alternative hash function with fixed colors, use preferrenLoginName for hashing, fix iam write permissions, user img upload (#1846)

* chore(deps-dev): bump @types/jasmine from 3.6.9 to 3.7.7 in /console (#1824)

Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.6.9 to 3.7.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine)

---
updated-dependencies:
- dependency-name: "@types/jasmine"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump google-protobuf from 3.15.8 to 3.17.2 in /console (#1823)

Bumps [google-protobuf](https://github.com/protocolbuffers/protobuf) from 3.15.8 to 3.17.2.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.15.8...v3.17.2)

---
updated-dependencies:
- dependency-name: google-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump @angular/animations from 12.0.0 to 12.0.3 in /console (#1821)

Bumps [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) from 12.0.0 to 12.0.3.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/12.0.3/packages/animations)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump @angular/material from 12.0.0 to 12.0.3 in /console (#1819)

Bumps [@angular/material](https://github.com/angular/components) from 12.0.0 to 12.0.3.
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/12.0.3/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/12.0.0...12.0.3)

---
updated-dependencies:
- dependency-name: "@angular/material"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump prettier from 2.2.1 to 2.3.1 in /console (#1818)

Bumps [prettier](https://github.com/prettier/prettier) from 2.2.1 to 2.3.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.2.1...2.3.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump @angular/platform-browser-dynamic in /console (#1817)

Bumps [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) from 12.0.0 to 12.0.3.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/12.0.3/packages/platform-browser-dynamic)

---
updated-dependencies:
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump @types/node from 14.14.37 to 15.12.1 in /console (#1815)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.37 to 15.12.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump @angular/common from 12.0.0 to 12.0.3 in /console (#1814)

Bumps [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) from 12.0.0 to 12.0.3.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/12.0.3/packages/common)

---
updated-dependencies:
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump @angular/material-moment-adapter from 12.0.0 to 12.0.3 in /console (#1816)

* chore(deps): bump @angular/material-moment-adapter in /console

Bumps [@angular/material-moment-adapter](https://github.com/angular/components) from 12.0.0 to 12.0.3.
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/12.0.3/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/12.0.0...12.0.3)

---
updated-dependencies:
- dependency-name: "@angular/material-moment-adapter"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* sort

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <max@caos.ch>

* chore(deps-dev): bump @angular/language-service in /console (#1822)

Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 12.0.0 to 12.0.3.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/12.0.3/packages/language-service)

---
updated-dependencies:
- dependency-name: "@angular/language-service"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <max@caos.ch>

* image cropper

* fix: avatar bg colors, preferred username, login script

* lint

* membership color

* rem logs

* profile picture component

* pic comp

* dialog tirgger btn

* trigger dialog, styles

* lock

* interceptor for org, upload, remove

* tooltip

* lint

* stylelint

* generate same credentials of username as in login

* deletepic

* fix disable privatelabeling on missing feature, i18n

* lint

* stylelint

* block loading images if no feature

* lint

* optimize feature check

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Max Peintner 2021-06-11 11:15:04 +02:00 committed by GitHub
parent 2502f379d9
commit 1e77b8aeae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 11967 additions and 8113 deletions

16221
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,16 +10,16 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~12.0.0",
"@angular/animations": "~12.0.3",
"@angular/cdk": "~12.0.0",
"@angular/common": "~12.0.0",
"@angular/common": "~12.0.3",
"@angular/compiler": "~12.0.0",
"@angular/core": "~12.0.0",
"@angular/forms": "~12.0.0",
"@angular/material": "^12.0.0",
"@angular/material-moment-adapter": "^12.0.0",
"@angular/material": "^12.0.3",
"@angular/material-moment-adapter": "^12.0.3",
"@angular/platform-browser": "~12.0.0",
"@angular/platform-browser-dynamic": "~12.0.0",
"@angular/platform-browser-dynamic": "~12.0.3",
"@angular/router": "~12.0.0",
"@angular/service-worker": "~12.0.0",
"@grpc/grpc-js": "^1.3.2",
@ -33,10 +33,11 @@
"cors": "^2.8.5",
"file-saver": "^2.0.5",
"google-proto-files": "^2.4.0",
"google-protobuf": "^3.15.8",
"google-protobuf": "^3.17.2",
"grpc-web": "^1.2.1",
"libphonenumber-js": "^1.9.16",
"moment": "^2.29.1",
"ngx-image-cropper": "^3.3.5",
"ngx-color": "^7.0.0",
"ngx-quicklink": "^0.2.6",
"rxjs": "~6.6.7",
@ -49,10 +50,10 @@
"@angular-devkit/build-angular": "~12.0.0",
"@angular/cli": "~12.0.0",
"@angular/compiler-cli": "~12.0.0",
"@angular/language-service": "~12.0.0",
"@types/jasmine": "~3.6.9",
"@angular/language-service": "~12.0.3",
"@types/jasmine": "~3.7.7",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^14.14.37",
"@types/node": "^15.12.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.7.1",
"jasmine-spec-reporter": "~7.0.0",
@ -61,7 +62,7 @@
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"prettier": "^2.2.1",
"prettier": "^2.3.1",
"protractor": "~7.0.0",
"stylelint": "^13.10.0",
"stylelint-config-standard": "^20.0.0",

View File

@ -60,7 +60,7 @@
<div (clickOutside)="closeAccountCard()" class="icon-container">
<app-avatar
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount"
class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount" [forColor]="user?.preferredLoginName"
[name]="user.human.profile.displayName ? user.human.profile.displayName : (user.human.profile.firstName + ' '+ user.human.profile.lastName)"
[size]="38">
</app-avatar>

View File

@ -2,6 +2,7 @@
<app-avatar
*ngIf="user.human?.profile && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar"
[forColor]="user.preferredLoginName"
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[size]="80">
</app-avatar>
@ -14,8 +15,7 @@
<div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)">
<app-avatar *ngIf="session && session.displayName" class="small-avatar"
[name]="session.displayName ? session.displayName : ''" [size]="32">
<app-avatar *ngIf="session && session.displayName" class="small-avatar" [forColor]="session.loginName" [size]="32">
</app-avatar>
<div class="col">

View File

@ -6,69 +6,70 @@ import { AuthenticationService } from 'src/app/services/authentication.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Component({
selector: 'app-accounts-card',
templateUrl: './accounts-card.component.html',
styleUrls: ['./accounts-card.component.scss'],
selector: 'app-accounts-card',
templateUrl: './accounts-card.component.html',
styleUrls: ['./accounts-card.component.scss'],
})
export class AccountsCardComponent implements OnInit {
@Input() public user!: User.AsObject;
@Input() public iamuser: boolean = false;
@Input() public user!: User.AsObject;
@Input() public iamuser: boolean = false;
@Output() public close: EventEmitter<void> = new EventEmitter();
public sessions: Session.AsObject[] = [];
public loadingUsers: boolean = false;
constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) {
this.userService.listMyUserSessions().then(sessions => {
this.sessions = sessions.resultList;
const index = this.sessions.findIndex(user => user.loginName === this.user.preferredLoginName);
if (index > -1) {
this.sessions.splice(index, 1);
}
@Output() public close: EventEmitter<void> = new EventEmitter();
public sessions: Session.AsObject[] = [];
public loadingUsers: boolean = false;
constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) {
this.userService.listMyUserSessions().then(sessions => {
this.sessions = sessions.resultList;
console.log(sessions.resultList);
const index = this.sessions.findIndex(user => user.loginName === this.user.preferredLoginName);
if (index > -1) {
this.sessions.splice(index, 1);
}
this.loadingUsers = false;
}).catch(() => {
this.loadingUsers = false;
});
this.loadingUsers = false;
}).catch(() => {
this.loadingUsers = false;
});
}
public ngOnInit(): void {
this.loadingUsers = true;
}
public editUserProfile(): void {
this.router.navigate(['users/me']);
this.close.emit();
}
public closeCard(element: HTMLElement): void {
if (!element.classList.contains('dontcloseonclick')) {
this.close.emit();
}
}
public ngOnInit(): void {
this.loadingUsers = true;
public selectAccount(loginHint?: string): void {
const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: {
// prompt: 'select_account',
} as any,
};
if (loginHint) {
(configWithPrompt as any).customQueryParams['login_hint'] = loginHint;
}
this.authService.authenticate(configWithPrompt);
}
public editUserProfile(): void {
this.router.navigate(['users/me']);
this.close.emit();
}
public selectNewAccount(): void {
const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: {
prompt: 'login',
} as any,
};
this.authService.authenticate(configWithPrompt);
}
public closeCard(element: HTMLElement): void {
if (!element.classList.contains('dontcloseonclick')) {
this.close.emit();
}
}
public selectAccount(loginHint?: string): void {
const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: {
// prompt: 'select_account',
} as any,
};
if (loginHint) {
(configWithPrompt as any).customQueryParams['login_hint'] = loginHint;
}
this.authService.authenticate(configWithPrompt);
}
public selectNewAccount(): void {
const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: {
prompt: 'login',
} as any,
};
this.authService.authenticate(configWithPrompt);
}
public logout(): void {
this.authService.signout();
this.close.emit();
}
public logout(): void {
this.authService.signout();
this.close.emit();
}
}

View File

@ -1,64 +1,87 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-avatar',
templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.scss'],
selector: 'app-avatar',
templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.scss'],
})
export class AvatarComponent implements OnInit {
@Input() name: string = '';
@Input() credentials: string = '';
@Input() size: number = 24;
@Input() fontSize: number = 14;
@Input() active: boolean = false;
@Input() color: string = '';
constructor() { }
@Input() name: string = '';
@Input() credentials: string = '';
@Input() size: number = 24;
@Input() fontSize: number = 14;
@Input() active: boolean = false;
@Input() color: string = '';
@Input() forColor: string = '';
constructor() { }
ngOnInit(): void {
if (!this.credentials) {
const split: string[] = this.name.split(' ');
this.credentials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
if (!this.color) {
this.color = this.getColor(this.name);
}
}
if (this.size > 50) {
this.fontSize = 32;
}
ngOnInit(): void {
if (!this.credentials && this.forColor) {
this.credentials = this.getInitials(this.forColor);
if (!this.color) {
this.color = this.getColor(this.forColor || '');
}
}
getColor(userName: string): string {
const colors = [
'linear-gradient(40deg, #B44D51 30%, rgb(241,138,138))',
'linear-gradient(40deg, #B75073 30%, rgb(234,96,143))',
'linear-gradient(40deg, #84498E 30%, rgb(214,116,230))',
'linear-gradient(40deg, #705998 30%, rgb(163,131,220))',
'linear-gradient(40deg, #5C6598 30%, rgb(135,148,222))',
'linear-gradient(40deg, #7F90D3 30%, rgb(181,196,247))',
'linear-gradient(40deg, #3E93B9 30%, rgb(150,215,245))',
'linear-gradient(40deg, #3494A0 30%, rgb(71,205,222))',
'linear-gradient(40deg, #25716A 30%, rgb(58,185,173))',
'linear-gradient(40deg, #427E41 30%, rgb(97,185,96))',
'linear-gradient(40deg, #89A568 30%, rgb(176,212,133))',
'linear-gradient(40deg, #90924D 30%, rgb(187,189,98))',
'linear-gradient(40deg, #E2B032 30%, rgb(245,203,99))',
'linear-gradient(40deg, #C97358 30%, rgb(245,148,118))',
'linear-gradient(40deg, #6D5B54 30%, rgb(152,121,108))',
'linear-gradient(40deg, #6B7980 30%, rgb(134,163,177))',
];
let hash = 0;
if (userName.length === 0) {
return colors[hash];
}
for (let i = 0; i < userName.length; i++) {
// tslint:disable-next-line: no-bitwise
hash = userName.charCodeAt(i) + ((hash << 5) - hash);
// tslint:disable-next-line: no-bitwise
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
return colors[hash];
if (this.size > 50) {
this.fontSize = 32;
}
}
getInitials(fromName: string): string {
const username = fromName.split('@')[0];
let separator = '_';
if (username.includes('-')) {
separator = '-';
}
if (username.includes('.')) {
separator = '.';
}
const split = username.split(separator);
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
return initials;
}
getColor(userName: string): string {
const colors = [
'linear-gradient(40deg, #B44D51 30%, rgb(241,138,138))',
'linear-gradient(40deg, #B75073 30%, rgb(234,96,143))',
'linear-gradient(40deg, #84498E 30%, rgb(214,116,230))',
'linear-gradient(40deg, #705998 30%, rgb(163,131,220))',
'linear-gradient(40deg, #5C6598 30%, rgb(135,148,222))',
'linear-gradient(40deg, #7F90D3 30%, rgb(181,196,247))',
'linear-gradient(40deg, #3E93B9 30%, rgb(150,215,245))',
'linear-gradient(40deg, #3494A0 30%, rgb(71,205,222))',
'linear-gradient(40deg, #25716A 30%, rgb(58,185,173))',
'linear-gradient(40deg, #427E41 30%, rgb(97,185,96))',
'linear-gradient(40deg, #89A568 30%, rgb(176,212,133))',
'linear-gradient(40deg, #90924D 30%, rgb(187,189,98))',
'linear-gradient(40deg, #E2B032 30%, rgb(245,203,99))',
'linear-gradient(40deg, #C97358 30%, rgb(245,148,118))',
'linear-gradient(40deg, #6D5B54 30%, rgb(152,121,108))',
'linear-gradient(40deg, #6B7980 30%, rgb(134,163,177))',
];
let hash = 0;
if (userName.length === 0) {
return colors[hash];
}
hash = this.hashCode(userName);
return colors[hash % colors.length];
}
// tslint:disable
private hashCode(str: string, seed: number = 0): number {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
// tslint:enable
}

View File

@ -13,7 +13,7 @@
<div class="row">
<app-avatar matTooltip="{{ dayelement.editorDisplayName }}"
*ngIf="dayelement.editorDisplayName; else spacer" class="avatar"
[name]="dayelement.editorDisplayName" [size]="32">
[name]="dayelement.editorDisplayName" [size]="32" [forColor]="dayelement?.preferredLoginName">
</app-avatar>
<ng-template #spacer>
<div class="spacer"></div>

View File

@ -6,254 +6,256 @@ import { catchError, debounceTime, scan, take, takeUntil, tap } from 'rxjs/opera
import { ListMyUserChangesResponse } from 'src/app/proto/generated/zitadel/auth_pb';
import { Change } from 'src/app/proto/generated/zitadel/change_pb';
import {
ListAppChangesResponse,
ListOrgChangesResponse,
ListProjectChangesResponse,
ListUserChangesResponse,
ListAppChangesResponse,
ListOrgChangesResponse,
ListProjectChangesResponse,
ListUserChangesResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
export enum ChangeType {
MYUSER = 'myuser',
USER = 'user',
ORG = 'org',
PROJECT = 'project',
APP = 'app',
MYUSER = 'myuser',
USER = 'user',
ORG = 'org',
PROJECT = 'project',
APP = 'app',
}
export interface MappedChange {
key: string;
values: Array<{
data: any[];
dates: Timestamp.AsObject[];
editorId: string;
editorName: string;
eventTypes: Array<{ key: string; localizedMessage: string; }>;
sequences: number[];
}>;
key: string;
values: Array<{
data: any[];
dates: Timestamp.AsObject[];
editorId: string;
editorName: string;
eventTypes: Array<{ key: string; localizedMessage: string; }>;
sequences: number[];
}>;
}
type ListChanges = ListMyUserChangesResponse.AsObject |
ListUserChangesResponse.AsObject |
ListProjectChangesResponse.AsObject |
ListOrgChangesResponse.AsObject |
ListAppChangesResponse.AsObject;
ListUserChangesResponse.AsObject |
ListProjectChangesResponse.AsObject |
ListOrgChangesResponse.AsObject |
ListAppChangesResponse.AsObject;
@Component({
selector: 'app-changes',
templateUrl: './changes.component.html',
styleUrls: ['./changes.component.scss'],
selector: 'app-changes',
templateUrl: './changes.component.html',
styleUrls: ['./changes.component.scss'],
})
export class ChangesComponent implements OnInit, OnDestroy {
@Input() public changeType: ChangeType = ChangeType.USER;
@Input() public id: string = '';
@Input() public secId: string = '';
@Input() public sortDirectionAsc: boolean = true;
@Input() public refresh!: Observable<void>;
public bottom: boolean = false;
@Input() public changeType: ChangeType = ChangeType.USER;
@Input() public id: string = '';
@Input() public secId: string = '';
@Input() public sortDirectionAsc: boolean = true;
@Input() public refresh!: Observable<void>;
public bottom: boolean = false;
private _done: BehaviorSubject<any> = new BehaviorSubject(false);
private _loading: BehaviorSubject<any> = new BehaviorSubject(false);
private _data: BehaviorSubject<any> = new BehaviorSubject([]);
private _done: BehaviorSubject<any> = new BehaviorSubject(false);
private _loading: BehaviorSubject<any> = new BehaviorSubject(false);
private _data: BehaviorSubject<any> = new BehaviorSubject([]);
loading: Observable<boolean> = this._loading.asObservable();
public data!: Observable<MappedChange[]>;
public changes!: ListChanges;
private destroyed$: Subject<void> = new Subject();
constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {
loading: Observable<boolean> = this._loading.asObservable();
public data!: Observable<MappedChange[]>;
public changes!: ListChanges;
private destroyed$: Subject<void> = new Subject();
constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {
}
}
ngOnInit(): void {
ngOnInit(): void {
this.init();
if (this.refresh) {
this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => {
this.init();
if (this.refresh) {
this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => {
this.init();
});
}
});
}
}
ngOnDestroy(): void {
this.destroyed$.next();
}
public scrollHandler(e: any): void {
if (e === 'bottom') {
this.more();
}
}
public init(): void {
let first: Promise<ListChanges>;
switch (this.changeType) {
case ChangeType.MYUSER: first = this.authUserService.listMyUserChanges(20, 0);
break;
case ChangeType.USER: first = this.mgmtUserService.listUserChanges(this.id, 20, 0);
break;
case ChangeType.PROJECT: first = this.mgmtUserService.listProjectChanges(this.id, 20, 0);
break;
case ChangeType.ORG: first = this.mgmtUserService.listOrgChanges(20, 0);
break;
case ChangeType.APP: first = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, 0);
break;
}
ngOnDestroy(): void {
this.destroyed$.next();
this.mapAndUpdate(first);
// Create the observable array for consumption in components
this.data = this._data.asObservable().pipe(
scan((acc, val) => {
return false ? val.concat(acc) : acc.concat(val);
}));
}
private more(): void {
const cursor = this.getCursor();
let more: Promise<ListChanges>;
switch (this.changeType) {
case ChangeType.MYUSER: more = this.authUserService.listMyUserChanges(20, cursor);
break;
case ChangeType.USER: more = this.mgmtUserService.listUserChanges(this.id, 20, cursor);
break;
case ChangeType.PROJECT: more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor);
break;
case ChangeType.ORG: more = this.mgmtUserService.listOrgChanges(20, cursor);
break;
case ChangeType.APP: more = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, cursor);
break;
}
public scrollHandler(e: any): void {
if (e === 'bottom') {
this.more();
}
this.mapAndUpdate(more);
}
// Determines the snapshot to paginate query
private getCursor(): number {
const current = this._data.value;
if (current.length) {
const lastElementValues = current[current.length - 1].values;
const seq = lastElementValues[lastElementValues.length - 1].sequences;
return seq[seq.length - 1];
}
return 0;
}
public init(): void {
let first: Promise<ListChanges>;
switch (this.changeType) {
case ChangeType.MYUSER: first = this.authUserService.listMyUserChanges(20, 0);
break;
case ChangeType.USER: first = this.mgmtUserService.listUserChanges(this.id, 20, 0);
break;
case ChangeType.PROJECT: first = this.mgmtUserService.listProjectChanges(this.id, 20, 0);
break;
case ChangeType.ORG: first = this.mgmtUserService.listOrgChanges(20, 0);
break;
case ChangeType.APP: first = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, 0);
break;
}
// Maps the snapshot to usable format the updates source
private mapAndUpdate(col: Promise<ListChanges>): any {
if (this._done.value || this._loading.value) { return; }
this.mapAndUpdate(first);
// Map snapshot with doc ref (needed for cursor)
if (!this.bottom) {
// loading
this._loading.next(true);
// Create the observable array for consumption in components
this.data = this._data.asObservable().pipe(
scan((acc, val) => {
return false ? val.concat(acc) : acc.concat(val);
}));
return from(col).pipe(
take(1),
tap((res: ListChanges) => {
const values = res.resultList;
const mapped = this.mapChanges(values);
// update source with new values, done loading
// this._data.next(values);
this._data.next(mapped);
this._loading.next(false);
// no more values, mark done
if (!values.length) {
this._done.next(true);
}
}),
catchError(_ => {
this._loading.next(false);
this.bottom = true;
return of([]);
}),
).subscribe();
}
}
private more(): void {
const cursor = this.getCursor();
private mapChanges(changes: Change.AsObject[]): {
key: string; values: any[];
}[] {
const splitted: { [editorId: string]: any[]; } = {};
changes.forEach((change) => {
if (change.changeDate) {
const index = `${this.getDateString(change.changeDate)}`;
// `${this.getDateString(change.changeDate)}:${change.editorId}`;
let more: Promise<ListChanges>;
if (index) {
if (splitted[index]) {
const userData: any = {
editor: change.editorDisplayName,
editorId: change.editorId,
editorDisplayName: change.editorDisplayName,
switch (this.changeType) {
case ChangeType.MYUSER: more = this.authUserService.listMyUserChanges(20, cursor);
break;
case ChangeType.USER: more = this.mgmtUserService.listUserChanges(this.id, 20, cursor);
break;
case ChangeType.PROJECT: more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor);
break;
case ChangeType.ORG: more = this.mgmtUserService.listOrgChanges(20, cursor);
break;
case ChangeType.APP: more = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, cursor);
break;
}
this.mapAndUpdate(more);
}
// Determines the snapshot to paginate query
private getCursor(): number {
const current = this._data.value;
if (current.length) {
const lastElementValues = current[current.length - 1].values;
const seq = lastElementValues[lastElementValues.length - 1].sequences;
return seq[seq.length - 1];
}
return 0;
}
// Maps the snapshot to usable format the updates source
private mapAndUpdate(col: Promise<ListChanges>): any {
if (this._done.value || this._loading.value) { return; }
// Map snapshot with doc ref (needed for cursor)
if (!this.bottom) {
// loading
this._loading.next(true);
return from(col).pipe(
take(1),
tap((res: ListChanges) => {
const values = res.resultList;
const mapped = this.mapChanges(values);
// update source with new values, done loading
// this._data.next(values);
this._data.next(mapped);
this._loading.next(false);
// no more values, mark done
if (!values.length) {
this._done.next(true);
}
}),
catchError(_ => {
this._loading.next(false);
this.bottom = true;
return of([]);
}),
).subscribe();
}
}
private mapChanges(changes: Change.AsObject[]): {
key: string; values: any[];
}[] {
const splitted: { [editorId: string]: any[]; } = {};
changes.forEach((change) => {
if (change.changeDate) {
const index = `${this.getDateString(change.changeDate)}`;
// `${this.getDateString(change.changeDate)}:${change.editorId}`;
if (index) {
if (splitted[index]) {
const userData: any = {
editor: change.editorDisplayName,
editorId: change.editorId,
editorDisplayName: change.editorDisplayName,
dates: [change.changeDate],
// data: [change.data],
eventTypes: [change.eventType],
sequences: [change.sequence],
};
const lastIndex = splitted[index].length - 1;
if (lastIndex > -1 && splitted[index][lastIndex].editor === change.editorDisplayName) {
splitted[index][lastIndex].dates.push(change.changeDate);
// splitted[index][lastIndex].data.push(change.data);
splitted[index][lastIndex].eventTypes.push(change.eventType);
splitted[index][lastIndex].sequences.push(change.sequence);
} else {
splitted[index].push(userData);
}
} else {
splitted[index] = [
{
editor: change.editorDisplayName,
editorId: change.editorId,
editorDisplayName: change.editorDisplayName,
dates: [change.changeDate],
// data: [change.data],
eventTypes: [change.eventType],
sequences: [change.sequence],
},
];
}
}
dates: [change.changeDate],
// data: [change.data],
eventTypes: [change.eventType],
sequences: [change.sequence],
};
const lastIndex = splitted[index].length - 1;
if (lastIndex > -1 && splitted[index][lastIndex].editor === change.editorDisplayName) {
splitted[index][lastIndex].dates.push(change.changeDate);
// splitted[index][lastIndex].data.push(change.data);
splitted[index][lastIndex].eventTypes.push(change.eventType);
splitted[index][lastIndex].sequences.push(change.sequence);
} else {
splitted[index].push(userData);
}
});
const arr = Object.keys(splitted).map(key => {
return { key: key, values: splitted[key] };
});
} else {
splitted[index] = [
{
editor: change.editorDisplayName,
editorId: change.editorId,
editorDisplayName: change.editorDisplayName,
arr.sort((a, b) => {
return parseFloat(b.key) - parseFloat(a.key);
});
dates: [change.changeDate],
// data: [change.data],
eventTypes: [change.eventType],
sequences: [change.sequence],
},
];
}
}
}
});
const arr = Object.keys(splitted).map(key => {
return { key: key, values: splitted[key] };
});
return arr;
}
arr.sort((a, b) => {
return parseFloat(b.key) - parseFloat(a.key);
});
getDateString(ts: Timestamp.AsObject): string {
const date = new Date(ts.seconds * 1000 + ts.nanos / 1000 / 1000);
return date.getUTCFullYear() + this.pad(date.getUTCMonth() + 1) + this.pad(date.getUTCDate());
}
return arr;
}
getTimestampIndex(date: any): number {
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000 / 1000);
return ts.getTime();
}
getDateString(ts: Timestamp.AsObject): string {
const date = new Date(ts.seconds * 1000 + ts.nanos / 1000 / 1000);
return date.getUTCFullYear() + this.pad(date.getUTCMonth() + 1) + this.pad(date.getUTCDate());
}
pad(n: number): string {
return n < 10 ? '0' + n : n.toString();
}
getTimestampIndex(date: any): number {
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000 / 1000);
return ts.getTime();
}
// Order by ascending property value
valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.value.localeCompare(b.value);
}
pad(n: number): string {
return n < 10 ? '0' + n : n.toString();
}
// Order by descending property key
keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
}
// Order by ascending property value
// tslint:disable
valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.value.localeCompare(b.value);
};
// Order by descending property key
keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
};
// tslint:enable
}

View File

@ -14,7 +14,7 @@
[ngStyle]="{'z-index': 100 - i}">
<app-avatar
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
class="avatar dontcloseonclick"
class="avatar dontcloseonclick" [forColor]="member?.userName"[forColor]="member?.preferredLoginName"
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)"
[size]="32">
</app-avatar>

View File

@ -22,7 +22,7 @@
<mat-checkbox [disabled]="!canWrite" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
<app-avatar *ngIf="row?.displayName && row.firstName && row.lastName; else cog" class="avatar"
[name]="row.displayName" [size]="32">
[name]="row.displayName" [forColor]="row?.preferredLoginName" [size]="32">
</app-avatar>
<ng-template #cog>
<div class="sa-icon">

View File

@ -24,7 +24,7 @@
<div class="content" *ngIf="loginData">
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.allowUsernamePassword">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
</mat-slide-toggle>
@ -62,7 +62,7 @@
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.allowExternalIdp">
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
</mat-slide-toggle>
@ -79,7 +79,7 @@
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
@ -96,7 +96,7 @@
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.hidePasswordReset">
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
</mat-slide-toggle>

View File

@ -98,7 +98,7 @@ export class LoginPolicyComponent implements OnDestroy {
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
this.disabled = ((this.loginData as LoginPolicy.AsObject)?.isDefault) ?? false;
this.disabled = this.isDefault;
}
});
this.getIdps().then(resp => {

View File

@ -72,7 +72,6 @@
</div>
<span class="fill-space"></span>
<div class="img-wrapper" *ngIf="images['darkLogo']">
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme, Preview.PREVIEW)">remove_circle</mat-icon> -->
<img matTooltip="Current" class="curr" [src]="images['darkLogo']" alt="dark logo"/>
</div>
</div>
@ -83,7 +82,6 @@
</div>
<span class="fill-space"></span>
<div class="img-wrapper" *ngIf="images['logo']">
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme, Preview.PREVIEW)">remove_circle</mat-icon> -->
<img matTooltip="Current" class="curr" [src]="images['logo']" alt="logo"/>
</div>
</div>
@ -93,10 +91,10 @@
[class.hovering]="isHoveringOverDarkLogo">
<label class="file-label">
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFile.click();" />
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
<i class="icon las la-cloud-upload-alt"></i>
<span>{{isHoveringOverDarkLogo ? 'Release': 'Drop your Logo here'}}</span>
<span>{{isHoveringOverDarkLogo ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
</label>
</div>
</div>
@ -133,10 +131,10 @@
[class.hovering]="isHoveringOverDarkIcon">
<label class="file-label">
<input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFileIcon.click();" />
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFileIcon.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
<i class="icon las la-cloud-upload-alt"></i>
<span>{{isHoveringOverDarkIcon ? 'Release': 'Drop your Logo here'}}</span>
<span>{{isHoveringOverDarkIcon ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
</label>
</div>
</div>
@ -158,19 +156,19 @@
<ng-container *ngIf="theme==Theme.DARK">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color Dark" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event" name="Preview Primary Color Dark" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event" name="Primary Color" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event" name="Preview Warn Color Dark" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event" name="Warn Color" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event" name="Font Color Dark" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event" name="Font Color" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
</div>
</div>
</ng-container>
@ -178,15 +176,15 @@
<ng-container *ngIf="theme==Theme.LIGHT">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color Light" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event" name="Preview Primary Color Light" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color>
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event" name="Primary Color" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.WARN" name="Preview Warn Color Light" (previewChanged)="previewData.warnColor= $event" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
<cnsl-color [colorType]="ColorType.WARN" name="Warn Color" (previewChanged)="previewData.warnColor= $event" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
</div>
<div class="color">
@ -214,7 +212,7 @@
<span>ABC • abc • 123</span>
<span class="fill-space"></span>
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button>
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button>
</div>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"
@ -222,10 +220,10 @@
[class.hovering]="isHoveringOverFont">
<label class="file-label">
<input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFontFile.click();" />
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFontFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
<i class="icon las la-cloud-upload-alt"></i>
<span >{{isHoveringOverFont ? 'Release': 'Drop your Logo here'}}</span>
<span >{{isHoveringOverFont ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
</label>
</div>
</div>

View File

@ -203,14 +203,20 @@
align-items: center;
.btn {
cursor: pointer;
border-radius: 6px;
padding: .5rem 1rem;
background-color: inherit;
border: 1px solid if($is-dark-theme, #ffffff20, #000);
color: if($is-dark-theme, white, #000);
margin-bottom: .5rem;
}
.btn:not[disabled] {
cursor: pointer;
}
i {
font-size: 2.5rem;
}
span {
color: var(--grey);
}
}
.icon {
@ -274,6 +280,7 @@
max-width: 120px;
.dl-btn {
z-index: 2;
position: absolute;
top: -12px;
left: -12px;

View File

@ -3,7 +3,7 @@ import { Component, EventEmitter, Injector, OnDestroy, Type } from '@angular/cor
import { DomSanitizer } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { switchMap, take } from 'rxjs/operators';
import {
GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
@ -15,10 +15,13 @@ import {
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
UpdateCustomLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { AssetEndpoint, AssetService, AssetType } from 'src/app/services/asset.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageService } from 'src/app/services/storage.service';
import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
@ -45,6 +48,8 @@ export enum ColorType {
BACKGROUNDLIGHT,
}
const ORG_STORAGE_KEY = 'organization';
@Component({
selector: 'app-private-labeling-policy',
templateUrl: './private-labeling-policy.component.html',
@ -86,14 +91,23 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
public refreshPreview: EventEmitter<void> = new EventEmitter();
public loadingImages: boolean = false;
private org!: Org.AsObject;
constructor(
private authService: GrpcAuthService,
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
private assetService: AssetService,
private sanitizer: DomSanitizer,
private storageService: StorageService,
) {
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
if (org) {
this.org = org;
}
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
@ -134,17 +148,17 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
if (theme === Theme.DARK) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKLOGO, formData));
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKLOGO, formData, this.org.id));
case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKLOGO, formData));
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKLOGO, formData, this.org.id));
}
}
if (theme === Theme.LIGHT) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTLOGO, formData));
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTLOGO, formData, this.org.id));
case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMLOGO, formData));
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMLOGO, formData, this.org.id));
}
}
@ -158,9 +172,9 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
formData.append('file', file);
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData));
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData, this.org.id));
case PolicyComponentServiceType.ADMIN:
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData));
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData, this.org.id));
}
}
}
@ -257,20 +271,20 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
if (theme === Theme.DARK) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKICON, formData));
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKICON, formData, this.org.id));
break;
case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKICON, formData));
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKICON, formData, this.org.id));
break;
}
}
if (theme === Theme.LIGHT) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTICON, formData));
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTICON, formData, this.org.id));
break;
case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMICON, formData));
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMICON, formData, this.org.id));
break;
}
}
@ -291,7 +305,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
}
private handleUploadPromise(task: Promise<any>): Promise<any> {
return task.then(() => {
const enhTask = task.then(() => {
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
setTimeout(() => {
this.loadingImages = true;
@ -304,37 +318,53 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
});
}, 1000);
}).catch(error => this.toast.showError(error));
if (this.serviceType === PolicyComponentServiceType.MGMT && ((this.previewData as LabelPolicy.AsObject).isDefault)) {
return this.savePolicy().then(() => enhTask);
} else {
return enhTask;
}
}
public fetchData(): void {
this.loading = true;
this.getPreviewData().then(data => {
console.log('preview', data);
this.loadingImages = true;
this.authService.canUseFeature(['label_policy.private_label']).pipe(take(1)).subscribe((canUse) => {
this.getPreviewData().then(data => {
console.log('preview', data);
if (data.policy) {
this.previewData = data.policy;
this.loading = false;
if (data.policy) {
this.previewData = data.policy;
this.loading = false;
this.loadPreviewImages();
}
}).catch(error => {
this.toast.showError(error);
if ((canUse === true && this.serviceType === PolicyComponentServiceType.MGMT) ||
this.serviceType === PolicyComponentServiceType.ADMIN) {
this.loadingImages = true;
this.loadPreviewImages();
}
}
}).catch(error => {
this.toast.showError(error);
});
this.getData().then(data => {
console.log('data', data);
if (data.policy) {
this.data = data.policy;
this.loading = false;
if ((canUse === true && this.serviceType === PolicyComponentServiceType.MGMT) ||
this.serviceType === PolicyComponentServiceType.ADMIN) {
// this.loadingImages = true;
this.loadImages();
}
}
}).catch(error => {
this.toast.showError(error);
});
});
this.getData().then(data => {
console.log('data', data);
if (data.policy) {
this.data = data.policy;
this.loading = false;
this.loadImages();
}
}).catch(error => {
this.toast.showError(error);
});
}
private loadImages(): void {
@ -451,7 +481,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
}
private loadAsset(imagekey: string, url: string): Promise<any> {
return this.assetService.load(`${url}`).then(data => {
return this.assetService.load(`${url}`, this.org.id).then(data => {
const objectURL = URL.createObjectURL(data);
this.images[imagekey] = this.sanitizer.bypassSecurityTrustUrl(objectURL);
this.refreshPreview.emit();
@ -462,7 +492,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.service.resetLabelPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
@ -473,14 +503,14 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
}
}
public savePolicy(): void {
public savePolicy(): Promise<any> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.previewData as LabelPolicy.AsObject).isDefault) {
const req0 = new AddCustomLabelPolicyRequest();
this.overwriteValues(req0);
(this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
return (this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch((error: HttpErrorResponse) => {
this.toast.showError(error);
@ -489,22 +519,20 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
const req1 = new UpdateCustomLabelPolicyRequest();
this.overwriteValues(req1);
(this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
return (this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
const req = new UpdateLabelPolicyRequest();
this.overwriteValues(req);
(this.service as AdminService).updateLabelPolicy(req).then(() => {
return (this.service as AdminService).updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}

View File

@ -22,8 +22,8 @@ export const IAM_LOGIN_POLICY_LINK = {
};
export const IAM_PRIVATELABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
i18nTitle: 'POLICY.PRIVATELABELING.TITLE',
i18nDesc: 'POLICY.PRIVATELABELING.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.PRIVATELABEL],
withRole: ['iam.policy.read'],
};
@ -51,8 +51,8 @@ export const ORG_LOGIN_POLICY_LINK = {
export const ORG_PRIVATELABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
i18nTitle: 'POLICY.PRIVATELABELING.TITLE',
i18nDesc: 'POLICY.PRIVATELABELING.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.PRIVATELABEL],
withRole: ['policy.read'],
};

View File

@ -57,7 +57,7 @@
<div class="circle">
<app-avatar
*ngIf="user.human && user.human.displayName && user.human?.firstName && user.human?.lastName; else cog"
class="avatar" [name]="user.human.displayName" [size]="32">
class="avatar" [name]="user.human.displayName" [forColor]="user?.preferredLoginName" [size]="32">
</app-avatar>
<ng-template #cog>
<div class="sa-icon">

View File

@ -31,7 +31,7 @@
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
<app-avatar
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
class="avatar" [name]="row.displayName" [size]="32">
class="avatar" [name]="row.displayName" [forColor]="row?.preferredLoginName" [size]="32">
</app-avatar>
</mat-checkbox>
</td>

View File

@ -3,6 +3,7 @@
<app-avatar [routerLink]="['/users/me']"
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar"
[forColor]="user?.preferredLoginName"
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[size]="100">
</app-avatar>

View File

@ -103,7 +103,6 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
this.grid = false;
}
this.dataSource.data = this.grantedProjectList;
console.log(resp.resultList);
this.loadingSubject.next(false);
}).catch(error => {

View File

@ -13,190 +13,189 @@ import { StorageKey, StorageService } from 'src/app/services/storage.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-user-grant-create',
templateUrl: './user-grant-create.component.html',
styleUrls: ['./user-grant-create.component.scss'],
selector: 'app-user-grant-create',
templateUrl: './user-grant-create.component.html',
styleUrls: ['./user-grant-create.component.scss'],
})
export class UserGrantCreateComponent implements OnDestroy {
public context!: UserGrantContext;
public context!: UserGrantContext;
public org!: Org.AsObject;
public userId: string = '';
public org!: Org.AsObject;
public userId: string = '';
public projectId: string = '';
public project!: GrantedProject.AsObject | Project.AsObject;
public projectId: string = '';
public project!: GrantedProject.AsObject | Project.AsObject;
public grantId: string = '';
public rolesList: string[] = [];
public grantId: string = '';
public rolesList: string[] = [];
public STEPS: number = 2; // project, roles
public currentCreateStep: number = 1;
public STEPS: number = 2; // project, roles
public currentCreateStep: number = 1;
public filterValue: string = '';
public filterValue: string = '';
private subscription: Subscription = new Subscription();
private subscription: Subscription = new Subscription();
public UserGrantContext: any = UserGrantContext;
public UserGrantContext: any = UserGrantContext;
public grantedRoleKeysList: string[] = [];
public grantedRoleKeysList: string[] = [];
public user!: User.AsObject;
public UserTarget: any = UserTarget;
public user!: User.AsObject;
public UserTarget: any = UserTarget;
constructor(
private userService: ManagementService,
private toast: ToastService,
private _location: Location,
private route: ActivatedRoute,
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private storage: StorageService,
) {
this.subscription = this.route.params.subscribe((params: Params) => {
const { projectid, grantid, userid } = params;
this.context = UserGrantContext.NONE;
constructor(
private userService: ManagementService,
private toast: ToastService,
private _location: Location,
private route: ActivatedRoute,
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private storage: StorageService,
) {
this.subscription = this.route.params.subscribe((params: Params) => {
const { projectid, grantid, userid } = params;
this.context = UserGrantContext.NONE;
this.projectId = projectid;
this.grantId = grantid;
this.userId = userid;
this.projectId = projectid;
this.grantId = grantid;
this.userId = userid;
if (this.projectId && !this.grantId) {
this.context = UserGrantContext.OWNED_PROJECT;
if (this.projectId && !this.grantId) {
this.context = UserGrantContext.OWNED_PROJECT;
this.mgmtService.getProjectByID(this.projectId).then(resp => {
if (resp.project) {
this.project = resp.project;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.projectId && this.grantId) {
this.context = UserGrantContext.GRANTED_PROJECT;
this.mgmtService.getGrantedProjectByID(this.projectId, this.grantId).then(resp => {
if (resp.grantedProject) {
this.project = resp.grantedProject;
}
if (resp.grantedProject?.grantedRoleKeysList) {
this.grantedRoleKeysList = resp.grantedProject?.grantedRoleKeysList;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.userId) {
this.context = UserGrantContext.USER;
this.mgmtService.getUserByID(this.userId).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch((error: any) => {
this.toast.showError(error);
});
}
this.mgmtService.getProjectByID(this.projectId).then(resp => {
if (resp.project) {
this.project = resp.project;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.projectId && this.grantId) {
this.context = UserGrantContext.GRANTED_PROJECT;
this.mgmtService.getGrantedProjectByID(this.projectId, this.grantId).then(resp => {
if (resp.grantedProject) {
this.project = resp.grantedProject;
}
if (resp.grantedProject?.grantedRoleKeysList) {
this.grantedRoleKeysList = resp.grantedProject?.grantedRoleKeysList;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.userId) {
this.context = UserGrantContext.USER;
this.mgmtService.getUserByID(this.userId).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch((error: any) => {
this.toast.showError(error);
});
}
});
const temporg = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (temporg) {
this.org = temporg;
const temporg = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (temporg) {
this.org = temporg;
}
}
public close(): void {
this._location.back();
}
public addGrant(): void {
switch (this.context) {
case UserGrantContext.OWNED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.GRANTED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
this.grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.USER:
let grantId;
if ((this.project as GrantedProject.AsObject)?.grantId) {
grantId = (this.project as GrantedProject.AsObject).grantId;
}
}
public close(): void {
this._location.back();
}
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.NONE:
let tempGrantId;
public addGrant(): void {
switch (this.context) {
case UserGrantContext.OWNED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.GRANTED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
this.grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.USER:
let grantId;
if ((this.project as GrantedProject.AsObject)?.grantId) {
grantId = (this.project as GrantedProject.AsObject).grantId;
}
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.NONE:
console.log('none');
let tempGrantId;
if ((this.project as GrantedProject.AsObject)?.grantId) {
tempGrantId = (this.project as GrantedProject.AsObject).grantId;
}
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
tempGrantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
if ((this.project as GrantedProject.AsObject)?.grantId) {
tempGrantId = (this.project as GrantedProject.AsObject).grantId;
}
}
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
this.project = project;
this.projectId = project.id || project.projectId;
this.grantedRoleKeysList = project.grantedRoleKeysList ?? [];
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
tempGrantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
}
}
public selectUser(user: User.AsObject): void {
this.userId = user.id;
}
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
this.project = project;
this.projectId = project.id || project.projectId;
public selectRoles(roles: Role.AsObject[]): void {
this.rolesList = roles.map(role => role.key);
}
this.grantedRoleKeysList = project.grantedRoleKeysList ?? [];
}
public next(): void {
this.currentCreateStep++;
}
public selectUser(user: User.AsObject): void {
this.userId = user.id;
}
public previous(): void {
this.currentCreateStep--;
}
public selectRoles(roles: Role.AsObject[]): void {
this.rolesList = roles.map(role => role.key);
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
public next(): void {
this.currentCreateStep++;
}
public previous(): void {
this.currentCreateStep--;
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}

View File

@ -28,7 +28,7 @@
</app-card>
<app-card *ngIf="user && user.human?.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
<app-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
</app-detail-form>
</app-card>

View File

@ -11,196 +11,197 @@ import { ToastService } from 'src/app/services/toast.service';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
@Component({
selector: 'app-auth-user-detail',
templateUrl: './auth-user-detail.component.html',
styleUrls: ['./auth-user-detail.component.scss'],
selector: 'app-auth-user-detail',
templateUrl: './auth-user-detail.component.html',
styleUrls: ['./auth-user-detail.component.scss'],
})
export class AuthUserDetailComponent implements OnDestroy {
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
private subscription: Subscription = new Subscription();
private subscription: Subscription = new Subscription();
public loading: boolean = false;
public loading: boolean = false;
public copied: string = '';
public copied: string = '';
public ChangeType: any = ChangeType;
public userLoginMustBeDomain: boolean = false;
public UserState: any = UserState;
public ChangeType: any = ChangeType;
public userLoginMustBeDomain: boolean = false;
public UserState: any = UserState;
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
constructor(
public translate: TranslateService,
private toast: ToastService,
public userService: GrpcAuthService,
private dialog: MatDialog,
) {
this.loading = true;
constructor(
public translate: TranslateService,
private toast: ToastService,
public userService: GrpcAuthService,
private dialog: MatDialog,
) {
this.loading = true;
this.refreshUser();
}
refreshUser(): void {
this.refreshChanges$.emit();
this.userService.getMyUser().then(resp => {
if (resp.user) {
this.user = resp.user;
console.log(resp.user);
}
this.loading = false;
}).catch(error => {
this.toast.showError(error);
this.loading = false;
});
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.profile = profileData;
this.userService
.updateMyProfile(
this.user.human.profile?.firstName,
this.user.human.profile?.lastName,
this.user.human.profile?.nickName,
this.user.human.profile?.displayName,
this.user.human.profile?.preferredLanguage,
this.user.human.profile?.gender,
)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public saveEmail(email: string): void {
this.userService
.setMyEmail(email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.human) {
const mailToSet = new Email();
mailToSet.setEmail(email);
this.user.human.email = mailToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
public enteredPhoneCode(code: string): void {
this.userService.verifyMyPhone(code).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
this.refreshUser();
}).catch(error => {
this.toast.showError(error);
});
}
public changedLanguage(language: string): void {
this.translate.use(language);
}
public resendPhoneVerification(): void {
this.userService.resendMyPhoneVerification().then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public resendEmailVerification(): void {
this.userService.resendMyEmailVerification().then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public deletePhone(): void {
this.userService.removeMyPhone().then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human?.phone) {
const phone = new Phone();
this.user.human.phone = phone.toObject();
this.refreshUser();
}
}
}).catch(error => {
this.toast.showError(error);
});
}
refreshUser(): void {
this.refreshChanges$.emit();
this.userService.getMyUser().then(resp => {
if (resp.user) {
this.user = resp.user;
}
this.loading = false;
}).catch(error => {
this.toast.showError(error);
this.loading = false;
});
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.profile = profileData;
this.userService
.updateMyProfile(
this.user.human.profile?.firstName,
this.user.human.profile?.lastName,
this.user.human.profile?.nickName,
this.user.human.profile?.displayName,
this.user.human.profile?.preferredLanguage,
this.user.human.profile?.gender,
)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public saveEmail(email: string): void {
this.userService
.setMyEmail(email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.human) {
const mailToSet = new Email();
mailToSet.setEmail(email);
this.user.human.email = mailToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
public enteredPhoneCode(code: string): void {
this.userService.verifyMyPhone(code).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
public savePhone(phone: string): void {
if (this.user.human) {
this.userService
.setMyPhone(phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
const phoneToSet = new Phone();
phoneToSet.setPhone(phone);
this.user.human.phone = phoneToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
this.toast.showError(error);
});
}
}
public changedLanguage(language: string): void {
this.translate.use(language);
}
public resendPhoneVerification(): void {
this.userService.resendMyPhoneVerification().then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
public openEditDialog(type: EditDialogType): void {
switch (type) {
case EditDialogType.PHONE:
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone,
type: type,
},
width: '400px',
});
}
public resendEmailVerification(): void {
this.userService.resendMyEmailVerification().then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp) {
this.savePhone(resp);
}
});
}
public deletePhone(): void {
this.userService.removeMyPhone().then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human?.phone) {
const phone = new Phone();
this.user.human.phone = phone.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
break;
case EditDialogType.EMAIL:
const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email,
type: type,
},
width: '400px',
});
dialogRefEmail.afterClosed().subscribe(resp => {
if (resp) {
this.saveEmail(resp);
}
});
break;
}
public savePhone(phone: string): void {
if (this.user.human) {
this.userService
.setMyPhone(phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
const phoneToSet = new Phone();
phoneToSet.setPhone(phone);
this.user.human.phone = phoneToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
}
public openEditDialog(type: EditDialogType): void {
switch (type) {
case EditDialogType.PHONE:
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone,
type: type,
},
width: '400px',
});
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp) {
this.savePhone(resp);
}
});
break;
case EditDialogType.EMAIL:
const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email,
type: type,
},
width: '400px',
});
dialogRefEmail.afterClosed().subscribe(resp => {
if (resp) {
this.saveEmail(resp);
}
});
break;
}
}
}
}

View File

@ -1,9 +1,22 @@
<form [formGroup]="profileForm" *ngIf="profileForm" (ngSubmit)="submitForm()">
<div class="content">
<div class="user-form-content">
<div class="user-form-content inner">
<button class="camera-wrapper" type="button" (click)="showEditImage ? openUploadDialog() : null">
<div class="i-wrapper" *ngIf="showEditImage">
<i class="las la-camera"></i>
</div>
<img class="pic" [src]="profilePic" *ngIf="profilePic"/>
<app-avatar
*ngIf="!profilePic && user && user.profile?.displayName && user.profile?.firstName && user?.profile.lastName"
class="avatar" [name]="user.profile?.displayName" [forColor]="preferredLoginName" [size]="80">
</app-avatar>
</button>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="userName" />
</cnsl-form-field>
</div>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="firstName" />

View File

@ -1,10 +1,60 @@
.content {
.user-form-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 -.5rem;
&.inner {
margin: 0;
width: 100%;
}
.camera-wrapper {
margin: 0 .5rem;
position: relative;
border-radius: 50%;
padding: 0;
border: none;
display: flex;
align-items: center;
justify-content: center;
background: none;
cursor: pointer;
transition: all .3s ease;
.i-wrapper {
border-radius: 50%;
background-color: #00000050;
position: absolute;
top: 0;
z-index: 1;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
i {
font-size: 3rem;
color: white;
}
}
&:hover {
.i-wrapper {
background-color: #00000080;
}
}
.pic {
height: 80px;
width: 80px;
object-fit: contain;
border-radius: 50%;
}
}
.formfield {
flex: 1 1 33%;
margin: 0 .5rem;

View File

@ -1,92 +1,131 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { DomSanitizer } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import { Gender, Human, User } from 'src/app/proto/generated/zitadel/user_pb';
import { AssetService } from 'src/app/services/asset.service';
import { ToastService } from 'src/app/services/toast.service';
import { ProfilePictureComponent } from './profile-picture/profile-picture.component';
@Component({
selector: 'app-detail-form',
templateUrl: './detail-form.component.html',
styleUrls: ['./detail-form.component.scss'],
selector: 'app-detail-form',
templateUrl: './detail-form.component.html',
styleUrls: ['./detail-form.component.scss'],
})
export class DetailFormComponent implements OnDestroy, OnChanges {
@Input() public username!: string;
@Input() public user!: Human.AsObject;
@Input() public disabled: boolean = false;
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
@Input() public showEditImage: boolean = false;
@Input() public preferredLoginName: string = '';
@Input() public username!: string;
@Input() public user!: Human.AsObject;
@Input() public disabled: boolean = false;
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
public profileForm!: FormGroup;
public profilePic: any = null;
public profileForm!: FormGroup;
private sub: Subscription = new Subscription();
private sub: Subscription = new Subscription();
constructor(private fb: FormBuilder) {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
}
constructor(
private fb: FormBuilder,
private dialog: MatDialog,
private assetService: AssetService,
private toast: ToastService,
private sanitizer: DomSanitizer,
) {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
public ngOnChanges(): void {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
this.loadAvatar();
}
this.profileForm.patchValue({ userName: this.username, ...this.user.profile });
public ngOnChanges(): void {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
if (this.preferredLanguage) {
this.sub = this.preferredLanguage.valueChanges.subscribe(value => {
this.changedLanguage.emit(value);
});
}
}
this.profileForm.patchValue({ userName: this.username, ...this.user.profile });
public ngOnDestroy(): void {
this.sub.unsubscribe();
if (this.preferredLanguage) {
this.sub = this.preferredLanguage.valueChanges.subscribe(value => {
this.changedLanguage.emit(value);
});
}
}
public submitForm(): void {
this.submitData.emit(this.profileForm.value);
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public get userName(): AbstractControl | null {
return this.profileForm.get('userName');
}
public submitForm(): void {
this.submitData.emit(this.profileForm.value);
}
public get firstName(): AbstractControl | null {
return this.profileForm.get('firstName');
}
public get lastName(): AbstractControl | null {
return this.profileForm.get('lastName');
}
public get nickName(): AbstractControl | null {
return this.profileForm.get('nickName');
}
public get displayName(): AbstractControl | null {
return this.profileForm.get('displayName');
}
public get gender(): AbstractControl | null {
return this.profileForm.get('gender');
}
public get preferredLanguage(): AbstractControl | null {
return this.profileForm.get('preferredLanguage');
}
public openUploadDialog(): void {
const dialogRef = this.dialog.open(ProfilePictureComponent, {
data: {
profilePic: this.profilePic,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
}
});
}
public loadAvatar(): Promise<any> {
return this.assetService.load(`users/me/avatar`).then(data => {
const objectURL = URL.createObjectURL(data);
this.profilePic = this.sanitizer.bypassSecurityTrustUrl(objectURL);
}).catch(error => {
this.toast.showError(error);
});
}
public get userName(): AbstractControl | null {
return this.profileForm.get('userName');
}
public get firstName(): AbstractControl | null {
return this.profileForm.get('firstName');
}
public get lastName(): AbstractControl | null {
return this.profileForm.get('lastName');
}
public get nickName(): AbstractControl | null {
return this.profileForm.get('nickName');
}
public get displayName(): AbstractControl | null {
return this.profileForm.get('displayName');
}
public get gender(): AbstractControl | null {
return this.profileForm.get('gender');
}
public get preferredLanguage(): AbstractControl | null {
return this.profileForm.get('preferredLanguage');
}
}

View File

@ -4,28 +4,38 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { ImageCropperModule } from 'ngx-image-cropper';
import { DropzoneModule } from 'src/app/directives/dropzone/dropzone.module';
import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { DetailFormComponent } from './detail-form.component';
import { ProfilePictureComponent } from './profile-picture/profile-picture.component';
@NgModule({
declarations: [
DetailFormComponent,
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule,
MatSelectModule,
MatButtonModule,
MatIconModule,
TranslateModule,
InputModule,
],
exports: [
DetailFormComponent,
],
declarations: [
DetailFormComponent,
ProfilePictureComponent,
],
imports: [
DropzoneModule,
AvatarModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
ImageCropperModule,
TranslateModule,
MatSelectModule,
MatButtonModule,
MatTooltipModule,
MatIconModule,
TranslateModule,
InputModule,
],
exports: [
DetailFormComponent,
],
})
export class DetailFormModule { }

View File

@ -0,0 +1,39 @@
<span class="title" mat-dialog-title>{{'USER.PROFILE.AVATAR.UPLOADTITLE' | translate}}</span>
<div mat-dialog-content>
<p class="desc">{{'USER.PROFILE.AVATAR.CURRENT' | translate}}</p>
<div class="current-pic-wrapper">
<img class="pic" [src]="data.profilePic" *ngIf="data.profilePic"/>
<span class="fill-space"></span>
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDrop($event)">
<button class="btn" mat-stroked-button type="button" (click)="selectedFile.click();">{{'USER.PROFILE.AVATAR.UPLOADBTN' | translate}}</button>
<button *ngIf="data.profilePic" matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" (click)="deletePic()" mat-icon-button><mat-icon>remove_circle</mat-icon></button>
</div>
<ng-container *ngIf="imageChangedEvent">
<p class="desc">{{'USER.PROFILE.AVATAR.PREVIEW' | translate}}</p>
<div class="cropped-preview" *ngIf="croppedImage">
<img class="pic" [src]="croppedImage"/>
<button color="primary" mat-raised-button (click)="upload()">{{'USER.PROFILE.AVATAR.UPLOAD' | translate}}</button>
</div>
<p class="error" *ngIf="showCropperError">{{'USER.PROFILE.AVATAR.CROPPERERROR' | translate}}</p>
<image-cropper
class="cropper"
[imageChangedEvent]="imageChangedEvent"
[maintainAspectRatio]="true"
[aspectRatio]="4 / 4"
[roundCropper]="true"
[autoCrop]="true"
(imageCropped)="imageCropped($event)"
(loadImageFailed)="loadImageFailed()"
></image-cropper>
</ng-container>
</div>
<div mat-dialog-actions class="action">
<button color="primary" mat-raised-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
</button>
</div>

View File

@ -0,0 +1,68 @@
.title {
font-size: 1.2rem;
margin-top: 0;
margin-bottom: 1rem;
display: block;
}
.desc {
color: var(--grey);
font-size: 14px;
}
.current-pic-wrapper {
display: flex;
align-items: center;
margin-bottom: 1rem;
.fill-space {
flex: 1;
}
.pic {
height: 80px;
width: 80px;
object-fit: contain;
border-radius: 50%;
background-color: #00000030;
}
}
.error {
color: #f44336;
font-size: 14px;
}
.cropped-preview {
display: flex;
align-items: center;
width: 100%;
justify-content: space-between;
.pic {
height: 80px;
width: 80px;
object-fit: contain;
border-radius: 50%;
background-color: #00000030;
}
}
.cropper {
margin: 1rem 0;
width: auto;
border-radius: .5rem;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: .5rem;
}
}
:root {
--cropper-outline-color: black !important;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfilePictureComponent } from './profile-picture.component';
describe('ProfilePictureComponent', () => {
let component: ProfilePictureComponent;
let fixture: ComponentFixture<ProfilePictureComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ProfilePictureComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProfilePictureComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,99 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ImageCroppedEvent } from 'ngx-image-cropper';
import { AssetService } from 'src/app/services/asset.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'cnsl-profile-picture',
templateUrl: './profile-picture.component.html',
styleUrls: ['./profile-picture.component.scss'],
})
export class ProfilePictureComponent implements OnInit {
public isHovering: boolean = false;
public imageChangedEvent: any = '';
public imageChangedFormat: string = '';
public croppedImage: any = '';
public showCropperError: boolean = false;
constructor(
private authService: GrpcAuthService,
public dialogRef: MatDialogRef<ProfilePictureComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private toast: ToastService,
private assetService: AssetService) { }
public ngOnInit(): void {
}
public toggleHover(isHovering: boolean): void {
this.isHovering = isHovering;
}
public onDrop(event: any): Promise<any> | void {
const filelist: FileList = event.target.files;
console.log(event.target.files);
this.imageChangedEvent = event;
const file = filelist.item(0);
if (file) {
this.imageChangedFormat = file.type;
}
}
public deletePic(): void {
console.log('delete');
this.authService.removeMyAvatar().then(() => {
this.toast.showInfo('USER.PROFILE.AVATAR.DELETESUCCESS', true);
this.data.profilePic = null;
}).catch(error => {
this.toast.showError(error);
});
}
private handleUploadPromise(task: Promise<any>): Promise<any> {
return task.then(() => {
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
this.data.profilePic = this.croppedImage;
}).catch(error => this.toast.showError(error));
}
public fileChangeEvent(event: any): void {
this.imageChangedEvent = event;
}
public imageCropped(event: ImageCroppedEvent): void {
this.showCropperError = false;
this.croppedImage = event.base64;
}
public upload(): Promise<any> | void {
const formData = new FormData();
const splitted = this.croppedImage.split(';base64,');
if (splitted[1]) {
const blob = this.base64toBlob(splitted[1]);
formData.append('file', blob);
return this.handleUploadPromise(this.assetService.upload('users/me/avatar', formData));
}
}
public loadImageFailed(): void {
this.showCropperError = true;
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public base64toBlob(b64: string): Blob {
const byteCharacters = atob(b64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: this.imageChangedFormat });
return blob;
}
}

View File

@ -11,195 +11,195 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-memberships',
templateUrl: './memberships.component.html',
styleUrls: ['./memberships.component.scss'],
animations: [
trigger('cardAnimation', [
transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }),
]),
]),
trigger('animate', [
transition(':enter', [
animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])),
]),
]),
],
selector: 'app-memberships',
templateUrl: './memberships.component.html',
styleUrls: ['./memberships.component.scss'],
animations: [
trigger('cardAnimation', [
transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }),
]),
]),
trigger('animate', [
transition(':enter', [
animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])),
]),
]),
],
})
export class MembershipsComponent implements OnInit {
public loading: boolean = false;
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
public totalResult: number = 0;
public loading: boolean = false;
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
public totalResult: number = 0;
@Input() public auth: boolean = false;
@Input() public user!: User.AsObject;
@Input() public disabled: boolean = false;
@Input() public auth: boolean = false;
@Input() public user!: User.AsObject;
@Input() public disabled: boolean = false;
constructor(
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private adminService: AdminService,
private dialog: MatDialog,
private toast: ToastService,
private router: Router,
) { }
constructor(
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private adminService: AdminService,
private dialog: MatDialog,
private toast: ToastService,
private router: Router,
) { }
ngOnInit(): void {
this.loadManager(this.user.id);
ngOnInit(): void {
this.loadManager(this.user.id);
}
public async loadManager(userId: string): Promise<void> {
if (this.auth) {
this.authService.listMyUserGrants(100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
} else {
this.mgmtService.listUserMemberships(userId, 100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
}
}
public async loadManager(userId: string): Promise<void> {
if (this.auth) {
this.authService.listMyUserGrants(100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
} else {
this.mgmtService.listUserMemberships(userId, 100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
public navigateToObject(): void {
if (!this.disabled) {
this.router.navigate(['/users', this.user.id, 'memberships']);
}
}
public addMember(): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
width: '400px',
data: {
user: this.user,
},
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.creationType !== undefined) {
switch (resp.creationType) {
case CreationType.IAM:
this.createIamMember(resp);
break;
case CreationType.ORG:
this.createOrgMember(resp);
break;
case CreationType.PROJECT_OWNED:
this.createOwnedProjectMember(resp);
break;
case CreationType.PROJECT_GRANTED:
this.createGrantedProjectMember(resp);
break;
}
}
}
});
}
public navigateToObject(): void {
if (!this.disabled) {
this.router.navigate(['/users', this.user.id, 'memberships']);
}
}
public createIamMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
public addMember(): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
width: '400px',
data: {
user: this.user,
},
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.creationType !== undefined) {
switch (resp.creationType) {
case CreationType.IAM:
this.createIamMember(resp);
break;
case CreationType.ORG:
this.createOrgMember(resp);
break;
case CreationType.PROJECT_OWNED:
this.createOwnedProjectMember(resp);
break;
case CreationType.PROJECT_GRANTED:
this.createGrantedProjectMember(resp);
break;
}
}
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.adminService.addIAMMember(user.id, roles);
})).then(() => {
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
private createOrgMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.mgmtService.addOrgMember(user.id, roles);
})).then(() => {
this.toast.showInfo('ORG.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
private createGrantedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectGrantMember(
response.projectId,
response.grantId,
user.id,
roles,
).then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
public createIamMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
private createOwnedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.adminService.addIAMMember(user.id, roles);
})).then(() => {
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectMember(response.projectId, user.id, roles)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
private createOrgMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
const gen = type.toString();
const colors = [
'rgb(201, 115, 88)',
'rgb(226, 176, 50)',
'rgb(112, 89, 152)',
];
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.mgmtService.addOrgMember(user.id, roles);
})).then(() => {
this.toast.showInfo('ORG.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
let hash = 0;
if (gen.length === 0) {
return colors[hash];
}
private createGrantedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectGrantMember(
response.projectId,
response.grantId,
user.id,
roles,
).then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
private createOwnedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectMember(response.projectId, user.id, roles)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
const gen = type.toString();
const colors = [
'rgb(201, 115, 88)',
'rgb(226, 176, 50)',
'rgb(112, 89, 152)',
];
let hash = 0;
if (gen.length === 0) {
return colors[hash];
}
for (let i = 0; i < gen.length; i++) {
// tslint:disable-next-line: no-bitwise
hash = gen.charCodeAt(i) + ((hash << 5) - hash);
// tslint:disable-next-line: no-bitwise
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
return colors[hash];
for (let i = 0; i < gen.length; i++) {
// tslint:disable-next-line: no-bitwise
hash = gen.charCodeAt(i) + ((hash << 5) - hash);
// tslint:disable-next-line: no-bitwise
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
return colors[hash];
}
}

View File

@ -51,59 +51,59 @@ import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
@NgModule({
declarations: [
AuthUserDetailComponent,
UserDetailComponent,
EditDialogComponent,
AuthUserMfaComponent,
AuthPasswordlessComponent,
UserMfaComponent,
PasswordlessComponent,
ThemeSettingComponent,
PasswordComponent,
CodeDialogComponent,
MembershipsComponent,
ExternalIdpsComponent,
ContactComponent,
ResendEmailDialogComponent,
DialogU2FComponent,
AuthFactorDialogComponent,
],
imports: [
UserDetailRoutingModule,
ChangesModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
DetailFormModule,
DetailFormMachineModule,
WarnDialogModule,
MatDialogModule,
QRCodeModule,
MetaLayoutModule,
MatCheckboxModule,
HasRolePipeModule,
UserGrantsModule,
MatButtonModule,
MatIconModule,
CardModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatTooltipModule,
HasRoleModule,
TranslateModule,
MatTableModule,
PaginatorModule,
SharedModule,
RefreshTableModule,
CopyToClipboardModule,
DetailLayoutModule,
PasswordComplexityViewModule,
MemberCreateDialogModule,
TimestampToDatePipeModule,
LocalizedDatePipeModule,
InputModule,
MachineKeysModule,
],
declarations: [
AuthUserDetailComponent,
UserDetailComponent,
EditDialogComponent,
AuthUserMfaComponent,
AuthPasswordlessComponent,
UserMfaComponent,
PasswordlessComponent,
ThemeSettingComponent,
PasswordComponent,
CodeDialogComponent,
MembershipsComponent,
ExternalIdpsComponent,
ContactComponent,
ResendEmailDialogComponent,
DialogU2FComponent,
AuthFactorDialogComponent,
],
imports: [
UserDetailRoutingModule,
ChangesModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
DetailFormModule,
DetailFormMachineModule,
WarnDialogModule,
MatDialogModule,
QRCodeModule,
MetaLayoutModule,
MatCheckboxModule,
HasRolePipeModule,
UserGrantsModule,
MatButtonModule,
MatIconModule,
CardModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatTooltipModule,
HasRoleModule,
TranslateModule,
MatTableModule,
PaginatorModule,
SharedModule,
RefreshTableModule,
CopyToClipboardModule,
DetailLayoutModule,
PasswordComplexityViewModule,
MemberCreateDialogModule,
TimestampToDatePipeModule,
LocalizedDatePipeModule,
InputModule,
MachineKeysModule,
],
})
export class UserDetailModule { }

View File

@ -44,7 +44,7 @@
<ng-template appHasRole [appHasRole]="['user.read$', 'user.read:'+user?.id]">
<app-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [disabled]="(canWrite$ | async) == false" [genders]="genders" [languages]="languages"
<app-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) == false" [genders]="genders" [languages]="languages"
[username]="user.userName" [user]="user.human" (submitData)="saveProfile($event)">
</app-detail-form>
</app-card>

View File

@ -35,7 +35,7 @@
<app-avatar
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
class="avatar" [name]="user.human.profile.displayName" [size]="32">
class="avatar" [name]="user.human.profile.displayName" [forColor]="user?.preferredLoginName" [size]="32">
</app-avatar>
<ng-template #cog>
<div class="sa-icon" *ngIf="user.machine">

View File

@ -3,10 +3,8 @@ import { Injectable } from '@angular/core';
import { PolicyComponentServiceType } from '../modules/policies/policy-component-types.enum';
import { Theme } from '../modules/policies/private-labeling-policy/private-labeling-policy.component';
import { Org } from '../proto/generated/zitadel/org_pb';
import { StorageService } from './storage.service';
const ORG_STORAGE_KEY = 'organization';
const authorizationKey = 'Authorization';
const orgKey = 'x-zitadel-orgid';
@ -70,62 +68,64 @@ export const ENDPOINT = {
providedIn: 'root',
})
export class AssetService {
private serviceUrl: string = '';
private serviceUrl!: Promise<string>;
private accessToken: string = '';
private org!: Org.AsObject;
constructor(private http: HttpClient, private storageService: StorageService) {
const aT = this.storageService.getItem(accessTokenStorageKey);
http.get('./assets/environment.json')
if (aT) {
this.accessToken = aT;
}
this.serviceUrl = this.getServiceUrl();
}
private async getServiceUrl(): Promise<string> {
const url = await this.http.get('./assets/environment.json')
.toPromise().then((data: any) => {
if (data && data.uploadServiceUrl) {
this.serviceUrl = data.uploadServiceUrl;
const aT = this.storageService.getItem(accessTokenStorageKey);
if (aT) {
this.accessToken = aT;
}
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
if (org) {
this.org = org;
}
if (data && data.assetServiceUrl) {
console.log(data.assetServiceUrl);
return data.assetServiceUrl;
}
}).catch(error => {
console.error(error);
});
return url;
}
public upload(endpoint: AssetEndpoint, body: any): Promise<any> {
return this.http.post(`${this.serviceUrl}/assets/v1/${endpoint}`,
body,
{
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
public upload(endpoint: AssetEndpoint | string, body: any, orgId?: string): Promise<any> {
const headers: any = {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
};
if (orgId) {
headers[orgKey] = `${orgId}`;
}
return this.serviceUrl.then((url) =>
this.http.post(`${url}/assets/v1/${endpoint}`,
body,
{
headers: headers,
}).toPromise(),
);
}
public load(endpoint: string): Promise<any> {
return this.http.get(`${this.serviceUrl}/assets/v1/${endpoint}`,
public load(endpoint: string, orgId?: string): Promise<any> {
const headers: any = {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
};
{
responseType: 'blob',
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
}
if (orgId) {
headers[orgKey] = `${orgId}`;
}
public delete(endpoint: AssetEndpoint): Promise<any> {
return this.http.delete(`${this.serviceUrl}/assets/v1/${endpoint}`,
{
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
return this.serviceUrl.then((url) =>
this.http.get(`${url}/assets/v1/${endpoint}`,
{
responseType: 'blob',
headers: headers,
}).toPromise(),
);
}
}

View File

@ -4,70 +4,72 @@ import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
import { catchError, filter, finalize, map, mergeMap, switchMap, take, timeout } from 'rxjs/operators';
import {
AddMyAuthFactorOTPRequest,
AddMyAuthFactorOTPResponse,
AddMyAuthFactorU2FRequest,
AddMyAuthFactorU2FResponse,
AddMyPasswordlessRequest,
AddMyPasswordlessResponse,
GetMyEmailRequest,
GetMyEmailResponse,
GetMyPasswordComplexityPolicyRequest,
GetMyPasswordComplexityPolicyResponse,
GetMyPhoneRequest,
GetMyPhoneResponse,
GetMyProfileRequest,
GetMyProfileResponse,
GetMyUserRequest,
GetMyUserResponse,
ListMyAuthFactorsRequest,
ListMyAuthFactorsResponse,
ListMyLinkedIDPsRequest,
ListMyLinkedIDPsResponse,
ListMyPasswordlessRequest,
ListMyPasswordlessResponse,
ListMyProjectOrgsRequest,
ListMyProjectOrgsResponse,
ListMyUserChangesRequest,
ListMyUserChangesResponse,
ListMyUserGrantsRequest,
ListMyUserGrantsResponse,
ListMyUserSessionsRequest,
ListMyUserSessionsResponse,
ListMyZitadelFeaturesRequest,
ListMyZitadelFeaturesResponse,
ListMyZitadelPermissionsRequest,
ListMyZitadelPermissionsResponse,
RemoveMyAuthFactorOTPRequest,
RemoveMyAuthFactorOTPResponse,
RemoveMyAuthFactorU2FRequest,
RemoveMyAuthFactorU2FResponse,
RemoveMyLinkedIDPRequest,
RemoveMyLinkedIDPResponse,
RemoveMyPasswordlessRequest,
RemoveMyPasswordlessResponse,
RemoveMyPhoneRequest,
RemoveMyPhoneResponse,
ResendMyEmailVerificationRequest,
ResendMyEmailVerificationResponse,
ResendMyPhoneVerificationRequest,
ResendMyPhoneVerificationResponse,
SetMyEmailRequest,
SetMyEmailResponse,
SetMyPhoneRequest,
SetMyPhoneResponse,
UpdateMyPasswordRequest,
UpdateMyPasswordResponse,
UpdateMyProfileRequest,
UpdateMyProfileResponse,
VerifyMyAuthFactorOTPRequest,
VerifyMyAuthFactorOTPResponse,
VerifyMyAuthFactorU2FRequest,
VerifyMyAuthFactorU2FResponse,
VerifyMyPasswordlessRequest,
VerifyMyPasswordlessResponse,
VerifyMyPhoneRequest,
VerifyMyPhoneResponse,
AddMyAuthFactorOTPRequest,
AddMyAuthFactorOTPResponse,
AddMyAuthFactorU2FRequest,
AddMyAuthFactorU2FResponse,
AddMyPasswordlessRequest,
AddMyPasswordlessResponse,
GetMyEmailRequest,
GetMyEmailResponse,
GetMyPasswordComplexityPolicyRequest,
GetMyPasswordComplexityPolicyResponse,
GetMyPhoneRequest,
GetMyPhoneResponse,
GetMyProfileRequest,
GetMyProfileResponse,
GetMyUserRequest,
GetMyUserResponse,
ListMyAuthFactorsRequest,
ListMyAuthFactorsResponse,
ListMyLinkedIDPsRequest,
ListMyLinkedIDPsResponse,
ListMyPasswordlessRequest,
ListMyPasswordlessResponse,
ListMyProjectOrgsRequest,
ListMyProjectOrgsResponse,
ListMyUserChangesRequest,
ListMyUserChangesResponse,
ListMyUserGrantsRequest,
ListMyUserGrantsResponse,
ListMyUserSessionsRequest,
ListMyUserSessionsResponse,
ListMyZitadelFeaturesRequest,
ListMyZitadelFeaturesResponse,
ListMyZitadelPermissionsRequest,
ListMyZitadelPermissionsResponse,
RemoveMyAuthFactorOTPRequest,
RemoveMyAuthFactorOTPResponse,
RemoveMyAuthFactorU2FRequest,
RemoveMyAuthFactorU2FResponse,
RemoveMyAvatarRequest,
RemoveMyAvatarResponse,
RemoveMyLinkedIDPRequest,
RemoveMyLinkedIDPResponse,
RemoveMyPasswordlessRequest,
RemoveMyPasswordlessResponse,
RemoveMyPhoneRequest,
RemoveMyPhoneResponse,
ResendMyEmailVerificationRequest,
ResendMyEmailVerificationResponse,
ResendMyPhoneVerificationRequest,
ResendMyPhoneVerificationResponse,
SetMyEmailRequest,
SetMyEmailResponse,
SetMyPhoneRequest,
SetMyPhoneResponse,
UpdateMyPasswordRequest,
UpdateMyPasswordResponse,
UpdateMyProfileRequest,
UpdateMyProfileResponse,
VerifyMyAuthFactorOTPRequest,
VerifyMyAuthFactorOTPResponse,
VerifyMyAuthFactorU2FRequest,
VerifyMyAuthFactorU2FResponse,
VerifyMyPasswordlessRequest,
VerifyMyPasswordlessResponse,
VerifyMyPhoneRequest,
VerifyMyPhoneResponse,
} from '../proto/generated/zitadel/auth_pb';
import { ChangeQuery } from '../proto/generated/zitadel/change_pb';
import { ListQuery } from '../proto/generated/zitadel/object_pb';
@ -78,443 +80,448 @@ import { StorageKey, StorageService } from './storage.service';
@Injectable({
providedIn: 'root',
providedIn: 'root',
})
export class GrpcAuthService {
private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
public user!: Observable<User.AsObject | undefined>;
private zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject(['user.resourceowner']);
private zitadelFeatures: BehaviorSubject<string[]> = new BehaviorSubject(['']);
private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
public user!: Observable<User.AsObject | undefined>;
private zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject(['user.resourceowner']);
private zitadelFeatures: BehaviorSubject<string[]> = new BehaviorSubject(['']);
public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject(false as boolean);
public readonly fetchedZitadelFeatures: BehaviorSubject<boolean> = new BehaviorSubject(false as boolean);
public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject(false as boolean);
public readonly fetchedZitadelFeatures: BehaviorSubject<boolean> = new BehaviorSubject(false as boolean);
private cachedOrgs: Org.AsObject[] = [];
private cachedOrgs: Org.AsObject[] = [];
constructor(
private readonly grpcService: GrpcService,
private oauthService: OAuthService,
private storage: StorageService,
) {
this.user = merge(
of(this.oauthService.getAccessToken()).pipe(
filter(token => token ? true : false),
),
this.oauthService.events.pipe(
filter(e => e.type === 'token_received'),
timeout(this.oauthService.waitForTokenInMsec || 0),
catchError(_ => of(null)), // timeout is not an error
map(_ => this.oauthService.getAccessToken()),
),
).pipe(
take(1),
mergeMap(() => {
return from(this.getMyUser().then(resp => {
const user = resp.user;
if (user) {
return user;
} else {
return undefined;
}
}));
}),
finalize(() => {
this.loadPermissions();
this.loadFeatures();
}),
);
constructor(
private readonly grpcService: GrpcService,
private oauthService: OAuthService,
private storage: StorageService,
) {
this.user = merge(
of(this.oauthService.getAccessToken()).pipe(
filter(token => token ? true : false),
),
this.oauthService.events.pipe(
filter(e => e.type === 'token_received'),
timeout(this.oauthService.waitForTokenInMsec || 0),
catchError(_ => of(null)), // timeout is not an error
map(_ => this.oauthService.getAccessToken()),
),
).pipe(
take(1),
mergeMap(() => {
return from(this.getMyUser().then(resp => {
const user = resp.user;
if (user) {
return user;
} else {
return undefined;
}
}));
}),
finalize(() => {
this.loadPermissions();
this.loadFeatures();
}),
);
this.activeOrgChanged.subscribe(() => {
this.loadPermissions();
this.loadFeatures();
});
this.activeOrgChanged.subscribe(() => {
this.loadPermissions();
this.loadFeatures();
});
}
public async getActiveOrg(id?: string): Promise<Org.AsObject> {
if (id) {
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (org && this.cachedOrgs.find(tmp => tmp.id === org.id)) {
return org;
}
return Promise.reject(new Error('no cached org'));
} else {
let orgs = this.cachedOrgs;
if (orgs.length === 0) {
orgs = (await this.listMyProjectOrgs(10, 0)).resultList;
this.cachedOrgs = orgs;
}
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (org && orgs.find(tmp => tmp.id === org.id)) {
return org;
}
if (orgs.length === 0) {
return Promise.reject(new Error('No organizations found!'));
}
const orgToSet = orgs.find(element => element.id !== '0' && element.name !== '');
if (orgToSet) {
this.setActiveOrg(orgToSet);
return Promise.resolve(orgToSet);
}
return Promise.resolve(orgs[0]);
}
}
public get activeOrgChanged(): Observable<Org.AsObject> {
return this._activeOrgChanged;
}
public setActiveOrg(org: Org.AsObject): void {
this.storage.setItem(StorageKey.organization, org);
this._activeOrgChanged.next(org);
}
private loadPermissions(): void {
from(this.listMyZitadelPermissions()).pipe(
map(rolesResp => rolesResp.resultList),
catchError(_ => {
return of([]);
}),
finalize(() => {
this.fetchedZitadelPermissions.next(true);
}),
).subscribe(roles => {
this.zitadelPermissions.next(roles);
});
}
private loadFeatures(): void {
from(this.listMyZitadelFeatures()).pipe(
map(featuresResp => featuresResp.resultList),
catchError(_ => {
return of([]);
}),
finalize(() => {
this.fetchedZitadelFeatures.next(true);
}),
).subscribe(features => {
this.zitadelFeatures.next(features);
});
}
/**
* returns true if user has one of the provided roles
* @param roles roles of the user
*/
public isAllowed(roles: string[] | RegExp[]): Observable<boolean> {
if (roles && roles.length > 0) {
return this.zitadelPermissions.pipe(switchMap(zroles => of(this.hasRoles(zroles, roles))));
} else {
return of(false);
}
}
/**
* returns true if user has one of the provided roles
* @param userRoles roles of the user
* @param requestedRoles required roles for accessing the respective component
*/
public hasRoles(userRoles: string[], requestedRoles: string[] | RegExp[]): boolean {
return requestedRoles.findIndex((regexp: any) => {
return userRoles.findIndex(role => {
return new RegExp(regexp).test(role);
}) > -1;
}) > -1;
}
/**
* returns true if user has one of the provided features
* @param features regex of the user
*/
public canUseFeature(features: string[] | RegExp[]): Observable<boolean> {
if (features && features.length > 0) {
return this.zitadelFeatures.pipe(switchMap(zFeatures => of(this.hasFeature(zFeatures, features))));
} else {
return of(false);
}
}
/**
* returns true if user has one of the provided features
* @param userFeature features of the user
* @param requestedFeature required features for accessing the respective component
*/
public hasFeature(userFeatures: string[], requestedFeatures: string[] | RegExp[]): boolean {
return requestedFeatures.findIndex((regexp: any) => {
return userFeatures.findIndex(feature => {
return new RegExp(regexp).test(feature);
}) > -1;
}) > -1;
}
public getMyProfile(): Promise<GetMyProfileResponse.AsObject> {
return this.grpcService.auth.getMyProfile(new GetMyProfileRequest(), null).then(resp => resp.toObject());
}
public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse.AsObject> {
return this.grpcService.auth.getMyPasswordComplexityPolicy(
new GetMyPasswordComplexityPolicyRequest(), null,
).then(resp => resp.toObject());
}
public getMyUser(): Promise<GetMyUserResponse.AsObject> {
return this.grpcService.auth.getMyUser(
new GetMyUserRequest(), null,
).then(resp => resp.toObject());
}
public listMyMultiFactors(): Promise<ListMyAuthFactorsResponse.AsObject> {
return this.grpcService.auth.listMyAuthFactors(
new ListMyAuthFactorsRequest(), null,
).then(resp => resp.toObject());
}
public listMyProjectOrgs(
limit: number,
offset: number,
queryList?: OrgQuery[],
): Promise<ListMyProjectOrgsResponse.AsObject> {
const req = new ListMyProjectOrgsRequest();
const metadata = new ListQuery();
if (offset) {
metadata.setOffset(offset);
}
if (limit) {
metadata.setLimit(limit);
}
if (queryList) {
req.setQueriesList(queryList);
}
public async getActiveOrg(id?: string): Promise<Org.AsObject> {
if (id) {
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (org && this.cachedOrgs.find(tmp => tmp.id === org.id)) {
return org;
}
return Promise.reject(new Error('no cached org'));
} else {
let orgs = this.cachedOrgs;
if (orgs.length === 0) {
orgs = (await this.listMyProjectOrgs(10, 0)).resultList;
this.cachedOrgs = orgs;
}
return this.grpcService.auth.listMyProjectOrgs(req, null).then(resp => resp.toObject());
}
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (org && orgs.find(tmp => tmp.id === org.id)) {
return org;
}
if (orgs.length === 0) {
return Promise.reject(new Error('No organizations found!'));
}
const orgToSet = orgs.find(element => element.id !== '0' && element.name !== '');
if (orgToSet) {
this.setActiveOrg(orgToSet);
return Promise.resolve(orgToSet);
}
return Promise.resolve(orgs[0]);
}
public updateMyProfile(
firstName?: string,
lastName?: string,
nickName?: string,
displayName?: string,
preferredLanguage?: string,
gender?: Gender,
): Promise<UpdateMyProfileResponse.AsObject> {
const req = new UpdateMyProfileRequest();
if (firstName) {
req.setFirstName(firstName);
}
public get activeOrgChanged(): Observable<Org.AsObject> {
return this._activeOrgChanged;
if (lastName) {
req.setLastName(lastName);
}
public setActiveOrg(org: Org.AsObject): void {
this.storage.setItem(StorageKey.organization, org);
this._activeOrgChanged.next(org);
if (nickName) {
req.setNickName(nickName);
}
private loadPermissions(): void {
from(this.listMyZitadelPermissions()).pipe(
map(rolesResp => rolesResp.resultList),
catchError(_ => {
return of([]);
}),
finalize(() => {
this.fetchedZitadelPermissions.next(true);
}),
).subscribe(roles => {
this.zitadelPermissions.next(roles);
});
if (displayName) {
req.setDisplayName(displayName);
}
private loadFeatures(): void {
from(this.listMyZitadelFeatures()).pipe(
map(featuresResp => featuresResp.resultList),
catchError(_ => {
return of([]);
}),
finalize(() => {
this.fetchedZitadelFeatures.next(true);
}),
).subscribe(features => {
this.zitadelFeatures.next(features);
});
if (gender) {
req.setGender(gender);
}
/**
* returns true if user has one of the provided roles
* @param roles roles of the user
*/
public isAllowed(roles: string[] | RegExp[]): Observable<boolean> {
if (roles && roles.length > 0) {
return this.zitadelPermissions.pipe(switchMap(zroles => of(this.hasRoles(zroles, roles))));
} else {
return of(false);
}
if (preferredLanguage) {
req.setPreferredLanguage(preferredLanguage);
}
return this.grpcService.auth.updateMyProfile(req, null).then(resp => resp.toObject());
}
/**
* returns true if user has one of the provided roles
* @param userRoles roles of the user
* @param requestedRoles required roles for accessing the respective component
*/
public hasRoles(userRoles: string[], requestedRoles: string[] | RegExp[]): boolean {
return requestedRoles.findIndex((regexp: any) => {
return userRoles.findIndex(role => {
return new RegExp(regexp).test(role);
}) > -1;
}) > -1;
public get zitadelPermissionsChanged(): Observable<string[]> {
return this.zitadelPermissions;
}
public listMyUserSessions(): Promise<ListMyUserSessionsResponse.AsObject> {
const req = new ListMyUserSessionsRequest();
return this.grpcService.auth.listMyUserSessions(req, null).then(resp => resp.toObject());
}
public listMyUserGrants(limit?: number, offset?: number, queryList?: ListQuery[]):
Promise<ListMyUserGrantsResponse.AsObject> {
const req = new ListMyUserGrantsRequest();
const query = new ListQuery();
if (limit) {
query.setLimit(limit);
}
/**
* returns true if user has one of the provided features
* @param features regex of the user
*/
public canUseFeature(features: string[] | RegExp[]): Observable<boolean> {
if (features && features.length > 0) {
return this.zitadelFeatures.pipe(switchMap(zFeatures => of(this.hasFeature(zFeatures, features))));
} else {
return of(false);
}
if (offset) {
query.setOffset(offset);
}
req.setQuery(query);
return this.grpcService.auth.listMyUserGrants(req, null).then(resp => resp.toObject());
}
/**
* returns true if user has one of the provided features
* @param userFeature features of the user
* @param requestedFeature required features for accessing the respective component
*/
public hasFeature(userFeatures: string[], requestedFeatures: string[] | RegExp[]): boolean {
return requestedFeatures.findIndex((regexp: any) => {
return userFeatures.findIndex(feature => {
return new RegExp(regexp).test(feature);
}) > -1;
}) > -1;
public getMyEmail(): Promise<GetMyEmailResponse.AsObject> {
const req = new GetMyEmailRequest();
return this.grpcService.auth.getMyEmail(req, null).then(resp => resp.toObject());
}
public setMyEmail(email: string): Promise<SetMyEmailResponse.AsObject> {
const req = new SetMyEmailRequest();
req.setEmail(email);
return this.grpcService.auth.setMyEmail(req, null).then(resp => resp.toObject());
}
public resendMyEmailVerification(): Promise<ResendMyEmailVerificationResponse.AsObject> {
const req = new ResendMyEmailVerificationRequest();
return this.grpcService.auth.resendMyEmailVerification(req, null).then(resp => resp.toObject());
}
public removeMyPhone(): Promise<RemoveMyPhoneResponse.AsObject> {
return this.grpcService.auth.removeMyPhone(
new RemoveMyPhoneRequest(), null,
).then(resp => resp.toObject());
}
public listMyZitadelPermissions(): Promise<ListMyZitadelPermissionsResponse.AsObject> {
return this.grpcService.auth.listMyZitadelPermissions(
new ListMyZitadelPermissionsRequest(), null,
).then(resp => resp.toObject());
}
public listMyZitadelFeatures(): Promise<ListMyZitadelFeaturesResponse.AsObject> {
return this.grpcService.auth.listMyZitadelFeatures(
new ListMyZitadelFeaturesRequest(), null,
).then(resp => resp.toObject());
}
public getMyPhone(): Promise<GetMyPhoneResponse.AsObject> {
return this.grpcService.auth.getMyPhone(
new GetMyPhoneRequest(), null,
).then(resp => resp.toObject());
}
public setMyPhone(phone: string): Promise<SetMyPhoneResponse.AsObject> {
const req = new SetMyPhoneRequest();
req.setPhone(phone);
return this.grpcService.auth.setMyPhone(req, null).then(resp => resp.toObject());
}
public resendMyPhoneVerification(): Promise<ResendMyPhoneVerificationResponse.AsObject> {
const req = new ResendMyPhoneVerificationRequest();
return this.grpcService.auth.resendMyPhoneVerification(req, null).then(resp => resp.toObject());
}
public updateMyPassword(oldPassword: string, newPassword: string): Promise<UpdateMyPasswordResponse.AsObject> {
const req = new UpdateMyPasswordRequest();
req.setOldPassword(oldPassword);
req.setNewPassword(newPassword);
return this.grpcService.auth.updateMyPassword(req, null).then(resp => resp.toObject());
}
public removeMyLinkedIDP(
externalUserId: string,
idpId: string,
): Promise<RemoveMyLinkedIDPResponse.AsObject> {
const req = new RemoveMyLinkedIDPRequest();
req.setLinkedUserId(externalUserId);
req.setIdpId(idpId);
return this.grpcService.auth.removeMyLinkedIDP(req, null).then(resp => resp.toObject());
}
public removeMyAvatar(): Promise<RemoveMyAvatarResponse.AsObject> {
const req = new RemoveMyAvatarRequest();
return this.grpcService.auth.removeMyAvatar(req, null).then(resp => resp.toObject());
}
public listMyLinkedIDPs(
limit: number,
offset: number,
): Promise<ListMyLinkedIDPsResponse.AsObject> {
const req = new ListMyLinkedIDPsRequest();
const metadata = new ListQuery();
if (limit) {
metadata.setLimit(limit);
}
public getMyProfile(): Promise<GetMyProfileResponse.AsObject> {
return this.grpcService.auth.getMyProfile(new GetMyProfileRequest(), null).then(resp => resp.toObject());
if (offset) {
metadata.setOffset(offset);
}
req.setQuery(metadata);
return this.grpcService.auth.listMyLinkedIDPs(req, null).then(resp => resp.toObject());
}
public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse.AsObject> {
return this.grpcService.auth.getMyPasswordComplexityPolicy(
new GetMyPasswordComplexityPolicyRequest(), null,
).then(resp => resp.toObject());
public addMyMultiFactorOTP(): Promise<AddMyAuthFactorOTPResponse.AsObject> {
return this.grpcService.auth.addMyAuthFactorOTP(
new AddMyAuthFactorOTPRequest(), null,
).then(resp => resp.toObject());
}
public addMyMultiFactorU2F(): Promise<AddMyAuthFactorU2FResponse.AsObject> {
return this.grpcService.auth.addMyAuthFactorU2F(
new AddMyAuthFactorU2FRequest(), null,
).then(resp => resp.toObject());
}
public removeMyMultiFactorU2F(tokenId: string): Promise<RemoveMyAuthFactorU2FResponse.AsObject> {
const req = new RemoveMyAuthFactorU2FRequest();
req.setTokenId(tokenId);
return this.grpcService.auth.removeMyAuthFactorU2F(req, null).then(resp => resp.toObject());
}
public verifyMyMultiFactorU2F(credential: string, tokenname: string): Promise<VerifyMyAuthFactorU2FResponse.AsObject> {
const req = new VerifyMyAuthFactorU2FRequest();
const verification = new WebAuthNVerification();
verification.setPublicKeyCredential(credential);
verification.setTokenName(tokenname);
req.setVerification(verification);
return this.grpcService.auth.verifyMyAuthFactorU2F(req, null).then(resp => resp.toObject());
}
public listMyPasswordless(): Promise<ListMyPasswordlessResponse.AsObject> {
return this.grpcService.auth.listMyPasswordless(
new ListMyPasswordlessRequest(), null,
).then(resp => resp.toObject());
}
public addMyPasswordless(): Promise<AddMyPasswordlessResponse.AsObject> {
return this.grpcService.auth.addMyPasswordless(
new AddMyPasswordlessRequest(), null,
).then(resp => resp.toObject());
}
public removeMyPasswordless(tokenId: string): Promise<RemoveMyPasswordlessResponse.AsObject> {
const req = new RemoveMyPasswordlessRequest();
req.setTokenId(tokenId);
return this.grpcService.auth.removeMyPasswordless(req, null).then(resp => resp.toObject());
}
public verifyMyPasswordless(credential: string, tokenname: string): Promise<VerifyMyPasswordlessResponse.AsObject> {
const req = new VerifyMyPasswordlessRequest();
const verification = new WebAuthNVerification();
verification.setTokenName(tokenname);
verification.setPublicKeyCredential(credential);
req.setVerification(verification);
return this.grpcService.auth.verifyMyPasswordless(
req, null,
).then(resp => resp.toObject());
}
public removeMyMultiFactorOTP(): Promise<RemoveMyAuthFactorOTPResponse.AsObject> {
return this.grpcService.auth.removeMyAuthFactorOTP(
new RemoveMyAuthFactorOTPRequest(), null,
).then(resp => resp.toObject());
}
public verifyMyMultiFactorOTP(code: string): Promise<VerifyMyAuthFactorOTPResponse.AsObject> {
const req = new VerifyMyAuthFactorOTPRequest();
req.setCode(code);
return this.grpcService.auth.verifyMyAuthFactorOTP(req, null).then(resp => resp.toObject());
}
public verifyMyPhone(code: string): Promise<VerifyMyPhoneResponse.AsObject> {
const req = new VerifyMyPhoneRequest();
req.setCode(code);
return this.grpcService.auth.verifyMyPhone(req, null).then(resp => resp.toObject());
}
public listMyUserChanges(limit: number, sequence: number): Promise<ListMyUserChangesResponse.AsObject> {
const req = new ListMyUserChangesRequest();
const query = new ChangeQuery();
if (limit) {
query.setLimit(limit);
}
public getMyUser(): Promise<GetMyUserResponse.AsObject> {
return this.grpcService.auth.getMyUser(
new GetMyUserRequest(), null,
).then(resp => resp.toObject());
}
public listMyMultiFactors(): Promise<ListMyAuthFactorsResponse.AsObject> {
return this.grpcService.auth.listMyAuthFactors(
new ListMyAuthFactorsRequest(), null,
).then(resp => resp.toObject());
}
public listMyProjectOrgs(
limit: number,
offset: number,
queryList?: OrgQuery[],
): Promise<ListMyProjectOrgsResponse.AsObject> {
const req = new ListMyProjectOrgsRequest();
const metadata = new ListQuery();
if (offset) {
metadata.setOffset(offset);
}
if (limit) {
metadata.setLimit(limit);
}
if (queryList) {
req.setQueriesList(queryList);
}
return this.grpcService.auth.listMyProjectOrgs(req, null).then(resp => resp.toObject());
}
public updateMyProfile(
firstName?: string,
lastName?: string,
nickName?: string,
displayName?: string,
preferredLanguage?: string,
gender?: Gender,
): Promise<UpdateMyProfileResponse.AsObject> {
const req = new UpdateMyProfileRequest();
if (firstName) {
req.setFirstName(firstName);
}
if (lastName) {
req.setLastName(lastName);
}
if (nickName) {
req.setNickName(nickName);
}
if (displayName) {
req.setDisplayName(displayName);
}
if (gender) {
req.setGender(gender);
}
if (preferredLanguage) {
req.setPreferredLanguage(preferredLanguage);
}
return this.grpcService.auth.updateMyProfile(req, null).then(resp => resp.toObject());
}
public get zitadelPermissionsChanged(): Observable<string[]> {
return this.zitadelPermissions;
}
public listMyUserSessions(): Promise<ListMyUserSessionsResponse.AsObject> {
const req = new ListMyUserSessionsRequest();
return this.grpcService.auth.listMyUserSessions(req, null).then(resp => resp.toObject());
}
public listMyUserGrants(limit?: number, offset?: number, queryList?: ListQuery[]):
Promise<ListMyUserGrantsResponse.AsObject> {
const req = new ListMyUserGrantsRequest();
const query = new ListQuery();
if (limit) {
query.setLimit(limit);
}
if (offset) {
query.setOffset(offset);
}
req.setQuery(query);
return this.grpcService.auth.listMyUserGrants(req, null).then(resp => resp.toObject());
}
public getMyEmail(): Promise<GetMyEmailResponse.AsObject> {
const req = new GetMyEmailRequest();
return this.grpcService.auth.getMyEmail(req, null).then(resp => resp.toObject());
}
public setMyEmail(email: string): Promise<SetMyEmailResponse.AsObject> {
const req = new SetMyEmailRequest();
req.setEmail(email);
return this.grpcService.auth.setMyEmail(req, null).then(resp => resp.toObject());
}
public resendMyEmailVerification(): Promise<ResendMyEmailVerificationResponse.AsObject> {
const req = new ResendMyEmailVerificationRequest();
return this.grpcService.auth.resendMyEmailVerification(req, null).then(resp => resp.toObject());
}
public removeMyPhone(): Promise<RemoveMyPhoneResponse.AsObject> {
return this.grpcService.auth.removeMyPhone(
new RemoveMyPhoneRequest(), null,
).then(resp => resp.toObject());
}
public listMyZitadelPermissions(): Promise<ListMyZitadelPermissionsResponse.AsObject> {
return this.grpcService.auth.listMyZitadelPermissions(
new ListMyZitadelPermissionsRequest(), null,
).then(resp => resp.toObject());
}
public listMyZitadelFeatures(): Promise<ListMyZitadelFeaturesResponse.AsObject> {
return this.grpcService.auth.listMyZitadelFeatures(
new ListMyZitadelFeaturesRequest(), null,
).then(resp => resp.toObject());
}
public getMyPhone(): Promise<GetMyPhoneResponse.AsObject> {
return this.grpcService.auth.getMyPhone(
new GetMyPhoneRequest(), null,
).then(resp => resp.toObject());
}
public setMyPhone(phone: string): Promise<SetMyPhoneResponse.AsObject> {
const req = new SetMyPhoneRequest();
req.setPhone(phone);
return this.grpcService.auth.setMyPhone(req, null).then(resp => resp.toObject());
}
public resendMyPhoneVerification(): Promise<ResendMyPhoneVerificationResponse.AsObject> {
const req = new ResendMyPhoneVerificationRequest();
return this.grpcService.auth.resendMyPhoneVerification(req, null).then(resp => resp.toObject());
}
public updateMyPassword(oldPassword: string, newPassword: string): Promise<UpdateMyPasswordResponse.AsObject> {
const req = new UpdateMyPasswordRequest();
req.setOldPassword(oldPassword);
req.setNewPassword(newPassword);
return this.grpcService.auth.updateMyPassword(req, null).then(resp => resp.toObject());
}
public removeMyLinkedIDP(
externalUserId: string,
idpId: string,
): Promise<RemoveMyLinkedIDPResponse.AsObject> {
const req = new RemoveMyLinkedIDPRequest();
req.setLinkedUserId(externalUserId);
req.setIdpId(idpId);
return this.grpcService.auth.removeMyLinkedIDP(req, null).then(resp => resp.toObject());
}
public listMyLinkedIDPs(
limit: number,
offset: number,
): Promise<ListMyLinkedIDPsResponse.AsObject> {
const req = new ListMyLinkedIDPsRequest();
const metadata = new ListQuery();
if (limit) {
metadata.setLimit(limit);
}
if (offset) {
metadata.setOffset(offset);
}
req.setQuery(metadata);
return this.grpcService.auth.listMyLinkedIDPs(req, null).then(resp => resp.toObject());
}
public addMyMultiFactorOTP(): Promise<AddMyAuthFactorOTPResponse.AsObject> {
return this.grpcService.auth.addMyAuthFactorOTP(
new AddMyAuthFactorOTPRequest(), null,
).then(resp => resp.toObject());
}
public addMyMultiFactorU2F(): Promise<AddMyAuthFactorU2FResponse.AsObject> {
return this.grpcService.auth.addMyAuthFactorU2F(
new AddMyAuthFactorU2FRequest(), null,
).then(resp => resp.toObject());
}
public removeMyMultiFactorU2F(tokenId: string): Promise<RemoveMyAuthFactorU2FResponse.AsObject> {
const req = new RemoveMyAuthFactorU2FRequest();
req.setTokenId(tokenId);
return this.grpcService.auth.removeMyAuthFactorU2F(req, null).then(resp => resp.toObject());
}
public verifyMyMultiFactorU2F(credential: string, tokenname: string): Promise<VerifyMyAuthFactorU2FResponse.AsObject> {
const req = new VerifyMyAuthFactorU2FRequest();
const verification = new WebAuthNVerification();
verification.setPublicKeyCredential(credential);
verification.setTokenName(tokenname);
req.setVerification(verification);
return this.grpcService.auth.verifyMyAuthFactorU2F(req, null).then(resp => resp.toObject());
}
public listMyPasswordless(): Promise<ListMyPasswordlessResponse.AsObject> {
return this.grpcService.auth.listMyPasswordless(
new ListMyPasswordlessRequest(), null,
).then(resp => resp.toObject());
}
public addMyPasswordless(): Promise<AddMyPasswordlessResponse.AsObject> {
return this.grpcService.auth.addMyPasswordless(
new AddMyPasswordlessRequest(), null,
).then(resp => resp.toObject());
}
public removeMyPasswordless(tokenId: string): Promise<RemoveMyPasswordlessResponse.AsObject> {
const req = new RemoveMyPasswordlessRequest();
req.setTokenId(tokenId);
return this.grpcService.auth.removeMyPasswordless(req, null).then(resp => resp.toObject());
}
public verifyMyPasswordless(credential: string, tokenname: string): Promise<VerifyMyPasswordlessResponse.AsObject> {
const req = new VerifyMyPasswordlessRequest();
const verification = new WebAuthNVerification();
verification.setTokenName(tokenname);
verification.setPublicKeyCredential(credential);
req.setVerification(verification);
return this.grpcService.auth.verifyMyPasswordless(
req, null,
).then(resp => resp.toObject());
}
public removeMyMultiFactorOTP(): Promise<RemoveMyAuthFactorOTPResponse.AsObject> {
return this.grpcService.auth.removeMyAuthFactorOTP(
new RemoveMyAuthFactorOTPRequest(), null,
).then(resp => resp.toObject());
}
public verifyMyMultiFactorOTP(code: string): Promise<VerifyMyAuthFactorOTPResponse.AsObject> {
const req = new VerifyMyAuthFactorOTPRequest();
req.setCode(code);
return this.grpcService.auth.verifyMyAuthFactorOTP(req, null).then(resp => resp.toObject());
}
public verifyMyPhone(code: string): Promise<VerifyMyPhoneResponse.AsObject> {
const req = new VerifyMyPhoneRequest();
req.setCode(code);
return this.grpcService.auth.verifyMyPhone(req, null).then(resp => resp.toObject());
}
public listMyUserChanges(limit: number, sequence: number): Promise<ListMyUserChangesResponse.AsObject> {
const req = new ListMyUserChangesRequest();
const query = new ChangeQuery();
if (limit) {
query.setLimit(limit);
}
if (sequence) {
query.setSequence(sequence);
}
req.setQuery(query);
return this.grpcService.auth.listMyUserChanges(req, null).then(resp => resp.toObject());
if (sequence) {
query.setSequence(sequence);
}
req.setQuery(query);
return this.grpcService.auth.listMyUserChanges(req, null).then(resp => resp.toObject());
}
}

View File

@ -14,69 +14,68 @@ import { OrgInterceptor } from './interceptors/org.interceptor';
import { StorageService } from './storage.service';
@Injectable({
providedIn: 'root',
providedIn: 'root',
})
export class GrpcService {
public auth!: AuthServiceClient;
public mgmt!: ManagementServiceClient;
public admin!: AdminServiceClient;
public auth!: AuthServiceClient;
public mgmt!: ManagementServiceClient;
public admin!: AdminServiceClient;
constructor(
private http: HttpClient,
private platformLocation: PlatformLocation,
private authenticationService: AuthenticationService,
private storageService: StorageService,
private dialog: MatDialog,
// private toast: ToastService,
) { }
constructor(
private http: HttpClient,
private platformLocation: PlatformLocation,
private authenticationService: AuthenticationService,
private storageService: StorageService,
private dialog: MatDialog,
) { }
public async loadAppEnvironment(): Promise<any> {
return this.http.get('./assets/environment.json')
.toPromise().then((data: any) => {
if (data && data.authServiceUrl && data.mgmtServiceUrl && data.issuer) {
const interceptors = {
unaryInterceptors: [
new OrgInterceptor(this.storageService),
new AuthInterceptor(this.authenticationService, this.storageService, this.dialog),
new I18nInterceptor(),
],
};
public async loadAppEnvironment(): Promise<any> {
return this.http.get('./assets/environment.json')
.toPromise().then((data: any) => {
if (data && data.authServiceUrl && data.mgmtServiceUrl && data.issuer) {
const interceptors = {
unaryInterceptors: [
new OrgInterceptor(this.storageService),
new AuthInterceptor(this.authenticationService, this.storageService, this.dialog),
new I18nInterceptor(),
],
};
this.auth = new AuthServiceClient(
data.authServiceUrl,
null,
// @ts-ignore
interceptors,
);
this.mgmt = new ManagementServiceClient(
data.mgmtServiceUrl,
null,
// @ts-ignore
interceptors,
);
this.admin = new AdminServiceClient(
// TODO: replace with service url
data.mgmtServiceUrl,
null,
// @ts-ignore
interceptors,
);
this.auth = new AuthServiceClient(
data.authServiceUrl,
null,
// @ts-ignore
interceptors,
);
this.mgmt = new ManagementServiceClient(
data.mgmtServiceUrl,
null,
// @ts-ignore
interceptors,
);
this.admin = new AdminServiceClient(
// TODO: replace with service url
data.mgmtServiceUrl,
null,
// @ts-ignore
interceptors,
);
const authConfig: AuthConfig = {
scope: 'openid profile email',
responseType: 'code',
oidc: true,
clientId: data.clientid,
issuer: data.issuer,
redirectUri: window.location.origin + this.platformLocation.getBaseHrefFromDOM() + 'auth/callback',
postLogoutRedirectUri: window.location.origin + this.platformLocation.getBaseHrefFromDOM() + 'signedout',
};
const authConfig: AuthConfig = {
scope: 'openid profile email',
responseType: 'code',
oidc: true,
clientId: data.clientid,
issuer: data.issuer,
redirectUri: window.location.origin + this.platformLocation.getBaseHrefFromDOM() + 'auth/callback',
postLogoutRedirectUri: window.location.origin + this.platformLocation.getBaseHrefFromDOM() + 'signedout',
};
this.authenticationService.initConfig(authConfig);
}
return Promise.resolve(data);
}).catch(() => {
console.error('Failed to load environment from assets');
});
}
this.authenticationService.initConfig(authConfig);
}
return Promise.resolve(data);
}).catch(() => {
console.error('Failed to load environment from assets');
});
}
}

View File

@ -0,0 +1,45 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { OAuthModuleConfig } from 'angular-oauth2-oidc';
import { Observable } from 'rxjs';
import { Org } from '../../proto/generated/zitadel/org_pb';
import { StorageService } from '../storage.service';
const orgKey = 'x-zitadel-orgid';
const ORG_STORAGE_KEY = 'organization';
export abstract class HttpOrgInterceptor implements HttpInterceptor {
private org!: Org.AsObject;
protected get validUrls(): string[] {
return this.oauthModuleConfig.resourceServer.allowedUrls || [];
}
constructor(
private storageService: StorageService,
protected oauthModuleConfig: OAuthModuleConfig,
) {
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
if (org) {
this.org = org;
}
}
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.urlValidation(req.url)) {
return next.handle(req);
}
return next.handle(req.clone({
setHeaders: {
[orgKey]: this.org.id
},
}));
}
private urlValidation(toIntercept: string): boolean {
const URLS = ['https://api.zitadel.dev/assets', 'https://api.zitadel.ch/assets'];
return URLS.findIndex(url => toIntercept.startsWith(url)) > -1;
}
}

View File

@ -3,7 +3,7 @@
"mgmtServiceUrl": "https://api.zitadel.dev",
"adminServiceUrl":"https://api.zitadel.dev",
"subscriptionServiceUrl":"https://sub.zitadel.dev",
"uploadServiceUrl":"https://api.zitadel.dev",
"assetServiceUrl":"https://api.zitadel.dev",
"issuer": "https://issuer.zitadel.dev",
"clientid": "70669160379706195@zitadel"
}

View File

@ -306,7 +306,16 @@
"DISPLAYNAME": "Anzeigename",
"PREFERRED_LANGUAGE": "Sprache",
"GENDER": "Geschlecht",
"PASSWORD": "Passwort"
"PASSWORD": "Passwort",
"AVATAR": {
"UPLOADTITLE":"Profilfoto uploaden",
"UPLOADBTN":"Datei auswählen",
"UPLOAD":"Hochladen",
"CURRENT":"Aktuelles Bild",
"PREVIEW":"Vorschau",
"DELETESUCCESS":"Erfolgreich gelöscht!",
"CROPPERERROR":"Ein Fehler beim hochladen Ihrer Datei ist fehlgeschlagen. Versuchen Sie es mit ggf mit einem anderen Format und Grösse."
}
},
"MACHINE": {
"TITLE": "Details Service-Benutzer",
@ -642,7 +651,7 @@
"PRIVATELABELING": {
"TITLE":"Private Labeling",
"DESCRIPTION":"Verleihe dem Login deinen benutzerdefinierten Style und passe das Verhalten an.",
"PREVIEW_DESCRIPTION":"Änderungen dieser Richtlinie werden automatisch in der Preview Umgebung verfügbar. Um die Preview zu Testen muss dem login flow der scope 'x-preview' mitgegeben werden.",
"PREVIEW_DESCRIPTION":"Änderungen dieser Richtlinie werden automatisch in der Preview Umgebung verfügbar.",
"BTN":"Datei auswählen",
"ACTIVATEPREVIEW":"Konfiguration übernehmen",
"DARK":"Dunkler Modus",
@ -653,6 +662,8 @@
"COLORS":"Farben",
"FONT":"Schrift",
"ADVANCEDBEHAVIOR":"Erweitertes Verhalten",
"DROP":"Bild hier ablegen",
"RELEASE":"Jetzt loslassen",
"PREVIEW": {
"TITLE":"Anmeldung",
"SECOND":"mit ZITADEL-Konto anmelden.",

View File

@ -306,7 +306,16 @@
"DISPLAYNAME": "Display Name",
"PREFERRED_LANGUAGE": "Language",
"GENDER": "Gender",
"PASSWORD": "Password"
"PASSWORD": "Password",
"AVATAR": {
"UPLOADTITLE":"Upload your Profile Picture",
"UPLOADBTN":"Choose file",
"UPLOAD":"Upload",
"CURRENT":"Current Picture",
"PREVIEW":"Preview",
"DELETESUCCESS":"Deleted successfully!",
"CROPPERERROR":"An error while uploading your file failed. Try a different format and size if necessary."
}
},
"MACHINE": {
"TITLE": "Service User Details",
@ -642,7 +651,7 @@
"PRIVATELABELING": {
"TITLE":"Private Labeling",
"DESCRIPTION":"Give the login your personalized style and modify its behavior.",
"PREVIEW_DESCRIPTION":"Changes of the policy will automatically deployed to preview environment. To view those changes, a 'x-preview' scope will have to be added to your login scopes.",
"PREVIEW_DESCRIPTION":"Changes of the policy will automatically deployed to preview environment.",
"BTN":"Select File",
"ACTIVATEPREVIEW":"Set preview as current configuration",
"DARK":"Dark Mode",
@ -653,6 +662,8 @@
"COLORS":"Colors",
"FONT":"Font",
"ADVANCEDBEHAVIOR":"Advanced Behavior",
"DROP":"Drop image here",
"RELEASE":"Release",
"PREVIEW": {
"TITLE":"Login",
"SECOND":"login with your ZITADEL-Account.",

View File

@ -118,7 +118,6 @@ export class AuthenticationService {
public async authenticate(
setState: boolean = true,
): Promise<boolean> {
console.log('auth');
this.oauthService.configure(this.authConfig);
this.oauthService.strictDiscoveryDocumentValidation = false;

2
go.mod
View File

@ -69,7 +69,7 @@ require (
go.opentelemetry.io/otel/exporters/stdout v0.13.0
go.opentelemetry.io/otel/sdk v0.13.0
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.6
golang.org/x/tools v0.1.0
google.golang.org/api v0.34.0

View File

@ -1,36 +1,62 @@
const avatars = document.getElementsByClassName('lgn-avatar');
for (let i = 0; i < avatars.length; i++) {
const displayName = avatars[i].getAttribute('loginname');
if (displayName) {
const username = displayName.split('@')[0];
let separator = '_';
if (username.includes('-')) {
separator = '-';
}
if (username.includes('.')) {
separator = '.';
}
const split = username.split(separator);
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
avatars[i].getElementsByClassName('initials')[0].innerHTML = initials;
avatars[i].style.background = this.getColor(displayName);
// set default white text instead of contrast text mode
avatars[i].style.color = '#ffffff';
const displayName = avatars[i].getAttribute('loginname');
if (displayName) {
const username = displayName.split('@')[0];
let separator = '_';
if (username.includes('-')) {
separator = '-';
}
if (username.includes('.')) {
separator = '.';
}
const split = username.split(separator);
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
avatars[i].getElementsByClassName('initials')[0].innerHTML = initials;
avatars[i].style.background = this.getColor(displayName);
// set default white text instead of contrast text mode
avatars[i].style.color = '#ffffff';
}
}
function getColor(username) {
const s = 40;
const l = 50;
const l2 = 62.5;
let hash = 0;
for (let i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + ((hash << 5) - hash);
}
function getColor(userName) {
const colors = [
'linear-gradient(40deg, #B44D51 30%, rgb(241,138,138))',
'linear-gradient(40deg, #B75073 30%, rgb(234,96,143))',
'linear-gradient(40deg, #84498E 30%, rgb(214,116,230))',
'linear-gradient(40deg, #705998 30%, rgb(163,131,220))',
'linear-gradient(40deg, #5C6598 30%, rgb(135,148,222))',
'linear-gradient(40deg, #7F90D3 30%, rgb(181,196,247))',
'linear-gradient(40deg, #3E93B9 30%, rgb(150,215,245))',
'linear-gradient(40deg, #3494A0 30%, rgb(71,205,222))',
'linear-gradient(40deg, #25716A 30%, rgb(58,185,173))',
'linear-gradient(40deg, #427E41 30%, rgb(97,185,96))',
'linear-gradient(40deg, #89A568 30%, rgb(176,212,133))',
'linear-gradient(40deg, #90924D 30%, rgb(187,189,98))',
'linear-gradient(40deg, #E2B032 30%, rgb(245,203,99))',
'linear-gradient(40deg, #C97358 30%, rgb(245,148,118))',
'linear-gradient(40deg, #6D5B54 30%, rgb(152,121,108))',
'linear-gradient(40deg, #6B7980 30%, rgb(134,163,177))',
];
const h = hash % 360;
const col1 = 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
const col2 = 'hsl(' + h + ', ' + s + '%, ' + l2 + '%)';
return 'linear-gradient(40deg, ' + col1 + ' 30%, ' + col2 + ')';
let hash = 0;
if (userName.length === 0) {
return colors[hash];
}
hash = this.hashCode(userName);
return colors[hash % colors.length];
}
function hashCode(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}