1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Add Registry Lock password reset on front end (#2785)

This is only enabled for admins, for now at least. It sends an email to
the registry lock email address to reset it.
This commit is contained in:
gbrodman
2025-07-28 16:23:39 -04:00
committed by GitHub
parent 39c2a79898
commit 9f191e9392
13 changed files with 322 additions and 175 deletions

View File

@@ -280,4 +280,15 @@ export class BackendService {
`/console-api/registry-lock-verify?lockVerificationCode=${lockVerificationCode}`
);
}
requestRegistryLockPasswordReset(
registrarId: string,
registryLockEmail: string
) {
return this.http.post('/console-api/password-reset-request', {
type: 'REGISTRY_LOCK',
registrarId,
registryLockEmail,
});
}
}

View File

@@ -80,7 +80,15 @@
roleToDescription(userDetails().role)
}}</span>
</mat-list-item>
@if (userDetails().password) {
@if (userDetails().registryLockEmailAddress) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Registry Lock email</span>
<span class="console-app__list-value">{{
userDetails().registryLockEmailAddress
}}</span>
</mat-list-item>
} @if (userDetails().password) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Password</span>

View File

@@ -35,5 +35,8 @@
border: 1px solid #ddd;
border-radius: 10px;
}
.console-app__list-key {
width: 160px;
}
}
}

View File

@@ -1,45 +1,57 @@
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
<mat-icon
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
>help_outline</mat-icon
></mat-label
>
<input
matInput
minlength="3"
maxlength="3"
[required]="true"
[(ngModel)]="user().emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</p>
<p>
<mat-form-field appearance="outline">
<mat-label
>User Role:
<mat-icon
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
>help_outline</mat-icon
></mat-label
>
<mat-select [(ngModel)]="user().role" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<div class="console-app__user-edit">
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
<mat-icon
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
>help_outline</mat-icon
></mat-label
>
<input
matInput
minlength="3"
maxlength="3"
[required]="true"
[(ngModel)]="user().emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</p>
<p>
<mat-form-field appearance="outline">
<mat-label
>User Role:
<mat-icon
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
>help_outline</mat-icon
></mat-label
>
<mat-select [(ngModel)]="user().role" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
aria-label="Save changes to the user"
>
Save
</button>
</form>
@if(userDataService.userData()?.isAdmin) {
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
aria-label="Save changes to the user"
aria-label="Reset registry lock password"
(click)="requestRegistryLockPasswordReset()"
>
Save
Reset registry lock password
</button>
</form>
}
</div>

View File

@@ -0,0 +1,20 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
.console-app__user-edit {
button {
display: block;
margin-bottom: 5px;
}
}

View File

@@ -17,13 +17,56 @@ import {
Component,
ElementRef,
EventEmitter,
Inject,
input,
Output,
ViewChild,
} from '@angular/core';
import { MaterialModule } from '../material.module';
import { FormsModule } from '@angular/forms';
import { User } from './users.service';
import { User, UsersService } from './users.service';
import { UserDataService } from '../shared/services/userData.service';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarService } from '../registrar/registrar.service';
import {
MAT_DIALOG_DATA,
MatDialog,
MatDialogRef,
} from '@angular/material/dialog';
import { filter, switchMap, take } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'app-reset-lock-password-dialog',
template: `
<h2 mat-dialog-title>Please confirm the password reset:</h2>
<mat-dialog-content>
This will send a registry lock password reset email to
{{ data.registryLockEmailAddress }}.
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-button color="warn" (click)="onSave()">Confirm</button>
</mat-dialog-actions>
`,
imports: [CommonModule, MaterialModule],
})
export class ResetRegistryLockPasswordComponent {
constructor(
public dialogRef: MatDialogRef<ResetRegistryLockPasswordComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { registryLockEmailAddress: string }
) {}
onSave(): void {
this.dialogRef.close(true);
}
onCancel(): void {
this.dialogRef.close(false);
}
}
@Component({
selector: 'app-user-edit-form',
@@ -39,12 +82,22 @@ export class UserEditFormComponent {
{
emailAddress: '',
role: 'ACCOUNT_MANAGER',
registryLockEmailAddress: '',
},
{ transform: (user: User) => structuredClone(user) }
);
@Output() onEditComplete = new EventEmitter<User>();
constructor(
protected userDataService: UserDataService,
private backendService: BackendService,
private resetRegistryLockPasswordDialog: MatDialog,
private registrarService: RegistrarService,
private usersService: UsersService,
private _snackBar: MatSnackBar
) {}
saveEdit(e: SubmitEvent) {
e.preventDefault();
if (this.form.nativeElement.checkValidity()) {
@@ -53,4 +106,34 @@ export class UserEditFormComponent {
this.form.nativeElement.reportValidity();
}
}
sendRegistryLockPasswordResetRequest() {
return this.backendService.requestRegistryLockPasswordReset(
this.registrarService.registrarId(),
this.user().registryLockEmailAddress!
);
}
requestRegistryLockPasswordReset() {
const dialogRef = this.resetRegistryLockPasswordDialog.open(
ResetRegistryLockPasswordComponent,
{
data: {
registryLockEmailAddress: this.user().registryLockEmailAddress,
},
}
);
dialogRef
.afterClosed()
.pipe(
take(1),
filter((result) => !!result)
)
.pipe(switchMap((_) => this.sendRegistryLockPasswordResetRequest()))
.subscribe({
next: (_) => this.usersService.currentlyOpenUserEmail.set(''),
error: (err: HttpErrorResponse) =>
this._snackBar.open(err.error || err.message),
});
}
}

View File

@@ -33,6 +33,7 @@ export interface User {
emailAddress: string;
role: string;
password?: string;
registryLockEmailAddress?: string;
}
@Injectable()