import { Component, Input, OnInit } from '@angular/core'; import { AbstractControl, Validators } from '@angular/forms'; import { SpinnerService } from '@core/services/spinner.service'; import { SFTPAPI } from '@core/typings/api/sftp.typing'; import { UserFromApi } from '@core/typings/client-user.typing'; import { UserService } from '@features/users/user.service'; import { ArrayHelpersService, FileService, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { AnalyticsService, EventType } from '@yourcause/common/analytics'; import { I18nService } from '@yourcause/common/i18n'; import { YCModalComponent } from '@yourcause/common/modals'; import { SFTPService } from '../sftp.service'; interface CredentialsFormGroup { id: number; siteName: string; folder: string; server: string; userName: string; password: string; description: string; userList: number[]; pgpPublicEncryptionKey?: string; pgpPublicKeyDropzone?: string; } @Component({ selector: 'gc-credentials-modal', templateUrl: './credentials-modal.component.html', styleUrls: ['./credentials-modal.component.scss'] }) export class CredentialsModalComponent extends YCModalComponent implements OnInit { @Input() credential?: SFTPAPI.SFTPCredentialsListView; @Input() context: 'add'|'edit'; userOptions: TypeaheadSelectOption[] = []; allUsers: UserFromApi[]; page: 'setup'|'confirm' = 'setup'; modalHeader: string; alertMessage: string; alertClass: string; confirmSummaryText = this.i18n.translate( 'CONFIG:textConfirmSummaryText', {}, 'Confirm the information below and click \'Save\' to add this SFTP connection.' ); noFolderText = this.i18n.translate( 'CONFIG:textNoFolder', {}, 'No folder' ); confirmHeader = this.i18n.translate( 'CONFIG:textConfirmConnections', {}, 'Confirm Connection' ); primaryButtonText = this.i18n.translate( 'CONFIG:textTestSFTPConnection', {}, 'Test SFTP connection' ); successMessage = this.i18n.translate( 'CONFIG:textSuccessfullyEstablishedSFTPConnection', {}, 'Successfully established SFTP connection' ); saveText = this.i18n.translate( 'common:btnSave', {}, 'Save' ); sftpFolderHelpText = this.i18n.translate( 'CONFIG:textSftpFolderHelpText', {}, `Use "/" to designate sub-paths. For example, use "files/Examples" to place your file in the "Examples" sub-folder and "files" to add the file to the files folder. Leaving the field blank will add the file to the root. Folder names are case sensitive and must currently exist on the SFTP server.` ); testSuccessful = false; passwordVisible = false; formGroup: TypeSafeFormGroup; confirmSummary: SFTPAPI.SFTPCredentialsListView; sshFingerprint: string; constructor ( private formBuilder: TypeSafeFormBuilder, private i18n: I18nService, private sftpService: SFTPService, private spinnerService: SpinnerService, private arrayHelper: ArrayHelpersService, private userService: UserService, private fileService: FileService, private analyticsService: AnalyticsService ) { super(); } async ngOnInit () { this.setModalText(); this.setUserOptions(); const credential = this.credential || {} as SFTPAPI.SFTPCredentialsListView; this.formGroup = this.formBuilder.group({ id: credential.id || null, siteName: [credential.siteName || '', Validators.required], folder: [credential.folder || ''], server: [credential.server || '', Validators.required], userName: ['', Validators.required], // Username and password always re-entered password: ['', Validators.required], userList: [credential.userIds || null, Validators.required], description: credential.description || '', pgpPublicEncryptionKey: credential.pgpPublicEncryptionKey || '', pgpPublicKeyDropzone: '' }, { validator: [ this.pgpArmoredKeyValidator() ] }); } pgpArmoredKeyValidator () { return (group: AbstractControl) => { let returnVal = null; if (group.value.pgpPublicEncryptionKey) { const startMarker = group.value.pgpPublicEncryptionKey.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') !== -1; const endMarker = group.value.pgpPublicEncryptionKey.indexOf('-----END PGP PUBLIC KEY BLOCK-----') !== -1; if (!startMarker || !endMarker) { returnVal = { pgpPublicEncryptionKey: { incorrectFormat: { i18nKey: 'common:textPGPEncryptionKeyValidation', defaultValue: 'Public key must be ASCII-armored' } } }; } } return returnVal; }; } async populatePGPKey (file: any) { const string = await this.fileService.readAsString(file.file); this.formGroup.get('pgpPublicEncryptionKey').setValue(string); } async handlePrimaryClick () { if (this.page === 'setup') { const response = await this.testConnection(); if (typeof response !== 'string') { this.page = 'confirm'; this.modalHeader = this.confirmHeader; this.primaryButtonText = this.saveText; this.confirmSummary = response; } this.analyticsService.emitEvent({ eventName: 'Test SFTP connection', eventType: EventType.Click, extras: null }); } else { this.closeModal.emit({ ...this.formGroup.value, sshKeyFingerprint: this.sshFingerprint }); this.analyticsService.emitEvent({ eventName: 'Save SFTP setup', eventType: EventType.Click, extras: null }); } } async testConnection () { this.alertMessage = null; this.spinnerService.startSpinner(); const response = await this.sftpService.testSFTPCredentials(this.formGroup.value); if (typeof response === 'string') { this.alertClass = 'danger'; this.alertMessage = response; } else { this.sshFingerprint = response.sshKeyFingerprint; } this.spinnerService.stopSpinner(); return response; } onCancel () { this.closeModal.emit(); } setUserOptions () { this.allUsers = this.userService.allUsers.filter((user) => { return !user.isDeactivated; }); this.userOptions = this.arrayHelper.sort( this.allUsers.map((user) => { return { label: user.fullName, value: user.userId, option: `
${user.fullName}
${user.email}
` }; }), 'label' ); } togglePasswordVisible = () => { this.passwordVisible = !this.passwordVisible; }; resetConnectionTest () { this.alertMessage = null; } setModalText () { if (this.context === 'add') { this.modalHeader = this.i18n.translate( 'CONFIG:textAddConnection', {}, 'Add Connection' ); } else { this.modalHeader = this.i18n.translate( 'CONFIG:textEditConnection', {}, 'Edit Connection' ); } } }