mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:17:23 +00:00
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:
parent
2502f379d9
commit
1e77b8aeae
16221
console/package-lock.json
generated
16221
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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 => {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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'],
|
||||
};
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 => {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 { }
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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 { }
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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
2
go.mod
@ -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
|
||||
|
@ -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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user