feat(console): add and remove org domains, show userLoginNames (#235)

* org domains, user login names

* switch user to userview, regen proto
This commit is contained in:
Max Peintner
2020-06-17 17:11:36 +02:00
committed by GitHub
parent e7b139ba2c
commit b5376b2e5a
18 changed files with 613 additions and 339 deletions

View File

@@ -3,6 +3,38 @@
<h1>{{org?.name}}</h1>
<p class="sub">{{'ORG_DETAIL.DESCRIPTION' | translate}}
</p>
<app-card title="{{ 'ORG.DOMAINS.TITLE' | translate }}"
description="{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}">
<div *ngFor="let domain of domains" class="domain">
<span class="title">{{domain.domain}}</span>
<i matTooltip="verified" *ngIf="domain.verified" class="verified las la-check-circle"></i>
<i matTooltip="primary" *ngIf="domain.primary" class="primary las la-chess-queen"></i>
<span class="fill-space"></span>
<button disabled mat-icon-button
matTooltip="download /.well-known/caos-developer-domain-association.txt and deploy it on your domain. Then verify"><i
class="las la-file-download"></i></button>
<button matTooltip="Remove domain" color="warn" mat-icon-button (click)="removeDomain(domain.domain)"><i
class="las la-trash"></i></button>
</div>
<p class="new-desc">{{'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate}}</p>
<div class="new-row">
<mat-form-field appearance="outline">
<mat-label>new domain</mat-label>
<input matInput [(ngModel)]="newDomain" />
</mat-form-field>
<button matTooltip="Add domain" mat-icon-button color="accent" (click)="saveNewOrgDomain()">
<mat-icon>check</mat-icon>
</button>
<button disabled mat-icon-button
matTooltip="download /.well-known/caos-developer-domain-association.txt and deploy it on your domain. Then verify"><i
class="las la-file-download"></i></button>
<button disabled mat-icon-button matTooltip="Verify"><i class=" las la-check-circle"></i></button>
</div>
</app-card>
<ng-template appHasRole [appHasRole]="['policy.read']">
<app-policy-grid></app-policy-grid>
</ng-template>
@@ -12,7 +44,8 @@
<div class="details">
<div class="row">
<span class="first">Domains:</span>
<span *ngFor="let domain of domains" class="second">{{domain.domain}}</span>
<span class="second"><span style="display: block;"
*ngFor="let domain of domains">{{domain.domain}}</span></span>
</div>
<div class="row">
<span class="first">State:</span>

View File

@@ -55,6 +55,43 @@ h1 {
}
}
.domain {
display: flex;
align-items: center;
padding: .5rem 0;
flex-wrap: wrap;
.title {
font-size: 16px;
margin-right: 1rem;
}
.verified, .primary{
color: #5282c1;
margin-right: 1rem;
}
.fill-space {
flex: 1;
}
}
.new-desc {
font-size: 14px;
color: #818a8a;
}
.new-row {
display: flex;
flex-wrap: wrap;
align-items: center;
mat-form-field {
flex: 1;
}
}
.side {
.details {
margin-bottom: 1rem;
@@ -96,6 +133,7 @@ h1 {
text-overflow: ellipsis;
overflow: hidden;
margin-left: 1rem;
text-align: right;
}
a {

View File

@@ -30,6 +30,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
private subscription: Subscription = new Subscription();
public domains: OrgDomainView.AsObject[] = [];
public newDomain: string = '';
constructor(
public translate: TranslateService,
@@ -76,4 +77,19 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
});
}
}
public saveNewOrgDomain(): void {
this.orgService.AddMyOrgDomain(this.newDomain).then(domain => {
this.domains.push(domain.toObject());
});
}
public removeDomain(domain: string): void {
console.log(domain);
this.orgService.RemoveMyOrgDomain(domain).then(() => {
this.toast.showInfo('Removed');
}).catch(error => {
this.toast.showError(error.message);
});
}
}

View File

@@ -6,8 +6,7 @@ import { MatTable } from '@angular/material/table';
import { ActivatedRoute } from '@angular/router';
import { tap } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { User } from 'src/app/proto/generated/auth_pb';
import { Org, ProjectMember, ProjectType } from 'src/app/proto/generated/management_pb';
import { Org, ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb';
import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service';

View File

@@ -3,7 +3,6 @@ import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { BehaviorSubject, from, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { User } from 'src/app/proto/generated/auth_pb';
import {
ProjectGrantView,
ProjectMemberSearchResponse,
@@ -11,6 +10,7 @@ import {
ProjectState,
ProjectType,
ProjectView,
User,
} from 'src/app/proto/generated/management_pb';
import { ProjectService } from 'src/app/services/project.service';
import { ToastService } from 'src/app/services/toast.service';

View File

@@ -3,7 +3,7 @@ import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/fo
import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { Gender, User, UserAddress, UserEmail, UserPhone, UserProfile } from 'src/app/proto/generated/auth_pb';
import { Gender, UserAddress, UserEmail, UserPhone, UserProfile } from 'src/app/proto/generated/auth_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/management_pb';
import { AuthUserService } from 'src/app/services/auth-user.service';
import { MgmtUserService } from 'src/app/services/mgmt-user.service';
@@ -33,8 +33,6 @@ function passwordConfirmValidator(c: AbstractControl): any {
styleUrls: ['./auth-user-detail.component.scss'],
})
export class AuthUserDetailComponent implements OnDestroy {
public user!: User.AsObject;
public profile!: UserProfile.AsObject;
public email: UserEmail.AsObject = { email: '' } as any;
public phone: UserPhone.AsObject = { phone: '' } as any;
@@ -267,12 +265,6 @@ export class AuthUserDetailComponent implements OnDestroy {
}
private async getData(): Promise<void> {
// this.mgmtUserService.GetUserByID(id).then(user => {
// console.log(user.toObject());
// this.user = user.toObject();
// }).catch(err => {
// console.error(err);
// });
this.profile = (await this.userService.GetMyUserProfile()).toObject();
this.email = (await this.userService.GetMyUserEmail()).toObject();
this.phone = (await this.userService.GetMyUserPhone()).toObject();

View File

@@ -188,6 +188,14 @@
</div>
<metainfo *ngIf="user" class="side">
<div class="details">
<div class="row" *ngIf="user?.loginNamesList">
<span class="first">Login Names:</span>
<span class="second"><span style="display: block;"
*ngFor="let login of user?.loginNamesList">{{login}}</span></span>
</div>
</div>
<p class="side-section">{{ 'CHANGES.USER.TITLE' | translate }}</p>
<app-changes [changeType]="ChangeType.USER" [id]="user.id"></app-changes>
</metainfo>

View File

@@ -126,6 +126,63 @@
// display: flex;
// flex-direction: column;
.details {
margin-bottom: 1rem;
border-bottom: 1px solid #81868a40;
padding-bottom: 1rem;
.row {
display: flex;
margin-bottom: 0.5rem;
align-items: center;
button {
display: none;
visibility: hidden;
}
&:hover {
button {
display: inline-block;
visibility: visible;
mat-icon {
font-size: 1.2rem;
}
}
}
.first {
flex: 1;
font-size: 0.8rem;
margin-right: 0.5rem;
white-space: nowrap;
}
.fill-space {
flex: 1;
}
.second {
font-size: 0.8rem;
text-overflow: ellipsis;
overflow: hidden;
margin-left: 1rem;
text-align: right;
}
a {
&:hover {
cursor: pointer;
text-decoration: underline;
}
}
}
.side-section {
color: #81868a;
}
}
.details {
margin-bottom: 1rem;
border-bottom: 1px solid #81868a40;

View File

@@ -10,12 +10,12 @@ import {
Gender,
NotificationType,
PasswordComplexityPolicy,
User,
UserAddress,
UserEmail,
UserPhone,
UserProfile,
UserState,
UserView,
} from 'src/app/proto/generated/management_pb';
import { AuthUserService } from 'src/app/services/auth-user.service';
import { MgmtUserService } from 'src/app/services/mgmt-user.service';
@@ -45,7 +45,7 @@ function passwordConfirmValidator(c: AbstractControl): any {
styleUrls: ['./user-detail.component.scss'],
})
export class UserDetailComponent implements OnInit, OnDestroy {
public user!: User.AsObject;
public user!: UserView.AsObject;
// public email: UserEmail.AsObject = { email: '' } as any;
// public phone: UserPhone.AsObject = { phone: '' } as any;
public address: UserAddress.AsObject = { id: '' } as any;

View File

@@ -30,6 +30,7 @@ import {
UserProfile,
UserProfileView,
UserSessionViews,
UserView,
VerifyMfaOtp,
VerifyMyUserEmailRequest,
VerifyUserPhoneRequest} from './auth_pb';
@@ -67,6 +68,13 @@ export class AuthServiceClient {
response: UserSessionViews) => void
): grpcWeb.ClientReadableStream<UserSessionViews>;
getMyUser(
request: google_protobuf_empty_pb.Empty,
metadata: grpcWeb.Metadata | undefined,
callback: (err: grpcWeb.Error,
response: UserView) => void
): grpcWeb.ClientReadableStream<UserView>;
getMyUserProfile(
request: google_protobuf_empty_pb.Empty,
metadata: grpcWeb.Metadata | undefined,
@@ -234,6 +242,11 @@ export class AuthServicePromiseClient {
metadata?: grpcWeb.Metadata
): Promise<UserSessionViews>;
getMyUser(
request: google_protobuf_empty_pb.Empty,
metadata?: grpcWeb.Metadata
): Promise<UserView>;
getMyUserProfile(
request: google_protobuf_empty_pb.Empty,
metadata?: grpcWeb.Metadata

View File

@@ -404,6 +404,86 @@ proto.caos.zitadel.auth.api.v1.AuthServicePromiseClient.prototype.getMyUserSessi
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.google.protobuf.Empty,
* !proto.caos.zitadel.auth.api.v1.UserView>}
*/
const methodDescriptor_AuthService_GetMyUser = new grpc.web.MethodDescriptor(
'/caos.zitadel.auth.api.v1.AuthService/GetMyUser',
grpc.web.MethodType.UNARY,
google_protobuf_empty_pb.Empty,
proto.caos.zitadel.auth.api.v1.UserView,
/**
* @param {!proto.google.protobuf.Empty} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.caos.zitadel.auth.api.v1.UserView.deserializeBinary
);
/**
* @const
* @type {!grpc.web.AbstractClientBase.MethodInfo<
* !proto.google.protobuf.Empty,
* !proto.caos.zitadel.auth.api.v1.UserView>}
*/
const methodInfo_AuthService_GetMyUser = new grpc.web.AbstractClientBase.MethodInfo(
proto.caos.zitadel.auth.api.v1.UserView,
/**
* @param {!proto.google.protobuf.Empty} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.caos.zitadel.auth.api.v1.UserView.deserializeBinary
);
/**
* @param {!proto.google.protobuf.Empty} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.Error, ?proto.caos.zitadel.auth.api.v1.UserView)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.caos.zitadel.auth.api.v1.UserView>|undefined}
* The XHR Node Readable Stream
*/
proto.caos.zitadel.auth.api.v1.AuthServiceClient.prototype.getMyUser =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/caos.zitadel.auth.api.v1.AuthService/GetMyUser',
request,
metadata || {},
methodDescriptor_AuthService_GetMyUser,
callback);
};
/**
* @param {!proto.google.protobuf.Empty} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @return {!Promise<!proto.caos.zitadel.auth.api.v1.UserView>}
* A native promise that resolves to the response
*/
proto.caos.zitadel.auth.api.v1.AuthServicePromiseClient.prototype.getMyUser =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/caos.zitadel.auth.api.v1.AuthService/GetMyUser',
request,
metadata || {},
methodDescriptor_AuthService_GetMyUser);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<

View File

@@ -47,6 +47,12 @@ export class UserSessionView extends jspb.Message {
getSequence(): number;
setSequence(value: number): void;
getLoginName(): string;
setLoginName(value: string): void;
getDisplayName(): string;
setDisplayName(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): UserSessionView.AsObject;
static toObject(includeInstance: boolean, msg: UserSessionView): UserSessionView.AsObject;
@@ -63,10 +69,12 @@ export namespace UserSessionView {
userId: string,
userName: string,
sequence: number,
loginName: string,
displayName: string,
}
}
export class User extends jspb.Message {
export class UserView extends jspb.Message {
getId(): string;
setId(value: string): void;
@@ -78,11 +86,6 @@ export class User extends jspb.Message {
hasCreationDate(): boolean;
clearCreationDate(): void;
getActivationDate(): google_protobuf_timestamp_pb.Timestamp | undefined;
setActivationDate(value?: google_protobuf_timestamp_pb.Timestamp): void;
hasActivationDate(): boolean;
clearActivationDate(): void;
getChangeDate(): google_protobuf_timestamp_pb.Timestamp | undefined;
setChangeDate(value?: google_protobuf_timestamp_pb.Timestamp): void;
hasChangeDate(): boolean;
@@ -107,12 +110,12 @@ export class User extends jspb.Message {
getLastName(): string;
setLastName(value: string): void;
getNickName(): string;
setNickName(value: string): void;
getDisplayName(): string;
setDisplayName(value: string): void;
getNickName(): string;
setNickName(value: string): void;
getPreferredLanguage(): string;
setPreferredLanguage(value: string): void;
@@ -146,12 +149,12 @@ export class User extends jspb.Message {
getStreetAddress(): string;
setStreetAddress(value: string): void;
getPasswordChangeRequired(): boolean;
setPasswordChangeRequired(value: boolean): void;
getSequence(): number;
setSequence(value: number): void;
getResourceOwner(): string;
setResourceOwner(value: string): void;
getLoginNamesList(): Array<string>;
setLoginNamesList(value: Array<string>): void;
clearLoginNamesList(): void;
@@ -161,27 +164,26 @@ export class User extends jspb.Message {
setPreferredLoginName(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): User.AsObject;
static toObject(includeInstance: boolean, msg: User): User.AsObject;
static serializeBinaryToWriter(message: User, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): User;
static deserializeBinaryFromReader(message: User, reader: jspb.BinaryReader): User;
toObject(includeInstance?: boolean): UserView.AsObject;
static toObject(includeInstance: boolean, msg: UserView): UserView.AsObject;
static serializeBinaryToWriter(message: UserView, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): UserView;
static deserializeBinaryFromReader(message: UserView, reader: jspb.BinaryReader): UserView;
}
export namespace User {
export namespace UserView {
export type AsObject = {
id: string,
state: UserState,
creationDate?: google_protobuf_timestamp_pb.Timestamp.AsObject,
activationDate?: google_protobuf_timestamp_pb.Timestamp.AsObject,
changeDate?: google_protobuf_timestamp_pb.Timestamp.AsObject,
lastLogin?: google_protobuf_timestamp_pb.Timestamp.AsObject,
passwordChanged?: google_protobuf_timestamp_pb.Timestamp.AsObject,
userName: string,
firstName: string,
lastName: string,
nickName: string,
displayName: string,
nickName: string,
preferredLanguage: string,
gender: Gender,
email: string,
@@ -193,8 +195,8 @@ export namespace User {
postalCode: string,
region: string,
streetAddress: string,
passwordChangeRequired: boolean,
sequence: number,
resourceOwner: string,
loginNamesList: Array<string>,
preferredLoginName: string,
}
@@ -1215,7 +1217,7 @@ export enum OIDCResponseType {
OIDCRESPONSETYPE_ID_TOKEN_TOKEN = 2,
}
export enum UserState {
USERSTATE_UNSPECIEFIED = 0,
USERSTATE_UNSPECIFIED = 0,
USERSTATE_ACTIVE = 1,
USERSTATE_INACTIVE = 2,
USERSTATE_DELETED = 3,

File diff suppressed because it is too large Load Diff

View File

@@ -6054,7 +6054,7 @@ proto.caos.zitadel.management.api.v1.UserView.toObject = function(includeInstanc
sequence: jspb.Message.getFieldWithDefault(msg, 23, 0),
resourceOwner: jspb.Message.getFieldWithDefault(msg, 24, ""),
loginNamesList: jspb.Message.getRepeatedField(msg, 25),
preferredLoginName: jspb.Message.getFieldWithDefault(msg, 27, "")
preferredLoginName: jspb.Message.getFieldWithDefault(msg, 26, "")
};
if (includeInstance) {
@@ -6195,7 +6195,7 @@ proto.caos.zitadel.management.api.v1.UserView.deserializeBinaryFromReader = func
var value = /** @type {string} */ (reader.readString());
msg.addLoginNames(value);
break;
case 27:
case 26:
var value = /** @type {string} */ (reader.readString());
msg.setPreferredLoginName(value);
break;
@@ -6410,7 +6410,7 @@ proto.caos.zitadel.management.api.v1.UserView.serializeBinaryToWriter = function
f = message.getPreferredLoginName();
if (f.length > 0) {
writer.writeString(
27,
26,
f
);
}
@@ -6886,17 +6886,17 @@ proto.caos.zitadel.management.api.v1.UserView.prototype.clearLoginNamesList = fu
/**
* optional string preferred_login_name = 27;
* optional string preferred_login_name = 26;
* @return {string}
*/
proto.caos.zitadel.management.api.v1.UserView.prototype.getPreferredLoginName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 27, ""));
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 26, ""));
};
/** @param {string} value */
proto.caos.zitadel.management.api.v1.UserView.prototype.setPreferredLoginName = function(value) {
jspb.Message.setProto3StringField(this, 27, value);
jspb.Message.setProto3StringField(this, 26, value);
};

View File

@@ -34,6 +34,7 @@ import {
UserSearchQuery,
UserSearchRequest,
UserSearchResponse,
UserView,
} from '../proto/generated/management_pb';
import { GrpcBackendService } from './grpc-backend.service';
import { GrpcService, RequestFactory, ResponseMapper } from './grpc.service';
@@ -82,7 +83,7 @@ export class MgmtUserService {
);
}
public async GetUserByID(id: string): Promise<User> {
public async GetUserByID(id: string): Promise<UserView> {
const req = new UserID();
req.setId(id);
return await this.request(

View File

@@ -4,6 +4,7 @@ import { Metadata } from 'grpc-web';
import { ManagementServicePromiseClient } from '../proto/generated/management_grpc_web_pb';
import {
AddOrgDomainRequest,
AddOrgMemberRequest,
Iam,
Org,
@@ -29,6 +30,7 @@ import {
PasswordLockoutPolicyUpdate,
ProjectGrant,
ProjectGrantCreate,
RemoveOrgDomainRequest,
RemoveOrgMemberRequest,
} from '../proto/generated/management_pb';
import { GrpcBackendService } from './grpc-backend.service';
@@ -74,6 +76,26 @@ export class OrgService {
);
}
public async AddMyOrgDomain(domain: string): Promise<OrgDomain> {
const req: AddOrgDomainRequest = new AddOrgDomainRequest();
req.setDomain(domain);
return await this.request(
c => c.addMyOrgDomain,
req,
f => f,
);
}
public async RemoveMyOrgDomain(domain: string): Promise<Empty> {
const req: RemoveOrgDomainRequest = new RemoveOrgDomainRequest();
req.setDomain(domain);
return await this.request(
c => c.removeMyOrgDomain,
req,
f => f,
);
}
public async SearchMyOrgDomains(offset: number, limit: number, queryList?: OrgDomainSearchQuery[]):
Promise<OrgDomainSearchResponse> {
const req: OrgDomainSearchRequest = new OrgDomainSearchRequest();

View File

@@ -185,7 +185,7 @@
"LISTDESCRIPTION":"Wählen Sie die Organisation für den neuen Kontext aus!",
"ACTIVE":"AKTIV",
"CREATE":"Organisation erstellen",
"ORGDETAIL_TITLE":"Geben Sie den Namen und die Domain für die neue Organisatino ein",
"ORGDETAIL_TITLE":"Geben Sie den Namen und die Domain für die neue Organisation ein",
"ORGDOMAIN_TITLE":"Organisations Domain Verifikation",
"ORGDOMAIN_VERIFICATION":"Stellen Sie Ihre Webdomain bereit und überprüfen Sie deren Besitz indem sie eine Bestätigungsdatei herunterladen und unter der unten angegebenen URL hochladen. Klicken Sie zum Abschluss auf die Schaltfläche, um diese zu überprüfen.",
"ORGDOMAIN_VERIFICATION_SKIP":"Sie können die Überprüfung vorerst überspringen und Ihre Organisation weiter erstellen. Um Ihre Organisation jedoch verwenden zu können, muss dieser Schritt abgeschlossen sein!",
@@ -193,6 +193,10 @@
"DOWNLOAD_FILE":"File download",
"SELECTORGTOOLTIP":"Wähle diese Organisation"
},
"DOMAINS": {
"TITLE":"Domains",
"DESCRIPTION":"Konfigurieren Sie Ihre Domains mit denen sich Ihre Nutzer einloggen können."
},
"STATE": {
"0": "nicht definiert",
"1": "aktiv",

View File

@@ -193,6 +193,10 @@
"DOWNLOAD_FILE":"Download file",
"SELECTORGTOOLTIP":"Select this organization"
},
"DOMAINS": {
"TITLE":"Domains",
"DESCRIPTION":"Configure your domains on which your users will be registered."
},
"STATE": {
"0": "nicht definiert",
"1": "aktiv",