mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +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;
|
||||
border-radius: .5rem;
|
||||
padding-top: 1rem;
|
||||
min-width: 350px;
|
||||
|
||||
.header {
|
||||
margin-top: 0;
|
||||
|
@ -11,6 +11,10 @@
|
||||
.action {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.ok-button {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
@ -40,10 +40,15 @@
|
||||
|
||||
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<app-contact *ngIf="user?.human" [human]="user.human" (savedPhone)="savePhone($event)"
|
||||
(savedEmail)="saveEmail($event)" (enteredPhoneCode)="enteredPhoneCode($event)"
|
||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
||||
(resendPhoneVerification)="resendPhoneVerification()"></app-contact>
|
||||
<button card-actions mat-icon-button (click)="refreshUser()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
<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-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 { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
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 { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { EditDialogType } from '../user-detail/user-detail.component';
|
||||
import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-user-detail',
|
||||
templateUrl: './auth-user-detail.component.html',
|
||||
@ -26,6 +38,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
|
||||
public ChangeType: any = ChangeType;
|
||||
public userLoginMustBeDomain: boolean = false;
|
||||
public UserState: any = UserState;
|
||||
|
||||
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
||||
|
||||
@ -33,8 +46,13 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
public translate: TranslateService,
|
||||
private toast: ToastService,
|
||||
public userService: GrpcAuthService,
|
||||
private dialog: MatDialog,
|
||||
) {
|
||||
this.loading = true;
|
||||
this.refreshUser();
|
||||
}
|
||||
|
||||
refreshUser(): void {
|
||||
this.userService.GetMyUser().then(user => {
|
||||
this.user = user.toObject();
|
||||
this.loading = false;
|
||||
@ -81,6 +99,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.email = data.toObject().email;
|
||||
this.refreshUser();
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
@ -90,6 +109,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
public enteredPhoneCode(code: string): void {
|
||||
this.userService.VerifyMyUserPhone(code).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
this.refreshUser();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
@ -99,14 +119,6 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
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 {
|
||||
this.userService.ResendPhoneVerification().then(() => {
|
||||
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 {
|
||||
this.userService.RemoveMyUserPhone().then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = '';
|
||||
this.refreshUser();
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
@ -133,10 +154,54 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = data.toObject().phone;
|
||||
this.refreshUser();
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public openEditDialog(type: EditDialogType): void {
|
||||
switch (type) {
|
||||
case EditDialogType.PHONE:
|
||||
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.SAVE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
labelKey: 'ACTIONS.NEWVALUE',
|
||||
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
|
||||
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
|
||||
value: this.user.human?.phone,
|
||||
},
|
||||
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-row">
|
||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||
<div class="left">
|
||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||
<span class="name">*********</span>
|
||||
|
||||
<span>*********</span>
|
||||
<div class="overflow">
|
||||
<ng-content select="[phoneAction]"></ng-content>
|
||||
<a [disabled]="!canWrite" [routerLink]="['password']" mat-icon-button>
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
<ng-content select="[pwdAction]"></ng-content>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<a matTooltip="{{'USER.PASSWORD.SET' | translate}}" [disabled]="!canWrite" [routerLink]="['password']"
|
||||
mat-icon-button>
|
||||
<i class="las la-edit"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
<div class="left">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
<span class="name">{{human?.email}}</span>
|
||||
<span *ngIf="human?.isEmailVerified" class="state verified">{{'USER.EMAILVERIFIED' | translate}}</span>
|
||||
<div *ngIf="!human?.isEmailVerified" class="block">
|
||||
<span class="state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
|
||||
|
||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{human?.email}}</span>
|
||||
<mat-icon class="icon" *ngIf="human?.isEmailVerified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="human?.email && !human?.isEmailVerified">
|
||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
<ng-container *ngIf="human?.email">
|
||||
<a *ngIf="canWrite && state != userStateEnum?.USERSTATE_INITIAL" class="verify"
|
||||
matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
(click)="emitEmailVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button [disabled]="!canWrite" (click)="emailEditState = true" mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</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>
|
||||
<ng-content select="[emailAction]"></ng-content>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<button [disabled]="!canWrite" (click)="openEditDialog(EditDialogType.EMAIL)" mat-icon-button>
|
||||
<i class="las la-edit"></i>
|
||||
</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">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
<div class="left">
|
||||
<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">
|
||||
<div class="actions">
|
||||
<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"
|
||||
<ng-container *ngIf="human?.phone">
|
||||
<a *ngIf="!disablePhoneCode && canWrite" class="verify"
|
||||
matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
|
||||
(click)="enterCode()">{{'USER.LOGINMETHODS.ENTERCODE' | translate}}</a>
|
||||
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
|
||||
@ -71,27 +57,18 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button [disabled]="!canWrite" (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-content select="[phoneAction]"></ng-content>
|
||||
</div>
|
||||
|
||||
<ng-template #phoneEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.PHONE' | translate }}</mat-label>
|
||||
<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>
|
||||
<div class="right">
|
||||
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" *ngIf="human && human.phone" color="warn"
|
||||
(click)="emitDeletePhone()" mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
<button *ngIf="human" [disabled]="!human.phone" type="button" color="primary" (click)="savePhone()"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
<button matTooltip="{{'ACTIONS.EDIT' | translate}}" [disabled]="!canWrite"
|
||||
(click)="openEditDialog(EditDialogType.PHONE)" mat-icon-button>
|
||||
<i class="las la-edit"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -5,29 +5,57 @@
|
||||
|
||||
.method-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
border-bottom: 1px solid #ffffff20;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.actions {
|
||||
flex: 1;
|
||||
.left {
|
||||
.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;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: .9rem;
|
||||
max-width: 100px;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: .5rem;
|
||||
.verified-icon {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.2rem;
|
||||
height: 1.2rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.verify {
|
||||
@ -38,6 +66,7 @@
|
||||
cursor: pointer;
|
||||
word-wrap: none;
|
||||
white-space: nowrap;
|
||||
margin-right: 1rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
@ -46,6 +75,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overflow {
|
||||
overflow: auto;
|
||||
.mat-form-field-wrapper {
|
||||
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 { HumanView as AuthHumanView } from 'src/app/proto/generated/auth_pb';
|
||||
import { HumanView as MgmtHumanView } from 'src/app/proto/generated/management_pb';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
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 { EditDialogType } from '../user-detail/user-detail.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
})
|
||||
export class ContactComponent implements OnInit {
|
||||
export class ContactComponent {
|
||||
@Input() disablePhoneCode: boolean = false;
|
||||
@Input() canWrite: boolean = false;
|
||||
@Input() human!: AuthHumanView.AsObject | MgmtHumanView.AsObject;
|
||||
@Output() savedPhone: EventEmitter<string> = new EventEmitter();
|
||||
@Output() savedEmail: EventEmitter<string> = new EventEmitter();
|
||||
@Input() state!: AuthUserState | MgmtUserState;
|
||||
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter();
|
||||
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
|
||||
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
|
||||
@Input() public userStateEnum: any;
|
||||
|
||||
public emailEditState: boolean = false;
|
||||
public phoneEditState: boolean = false;
|
||||
public EditDialogType: any = EditDialogType;
|
||||
constructor(private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
savePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
this.savedPhone.emit(this.human.phone);
|
||||
}
|
||||
|
||||
emitDeletePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
this.deletedPhone.emit();
|
||||
}
|
||||
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',
|
||||
});
|
||||
|
||||
saveEmail(): void {
|
||||
this.emailEditState = false;
|
||||
this.savedEmail.emit(this.human.email);
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
this.deletedPhone.emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.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 { ContactComponent } from './contact/contact.component';
|
||||
import { DetailFormMachineModule } from './detail-form-machine/detail-form-machine.module';
|
||||
import { DetailFormModule } from './detail-form/detail-form.module';
|
||||
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 { UserDetailComponent } from './user-detail/user-detail.component';
|
||||
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||
import { ContactComponent } from './contact/contact.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AuthUserDetailComponent,
|
||||
UserDetailComponent,
|
||||
DialogOtpComponent,
|
||||
EditDialogComponent,
|
||||
AuthUserMfaComponent,
|
||||
UserMfaComponent,
|
||||
ThemeSettingComponent,
|
||||
@ -62,6 +65,7 @@ import { ContactComponent } from './contact/contact.component';
|
||||
MachineKeysComponent,
|
||||
ExternalIdpsComponent,
|
||||
ContactComponent,
|
||||
ResendEmailDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
UserDetailRoutingModule,
|
||||
|
@ -67,15 +67,20 @@
|
||||
|
||||
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
|
||||
<button card-actions mat-icon-button (click)="refreshUser()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
<app-contact disablePhoneCode="true"
|
||||
[canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)" *ngIf="user?.human"
|
||||
[human]="user.human" (savedPhone)="savePhone($event)" (savedEmail)="saveEmail($event)"
|
||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
||||
[human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
|
||||
(resendEmailVerification)="resendEmailVerification()"
|
||||
(resendPhoneVerification)="resendPhoneVerification()">
|
||||
<button phoneAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
||||
<button pwdAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
||||
mat-stroked-button color="primary"
|
||||
*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-card>
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 { ActivatedRoute } from '@angular/router';
|
||||
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 { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||
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 { 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({
|
||||
selector: 'app-user-detail',
|
||||
templateUrl: './user-detail.component.html',
|
||||
styleUrls: ['./user-detail.component.scss'],
|
||||
})
|
||||
export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
export class UserDetailComponent implements OnInit {
|
||||
public user!: UserView.AsObject;
|
||||
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
|
||||
public languages: string[] = ['de', 'en'];
|
||||
|
||||
private subscription: Subscription = new Subscription();
|
||||
|
||||
public ChangeType: any = ChangeType;
|
||||
public loading: boolean = false;
|
||||
|
||||
@ -40,6 +46,8 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
public copied: string = '';
|
||||
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
||||
|
||||
public EditDialogType: any = EditDialogType;
|
||||
|
||||
constructor(
|
||||
public translate: TranslateService,
|
||||
private route: ActivatedRoute,
|
||||
@ -49,8 +57,8 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
private dialog: MatDialog,
|
||||
) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.subscription = this.route.params.subscribe(params => {
|
||||
refreshUser(): void {
|
||||
this.route.params.pipe(take(1)).subscribe(params => {
|
||||
const { id } = params;
|
||||
this.mgmtUserService.GetUserByID(id).then(user => {
|
||||
this.user = user.toObject();
|
||||
@ -60,8 +68,8 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe();
|
||||
public ngOnInit(): void {
|
||||
this.refreshUser();
|
||||
}
|
||||
|
||||
public changeState(newState: UserState): void {
|
||||
@ -149,6 +157,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = '';
|
||||
this.refreshUser();
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
@ -158,9 +167,10 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
public saveEmail(email: string): void {
|
||||
if (this.user.id && email) {
|
||||
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) {
|
||||
this.user.human.email = data.toObject().email;
|
||||
this.refreshUser();
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
@ -175,6 +185,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = data.toObject().phone;
|
||||
this.refreshUser();
|
||||
}
|
||||
}).catch(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);
|
||||
}
|
||||
|
||||
public ResendMyEmailVerificationMail(): Promise<Empty> {
|
||||
return this.grpcService.auth.resendMyEmailVerificationMail(
|
||||
new Empty(),
|
||||
);
|
||||
}
|
||||
|
||||
public RemoveMyUserPhone(): Promise<Empty> {
|
||||
return this.grpcService.auth.removeMyUserPhone(
|
||||
new Empty(),
|
||||
|
@ -40,6 +40,7 @@ import {
|
||||
IdpSearchResponse,
|
||||
IdpUpdate,
|
||||
IdpView,
|
||||
InitialMailRequest,
|
||||
LoginName,
|
||||
LoginPolicy,
|
||||
LoginPolicyRequest,
|
||||
@ -786,6 +787,16 @@ export class ManagementService {
|
||||
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> {
|
||||
const req = new UserID();
|
||||
req.setId(id);
|
||||
|
@ -77,7 +77,9 @@
|
||||
"LOGIN":"Einloggen",
|
||||
"EDIT":"Bearbeiten",
|
||||
"PIN":"Anpinnen",
|
||||
"CONFIGURE":"Konfigurieren"
|
||||
"CONFIGURE":"Konfigurieren",
|
||||
"SEND":"Senden",
|
||||
"NEWVALUE":"Neuer Wert"
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Bitte fülle alle benötigten Felder aus.",
|
||||
@ -112,6 +114,11 @@
|
||||
"DELETE_TITLE":"User löschen",
|
||||
"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":{
|
||||
"DEACTIVATE":"Deaktivieren",
|
||||
"ACTIVATE":"Aktivieren",
|
||||
@ -241,6 +248,10 @@
|
||||
},
|
||||
"EMAIL": "E-Mail",
|
||||
"PHONE": "Telefonnummer",
|
||||
"PHONEEMPTY":"Keine Telefonnummer hinterlegt",
|
||||
"PHONEVERIFIED":"Telefonnummer bestätigt.",
|
||||
"EMAILVERIFIED":"Email verifiziert",
|
||||
"NOTVERIFIED":"nicht verifiziert",
|
||||
"PREFERRED_LOGINNAME":"Bevorzugter Loginname",
|
||||
"LOGINMETHODS": {
|
||||
"TITLE": "Kontaktinformationen",
|
||||
@ -248,12 +259,18 @@
|
||||
"EMAIL": {
|
||||
"TITLE": "E-Mail",
|
||||
"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": {
|
||||
"TITLE": "Telefon",
|
||||
"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",
|
||||
"ENTERCODE":"Verifizieren",
|
||||
@ -294,10 +311,13 @@
|
||||
"SIGNEDOUT_BTN":"Anmelden",
|
||||
"EDITACCOUNT":"Konto bearbeiten",
|
||||
"ADDACCOUNT":"Konto hinzufügen",
|
||||
"RESENDINITIALEMAIL":"Neue Initialisierungsmail senden",
|
||||
"RESENDEMAILNOTIFICATION":"Benachrichtigungsmail senden",
|
||||
"TOAST": {
|
||||
"CREATED":"Benutzer erfolgreich erstellt.",
|
||||
"SAVED":"Profil gespeichert.",
|
||||
"EMAILSAVED":"E-Mail gespeichert.",
|
||||
"INITEMAILSENT":"Initialisierung Email gesendet.",
|
||||
"PHONESAVED":"Telefonnummer gespeichert.",
|
||||
"PHONEREMOVED":"Telefonnummer gelöscht.",
|
||||
"PHONEVERIFIED":"Telefonnummer bestätigt.",
|
||||
|
@ -77,7 +77,9 @@
|
||||
"LOGIN":"Login",
|
||||
"EDIT":"Edit",
|
||||
"PIN":"Pin / Unpin",
|
||||
"CONFIGURE":"Configure"
|
||||
"CONFIGURE":"Configure",
|
||||
"SEND":"Send",
|
||||
"NEWVALUE":"New Value"
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Some required fields are missing.",
|
||||
@ -112,6 +114,11 @@
|
||||
"DELETE_TITLE":"Delete User",
|
||||
"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":{
|
||||
"DEACTIVATE":"Deactivate",
|
||||
"ACTIVATE":"Activate",
|
||||
@ -240,7 +247,11 @@
|
||||
"NOTEQUAL":"The passwords provided do not match."
|
||||
},
|
||||
"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",
|
||||
"LOGINMETHODS": {
|
||||
"TITLE": "Contact Information",
|
||||
@ -248,12 +259,18 @@
|
||||
"EMAIL": {
|
||||
"TITLE": "E-mail",
|
||||
"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": {
|
||||
"TITLE": "Phone",
|
||||
"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",
|
||||
"ENTERCODE":"Verify",
|
||||
@ -294,10 +311,13 @@
|
||||
"SIGNEDOUT_BTN":"Sign In",
|
||||
"EDITACCOUNT":"Edit Account",
|
||||
"ADDACCOUNT":"Log in With Another Account",
|
||||
"RESENDINITIALEMAIL":"Send new initialisation mail",
|
||||
"RESENDEMAILNOTIFICATION":"Resend Email notification",
|
||||
"TOAST": {
|
||||
"CREATED":"User created successfully.",
|
||||
"SAVED":"Profile saved successfully.",
|
||||
"EMAILSAVED":"E-mail saved successfully.",
|
||||
"INITEMAILSENT":"Initializing mail sent.",
|
||||
"PHONESAVED":"Phone saved successfully.",
|
||||
"PHONEREMOVED":"Phone has been removed.",
|
||||
"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/icon?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lato&display=swap" 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">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
|
@ -195,6 +195,11 @@ func (s *Server) SetInitialPassword(ctx context.Context, request *management.Pas
|
||||
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) {
|
||||
externalIDP, err := s.user.SearchExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
||||
if err != nil {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
usr_grant_event "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"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)
|
||||
}
|
||||
|
||||
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) {
|
||||
user, err := repo.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
|
@ -54,4 +54,6 @@ type UserRepository interface {
|
||||
ChangeAddress(ctx context.Context, address *model.Address) (*model.Address, 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
|
||||
NotInactive: Benutzer ist nicht inaktiv
|
||||
ShouldBeActiveOrInitial: Benutzer ist nicht aktiv oder initialisiert
|
||||
AlreadyInitialised: Benutzer ist bereits initialisiert
|
||||
NotInitialised: Benutzer ist noch nicht initialisiert
|
||||
NotLocked: Benutzer ist nicht gesperrt
|
||||
NoChanges: Keine Änderungen gefunden
|
||||
InitCodeNotFound: Kein Initialisierungs Code gefunden
|
||||
|
@ -12,6 +12,8 @@ Errors:
|
||||
AlreadyInactive: User already inactive
|
||||
NotInactive: User is not inactive
|
||||
ShouldBeActiveOrInitial: User is not active or inital
|
||||
AlreadyInitialised: User is already initialised
|
||||
NotInitialised: User is not yet initialised
|
||||
NotLocked: User is not locked
|
||||
NoChanges: No changes found
|
||||
InitCodeNotFound: Initialization Code not found
|
||||
|
@ -737,6 +737,9 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
||||
if user.Human == nil {
|
||||
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)
|
||||
err = es.generatePasswordCode(passwordCode, notifyType)
|
||||
@ -754,6 +757,35 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
||||
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 {
|
||||
if userID == "" {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
@ -1018,6 +1053,9 @@ func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userI
|
||||
if user.Human == nil {
|
||||
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 {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "Errors.User.EmailNotFound")
|
||||
}
|
||||
|
@ -4,15 +4,15 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
mock_cache "github.com/caos/zitadel/internal/cache/mock"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/mock"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
global_model "github.com/caos/zitadel/internal/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User, verified bool) *UserEventstore {
|
||||
data, _ := json.Marshal(user)
|
||||
events := []*es_models.Event{
|
||||
{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.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
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)
|
||||
}
|
||||
|
||||
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User, verified bool) *UserEventstore {
|
||||
data, _ := json.Marshal(user)
|
||||
code, _ := json.Marshal(user.PasswordCode)
|
||||
events := []*es_models.Event{
|
||||
{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.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
@ -382,7 +392,7 @@ func GetMockManipulateUserVerifiedPhone(ctrl *gomock.Controller) *UserEventstore
|
||||
return GetMockedEventstore(ctrl, mockEs)
|
||||
}
|
||||
|
||||
func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
||||
func GetMockManipulateUserFull(ctrl *gomock.Controller, verified bool) *UserEventstore {
|
||||
user := model.Human{
|
||||
Profile: &model.Profile{
|
||||
DisplayName: "DisplayName",
|
||||
@ -407,6 +417,9 @@ func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
||||
events := []*es_models.Event{
|
||||
{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.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
|
@ -3,7 +3,6 @@ package eventsourcing
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
@ -15,6 +14,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
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"
|
||||
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",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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",
|
||||
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"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
|
||||
@ -1516,8 +1516,10 @@ func TestCheckPassword(t *testing.T) {
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("password"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "userID",
|
||||
@ -1582,8 +1584,10 @@ func TestSetPassword(t *testing.T) {
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@ -1627,8 +1631,11 @@ func TestSetPassword(t *testing.T) {
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
||||
repo_model.User{
|
||||
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"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@ -1653,8 +1660,10 @@ func TestSetPassword(t *testing.T) {
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code2"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@ -1759,8 +1768,11 @@ func TestChangePassword(t *testing.T) {
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
||||
repo_model.User{
|
||||
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"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@ -1784,8 +1796,10 @@ func TestChangePassword(t *testing.T) {
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("older"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@ -1859,7 +1873,12 @@ func TestRequestSetPassword(t *testing.T) {
|
||||
{
|
||||
name: "create pw",
|
||||
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"),
|
||||
userID: "AggregateID",
|
||||
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},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
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) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
@ -2170,7 +2279,7 @@ func TestProfileByID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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"},
|
||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
args: args{
|
||||
@ -2423,7 +2550,9 @@ func TestChangeEmail(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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 == "" {
|
||||
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 {
|
||||
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{
|
||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||
}}),
|
||||
}}, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "userID",
|
||||
},
|
||||
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",
|
||||
args: args{
|
||||
@ -2606,7 +2750,7 @@ func TestCreateEmailVerificationCode(t *testing.T) {
|
||||
err := tt.args.es.CreateEmailVerificationCode(tt.args.ctx, tt.args.userID)
|
||||
|
||||
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) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
@ -2695,7 +2839,7 @@ func TestPhoneByID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
@ -2762,7 +2906,7 @@ func TestChangePhone(t *testing.T) {
|
||||
{
|
||||
name: "change phone, verified",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
@ -3216,7 +3360,7 @@ func TestChangeAddress(t *testing.T) {
|
||||
{
|
||||
name: "change address ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
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() {
|
||||
if h.State == int32(model.UserStateUnspecified) {
|
||||
if h.State == int32(model.UserStateUnspecified) || h.State == int32(model.UserStateInitial) {
|
||||
if h.Email != nil && h.IsEmailVerified {
|
||||
h.State = int32(model.UserStateActive)
|
||||
} else {
|
||||
|
@ -2,14 +2,15 @@ package eventsourcing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
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"
|
||||
usr_model "github.com/caos/zitadel/internal/user/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) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||
|
@ -5,11 +5,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
@ -184,6 +184,11 @@ var ManagementService_AuthMethods = authz.MethodMapping{
|
||||
CheckParam: "",
|
||||
},
|
||||
|
||||
"/caos.zitadel.management.api.v1.ManagementService/ResendInitialMail": authz.Option{
|
||||
Permission: "user.write",
|
||||
CheckParam: "",
|
||||
},
|
||||
|
||||
"/caos.zitadel.management.api.v1.ManagementService/SearchUserMemberships": authz.Option{
|
||||
Permission: "user.membership.read",
|
||||
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
|
||||
} = 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
|
||||
// in the proto definition for this message. If any rules are violated, an
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (m *MockManagementServiceClient) ResendPhoneVerificationCode(arg0 context.Context, arg1 *management.UserID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
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) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/{user_id}/memberships/_search"
|
||||
@ -2049,6 +2060,11 @@ enum NotificationType {
|
||||
NOTIFICATIONTYPE_SMS = 1;
|
||||
}
|
||||
|
||||
message InitialMailRequest {
|
||||
string id = 1 [(validate.rules).string.min_len = 1];
|
||||
string email = 2;
|
||||
}
|
||||
|
||||
enum PolicyState {
|
||||
POLICYSTATE_UNSPECIFIED = 0;
|
||||
POLICYSTATE_ACTIVE = 1;
|
||||
|
Loading…
Reference in New Issue
Block a user