mirror of
https://github.com/google/nomulus
synced 2026-09-19 06:32:00 +00:00
Add UI for EPP Password update (#2393)
This commit is contained in:
@@ -25,6 +25,9 @@
|
||||
font-weight: 500;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.contact__name-column-roles {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
&__empty-contacts {
|
||||
display: flex;
|
||||
|
||||
@@ -17,10 +17,9 @@ import { MatTableDataSource } from '@angular/material/table';
|
||||
import { take } from 'rxjs';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import {
|
||||
Contact,
|
||||
ContactService,
|
||||
ViewReadyContact,
|
||||
contactTypeToViewReadyContact,
|
||||
ViewReadyContact,
|
||||
} from './contact.service';
|
||||
|
||||
@Component({
|
||||
@@ -85,7 +84,7 @@ export default class ContactComponent {
|
||||
});
|
||||
}
|
||||
|
||||
openDetails(contact: Contact) {
|
||||
openDetails(contact: ViewReadyContact) {
|
||||
this.contactService.setEditableContact(contact);
|
||||
this.contactService.isContactDetailsView = true;
|
||||
}
|
||||
|
||||
@@ -77,19 +77,17 @@ export class ContactService {
|
||||
private registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
setEditableContact(contact?: Contact) {
|
||||
this.contactInEdit = contactTypeToViewReadyContact(
|
||||
contact
|
||||
? contact
|
||||
: {
|
||||
emailAddress: '',
|
||||
name: '',
|
||||
types: ['ADMIN'],
|
||||
faxNumber: '',
|
||||
phoneNumber: '',
|
||||
registrarId: '',
|
||||
}
|
||||
);
|
||||
setEditableContact(contact?: ViewReadyContact) {
|
||||
this.contactInEdit = contact
|
||||
? contact
|
||||
: contactTypeToViewReadyContact({
|
||||
emailAddress: '',
|
||||
name: '',
|
||||
types: ['ADMIN'],
|
||||
faxNumber: '',
|
||||
phoneNumber: '',
|
||||
registrarId: '',
|
||||
});
|
||||
}
|
||||
|
||||
fetchContacts(): Observable<Contact[]> {
|
||||
@@ -106,13 +104,6 @@ export class ContactService {
|
||||
.pipe(switchMap((_) => this.fetchContacts()));
|
||||
}
|
||||
|
||||
updateContact(index: number, contact: ViewReadyContact) {
|
||||
const newContacts = this.contacts().map((c, i) =>
|
||||
i === index ? contact : c
|
||||
);
|
||||
return this.saveContacts(newContacts);
|
||||
}
|
||||
|
||||
addContact(contact: ViewReadyContact) {
|
||||
const newContacts = this.contacts().concat([contact]);
|
||||
return this.saveContacts(newContacts);
|
||||
|
||||
@@ -65,16 +65,17 @@ export class ContactDetailsComponent {
|
||||
|
||||
save(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
this.contactService
|
||||
.saveContacts([this.contactService.contactInEdit])
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
this.goBack();
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error);
|
||||
},
|
||||
});
|
||||
const request = this.contactService.isContactNewView
|
||||
? this.contactService.addContact(this.contactService.contactInEdit)
|
||||
: this.contactService.saveContacts(this.contactService.contacts());
|
||||
request.subscribe({
|
||||
complete: () => {
|
||||
this.goBack();
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
checkboxIsChecked(type: string) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<div class="settings-security__edit-password">
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to security settings"
|
||||
(click)="goBack()"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
<h1>Update EPP password</h1>
|
||||
<p class="secondary-text">
|
||||
Passwords must be between 6 and 16 alphanumeric characters
|
||||
</p>
|
||||
<form
|
||||
(ngSubmit)="save()"
|
||||
[formGroup]="passwordUpdateForm"
|
||||
class="settings-security__edit-password-form"
|
||||
>
|
||||
<div class="settings-security__edit-password-field">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Old password: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
formControlName="oldPassword"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<mat-error *ngIf="hasError('oldPassword') as errorText">{{
|
||||
errorText
|
||||
}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="settings-security__edit-password-field">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>New password: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
formControlName="newPassword"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<mat-error *ngIf="hasError('newPassword') as errorText">{{
|
||||
errorText
|
||||
}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="settings-security__edit-password-field">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Confirm new password: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
formControlName="newPasswordRepeat"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<mat-error *ngIf="hasError('newPasswordRepeat') as errorText">{{
|
||||
errorText
|
||||
}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[disabled]="!passwordUpdateForm.valid"
|
||||
aria-label="Save epp password update"
|
||||
type="submit"
|
||||
class="settings-security__edit-password-save"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
.settings-security__edit-password {
|
||||
max-width: 616px;
|
||||
&-field {
|
||||
width: 100%;
|
||||
mat-form-field {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
&-form {
|
||||
margin-top: 30px;
|
||||
}
|
||||
&-save {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ValidatorFn,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import { SecurityService } from './security.service';
|
||||
|
||||
type errorCode = 'required' | 'maxlength' | 'minlength' | 'passwordsDontMatch';
|
||||
|
||||
type errorFriendlyText = { [type in errorCode]: String };
|
||||
|
||||
@Component({
|
||||
selector: 'app-epp-password-edit',
|
||||
templateUrl: './eppPasswordEdit.component.html',
|
||||
styleUrls: ['./eppPasswordEdit.component.scss'],
|
||||
})
|
||||
export default class EppPasswordEditComponent {
|
||||
MIN_MAX_LENGHT = new String(
|
||||
'Passwords must be between 6 and 16 alphanumeric characters'
|
||||
);
|
||||
|
||||
errorTextMap: errorFriendlyText = {
|
||||
required: "This field can't be empty",
|
||||
maxlength: this.MIN_MAX_LENGHT,
|
||||
minlength: this.MIN_MAX_LENGHT,
|
||||
passwordsDontMatch: "Passwords don't match",
|
||||
};
|
||||
|
||||
constructor(
|
||||
public securityService: SecurityService,
|
||||
private _snackBar: MatSnackBar,
|
||||
public registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
hasError(controlName: string) {
|
||||
const maybeErrors = this.passwordUpdateForm.get(controlName)?.errors;
|
||||
const maybeError =
|
||||
maybeErrors && (Object.keys(maybeErrors)[0] as errorCode);
|
||||
if (maybeError) {
|
||||
return this.errorTextMap[maybeError];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
newPasswordsMatch: ValidatorFn = (control: AbstractControl) => {
|
||||
if (
|
||||
this.passwordUpdateForm?.get('newPassword')?.value ===
|
||||
this.passwordUpdateForm?.get('newPasswordRepeat')?.value
|
||||
) {
|
||||
this.passwordUpdateForm?.get('newPasswordRepeat')?.setErrors(null);
|
||||
} else {
|
||||
this.passwordUpdateForm
|
||||
?.get('newPasswordRepeat')
|
||||
?.setErrors({ passwordsDontMatch: control.value });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
passwordUpdateForm = new FormGroup({
|
||||
oldPassword: new FormControl('', [Validators.required]),
|
||||
newPassword: new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(16),
|
||||
this.newPasswordsMatch,
|
||||
]),
|
||||
newPasswordRepeat: new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(16),
|
||||
this.newPasswordsMatch,
|
||||
]),
|
||||
});
|
||||
|
||||
save() {
|
||||
debugger;
|
||||
// this.securityService.saveEppPassword().subscribe({
|
||||
// complete: () => {
|
||||
// this.goBack();
|
||||
// },
|
||||
// error: (err: HttpErrorResponse) => {
|
||||
// this._snackBar.open(err.error);
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.securityService.isEditingPassword = false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,48 @@
|
||||
@if(securityService.isEditingSecurity) {
|
||||
<app-security-edit></app-security-edit>
|
||||
} @else if(securityService.isEditingPassword) {
|
||||
<app-epp-password-edit></app-epp-password-edit>
|
||||
} @else {
|
||||
<div class="settings-security">
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-list role="list">
|
||||
<!-- IP Allowlist Start -->
|
||||
<mat-list-item role="listitem">
|
||||
<div class="settings-security__section-header">
|
||||
<h2>EPP Password</h2>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Edit EPP Password"
|
||||
(click)="editEppPassword()"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item role="listitem" lines="3">
|
||||
<span class="console-app__list-value"
|
||||
>todo: come up with a text here</span
|
||||
>
|
||||
</mat-list-item>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">Password</span>
|
||||
<span class="console-app__list-value">••••••••••••••</span>
|
||||
</mat-list-item>
|
||||
@if(dataSource.eppPasswordLastUpdated) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">Last Changed</span>
|
||||
<span class="console-app__list-value">{{
|
||||
dataSource.eppPasswordLastUpdated
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
}
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-list role="list">
|
||||
@@ -12,7 +53,7 @@
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Edit Contact"
|
||||
aria-label="Edit security settings"
|
||||
(click)="editSecurity()"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, effect } from '@angular/core';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import {
|
||||
SecurityService,
|
||||
RegistrarService,
|
||||
SecuritySettings,
|
||||
apiToUiConverter,
|
||||
} from './security.service';
|
||||
} from 'src/app/registrar/registrar.service';
|
||||
import { SecurityService, apiToUiConverter } from './security.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-security',
|
||||
@@ -43,4 +42,8 @@ export default class SecurityComponent {
|
||||
editSecurity() {
|
||||
this.securityService.isEditingSecurity = true;
|
||||
}
|
||||
|
||||
editEppPassword() {
|
||||
this.securityService.isEditingPassword = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,14 @@
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { switchMap } from 'rxjs';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import {
|
||||
IpAllowListItem,
|
||||
RegistrarService,
|
||||
SecuritySettings,
|
||||
SecuritySettingsBackendModel,
|
||||
} from 'src/app/registrar/registrar.service';
|
||||
import { BackendService } from 'src/app/shared/services/backend.service';
|
||||
|
||||
export interface ipAllowListItem {
|
||||
value: string;
|
||||
}
|
||||
export interface SecuritySettings {
|
||||
clientCertificate?: string;
|
||||
failoverClientCertificate?: string;
|
||||
ipAddressAllowList?: Array<ipAllowListItem>;
|
||||
}
|
||||
|
||||
export interface SecuritySettingsBackendModel {
|
||||
clientCertificate?: string;
|
||||
failoverClientCertificate?: string;
|
||||
ipAddressAllowList?: Array<string>;
|
||||
}
|
||||
|
||||
export function apiToUiConverter(
|
||||
securitySettings: SecuritySettingsBackendModel = {}
|
||||
): SecuritySettings {
|
||||
@@ -48,7 +38,7 @@ export function uiToApiConverter(
|
||||
return Object.assign({}, securitySettings, {
|
||||
ipAddressAllowList: (securitySettings.ipAddressAllowList || [])
|
||||
.filter((s) => s.value)
|
||||
.map((ipAllowItem: ipAllowListItem) => ipAllowItem.value),
|
||||
.map((ipAllowItem: IpAllowListItem) => ipAllowItem.value),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,6 +48,7 @@ export function uiToApiConverter(
|
||||
export class SecurityService {
|
||||
securitySettings: SecuritySettings = {};
|
||||
isEditingSecurity: boolean = false;
|
||||
isEditingPassword: boolean = false;
|
||||
|
||||
constructor(
|
||||
private backend: BackendService,
|
||||
@@ -76,4 +67,6 @@ export class SecurityService {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
saveEppPassword() {}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import {
|
||||
SecurityService,
|
||||
IpAllowListItem,
|
||||
RegistrarService,
|
||||
SecuritySettings,
|
||||
apiToUiConverter,
|
||||
ipAllowListItem,
|
||||
} from './security.service';
|
||||
} from 'src/app/registrar/registrar.service';
|
||||
import { SecurityService, apiToUiConverter } from './security.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-security-edit',
|
||||
@@ -58,7 +57,7 @@ export default class SecurityEditComponent {
|
||||
this.securityService.isEditingSecurity = false;
|
||||
}
|
||||
|
||||
removeIpEntry(ip: ipAllowListItem) {
|
||||
removeIpEntry(ip: IpAllowListItem) {
|
||||
this.dataSource.ipAddressAllowList =
|
||||
this.dataSource.ipAddressAllowList?.filter((item) => item !== ip);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user