mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 03:24:26 +00:00
fix: user init mail (for wrong email) (#891)
* add resendInitialMail * disable email notifications (when not initialised) * fix resend init mail * add tests * cleanup * cleanup * fix tests * add resend trigger, dialog * refactor contact component, add sendinitmail fnc * skip email if empty * reload user on phone email changes, i18n warndialog on dl * lint * rebuild mgmt proto * remove initial focus * Update console/src/assets/i18n/de.json Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
parent
69c39b5eb2
commit
376fba72d8
@ -4,6 +4,7 @@
|
|||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
|
min-width: 350px;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
.action {
|
.action {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ok-button {
|
.ok-button {
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,15 @@
|
|||||||
|
|
||||||
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||||
<app-contact *ngIf="user?.human" [human]="user.human" (savedPhone)="savePhone($event)"
|
<button card-actions mat-icon-button (click)="refreshUser()">
|
||||||
(savedEmail)="saveEmail($event)" (enteredPhoneCode)="enteredPhoneCode($event)"
|
<mat-icon>refresh</mat-icon>
|
||||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
</button>
|
||||||
(resendPhoneVerification)="resendPhoneVerification()"></app-contact>
|
<app-contact *ngIf="user?.human" [human]="user.human" [state]="user.state" canWrite="true"
|
||||||
|
[userStateEnum]="UserState" (editType)="openEditDialog($event)"
|
||||||
|
(enteredPhoneCode)="enteredPhoneCode($event)" (deletedPhone)="deletePhone()"
|
||||||
|
(resendEmailVerification)="resendEmailVerification()"
|
||||||
|
(resendPhoneVerification)="resendPhoneVerification()">
|
||||||
|
</app-contact>
|
||||||
</app-card>
|
</app-card>
|
||||||
|
|
||||||
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
||||||
|
@ -98,3 +98,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resendemail {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
@ -1,12 +1,24 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||||
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||||
import { Gender, UserAddress, UserEmail, UserPhone, UserProfile, UserView } from 'src/app/proto/generated/auth_pb';
|
import {
|
||||||
|
Gender,
|
||||||
|
UserAddress,
|
||||||
|
UserEmail,
|
||||||
|
UserPhone,
|
||||||
|
UserProfile,
|
||||||
|
UserState,
|
||||||
|
UserView,
|
||||||
|
} from 'src/app/proto/generated/auth_pb';
|
||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { EditDialogType } from '../user-detail/user-detail.component';
|
||||||
|
import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-auth-user-detail',
|
selector: 'app-auth-user-detail',
|
||||||
templateUrl: './auth-user-detail.component.html',
|
templateUrl: './auth-user-detail.component.html',
|
||||||
@ -26,6 +38,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
|
|
||||||
public ChangeType: any = ChangeType;
|
public ChangeType: any = ChangeType;
|
||||||
public userLoginMustBeDomain: boolean = false;
|
public userLoginMustBeDomain: boolean = false;
|
||||||
|
public UserState: any = UserState;
|
||||||
|
|
||||||
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
||||||
|
|
||||||
@ -33,8 +46,13 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
public userService: GrpcAuthService,
|
public userService: GrpcAuthService,
|
||||||
|
private dialog: MatDialog,
|
||||||
) {
|
) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.refreshUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshUser(): void {
|
||||||
this.userService.GetMyUser().then(user => {
|
this.userService.GetMyUser().then(user => {
|
||||||
this.user = user.toObject();
|
this.user = user.toObject();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@ -81,6 +99,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
||||||
if (this.user.human) {
|
if (this.user.human) {
|
||||||
this.user.human.email = data.toObject().email;
|
this.user.human.email = data.toObject().email;
|
||||||
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@ -90,6 +109,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
public enteredPhoneCode(code: string): void {
|
public enteredPhoneCode(code: string): void {
|
||||||
this.userService.VerifyMyUserPhone(code).then(() => {
|
this.userService.VerifyMyUserPhone(code).then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||||
|
this.refreshUser();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
@ -99,14 +119,6 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
this.translate.use(language);
|
this.translate.use(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
public resendEmailVerification(): void {
|
|
||||||
this.userService.ResendEmailVerification().then(() => {
|
|
||||||
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public resendPhoneVerification(): void {
|
public resendPhoneVerification(): void {
|
||||||
this.userService.ResendPhoneVerification().then(() => {
|
this.userService.ResendPhoneVerification().then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
|
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
|
||||||
@ -115,11 +127,20 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resendEmailVerification(): void {
|
||||||
|
this.userService.ResendMyEmailVerificationMail().then(() => {
|
||||||
|
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public deletePhone(): void {
|
public deletePhone(): void {
|
||||||
this.userService.RemoveMyUserPhone().then(() => {
|
this.userService.RemoveMyUserPhone().then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
||||||
if (this.user.human) {
|
if (this.user.human) {
|
||||||
this.user.human.phone = '';
|
this.user.human.phone = '';
|
||||||
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@ -133,10 +154,54 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||||
if (this.user.human) {
|
if (this.user.human) {
|
||||||
this.user.human.phone = data.toObject().phone;
|
this.user.human.phone = data.toObject().phone;
|
||||||
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(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,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRefEmail.afterClosed().subscribe(resp => {
|
||||||
|
if (resp) {
|
||||||
|
this.saveEmail(resp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span class="title">{{data.titleKey | translate}}</span>
|
||||||
|
</h1>
|
||||||
|
<p class="desc">{{data.descriptionKey | translate}}</p>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<mat-form-field class="formfield">
|
||||||
|
<mat-label>{{data.labelKey | translate }}</mat-label>
|
||||||
|
<input matInput [(ngModel)]="value" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions class="action">
|
||||||
|
<button cdkFocusInitial color="primary" mat-button class="ok-button" (click)="closeDialog()">
|
||||||
|
{{data.cancelKey | translate}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button [disabled]="!value" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
|
||||||
|
(click)="closeDialogWithValue(value)">
|
||||||
|
{{data.confirmKey | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,21 @@
|
|||||||
|
.formfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.ok-button {
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CodeDialogComponent } from './code-dialog.component';
|
||||||
|
|
||||||
|
describe('CodeDialogComponent', () => {
|
||||||
|
let component: CodeDialogComponent;
|
||||||
|
let fixture: ComponentFixture<CodeDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [CodeDialogComponent],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CodeDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-email-dialog',
|
||||||
|
templateUrl: './edit-dialog.component.html',
|
||||||
|
styleUrls: ['./edit-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class EditDialogComponent {
|
||||||
|
public value: string = '';
|
||||||
|
constructor(public dialogRef: MatDialogRef<EditDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||||
|
this.value = data.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog(email: string = ''): void {
|
||||||
|
this.dialogRef.close(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialogWithValue(value: string = ''): void {
|
||||||
|
this.dialogRef.close(value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span class="title">{{'USER.SENDEMAILDIALOG.TITLE' | translate}} {{data?.number}}</span>
|
||||||
|
</h1>
|
||||||
|
<p class="desc">{{'USER.SENDEMAILDIALOG.DESCRIPTION' | translate}}</p>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<mat-form-field class="formfield">
|
||||||
|
<mat-label>{{ 'USER.SENDEMAILDIALOG.NEWEMAIL' | translate }}</mat-label>
|
||||||
|
<input matInput [(ngModel)]="email" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions class="action">
|
||||||
|
<button color="primary" mat-button class="ok-button" (click)="closeDialog()">
|
||||||
|
{{'ACTIONS.CLOSE' | translate}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button cdkFocusInitial color="primary" mat-raised-button class="ok-button" (click)="closeDialogWithSend(email)">
|
||||||
|
{{'ACTIONS.SEND' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,21 @@
|
|||||||
|
.formfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.ok-button {
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CodeDialogComponent } from './code-dialog.component';
|
||||||
|
|
||||||
|
describe('CodeDialogComponent', () => {
|
||||||
|
let component: CodeDialogComponent;
|
||||||
|
let fixture: ComponentFixture<CodeDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [CodeDialogComponent],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CodeDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-resend-email-dialog',
|
||||||
|
templateUrl: './resend-email-dialog.component.html',
|
||||||
|
styleUrls: ['./resend-email-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class ResendEmailDialogComponent {
|
||||||
|
public email: string = '';
|
||||||
|
constructor(public dialogRef: MatDialogRef<ResendEmailDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||||
|
|
||||||
|
closeDialog(email: string = ''): void {
|
||||||
|
this.dialogRef.close(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialogWithSend(email: string = ''): void {
|
||||||
|
this.dialogRef.close({ send: true, email });
|
||||||
|
}
|
||||||
|
}
|
@ -1,69 +1,55 @@
|
|||||||
<div class="method-col">
|
<div class="method-col">
|
||||||
<div class="method-row">
|
<div class="method-row">
|
||||||
|
<div class="left">
|
||||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||||
|
<span class="name">*********</span>
|
||||||
|
|
||||||
<span>*********</span>
|
<ng-content select="[pwdAction]"></ng-content>
|
||||||
<div class="overflow">
|
</div>
|
||||||
<ng-content select="[phoneAction]"></ng-content>
|
|
||||||
<a [disabled]="!canWrite" [routerLink]="['password']" mat-icon-button>
|
<div class="right">
|
||||||
<mat-icon class="icon">chevron_right</mat-icon>
|
<a matTooltip="{{'USER.PASSWORD.SET' | translate}}" [disabled]="!canWrite" [routerLink]="['password']"
|
||||||
|
mat-icon-button>
|
||||||
|
<i class="las la-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="method-row">
|
<div class="method-row">
|
||||||
|
<div class="left">
|
||||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||||
|
|
||||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
|
||||||
<div class="actions">
|
|
||||||
<span class="name">{{human?.email}}</span>
|
<span class="name">{{human?.email}}</span>
|
||||||
<mat-icon class="icon" *ngIf="human?.isEmailVerified" color="primary" aria-hidden="false"
|
<span *ngIf="human?.isEmailVerified" class="state verified">{{'USER.EMAILVERIFIED' | translate}}</span>
|
||||||
aria-label="verified icon">
|
<div *ngIf="!human?.isEmailVerified" class="block">
|
||||||
check_circle_outline</mat-icon>
|
<span class="state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
|
||||||
<ng-container *ngIf="human?.email && !human?.isEmailVerified">
|
|
||||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
<ng-container *ngIf="human?.email">
|
||||||
highlight_off
|
<a *ngIf="canWrite && state != userStateEnum?.USERSTATE_INITIAL" class="verify"
|
||||||
</mat-icon>
|
matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||||
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
|
||||||
(click)="emitEmailVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
(click)="emitEmailVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<ng-content select="[emailAction]"></ng-content>
|
||||||
<button [disabled]="!canWrite" (click)="emailEditState = true" mat-icon-button>
|
</div>
|
||||||
<mat-icon class="icon">edit</mat-icon>
|
|
||||||
|
<div class="right">
|
||||||
|
<button [disabled]="!canWrite" (click)="openEditDialog(EditDialogType.EMAIL)" mat-icon-button>
|
||||||
|
<i class="las la-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
|
||||||
<ng-template #emailEdit>
|
|
||||||
<mat-form-field class="name">
|
|
||||||
<mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>
|
|
||||||
<input *ngIf="human && human.email !== undefined && human.email !== null" matInput
|
|
||||||
[(ngModel)]="human.email" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button (click)="emailEditState = false" mat-icon-button>
|
|
||||||
<mat-icon class="icon">close</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button *ngIf="human" [disabled]="!human.email" type="button" color="primary" (click)="saveEmail()"
|
|
||||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="method-row">
|
<div class="method-row">
|
||||||
|
<div class="left">
|
||||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||||
|
<span class="name">{{human?.phone ? human.phone : ('USER.PHONEEMPTY' | translate)}}</span>
|
||||||
|
<span *ngIf="human?.isPhoneVerified" class="state verified">{{'USER.PHONEVERIFIED' | translate}}</span>
|
||||||
|
<div *ngIf="!human?.isPhoneVerified" class="block">
|
||||||
|
<span class="state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
|
||||||
|
|
||||||
<ng-container *ngIf="!phoneEditState; else phoneEdit">
|
<ng-container *ngIf="human?.phone">
|
||||||
<div class="actions">
|
<a *ngIf="!disablePhoneCode && canWrite" class="verify"
|
||||||
<span class="name">{{human?.phone}}</span>
|
|
||||||
<mat-icon class="icon" *ngIf="human?.isPhoneVerified" color="primary" aria-hidden="false"
|
|
||||||
aria-label="verified icon">
|
|
||||||
check_circle_outline</mat-icon>
|
|
||||||
<ng-container *ngIf="human?.phone && !human?.isPhoneVerified">
|
|
||||||
<mat-icon class="icon" matTooltip="not verified" color="warn" aria-hidden="false"
|
|
||||||
aria-label="not verified icon">
|
|
||||||
highlight_off
|
|
||||||
</mat-icon>
|
|
||||||
<a *ngIf="!disablePhoneCode && !canWrite" class="verify"
|
|
||||||
matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
|
matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
|
||||||
(click)="enterCode()">{{'USER.LOGINMETHODS.ENTERCODE' | translate}}</a>
|
(click)="enterCode()">{{'USER.LOGINMETHODS.ENTERCODE' | translate}}</a>
|
||||||
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
|
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
|
||||||
@ -71,27 +57,18 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<ng-content select="[phoneAction]"></ng-content>
|
||||||
<button [disabled]="!canWrite" (click)="phoneEditState = true" mat-icon-button>
|
|
||||||
<mat-icon class="icon">edit</mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-template #phoneEdit>
|
<div class="right">
|
||||||
<mat-form-field class="name">
|
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" *ngIf="human && human.phone" color="warn"
|
||||||
<mat-label>{{ 'USER.PHONE' | translate }}</mat-label>
|
(click)="emitDeletePhone()" mat-icon-button>
|
||||||
<input *ngIf="human && human.phone !== undefined && human.phone !== null" matInput
|
|
||||||
[(ngModel)]="human.phone" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button (click)="phoneEditState = false" mat-icon-button>
|
|
||||||
<mat-icon class="icon">close</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button *ngIf="human && human.phone" color="warn" (click)="emitDeletePhone()" mat-icon-button>
|
|
||||||
<i class="las la-trash"></i>
|
<i class="las la-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="human" [disabled]="!human.phone" type="button" color="primary" (click)="savePhone()"
|
<button matTooltip="{{'ACTIONS.EDIT' | translate}}" [disabled]="!canWrite"
|
||||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
(click)="openEditDialog(EditDialogType.PHONE)" mat-icon-button>
|
||||||
</ng-template>
|
<i class="las la-edit"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -5,29 +5,57 @@
|
|||||||
|
|
||||||
.method-row {
|
.method-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
border-bottom: 1px solid #ffffff20;
|
border-bottom: 1px solid #ffffff20;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.actions {
|
.left {
|
||||||
flex: 1;
|
.label {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
min-width: 100px;
|
||||||
|
color: var(--grey);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
|
||||||
|
&.verified {
|
||||||
|
color: #85d996;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.notverified {
|
||||||
|
color: #ff4436;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex-basis: 70px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.verified-icon {
|
||||||
font-size: .9rem;
|
font-size: 1.2rem;
|
||||||
max-width: 100px;
|
line-height: 1.2rem;
|
||||||
color: var(--grey);
|
height: 1.2rem;
|
||||||
}
|
cursor: default;
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin: .5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.verify {
|
.verify {
|
||||||
@ -38,6 +66,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
word-wrap: none;
|
word-wrap: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
@ -46,6 +75,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overflow {
|
.mat-form-field-wrapper {
|
||||||
overflow: auto;
|
padding-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,48 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { HumanView as AuthHumanView } from 'src/app/proto/generated/auth_pb';
|
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||||
import { HumanView as MgmtHumanView } from 'src/app/proto/generated/management_pb';
|
import { HumanView as AuthHumanView, UserState as AuthUserState } from 'src/app/proto/generated/auth_pb';
|
||||||
|
import { HumanView as MgmtHumanView, UserState as MgmtUserState } from 'src/app/proto/generated/management_pb';
|
||||||
|
|
||||||
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
|
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
|
||||||
|
import { EditDialogType } from '../user-detail/user-detail.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-contact',
|
selector: 'app-contact',
|
||||||
templateUrl: './contact.component.html',
|
templateUrl: './contact.component.html',
|
||||||
styleUrls: ['./contact.component.scss'],
|
styleUrls: ['./contact.component.scss'],
|
||||||
})
|
})
|
||||||
export class ContactComponent implements OnInit {
|
export class ContactComponent {
|
||||||
@Input() disablePhoneCode: boolean = false;
|
@Input() disablePhoneCode: boolean = false;
|
||||||
@Input() canWrite: boolean = false;
|
@Input() canWrite: boolean = false;
|
||||||
@Input() human!: AuthHumanView.AsObject | MgmtHumanView.AsObject;
|
@Input() human!: AuthHumanView.AsObject | MgmtHumanView.AsObject;
|
||||||
@Output() savedPhone: EventEmitter<string> = new EventEmitter();
|
@Input() state!: AuthUserState | MgmtUserState;
|
||||||
@Output() savedEmail: EventEmitter<string> = new EventEmitter();
|
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter();
|
||||||
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
|
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
|
||||||
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
|
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
|
||||||
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
|
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
|
||||||
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
|
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
|
||||||
|
@Input() public userStateEnum: any;
|
||||||
|
|
||||||
public emailEditState: boolean = false;
|
public EditDialogType: any = EditDialogType;
|
||||||
public phoneEditState: boolean = false;
|
|
||||||
constructor(private dialog: MatDialog) { }
|
constructor(private dialog: MatDialog) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
savePhone(): void {
|
|
||||||
this.phoneEditState = false;
|
|
||||||
this.savedPhone.emit(this.human.phone);
|
|
||||||
}
|
|
||||||
|
|
||||||
emitDeletePhone(): void {
|
emitDeletePhone(): void {
|
||||||
this.phoneEditState = false;
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'USER.LOGINMETHODS.PHONE.DELETETITLE',
|
||||||
|
descriptionKey: 'USER.LOGINMETHODS.PHONE.DELETEDESC',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
|
if (resp) {
|
||||||
this.deletedPhone.emit();
|
this.deletedPhone.emit();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
saveEmail(): void {
|
|
||||||
this.emailEditState = false;
|
|
||||||
this.savedEmail.emit(this.human.email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitEmailVerification(): void {
|
emitEmailVerification(): void {
|
||||||
@ -67,4 +69,8 @@ export class ContactComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openEditDialog(type: EditDialogType): void {
|
||||||
|
this.editType.emit(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,10 @@ import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.com
|
|||||||
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
|
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
|
||||||
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
|
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
|
||||||
import { DialogOtpComponent } from './auth-user-detail/dialog-otp/dialog-otp.component';
|
import { DialogOtpComponent } from './auth-user-detail/dialog-otp/dialog-otp.component';
|
||||||
|
import { EditDialogComponent } from './auth-user-detail/edit-dialog/edit-dialog.component';
|
||||||
|
import { ResendEmailDialogComponent } from './auth-user-detail/resend-email-dialog/resend-email-dialog.component';
|
||||||
import { ThemeSettingComponent } from './auth-user-detail/theme-setting/theme-setting.component';
|
import { ThemeSettingComponent } from './auth-user-detail/theme-setting/theme-setting.component';
|
||||||
|
import { ContactComponent } from './contact/contact.component';
|
||||||
import { DetailFormMachineModule } from './detail-form-machine/detail-form-machine.module';
|
import { DetailFormMachineModule } from './detail-form-machine/detail-form-machine.module';
|
||||||
import { DetailFormModule } from './detail-form/detail-form.module';
|
import { DetailFormModule } from './detail-form/detail-form.module';
|
||||||
import { ExternalIdpsComponent } from './external-idps/external-idps.component';
|
import { ExternalIdpsComponent } from './external-idps/external-idps.component';
|
||||||
@ -46,13 +49,13 @@ import { PasswordComponent } from './password/password.component';
|
|||||||
import { UserDetailRoutingModule } from './user-detail-routing.module';
|
import { UserDetailRoutingModule } from './user-detail-routing.module';
|
||||||
import { UserDetailComponent } from './user-detail/user-detail.component';
|
import { UserDetailComponent } from './user-detail/user-detail.component';
|
||||||
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||||
import { ContactComponent } from './contact/contact.component';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AuthUserDetailComponent,
|
AuthUserDetailComponent,
|
||||||
UserDetailComponent,
|
UserDetailComponent,
|
||||||
DialogOtpComponent,
|
DialogOtpComponent,
|
||||||
|
EditDialogComponent,
|
||||||
AuthUserMfaComponent,
|
AuthUserMfaComponent,
|
||||||
UserMfaComponent,
|
UserMfaComponent,
|
||||||
ThemeSettingComponent,
|
ThemeSettingComponent,
|
||||||
@ -62,6 +65,7 @@ import { ContactComponent } from './contact/contact.component';
|
|||||||
MachineKeysComponent,
|
MachineKeysComponent,
|
||||||
ExternalIdpsComponent,
|
ExternalIdpsComponent,
|
||||||
ContactComponent,
|
ContactComponent,
|
||||||
|
ResendEmailDialogComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
UserDetailRoutingModule,
|
UserDetailRoutingModule,
|
||||||
|
@ -67,15 +67,20 @@
|
|||||||
|
|
||||||
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||||
|
<button card-actions mat-icon-button (click)="refreshUser()">
|
||||||
|
<mat-icon>refresh</mat-icon>
|
||||||
|
</button>
|
||||||
<app-contact disablePhoneCode="true"
|
<app-contact disablePhoneCode="true"
|
||||||
[canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)" *ngIf="user?.human"
|
[canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)" *ngIf="user?.human"
|
||||||
[human]="user.human" (savedPhone)="savePhone($event)" (savedEmail)="saveEmail($event)"
|
[human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
|
||||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
(resendEmailVerification)="resendEmailVerification()"
|
||||||
(resendPhoneVerification)="resendPhoneVerification()">
|
(resendPhoneVerification)="resendPhoneVerification()">
|
||||||
<button phoneAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
<button pwdAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
||||||
mat-stroked-button color="primary"
|
mat-stroked-button color="primary"
|
||||||
*ngIf="user.state === UserState.USERSTATE_INITIAL">{{ 'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
|
*ngIf="user.state === UserState.USERSTATE_INITIAL">{{ 'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
|
||||||
|
<button emailAction class="resendemail" *ngIf="user.state == UserState.USERSTATE_INITIAL"
|
||||||
|
mat-stroked-button color="primary"
|
||||||
|
(click)="resendInitEmail()">{{'USER.RESENDINITIALEMAIL' | translate}}</button>
|
||||||
</app-contact>
|
</app-contact>
|
||||||
</app-card>
|
</app-card>
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { take } from 'rxjs/operators';
|
||||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||||
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||||
@ -21,18 +21,24 @@ import {
|
|||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { EditDialogComponent } from '../auth-user-detail/edit-dialog/edit-dialog.component';
|
||||||
|
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
|
||||||
|
|
||||||
|
export enum EditDialogType {
|
||||||
|
PHONE = 1,
|
||||||
|
EMAIL = 2,
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-detail',
|
selector: 'app-user-detail',
|
||||||
templateUrl: './user-detail.component.html',
|
templateUrl: './user-detail.component.html',
|
||||||
styleUrls: ['./user-detail.component.scss'],
|
styleUrls: ['./user-detail.component.scss'],
|
||||||
})
|
})
|
||||||
export class UserDetailComponent implements OnInit, OnDestroy {
|
export class UserDetailComponent implements OnInit {
|
||||||
public user!: UserView.AsObject;
|
public user!: UserView.AsObject;
|
||||||
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
|
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
|
||||||
public languages: string[] = ['de', 'en'];
|
public languages: string[] = ['de', 'en'];
|
||||||
|
|
||||||
private subscription: Subscription = new Subscription();
|
|
||||||
|
|
||||||
public ChangeType: any = ChangeType;
|
public ChangeType: any = ChangeType;
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
|
|
||||||
@ -40,6 +46,8 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
public copied: string = '';
|
public copied: string = '';
|
||||||
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
||||||
|
|
||||||
|
public EditDialogType: any = EditDialogType;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -49,8 +57,8 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public ngOnInit(): void {
|
refreshUser(): void {
|
||||||
this.subscription = this.route.params.subscribe(params => {
|
this.route.params.pipe(take(1)).subscribe(params => {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
this.mgmtUserService.GetUserByID(id).then(user => {
|
this.mgmtUserService.GetUserByID(id).then(user => {
|
||||||
this.user = user.toObject();
|
this.user = user.toObject();
|
||||||
@ -60,8 +68,8 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy(): void {
|
public ngOnInit(): void {
|
||||||
this.subscription.unsubscribe();
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public changeState(newState: UserState): void {
|
public changeState(newState: UserState): void {
|
||||||
@ -149,6 +157,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
||||||
if (this.user.human) {
|
if (this.user.human) {
|
||||||
this.user.human.phone = '';
|
this.user.human.phone = '';
|
||||||
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@ -158,9 +167,10 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
public saveEmail(email: string): void {
|
public saveEmail(email: string): void {
|
||||||
if (this.user.id && email) {
|
if (this.user.id && email) {
|
||||||
this.mgmtUserService.SaveUserEmail(this.user.id, email).then((data: UserEmail) => {
|
this.mgmtUserService.SaveUserEmail(this.user.id, email).then((data: UserEmail) => {
|
||||||
this.toast.showInfo('USER.TOAST.EMAILSENT', true);
|
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
||||||
if (this.user.human) {
|
if (this.user.human) {
|
||||||
this.user.human.email = data.toObject().email;
|
this.user.human.email = data.toObject().email;
|
||||||
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@ -175,6 +185,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||||
if (this.user.human) {
|
if (this.user.human) {
|
||||||
this.user.human.phone = data.toObject().phone;
|
this.user.human.phone = data.toObject().phone;
|
||||||
|
this.refreshUser();
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@ -217,4 +228,63 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resendInitEmail(): void {
|
||||||
|
const dialogRef = this.dialog.open(ResendEmailDialogComponent, {
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
|
if (resp.send && this.user.id) {
|
||||||
|
this.mgmtUserService.ResendInitialMail(this.user.id, resp.email ?? '').then(() => {
|
||||||
|
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
|
||||||
|
}).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,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRefEmail.afterClosed().subscribe(resp => {
|
||||||
|
if (resp) {
|
||||||
|
this.saveEmail(resp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,6 +249,12 @@ export class GrpcAuthService {
|
|||||||
return this.grpcService.auth.changeMyUserEmail(req);
|
return this.grpcService.auth.changeMyUserEmail(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResendMyEmailVerificationMail(): Promise<Empty> {
|
||||||
|
return this.grpcService.auth.resendMyEmailVerificationMail(
|
||||||
|
new Empty(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public RemoveMyUserPhone(): Promise<Empty> {
|
public RemoveMyUserPhone(): Promise<Empty> {
|
||||||
return this.grpcService.auth.removeMyUserPhone(
|
return this.grpcService.auth.removeMyUserPhone(
|
||||||
new Empty(),
|
new Empty(),
|
||||||
|
@ -40,6 +40,7 @@ import {
|
|||||||
IdpSearchResponse,
|
IdpSearchResponse,
|
||||||
IdpUpdate,
|
IdpUpdate,
|
||||||
IdpView,
|
IdpView,
|
||||||
|
InitialMailRequest,
|
||||||
LoginName,
|
LoginName,
|
||||||
LoginPolicy,
|
LoginPolicy,
|
||||||
LoginPolicyRequest,
|
LoginPolicyRequest,
|
||||||
@ -786,6 +787,16 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.resendEmailVerificationMail(req);
|
return this.grpcService.mgmt.resendEmailVerificationMail(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResendInitialMail(userId: string, newemail: string): Promise<Empty> {
|
||||||
|
const req = new InitialMailRequest();
|
||||||
|
if (newemail) {
|
||||||
|
req.setEmail(newemail);
|
||||||
|
}
|
||||||
|
req.setId(userId);
|
||||||
|
|
||||||
|
return this.grpcService.mgmt.resendInitialMail(req);
|
||||||
|
}
|
||||||
|
|
||||||
public ResendPhoneVerification(id: string): Promise<any> {
|
public ResendPhoneVerification(id: string): Promise<any> {
|
||||||
const req = new UserID();
|
const req = new UserID();
|
||||||
req.setId(id);
|
req.setId(id);
|
||||||
|
@ -77,7 +77,9 @@
|
|||||||
"LOGIN":"Einloggen",
|
"LOGIN":"Einloggen",
|
||||||
"EDIT":"Bearbeiten",
|
"EDIT":"Bearbeiten",
|
||||||
"PIN":"Anpinnen",
|
"PIN":"Anpinnen",
|
||||||
"CONFIGURE":"Konfigurieren"
|
"CONFIGURE":"Konfigurieren",
|
||||||
|
"SEND":"Senden",
|
||||||
|
"NEWVALUE":"Neuer Wert"
|
||||||
},
|
},
|
||||||
"ERRORS": {
|
"ERRORS": {
|
||||||
"REQUIRED": "Bitte fülle alle benötigten Felder aus.",
|
"REQUIRED": "Bitte fülle alle benötigten Felder aus.",
|
||||||
@ -112,6 +114,11 @@
|
|||||||
"DELETE_TITLE":"User löschen",
|
"DELETE_TITLE":"User löschen",
|
||||||
"DELETE_DESCRIPTION":"Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?"
|
"DELETE_DESCRIPTION":"Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?"
|
||||||
},
|
},
|
||||||
|
"SENDEMAILDIALOG":{
|
||||||
|
"TITLE":"Email Benachrichtigung senden",
|
||||||
|
"DESCRIPTION":"Klicken Sie den untenstehenden Button um ein verifizierungs Email an die aktuelle Adresse zu versenden oder ändern Sie die Emailadresse in dem Feld.",
|
||||||
|
"NEWEMAIL":"Neue Email"
|
||||||
|
},
|
||||||
"TABLE":{
|
"TABLE":{
|
||||||
"DEACTIVATE":"Deaktivieren",
|
"DEACTIVATE":"Deaktivieren",
|
||||||
"ACTIVATE":"Aktivieren",
|
"ACTIVATE":"Aktivieren",
|
||||||
@ -241,6 +248,10 @@
|
|||||||
},
|
},
|
||||||
"EMAIL": "E-Mail",
|
"EMAIL": "E-Mail",
|
||||||
"PHONE": "Telefonnummer",
|
"PHONE": "Telefonnummer",
|
||||||
|
"PHONEEMPTY":"Keine Telefonnummer hinterlegt",
|
||||||
|
"PHONEVERIFIED":"Telefonnummer bestätigt.",
|
||||||
|
"EMAILVERIFIED":"Email verifiziert",
|
||||||
|
"NOTVERIFIED":"nicht verifiziert",
|
||||||
"PREFERRED_LOGINNAME":"Bevorzugter Loginname",
|
"PREFERRED_LOGINNAME":"Bevorzugter Loginname",
|
||||||
"LOGINMETHODS": {
|
"LOGINMETHODS": {
|
||||||
"TITLE": "Kontaktinformationen",
|
"TITLE": "Kontaktinformationen",
|
||||||
@ -248,12 +259,18 @@
|
|||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"TITLE": "E-Mail",
|
"TITLE": "E-Mail",
|
||||||
"VALID": "Validiert",
|
"VALID": "Validiert",
|
||||||
"RESEND": "Verifikationsmail erneut senden"
|
"RESEND": "Verifikationsmail erneut senden",
|
||||||
|
"EDITTITLE":"Email ändern",
|
||||||
|
"EDITDESC":"Geben Sie die neue Email in dem darunterliegenden Feld ein!"
|
||||||
},
|
},
|
||||||
"PHONE": {
|
"PHONE": {
|
||||||
"TITLE": "Telefon",
|
"TITLE": "Telefon",
|
||||||
"VALID": "Validiert",
|
"VALID": "Validiert",
|
||||||
"RESEND": "Verifikationsnachricht erneut senden"
|
"RESEND": "Verifikationsnachricht erneut senden",
|
||||||
|
"EDITTITLE":"Nummer ändern",
|
||||||
|
"EDITDESC":"Geben Sie die neue Nummer in dem darunterliegenden Feld ein!",
|
||||||
|
"DELETETITLE":"Telefonnummer löschen",
|
||||||
|
"DELETEDESC":"Wollen Sie die Telefonnummer wirklich löschen?"
|
||||||
},
|
},
|
||||||
"RESENDCODE": "Code erneut senden",
|
"RESENDCODE": "Code erneut senden",
|
||||||
"ENTERCODE":"Verifizieren",
|
"ENTERCODE":"Verifizieren",
|
||||||
@ -294,10 +311,13 @@
|
|||||||
"SIGNEDOUT_BTN":"Anmelden",
|
"SIGNEDOUT_BTN":"Anmelden",
|
||||||
"EDITACCOUNT":"Konto bearbeiten",
|
"EDITACCOUNT":"Konto bearbeiten",
|
||||||
"ADDACCOUNT":"Konto hinzufügen",
|
"ADDACCOUNT":"Konto hinzufügen",
|
||||||
|
"RESENDINITIALEMAIL":"Neue Initialisierungsmail senden",
|
||||||
|
"RESENDEMAILNOTIFICATION":"Benachrichtigungsmail senden",
|
||||||
"TOAST": {
|
"TOAST": {
|
||||||
"CREATED":"Benutzer erfolgreich erstellt.",
|
"CREATED":"Benutzer erfolgreich erstellt.",
|
||||||
"SAVED":"Profil gespeichert.",
|
"SAVED":"Profil gespeichert.",
|
||||||
"EMAILSAVED":"E-Mail gespeichert.",
|
"EMAILSAVED":"E-Mail gespeichert.",
|
||||||
|
"INITEMAILSENT":"Initialisierung Email gesendet.",
|
||||||
"PHONESAVED":"Telefonnummer gespeichert.",
|
"PHONESAVED":"Telefonnummer gespeichert.",
|
||||||
"PHONEREMOVED":"Telefonnummer gelöscht.",
|
"PHONEREMOVED":"Telefonnummer gelöscht.",
|
||||||
"PHONEVERIFIED":"Telefonnummer bestätigt.",
|
"PHONEVERIFIED":"Telefonnummer bestätigt.",
|
||||||
|
@ -77,7 +77,9 @@
|
|||||||
"LOGIN":"Login",
|
"LOGIN":"Login",
|
||||||
"EDIT":"Edit",
|
"EDIT":"Edit",
|
||||||
"PIN":"Pin / Unpin",
|
"PIN":"Pin / Unpin",
|
||||||
"CONFIGURE":"Configure"
|
"CONFIGURE":"Configure",
|
||||||
|
"SEND":"Send",
|
||||||
|
"NEWVALUE":"New Value"
|
||||||
},
|
},
|
||||||
"ERRORS": {
|
"ERRORS": {
|
||||||
"REQUIRED": "Some required fields are missing.",
|
"REQUIRED": "Some required fields are missing.",
|
||||||
@ -112,6 +114,11 @@
|
|||||||
"DELETE_TITLE":"Delete User",
|
"DELETE_TITLE":"Delete User",
|
||||||
"DELETE_DESCRIPTION":"You are about to permanently delete a user. Are you sure?"
|
"DELETE_DESCRIPTION":"You are about to permanently delete a user. Are you sure?"
|
||||||
},
|
},
|
||||||
|
"SENDEMAILDIALOG":{
|
||||||
|
"TITLE":"Send Email Notification",
|
||||||
|
"DESCRIPTION":"Click the button below to send a notification to the current email address or change the email address in the field.",
|
||||||
|
"NEWEMAIL":"New email address"
|
||||||
|
},
|
||||||
"TABLE":{
|
"TABLE":{
|
||||||
"DEACTIVATE":"Deactivate",
|
"DEACTIVATE":"Deactivate",
|
||||||
"ACTIVATE":"Activate",
|
"ACTIVATE":"Activate",
|
||||||
@ -240,7 +247,11 @@
|
|||||||
"NOTEQUAL":"The passwords provided do not match."
|
"NOTEQUAL":"The passwords provided do not match."
|
||||||
},
|
},
|
||||||
"EMAIL": "E-mail",
|
"EMAIL": "E-mail",
|
||||||
"PHONE": "Phone Number",
|
"PHONE": "Phonenumber",
|
||||||
|
"PHONEEMPTY":"No phonenumber defined",
|
||||||
|
"PHONEVERIFIED":"Phonenumber verified.",
|
||||||
|
"EMAILVERIFIED":"Email verified",
|
||||||
|
"NOTVERIFIED":"not verified",
|
||||||
"PREFERRED_LOGINNAME":"Preferred Loginname",
|
"PREFERRED_LOGINNAME":"Preferred Loginname",
|
||||||
"LOGINMETHODS": {
|
"LOGINMETHODS": {
|
||||||
"TITLE": "Contact Information",
|
"TITLE": "Contact Information",
|
||||||
@ -248,12 +259,18 @@
|
|||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"TITLE": "E-mail",
|
"TITLE": "E-mail",
|
||||||
"VALID": "validated",
|
"VALID": "validated",
|
||||||
"RESEND": "Resend Verification E-mail"
|
"RESEND": "Resend Verification E-mail",
|
||||||
|
"EDITTITLE":"Change Email",
|
||||||
|
"EDITDESC":"Enter the new email in the field below."
|
||||||
},
|
},
|
||||||
"PHONE": {
|
"PHONE": {
|
||||||
"TITLE": "Phone",
|
"TITLE": "Phone",
|
||||||
"VALID": "validated",
|
"VALID": "validated",
|
||||||
"RESEND": "Resend Verification Text Message"
|
"RESEND": "Resend Verification Text Message",
|
||||||
|
"EDITTITLE":"Change number",
|
||||||
|
"EDITDESC":"Enter the new phonenumber in the field below.",
|
||||||
|
"DELETETITLE":"Delete Phonenumber",
|
||||||
|
"DELETEDESC":"Do you really want to delete the phonenumber"
|
||||||
},
|
},
|
||||||
"RESENDCODE": "Resend Code",
|
"RESENDCODE": "Resend Code",
|
||||||
"ENTERCODE":"Verify",
|
"ENTERCODE":"Verify",
|
||||||
@ -294,10 +311,13 @@
|
|||||||
"SIGNEDOUT_BTN":"Sign In",
|
"SIGNEDOUT_BTN":"Sign In",
|
||||||
"EDITACCOUNT":"Edit Account",
|
"EDITACCOUNT":"Edit Account",
|
||||||
"ADDACCOUNT":"Log in With Another Account",
|
"ADDACCOUNT":"Log in With Another Account",
|
||||||
|
"RESENDINITIALEMAIL":"Send new initialisation mail",
|
||||||
|
"RESENDEMAILNOTIFICATION":"Resend Email notification",
|
||||||
"TOAST": {
|
"TOAST": {
|
||||||
"CREATED":"User created successfully.",
|
"CREATED":"User created successfully.",
|
||||||
"SAVED":"Profile saved successfully.",
|
"SAVED":"Profile saved successfully.",
|
||||||
"EMAILSAVED":"E-mail saved successfully.",
|
"EMAILSAVED":"E-mail saved successfully.",
|
||||||
|
"INITEMAILSENT":"Initializing mail sent.",
|
||||||
"PHONESAVED":"Phone saved successfully.",
|
"PHONESAVED":"Phone saved successfully.",
|
||||||
"PHONEREMOVED":"Phone has been removed.",
|
"PHONEREMOVED":"Phone has been removed.",
|
||||||
"PHONEVERIFIED":"Phone verified successfully.",
|
"PHONEVERIFIED":"Phone verified successfully.",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Lato&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Lato&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css">
|
href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css">
|
||||||
<link rel="manifest" href="manifest.webmanifest">
|
<link rel="manifest" href="manifest.webmanifest">
|
||||||
|
@ -195,6 +195,11 @@ func (s *Server) SetInitialPassword(ctx context.Context, request *management.Pas
|
|||||||
return &empty.Empty{}, err
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) ResendInitialMail(ctx context.Context, request *management.InitialMailRequest) (*empty.Empty, error) {
|
||||||
|
err := s.user.ResendInitialMail(ctx, request.Id, request.Email)
|
||||||
|
return &empty.Empty{}, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) SearchUserExternalIDPs(ctx context.Context, request *management.ExternalIDPSearchRequest) (*management.ExternalIDPSearchResponse, error) {
|
func (s *Server) SearchUserExternalIDPs(ctx context.Context, request *management.ExternalIDPSearchRequest) (*management.ExternalIDPSearchResponse, error) {
|
||||||
externalIDP, err := s.user.SearchExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
externalIDP, err := s.user.SearchExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
usr_grant_event "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing"
|
usr_grant_event "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
@ -238,6 +239,10 @@ func (repo *UserRepo) RequestSetPassword(ctx context.Context, id string, notifyT
|
|||||||
return repo.UserEvents.RequestSetPassword(ctx, id, notifyType)
|
return repo.UserEvents.RequestSetPassword(ctx, id, notifyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ResendInitialMail(ctx context.Context, userID, email string) error {
|
||||||
|
return repo.UserEvents.ResendInitialMail(ctx, userID, email)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
|
func (repo *UserRepo) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
|
||||||
user, err := repo.UserByID(ctx, userID)
|
user, err := repo.UserByID(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -54,4 +54,6 @@ type UserRepository interface {
|
|||||||
ChangeAddress(ctx context.Context, address *model.Address) (*model.Address, error)
|
ChangeAddress(ctx context.Context, address *model.Address) (*model.Address, error)
|
||||||
|
|
||||||
SearchUserMemberships(ctx context.Context, request *model.UserMembershipSearchRequest) (*model.UserMembershipSearchResponse, error)
|
SearchUserMemberships(ctx context.Context, request *model.UserMembershipSearchRequest) (*model.UserMembershipSearchResponse, error)
|
||||||
|
|
||||||
|
ResendInitialMail(ctx context.Context, userID, email string) error
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ Errors:
|
|||||||
AlreadyInactive: Benutzer ist bereits deaktiviert
|
AlreadyInactive: Benutzer ist bereits deaktiviert
|
||||||
NotInactive: Benutzer ist nicht inaktiv
|
NotInactive: Benutzer ist nicht inaktiv
|
||||||
ShouldBeActiveOrInitial: Benutzer ist nicht aktiv oder initialisiert
|
ShouldBeActiveOrInitial: Benutzer ist nicht aktiv oder initialisiert
|
||||||
|
AlreadyInitialised: Benutzer ist bereits initialisiert
|
||||||
|
NotInitialised: Benutzer ist noch nicht initialisiert
|
||||||
NotLocked: Benutzer ist nicht gesperrt
|
NotLocked: Benutzer ist nicht gesperrt
|
||||||
NoChanges: Keine Änderungen gefunden
|
NoChanges: Keine Änderungen gefunden
|
||||||
InitCodeNotFound: Kein Initialisierungs Code gefunden
|
InitCodeNotFound: Kein Initialisierungs Code gefunden
|
||||||
|
@ -12,6 +12,8 @@ Errors:
|
|||||||
AlreadyInactive: User already inactive
|
AlreadyInactive: User already inactive
|
||||||
NotInactive: User is not inactive
|
NotInactive: User is not inactive
|
||||||
ShouldBeActiveOrInitial: User is not active or inital
|
ShouldBeActiveOrInitial: User is not active or inital
|
||||||
|
AlreadyInitialised: User is already initialised
|
||||||
|
NotInitialised: User is not yet initialised
|
||||||
NotLocked: User is not locked
|
NotLocked: User is not locked
|
||||||
NoChanges: No changes found
|
NoChanges: No changes found
|
||||||
InitCodeNotFound: Initialization Code not found
|
InitCodeNotFound: Initialization Code not found
|
||||||
|
@ -737,6 +737,9 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
|||||||
if user.Human == nil {
|
if user.Human == nil {
|
||||||
return errors.ThrowPreconditionFailed(nil, "EVENT-33ywz", "Errors.User.NotHuman")
|
return errors.ThrowPreconditionFailed(nil, "EVENT-33ywz", "Errors.User.NotHuman")
|
||||||
}
|
}
|
||||||
|
if user.State == usr_model.UserStateInitial {
|
||||||
|
return errors.ThrowPreconditionFailed(nil, "EVENT-Hs11s", "Errors.User.NotInitialised")
|
||||||
|
}
|
||||||
|
|
||||||
passwordCode := new(model.PasswordCode)
|
passwordCode := new(model.PasswordCode)
|
||||||
err = es.generatePasswordCode(passwordCode, notifyType)
|
err = es.generatePasswordCode(passwordCode, notifyType)
|
||||||
@ -754,6 +757,35 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ResendInitialMail(ctx context.Context, userID, email string) error {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-G4bmn", "Errors.User.UserIDMissing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if user.Human == nil {
|
||||||
|
return errors.ThrowPreconditionFailed(nil, "EVENT-Hfsww", "Errors.User.NotHuman")
|
||||||
|
}
|
||||||
|
if user.State != usr_model.UserStateInitial {
|
||||||
|
return errors.ThrowPreconditionFailed(nil, "EVENT-BGbbe", "Errors.User.AlreadyInitialised")
|
||||||
|
}
|
||||||
|
err = user.GenerateInitCodeIfNeeded(es.InitializeUserCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
agg := ResendInitialPasswordAggregate(es.AggregateCreator(), repoUser, user.InitCode, email)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error {
|
func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "Errors.User.UserIDMissing")
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "Errors.User.UserIDMissing")
|
||||||
@ -946,6 +978,9 @@ func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Emai
|
|||||||
if user.Human == nil {
|
if user.Human == nil {
|
||||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-tgBdL", "Errors.User.NotHuman")
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-tgBdL", "Errors.User.NotHuman")
|
||||||
}
|
}
|
||||||
|
if user.State == usr_model.UserStateInitial {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-3H4q", "Errors.User.NotInitialised")
|
||||||
|
}
|
||||||
|
|
||||||
emailCode, err := email.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
emailCode, err := email.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1018,6 +1053,9 @@ func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userI
|
|||||||
if user.Human == nil {
|
if user.Human == nil {
|
||||||
return errors.ThrowPreconditionFailed(nil, "EVENT-hqUZP", "Errors.User.NotHuman")
|
return errors.ThrowPreconditionFailed(nil, "EVENT-hqUZP", "Errors.User.NotHuman")
|
||||||
}
|
}
|
||||||
|
if user.State == usr_model.UserStateInitial {
|
||||||
|
return errors.ThrowPreconditionFailed(nil, "EVENT-E3fbw", "Errors.User.NotInitialised")
|
||||||
|
}
|
||||||
if user.Email == nil {
|
if user.Email == nil {
|
||||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "Errors.User.EmailNotFound")
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "Errors.User.EmailNotFound")
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
mock_cache "github.com/caos/zitadel/internal/cache/mock"
|
mock_cache "github.com/caos/zitadel/internal/cache/mock"
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/eventstore/mock"
|
"github.com/caos/zitadel/internal/eventstore/mock"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
global_model "github.com/caos/zitadel/internal/model"
|
global_model "github.com/caos/zitadel/internal/model"
|
||||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *UserEventstore {
|
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *UserEventstore {
|
||||||
@ -162,11 +162,18 @@ func GetMockManipulateUserWithPasswordAndEmailCodeGen(ctrl *gomock.Controller, u
|
|||||||
return GetMockedEventstoreWithPw(ctrl, mockEs, false, true, false, true)
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, true, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User, verified bool) *UserEventstore {
|
||||||
data, _ := json.Marshal(user)
|
data, _ := json.Marshal(user)
|
||||||
events := []*es_models.Event{
|
events := []*es_models.Event{
|
||||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: data},
|
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
}
|
}
|
||||||
|
if verified {
|
||||||
|
email, _ := json.Marshal(model.Email{EmailAddress: "address"})
|
||||||
|
events = append(events,
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 2, Type: model.HumanEmailVerified},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 3, Type: model.HumanEmailChanged, Data: email},
|
||||||
|
)
|
||||||
|
}
|
||||||
mockEs := mock.NewMockEventstore(ctrl)
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
@ -186,12 +193,15 @@ func GetMockManipulateUserWithPhoneCodeGen(ctrl *gomock.Controller, user model.U
|
|||||||
return GetMockedEventstoreWithPw(ctrl, mockEs, false, false, true, false)
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, false, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User, verified bool) *UserEventstore {
|
||||||
data, _ := json.Marshal(user)
|
data, _ := json.Marshal(user)
|
||||||
code, _ := json.Marshal(user.PasswordCode)
|
code, _ := json.Marshal(user.PasswordCode)
|
||||||
events := []*es_models.Event{
|
events := []*es_models.Event{
|
||||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: data},
|
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserPasswordCodeAdded, Data: code},
|
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 2, Type: model.UserPasswordCodeAdded, Data: code},
|
||||||
|
}
|
||||||
|
if verified {
|
||||||
|
events = append(events, &es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 3, Type: model.HumanEmailVerified})
|
||||||
}
|
}
|
||||||
mockEs := mock.NewMockEventstore(ctrl)
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
@ -382,7 +392,7 @@ func GetMockManipulateUserVerifiedPhone(ctrl *gomock.Controller) *UserEventstore
|
|||||||
return GetMockedEventstore(ctrl, mockEs)
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
func GetMockManipulateUserFull(ctrl *gomock.Controller, verified bool) *UserEventstore {
|
||||||
user := model.Human{
|
user := model.Human{
|
||||||
Profile: &model.Profile{
|
Profile: &model.Profile{
|
||||||
DisplayName: "DisplayName",
|
DisplayName: "DisplayName",
|
||||||
@ -407,6 +417,9 @@ func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
|||||||
events := []*es_models.Event{
|
events := []*es_models.Event{
|
||||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
}
|
}
|
||||||
|
if verified {
|
||||||
|
events = append(events, &es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 2, Type: model.HumanEmailVerified})
|
||||||
|
}
|
||||||
mockEs := mock.NewMockEventstore(ctrl)
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
@ -3,7 +3,6 @@ package eventsourcing
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
"github.com/caos/zitadel/internal/user/model"
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
repo_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
repo_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
)
|
)
|
||||||
@ -1287,7 +1287,7 @@ func TestPasswordID(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "get by id, ok",
|
name: "get by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
},
|
},
|
||||||
@ -1366,7 +1366,7 @@ func TestSetOneTimePassword(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "create one time pw",
|
name: "create one time pw",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{Email: &repo_model.Email{EmailAddress: "email"}}}, true),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
|
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
|
||||||
@ -1516,8 +1516,10 @@ func TestCheckPassword(t *testing.T) {
|
|||||||
Algorithm: "hash",
|
Algorithm: "hash",
|
||||||
Crypted: []byte("password"),
|
Crypted: []byte("password"),
|
||||||
}},
|
}},
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
userID: "userID",
|
userID: "userID",
|
||||||
@ -1582,8 +1584,10 @@ func TestSetPassword(t *testing.T) {
|
|||||||
KeyID: "id",
|
KeyID: "id",
|
||||||
Crypted: []byte("code"),
|
Crypted: []byte("code"),
|
||||||
}},
|
}},
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||||
@ -1627,8 +1631,11 @@ func TestSetPassword(t *testing.T) {
|
|||||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
||||||
repo_model.User{
|
repo_model.User{
|
||||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
||||||
Human: &repo_model.Human{},
|
Human: &repo_model.Human{
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||||
@ -1653,8 +1660,10 @@ func TestSetPassword(t *testing.T) {
|
|||||||
KeyID: "id",
|
KeyID: "id",
|
||||||
Crypted: []byte("code2"),
|
Crypted: []byte("code2"),
|
||||||
}},
|
}},
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||||
@ -1759,8 +1768,11 @@ func TestChangePassword(t *testing.T) {
|
|||||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
||||||
repo_model.User{
|
repo_model.User{
|
||||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
||||||
Human: &repo_model.Human{},
|
Human: &repo_model.Human{
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||||
@ -1784,8 +1796,10 @@ func TestChangePassword(t *testing.T) {
|
|||||||
Algorithm: "hash",
|
Algorithm: "hash",
|
||||||
Crypted: []byte("older"),
|
Crypted: []byte("older"),
|
||||||
}},
|
}},
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||||
@ -1859,7 +1873,12 @@ func TestRequestSetPassword(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "create pw",
|
name: "create pw",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{
|
||||||
|
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
||||||
|
Human: &repo_model.Human{
|
||||||
|
Email: &repo_model.Email{EmailAddress: "email"},
|
||||||
|
}},
|
||||||
|
true),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
userID: "AggregateID",
|
userID: "AggregateID",
|
||||||
notifyType: model.NotificationTypeEmail,
|
notifyType: model.NotificationTypeEmail,
|
||||||
@ -1868,6 +1887,18 @@ func TestRequestSetPassword(t *testing.T) {
|
|||||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "initial state",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}, false),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
userID: "AggregateID",
|
||||||
|
notifyType: model.NotificationTypeEmail,
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "empty userid",
|
name: "empty userid",
|
||||||
args: args{
|
args: args{
|
||||||
@ -1905,6 +1936,84 @@ func TestRequestSetPassword(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResendInitialMail(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserEventstore
|
||||||
|
ctx context.Context
|
||||||
|
userID string
|
||||||
|
mail string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
password *model.Password
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "resend ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserWithInitCode(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
userID: "AggregateID",
|
||||||
|
mail: "",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resend with email ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserWithInitCode(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
userID: "AggregateID",
|
||||||
|
mail: "email",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty userid",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUser(ctrl),
|
||||||
|
mail: "",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing user not found",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserNoEvents(ctrl),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
userID: "AggregateID",
|
||||||
|
mail: "",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.args.es.ResendInitialMail(tt.args.ctx, tt.args.userID, tt.args.mail)
|
||||||
|
|
||||||
|
if tt.res.errFunc == nil && err != nil {
|
||||||
|
t.Errorf("should not get err")
|
||||||
|
}
|
||||||
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPasswordCodeSent(t *testing.T) {
|
func TestPasswordCodeSent(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
type args struct {
|
type args struct {
|
||||||
@ -2170,7 +2279,7 @@ func TestProfileByID(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "get by id, ok",
|
name: "get by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Human: &model.Human{}},
|
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Human: &model.Human{}},
|
||||||
},
|
},
|
||||||
@ -2237,7 +2346,7 @@ func TestChangeProfile(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "get by id, ok",
|
name: "get by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
profile: &model.Profile{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, FirstName: "FirstName Changed", LastName: "LastName Changed"},
|
profile: &model.Profile{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, FirstName: "FirstName Changed", LastName: "LastName Changed"},
|
||||||
},
|
},
|
||||||
@ -2304,7 +2413,7 @@ func TestEmailByID(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "get by id, ok",
|
name: "get by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
},
|
},
|
||||||
@ -2371,7 +2480,7 @@ func TestChangeEmail(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change email address, verified",
|
name: "change email address, verified",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, true),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: true},
|
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: true},
|
||||||
},
|
},
|
||||||
@ -2389,7 +2498,7 @@ func TestChangeEmail(t *testing.T) {
|
|||||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||||
},
|
},
|
||||||
}),
|
}, true),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
||||||
},
|
},
|
||||||
@ -2397,6 +2506,24 @@ func TestChangeEmail(t *testing.T) {
|
|||||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "user state initial",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserWithEmailCodeGen(ctrl, repo_model.User{
|
||||||
|
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
UserName: "UserName",
|
||||||
|
Human: &repo_model.Human{
|
||||||
|
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||||
|
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||||
|
},
|
||||||
|
}, false),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "empty userid",
|
name: "empty userid",
|
||||||
args: args{
|
args: args{
|
||||||
@ -2423,7 +2550,9 @@ func TestChangeEmail(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result, err := tt.args.es.ChangeEmail(tt.args.ctx, tt.args.email)
|
result, err := tt.args.es.ChangeEmail(tt.args.ctx, tt.args.email)
|
||||||
|
if (tt.res.errFunc != nil && !tt.res.errFunc(err)) || (tt.res.errFunc == nil && err != nil) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
if tt.res.errFunc == nil && result.AggregateID == "" {
|
if tt.res.errFunc == nil && result.AggregateID == "" {
|
||||||
t.Errorf("result has no id")
|
t.Errorf("result has no id")
|
||||||
}
|
}
|
||||||
@ -2433,9 +2562,6 @@ func TestChangeEmail(t *testing.T) {
|
|||||||
if tt.res.errFunc == nil && result.IsEmailVerified != tt.res.email.IsEmailVerified {
|
if tt.res.errFunc == nil && result.IsEmailVerified != tt.res.email.IsEmailVerified {
|
||||||
t.Errorf("got wrong result change required: expected: %v, actual: %v ", tt.res.email.IsEmailVerified, result.IsEmailVerified)
|
t.Errorf("got wrong result change required: expected: %v, actual: %v ", tt.res.email.IsEmailVerified, result.IsEmailVerified)
|
||||||
}
|
}
|
||||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
|
||||||
t.Errorf("got wrong err: %v ", err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2551,12 +2677,30 @@ func TestCreateEmailVerificationCode(t *testing.T) {
|
|||||||
Human: &repo_model.Human{
|
Human: &repo_model.Human{
|
||||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||||
}}),
|
}}, true),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
userID: "userID",
|
userID: "userID",
|
||||||
},
|
},
|
||||||
res: res{},
|
res: res{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "initial state",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserWithEmailCodeGen(ctrl, repo_model.User{
|
||||||
|
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
UserName: "UserName",
|
||||||
|
Human: &repo_model.Human{
|
||||||
|
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||||
|
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||||
|
},
|
||||||
|
}, false),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
userID: "userID",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "empty userid",
|
name: "empty userid",
|
||||||
args: args{
|
args: args{
|
||||||
@ -2606,7 +2750,7 @@ func TestCreateEmailVerificationCode(t *testing.T) {
|
|||||||
err := tt.args.es.CreateEmailVerificationCode(tt.args.ctx, tt.args.userID)
|
err := tt.args.es.CreateEmailVerificationCode(tt.args.ctx, tt.args.userID)
|
||||||
|
|
||||||
if tt.res.errFunc == nil && err != nil {
|
if tt.res.errFunc == nil && err != nil {
|
||||||
t.Errorf("should not ger err")
|
t.Errorf("should not get err, got: %v", err)
|
||||||
}
|
}
|
||||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
t.Errorf("got wrong err: %v ", err)
|
t.Errorf("got wrong err: %v ", err)
|
||||||
@ -2695,7 +2839,7 @@ func TestPhoneByID(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "get by id, ok",
|
name: "get by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
},
|
},
|
||||||
@ -2762,7 +2906,7 @@ func TestChangePhone(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change phone, verified",
|
name: "change phone, verified",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "0711234567", IsPhoneVerified: true},
|
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "0711234567", IsPhoneVerified: true},
|
||||||
},
|
},
|
||||||
@ -3149,7 +3293,7 @@ func TestAddressByID(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "get by id, ok",
|
name: "get by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
},
|
},
|
||||||
@ -3216,7 +3360,7 @@ func TestChangeAddress(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change address ok",
|
name: "change address ok",
|
||||||
args: args{
|
args: args{
|
||||||
es: GetMockManipulateUserFull(ctrl),
|
es: GetMockManipulateUserFull(ctrl, false),
|
||||||
ctx: authz.NewMockContext("orgID", "userID"),
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
address: &model.Address{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Country: "CountryChanged"},
|
address: &model.Address{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Country: "CountryChanged"},
|
||||||
},
|
},
|
||||||
|
@ -189,7 +189,7 @@ func (h *Human) AppendEvent(event *es_models.Event) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Human) ComputeObject() {
|
func (h *Human) ComputeObject() {
|
||||||
if h.State == int32(model.UserStateUnspecified) {
|
if h.State == int32(model.UserStateUnspecified) || h.State == int32(model.UserStateInitial) {
|
||||||
if h.Email != nil && h.IsEmailVerified {
|
if h.Email != nil && h.IsEmailVerified {
|
||||||
h.State = int32(model.UserStateActive)
|
h.State = int32(model.UserStateActive)
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,14 +2,15 @@ package eventsourcing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
|
||||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -454,6 +455,25 @@ func RequestSetPassword(aggCreator *es_models.AggregateCreator, user *model.User
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResendInitialPasswordAggregate(aggCreator *es_models.AggregateCreator, user *model.User, code *usr_model.InitUserCode, email string) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if code == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dfs3q", "Errors.Internal")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if email != "" && user.Email != nil && email != user.Email.EmailAddress {
|
||||||
|
agg, err = agg.AppendEvent(model.HumanEmailChanged, map[string]interface{}{"email": email})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.InitializedHumanCodeAdded, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func PasswordCodeSentAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
func PasswordCodeSentAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
agg, err := UserAggregate(ctx, aggCreator, user)
|
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||||
|
@ -5,11 +5,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore/models"
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1199,6 +1199,114 @@ func TestRequestSetPasswordAggregate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResendInitialPasswordAggregate(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
user *model.User
|
||||||
|
aggCreator *models.AggregateCreator
|
||||||
|
initcode *usr_model.InitUserCode
|
||||||
|
email string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
eventLen int
|
||||||
|
eventType models.EventType
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "resend initial password aggregate ok",
|
||||||
|
args: args{
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
user: &model.User{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserName: "UserName",
|
||||||
|
Human: &model.Human{
|
||||||
|
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||||
|
Email: &model.Email{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
initcode: &usr_model.InitUserCode{Expiry: time.Hour * 1},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resend initial password with same email ok",
|
||||||
|
args: args{
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
user: &model.User{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserName: "UserName",
|
||||||
|
Human: &model.Human{
|
||||||
|
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||||
|
Email: &model.Email{EmailAddress: "email"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
initcode: &usr_model.InitUserCode{Expiry: time.Hour * 1},
|
||||||
|
email: "email",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resend initial password with new email ok",
|
||||||
|
args: args{
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
user: &model.User{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserName: "UserName",
|
||||||
|
Human: &model.Human{
|
||||||
|
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||||
|
Email: &model.Email{EmailAddress: "old"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
initcode: &usr_model.InitUserCode{Expiry: time.Hour * 1},
|
||||||
|
email: "new",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request nil",
|
||||||
|
args: args{
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
user: &model.User{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserName: "UserName",
|
||||||
|
Human: &model.Human{
|
||||||
|
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
agg, err := ResendInitialPasswordAggregate(tt.args.aggCreator, tt.args.user, tt.args.initcode, tt.args.email)(tt.args.ctx)
|
||||||
|
if (tt.res.errFunc == nil && err != nil) || (tt.res.errFunc != nil && !tt.res.errFunc(err)) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||||
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPasswordCodeSentAggregate(t *testing.T) {
|
func TestPasswordCodeSentAggregate(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
@ -184,6 +184,11 @@ var ManagementService_AuthMethods = authz.MethodMapping{
|
|||||||
CheckParam: "",
|
CheckParam: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"/caos.zitadel.management.api.v1.ManagementService/ResendInitialMail": authz.Option{
|
||||||
|
Permission: "user.write",
|
||||||
|
CheckParam: "",
|
||||||
|
},
|
||||||
|
|
||||||
"/caos.zitadel.management.api.v1.ManagementService/SearchUserMemberships": authz.Option{
|
"/caos.zitadel.management.api.v1.ManagementService/SearchUserMemberships": authz.Option{
|
||||||
Permission: "user.membership.read",
|
Permission: "user.membership.read",
|
||||||
CheckParam: "",
|
CheckParam: "",
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4316,6 +4316,82 @@ var _ interface {
|
|||||||
ErrorName() string
|
ErrorName() string
|
||||||
} = SetPasswordNotificationRequestValidationError{}
|
} = SetPasswordNotificationRequestValidationError{}
|
||||||
|
|
||||||
|
// Validate checks the field values on InitialMailRequest with the rules
|
||||||
|
// defined in the proto definition for this message. If any rules are
|
||||||
|
// violated, an error is returned.
|
||||||
|
func (m *InitialMailRequest) Validate() error {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if utf8.RuneCountInString(m.GetId()) < 1 {
|
||||||
|
return InitialMailRequestValidationError{
|
||||||
|
field: "Id",
|
||||||
|
reason: "value length must be at least 1 runes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no validation rules for Email
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitialMailRequestValidationError is the validation error returned by
|
||||||
|
// InitialMailRequest.Validate if the designated constraints aren't met.
|
||||||
|
type InitialMailRequestValidationError struct {
|
||||||
|
field string
|
||||||
|
reason string
|
||||||
|
cause error
|
||||||
|
key bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field function returns field value.
|
||||||
|
func (e InitialMailRequestValidationError) Field() string { return e.field }
|
||||||
|
|
||||||
|
// Reason function returns reason value.
|
||||||
|
func (e InitialMailRequestValidationError) Reason() string { return e.reason }
|
||||||
|
|
||||||
|
// Cause function returns cause value.
|
||||||
|
func (e InitialMailRequestValidationError) Cause() error { return e.cause }
|
||||||
|
|
||||||
|
// Key function returns key value.
|
||||||
|
func (e InitialMailRequestValidationError) Key() bool { return e.key }
|
||||||
|
|
||||||
|
// ErrorName returns error name.
|
||||||
|
func (e InitialMailRequestValidationError) ErrorName() string {
|
||||||
|
return "InitialMailRequestValidationError"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error satisfies the builtin error interface
|
||||||
|
func (e InitialMailRequestValidationError) Error() string {
|
||||||
|
cause := ""
|
||||||
|
if e.cause != nil {
|
||||||
|
cause = fmt.Sprintf(" | caused by: %v", e.cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ""
|
||||||
|
if e.key {
|
||||||
|
key = "key for "
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"invalid %sInitialMailRequest.%s: %s%s",
|
||||||
|
key,
|
||||||
|
e.field,
|
||||||
|
e.reason,
|
||||||
|
cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = InitialMailRequestValidationError{}
|
||||||
|
|
||||||
|
var _ interface {
|
||||||
|
Field() string
|
||||||
|
Reason() string
|
||||||
|
Key() bool
|
||||||
|
Cause() error
|
||||||
|
ErrorName() string
|
||||||
|
} = InitialMailRequestValidationError{}
|
||||||
|
|
||||||
// Validate checks the field values on OrgIamPolicyView with the rules defined
|
// Validate checks the field values on OrgIamPolicyView with the rules defined
|
||||||
// in the proto definition for this message. If any rules are violated, an
|
// in the proto definition for this message. If any rules are violated, an
|
||||||
// error is returned.
|
// error is returned.
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2157,6 +2157,26 @@ func (mr *MockManagementServiceClientMockRecorder) ResendEmailVerificationMail(a
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResendEmailVerificationMail", reflect.TypeOf((*MockManagementServiceClient)(nil).ResendEmailVerificationMail), varargs...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResendEmailVerificationMail", reflect.TypeOf((*MockManagementServiceClient)(nil).ResendEmailVerificationMail), varargs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResendInitialMail mocks base method
|
||||||
|
func (m *MockManagementServiceClient) ResendInitialMail(arg0 context.Context, arg1 *management.InitialMailRequest, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
varargs := []interface{}{arg0, arg1}
|
||||||
|
for _, a := range arg2 {
|
||||||
|
varargs = append(varargs, a)
|
||||||
|
}
|
||||||
|
ret := m.ctrl.Call(m, "ResendInitialMail", varargs...)
|
||||||
|
ret0, _ := ret[0].(*emptypb.Empty)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResendInitialMail indicates an expected call of ResendInitialMail
|
||||||
|
func (mr *MockManagementServiceClientMockRecorder) ResendInitialMail(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResendInitialMail", reflect.TypeOf((*MockManagementServiceClient)(nil).ResendInitialMail), varargs...)
|
||||||
|
}
|
||||||
|
|
||||||
// ResendPhoneVerificationCode mocks base method
|
// ResendPhoneVerificationCode mocks base method
|
||||||
func (m *MockManagementServiceClient) ResendPhoneVerificationCode(arg0 context.Context, arg1 *management.UserID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
|
func (m *MockManagementServiceClient) ResendPhoneVerificationCode(arg0 context.Context, arg1 *management.UserID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -424,6 +424,17 @@ rpc GetUserByID(UserID) returns (UserView) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc ResendInitialMail(InitialMailRequest) returns (google.protobuf.Empty) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/users/{id}/_resendinitialisation"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (caos.zitadel.utils.v1.auth_option) = {
|
||||||
|
permission: "user.write"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
rpc SearchUserMemberships(UserMembershipSearchRequest) returns (UserMembershipSearchResponse) {
|
rpc SearchUserMemberships(UserMembershipSearchRequest) returns (UserMembershipSearchResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/users/{user_id}/memberships/_search"
|
post: "/users/{user_id}/memberships/_search"
|
||||||
@ -2049,6 +2060,11 @@ enum NotificationType {
|
|||||||
NOTIFICATIONTYPE_SMS = 1;
|
NOTIFICATIONTYPE_SMS = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message InitialMailRequest {
|
||||||
|
string id = 1 [(validate.rules).string.min_len = 1];
|
||||||
|
string email = 2;
|
||||||
|
}
|
||||||
|
|
||||||
enum PolicyState {
|
enum PolicyState {
|
||||||
POLICYSTATE_UNSPECIFIED = 0;
|
POLICYSTATE_UNSPECIFIED = 0;
|
||||||
POLICYSTATE_ACTIVE = 1;
|
POLICYSTATE_ACTIVE = 1;
|
||||||
|
Loading…
Reference in New Issue
Block a user