From 29f97e781ecc3177d3723100b3cece6ef62bb3a0 Mon Sep 17 00:00:00 2001 From: Lukas Metzger Date: Thu, 12 Apr 2018 14:19:12 +0200 Subject: [PATCH] Implemented credential editor --- .../src/app/apitypes/Credential.apitype.ts | 14 ++ frontend/src/app/app-routing.module.ts | 11 ++ frontend/src/app/app.module.ts | 6 +- .../app/operations/credentials.operations.ts | 111 ++++++++++++ .../edit-auth/edit-auth-line.component.ts | 8 +- .../edit-credentials.component.html | 82 +++++++++ .../edit-credentials.component.scss | 0 .../edit-credentials.component.ts | 158 ++++++++++++++++++ .../pages/edit-user/edit-user.component.ts | 2 +- 9 files changed, 387 insertions(+), 5 deletions(-) create mode 100644 frontend/src/app/apitypes/Credential.apitype.ts create mode 100644 frontend/src/app/operations/credentials.operations.ts create mode 100644 frontend/src/app/pages/edit-credentials/edit-credentials.component.html create mode 100644 frontend/src/app/pages/edit-credentials/edit-credentials.component.scss create mode 100644 frontend/src/app/pages/edit-credentials/edit-credentials.component.ts diff --git a/frontend/src/app/apitypes/Credential.apitype.ts b/frontend/src/app/apitypes/Credential.apitype.ts new file mode 100644 index 0000000..03dc8f5 --- /dev/null +++ b/frontend/src/app/apitypes/Credential.apitype.ts @@ -0,0 +1,14 @@ +export class CredentialApitype { + + public id = 0; + + public description = ''; + + public type = ''; + + public key: string = null; + + constructor(init: Object) { + Object.assign(this, init); + } +} diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index c5ff5ca..fe429e5 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,3 +1,4 @@ +import { EditCredentialsComponent } from './pages/edit-credentials/edit-credentials.component'; import { NativeGuard } from './services/native-guard.service'; import { LoggedOutGuard } from './services/logged-out-guard.service'; import { CreateUserComponent } from './pages/create-user/create-user.component'; @@ -51,6 +52,16 @@ const routes: Routes = [ component: EditAuthComponent, data: { type: 'NATIVE' } }, + { + path: 'domains/master/:domainId/records/:recordId/credentials', + component: EditCredentialsComponent, + data: { type: 'MASTER' } + }, + { + path: 'domains/native/:domainId/records/:recordId/credentials', + component: EditCredentialsComponent, + data: { type: 'NATIVE' } + }, { path: '', canActivate: [AdminGuard], diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index dff0d7c..6329608 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,3 +1,5 @@ +import { CredentialsOperation } from './operations/credentials.operations'; +import { EditCredentialsComponent } from './pages/edit-credentials/edit-credentials.component'; import { EditAuthAddComponent } from './pages/edit-auth/edit-auth-add.component'; import { EditAuthLineComponent } from './pages/edit-auth/edit-auth-line.component'; import { RecordsOperation } from './operations/records.operations'; @@ -69,7 +71,8 @@ import { UsersComponent } from './pages/users/users.component'; CreateUserComponent, SearchComponent, EditAuthLineComponent, - EditAuthAddComponent + EditAuthAddComponent, + EditCredentialsComponent ], imports: [ BrowserModule, @@ -85,6 +88,7 @@ import { UsersComponent } from './pages/users/users.component'; DomainsOperation, UsersOperation, RecordsOperation, + CredentialsOperation, AuthGuard, AdminGuard, NativeGuard, diff --git a/frontend/src/app/operations/credentials.operations.ts b/frontend/src/app/operations/credentials.operations.ts new file mode 100644 index 0000000..3b7bf15 --- /dev/null +++ b/frontend/src/app/operations/credentials.operations.ts @@ -0,0 +1,111 @@ +import { CredentialApitype } from './../apitypes/Credential.apitype'; +import { SoaApitype } from './../apitypes/Soa.apitype'; +import { DomainApitype } from './../apitypes/Domain.apitype'; +import { ListApitype } from './../apitypes/List.apitype'; +import { Injectable } from '@angular/core'; +import { HttpService } from '../services/http.service'; +import { StateService } from '../services/state.service'; +import { SessionApitype } from '../apitypes/Session.apitype'; + +@Injectable() +export class CredentialsOperation { + + constructor(private http: HttpService, private gs: StateService) { } + + public async getList(recordId: number): Promise> { + try { + return new ListApitype(await this.http.get(['/records', recordId.toString(), 'credentials'])); + } catch (e) { + console.error(e); + return new ListApitype({ paging: {}, results: [] }); + } + } + + public async delete(recordId: number, credentialId: number): Promise { + try { + await this.http.delete(['/records', recordId.toString(), 'credentials', credentialId.toString()]); + return true; + } catch (e) { + console.error(e); + return false; + } + } + + public async getSingle(recordId: number, credentialId: number): Promise { + try { + return new CredentialApitype(await this.http.get(['/records', recordId.toString(), 'credentials', credentialId.toString()])); + } catch (e) { + console.error(e); + return new CredentialApitype({}); + } + } + + public async updateKey(recordId: number, credentalId: number, description: string, key: string): Promise { + try { + await this.http.put(['/records', recordId.toString(), 'credentials', credentalId.toString()], { + description: description, + key: key + }); + + return true; + } catch (e) { + if (e.response.status || e.response.status === 400) { + throw new Error('The key is not a valid public key!'); + } else { + console.error(e); + return false; + } + } + } + + public async updatePassword(recordId: number, credentalId: number, description: string, password: string): Promise { + try { + const data = { + description: description + }; + + if (password.length > 0) { + data['password'] = password; + } + + await this.http.put(['/records', recordId.toString(), 'credentials', credentalId.toString()], data); + + return true; + } catch (e) { + console.error(e); + return false; + } + } + + public async createKey(recordId: number, description: string, key: string): Promise { + try { + const result = new DomainApitype(await this.http.post(['/records', recordId.toString(), 'credentials'], { + description: description, + type: 'key', + key: key + })); + + } catch (e) { + if (e.response.status || e.response.status === 400) { + throw new Error('The key is not a valid public key!'); + } else { + console.error(e); + return new CredentialApitype({}); + } + } + } + + public async createPassword(recordId: number, description: string, password: string): Promise { + try { + const result = new DomainApitype(await this.http.post(['/records', recordId.toString(), 'credentials'], { + description: description, + type: 'password', + password: password + })); + + } catch (e) { + console.error(e); + return new CredentialApitype({}); + } + } +} diff --git a/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts b/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts index 0b80064..0b1043d 100644 --- a/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts +++ b/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts @@ -1,3 +1,4 @@ +import { Router, ActivationEnd, ActivatedRoute } from '@angular/router'; import { ModalOptionsDatatype } from './../../datatypes/modal-options.datatype'; import { ModalService } from './../../services/modal.service'; import { RecordsOperation } from './../../operations/records.operations'; @@ -28,7 +29,8 @@ export class EditAuthLineComponent implements OnInit, OnChanges { public inputPriority: FormControl; public inputTtl: FormControl; - constructor(private fb: FormBuilder, public gs: StateService, private records: RecordsOperation, private modal: ModalService) { + constructor(private fb: FormBuilder, public gs: StateService, private records: RecordsOperation, + private modal: ModalService, private router: Router, private route: ActivatedRoute) { this.setupFormControls(); } @@ -87,7 +89,7 @@ export class EditAuthLineComponent implements OnInit, OnChanges { await this.modal.showMessage(new ModalOptionsDatatype({ heading: 'Confirm deletion', body: 'Are you shure you want to delete the ' + this.inputType.value + - ' record ' + this.fullName() + ' with content ' + this.inputContent.value + '?', + ' record ' + this.fullName() + ' with content ' + this.inputContent.value + '?', acceptText: 'Delete', dismisText: 'Cancel', acceptClass: 'danger' @@ -101,6 +103,6 @@ export class EditAuthLineComponent implements OnInit, OnChanges { } public async onRemoteClick() { - + this.router.navigate(['./records', this.entry.id.toString(), 'credentials'], { relativeTo: this.route }); } } diff --git a/frontend/src/app/pages/edit-credentials/edit-credentials.component.html b/frontend/src/app/pages/edit-credentials/edit-credentials.component.html new file mode 100644 index 0000000..308f042 --- /dev/null +++ b/frontend/src/app/pages/edit-credentials/edit-credentials.component.html @@ -0,0 +1,82 @@ +
+
+
+ + + +
+ + + + + + + + + + + + + + + +
DescriptionType
{{ credential.description }}{{ credential.type }} + + +
+
+
+
+
+ + +
+ Description can not be empty. +
+
+ +
+ + +
+ Key is required. +
+
+ + + The key is invalid. + + + +
+
+
+ + +
+ Description can not be empty. +
+
+ +
+ + +
+ Password is required. +
+
+ +
+ + +
+ Passwords do not match. +
+
+ + +
+
+
\ No newline at end of file diff --git a/frontend/src/app/pages/edit-credentials/edit-credentials.component.scss b/frontend/src/app/pages/edit-credentials/edit-credentials.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/pages/edit-credentials/edit-credentials.component.ts b/frontend/src/app/pages/edit-credentials/edit-credentials.component.ts new file mode 100644 index 0000000..7885198 --- /dev/null +++ b/frontend/src/app/pages/edit-credentials/edit-credentials.component.ts @@ -0,0 +1,158 @@ +import { CredentialApitype } from './../../apitypes/Credential.apitype'; +import { RecordsOperation } from './../../operations/records.operations'; +import { CredentialsOperation } from './../../operations/credentials.operations'; +import { RecordApitype } from './../../apitypes/Record.apitype'; +import { PagingApitype } from './../../apitypes/Paging.apitype'; +import { PermissionApitype } from './../../apitypes/Permission.apitype'; +import { ModalService } from './../../services/modal.service'; +import { ActivatedRoute, ParamMap, Router } from '@angular/router'; +import { FormGroup, Validators, FormBuilder } from '@angular/forms'; +import { Component, OnInit } from '@angular/core'; +import { ModalOptionsDatatype } from '../../datatypes/modal-options.datatype'; +import { PasswordValidationUtil } from '../../utils/password-validation.util'; + +@Component({ + selector: 'app-edit-credentials', + templateUrl: './edit-credentials.component.html', + styleUrls: ['./edit-credentials.component.scss'] +}) +export class EditCredentialsComponent implements OnInit { + public keyForm: FormGroup; + public passwordForm: FormGroup; + + public editType = ''; + public editId = 0; + + public keyInvalid = false; + + public credentialList: CredentialApitype[] = []; + + public domainId = 0; + public recordId = 0; + public record: RecordApitype = new RecordApitype({}); + + constructor(private fb: FormBuilder, private route: ActivatedRoute, private credentials: CredentialsOperation, + private router: Router, private modal: ModalService, public records: RecordsOperation) { } + + ngOnInit() { + this.createForm(); + + this.route.paramMap.subscribe((params) => this.initControl(params)); + } + + private async initControl(params: ParamMap) { + this.recordId = +params.get('recordId'); + this.domainId = +params.get('domainId'); + + this.loadCredentials(); + } + + private createForm() { + this.keyForm = this.fb.group({ + description: ['', Validators.required], + key: ['', Validators.required] + }); + + this.passwordForm = this.fb.group({ + description: ['', Validators.required], + password: ['', Validators.required], + password2: [''] + }, { validator: PasswordValidationUtil.matchPassword }); + } + + public async onSubmit() { + this.keyInvalid = false; + try { + if (this.editId === 0) { + if (this.editType === 'key') { + const v = this.keyForm.value; + await this.credentials.createKey(this.recordId, v.description, v.key); + } else if (this.editType === 'password') { + const v = this.passwordForm.value; + await this.credentials.createPassword(this.recordId, v.description, v.password); + } + } else { + if (this.editType === 'key') { + const v = this.keyForm.value; + await this.credentials.updateKey(this.recordId, this.editId, v.description, v.key); + } else if (this.editType === 'password') { + const v = this.passwordForm.value; + await this.credentials.updatePassword(this.recordId, this.editId, v.description, v.password); + } + } + + this.editId = 0; + this.editType = ''; + await this.loadCredentials(); + } catch (e) { + this.keyInvalid = true; + } + } + + public async onAddKey() { + this.editId = 0; + this.editType = 'key'; + this.keyInvalid = false; + + this.keyForm.reset({ + description: '', + key: '' + }); + } + + public async onAddPassword() { + this.editId = 0; + this.editType = 'password'; + this.passwordForm.controls['password'].setValidators(Validators.required); + + this.passwordForm.reset({ + description: '', + password: '', + password2: '' + }); + } + + public async onEditClick(credentialId: number) { + const credential = await this.credentials.getSingle(this.recordId, credentialId); + + if (credential.type === 'key') { + this.editType = 'key'; + this.editId = credentialId; + this.keyInvalid = false; + this.keyForm.reset({ + description: credential.description, + key: credential.key + }); + } else if (credential.type === 'password') { + this.editType = 'password'; + this.editId = credentialId; + this.passwordForm.controls['password'].clearValidators(); + this.passwordForm.reset({ + description: credential.description, + password: '', + password2: '' + }); + } + } + + public async loadCredentials() { + const res = await this.credentials.getList(this.recordId); + + this.credentialList = res.results; + } + + public async onRemoveCredential(credential: CredentialApitype) { + try { + await this.modal.showMessage(new ModalOptionsDatatype({ + heading: 'Confirm deletion', + body: 'Are you shure you want to delete the credential ' + credential.description + '?', + acceptText: 'Delete', + dismisText: 'Cancel', + acceptClass: 'danger' + })); + await this.credentials.delete(this.recordId, credential.id); + await this.loadCredentials(); + } catch (e) { + } + } +} diff --git a/frontend/src/app/pages/edit-user/edit-user.component.ts b/frontend/src/app/pages/edit-user/edit-user.component.ts index 5668947..cc51cd7 100644 --- a/frontend/src/app/pages/edit-user/edit-user.component.ts +++ b/frontend/src/app/pages/edit-user/edit-user.component.ts @@ -11,7 +11,7 @@ import { ModalOptionsDatatype } from '../../datatypes/modal-options.datatype'; import { PasswordValidationUtil } from '../../utils/password-validation.util'; @Component({ - selector: 'app-create-user', + selector: 'app-edit-user', templateUrl: './edit-user.component.html', styleUrls: ['./edit-user.component.scss'] })