Skip to content

Commit

Permalink
feat(webapp): add ssh key authentication (#796)
Browse files Browse the repository at this point in the history
  • Loading branch information
irvingoujAtDevolution authored Apr 5, 2024
1 parent 6e878d8 commit a884cbb
Show file tree
Hide file tree
Showing 17 changed files with 584 additions and 37 deletions.
2 changes: 2 additions & 0 deletions webapp/src/client/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { SidebarModule } from "primeng/sidebar";
import { ToastModule } from "primeng/toast";
import { TabView, TabViewModule } from "primeng/tabview";
import { AutoCompleteModule } from "primeng/autocomplete";
import { SshKeyService } from './shared/services/ssh-key.service';


@NgModule({
Expand Down Expand Up @@ -67,6 +68,7 @@ import { AutoCompleteModule } from "primeng/autocomplete";
LoadingService,
WebSessionService,
WebClientService,
SshKeyService,
TabView
],
exports: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
<div [formGroup]="form">
<div class="col-12 gateway-form-group">
<web-client-username-control [parentForm]="form"
[inputFormData]="inputFormData"></web-client-username-control>
<div class="col-4 gateway-form-group col-pad-right-lg">
<label for="authSettings">Authentication</label>
<div class="gateway-form-input">
<p-dropdown id="authSettings" appendTo="body" formControlName="authMode" [options]="authModeOptions">
</p-dropdown>
</div>
</div>

<div class="col-12 gateway-form-group">
<web-client-password-control [parentForm]="form"
[inputFormData]="inputFormData"></web-client-password-control>
</div>
</div>
<div class="col-12 gateway-form-group" *ngIf="formInputVisibility.showUsernameInput">
<web-client-username-control [parentForm]="form" [inputFormData]="inputFormData"></web-client-username-control>
</div>

<div class="col-12 gateway-form-group" *ngIf="formInputVisibility.showPasswordInput">
<web-client-password-control [parentForm]="form" [inputFormData]="inputFormData"
[isEnabled]="formInputVisibility.showPasswordInput"></web-client-password-control>
</div>

<div *ngIf="formInputVisibility.showPrivateKeyInput">
<app-file-control privateKeyFileForm ></app-file-control>
</div>

</div>
Original file line number Diff line number Diff line change
@@ -1,23 +1,126 @@
import {Component, Input, OnInit} from '@angular/core';
import {FormGroup} from "@angular/forms";
import {
AfterViewInit,
Component,
Injectable,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';

import {BaseComponent} from "@shared/bases/base.component";
import { BaseComponent } from '@shared/bases/base.component';
import { SelectItem } from 'primeng/api';
import { WebFormService } from '@gateway/shared/services/web-form.service';
import { map, startWith, switchMap, takeUntil, tap } from 'rxjs/operators';
import { SshAuthMode } from '@gateway/shared/enums/web-client-auth-mode.enum';
import { Observable, of } from 'rxjs';
import { SshKeyService } from '@gateway/shared/services/ssh-key.service';

interface FormInputVisibility {
showUsernameInput?: boolean;
showPasswordInput?: boolean;
showPrivateKeyInput?: boolean;
}

@Injectable({ providedIn: 'root' })
@Component({
selector: 'ssh-form',
templateUrl: 'ssh-form.component.html',
styleUrls: ['ssh-form.component.scss']
styleUrls: ['ssh-form.component.scss'],
})
export class SshFormComponent extends BaseComponent implements OnInit {

export class SshFormComponent
extends BaseComponent
implements OnInit, OnDestroy, AfterViewInit
{
@Input() form: FormGroup;
@Input() inputFormData: any;

constructor() {
authModeOptions: SelectItem[];

formInputVisibility: FormInputVisibility = {
showUsernameInput: true,
showPasswordInput: true,
showPrivateKeyInput: false,
};

constructor(
private formService: WebFormService,
private sshKeyService: SshKeyService
) {
super();
}

ngAfterViewInit(): void {
this.formService.canConnectIfAlsoTrue(() => {
if (!this.formInputVisibility.showPrivateKeyInput) {
return true;
}

return this.sshKeyService.hasValidPrivateKey();
});
}

ngOnInit(): void {
this.initializeFormOptions();
this.addControlsToParentForm(this.inputFormData);
}

ngOnDestroy(): void {
this.formService.resetCanConnectCallback();
}

private addControlsToParentForm(inputFormData?: any): void {
if (this.form) {
this.form.addControl(
'authMode',
new FormControl(
inputFormData?.authMode || SshAuthMode.Username_and_Password
)
);
this.subscribeToAuthModeChanges();
}
}

private initializeFormOptions(): void {
this.formService
.getAuthModeOptions('ssh')
.pipe(takeUntil(this.destroyed$))
.subscribe({
next: (authModeOptions) => {
this.authModeOptions = authModeOptions;
},
error: (error) =>
console.error('Error fetching dropdown options', error),
});
}

private subscribeToAuthModeChanges(): void {
this.form
.get('authMode')
.valueChanges.pipe(
takeUntil(this.destroyed$),
startWith(this.form.get('authMode').value as SshAuthMode),
switchMap((authMode) => this.getFormInputVisibility(authMode))
)
.subscribe(() => {});
}

private getFormInputVisibility(
authMode: SshAuthMode
): Observable<SshAuthMode> {
return of(this.formInputVisibility).pipe(
tap((visibility) => {
visibility.showUsernameInput =
authMode === SshAuthMode.Username_and_Password ||
authMode === SshAuthMode.Private_Key;
visibility.showPasswordInput =
authMode === SshAuthMode.Username_and_Password;
visibility.showPrivateKeyInput = authMode === SshAuthMode.Private_Key;
}),
map(() => {
return authMode;
})
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {BaseComponent} from "@shared/bases/base.component";
import {SelectItem} from "primeng/api";
import {map, startWith, switchMap, takeUntil, tap} from "rxjs/operators";
import {WebFormService} from "@shared/services/web-form.service";
import {AuthMode} from "@shared/enums/web-client-auth-mode.enum";
import {Observable, of} from "rxjs";
import { VncAuthMode as AuthMode } from '@shared/enums/web-client-auth-mode.enum';

interface FormInputVisibility {
showUsernameInput?: boolean;
Expand Down Expand Up @@ -56,7 +56,7 @@ export class VncFormComponent extends BaseComponent implements OnInit {
}

private initializeFormOptions(): void {
this.formService.getAuthModeOptions().pipe(
this.formService.getAuthModeOptions('vnc').pipe(
takeUntil(this.destroyed$)
).subscribe({
next: (authModeOptions) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div>
<div class="gateway-form-group">
<label>Private Key Data</label>
<div class="card flex justify-content-center">
<p-fileUpload
name="myFiles"
maxFileSize="10240"
(onSelect)="onSelect($event)"
customUpload="true"
[multiple]="false"
[showUploadButton]="false"
[showCancelButton]="false"
chooseLabel="Select File"
cancelLabel="Clear"
styleClass="private-key-file-upload"
chooseStyleClass = "select-file-button"
>
<ng-template
let-file
pTemplate="file"
>
<!-- Need to leave this as empty -->
</ng-template>
<ng-template pTemplate="content">
<textarea
cols="30"
rows="5"
class="private-key-textarea"
name="privateKeyData"
pInputTextarea
[(ngModel)]="privateKeyContent"
wrap="soft"
>
</textarea>
</ng-template>
</p-fileUpload>
</div>
</div>
</div>
<div class="gateway-form-input password-container">
<div
class="form-helper-text"
*ngIf="privateKeyContent !== '' && !isValidFile()"
>
{{ getErrorMessage() }}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@import '../../../../../../../assets/css/style/mixins';
@import "../../../../../../../assets/css/style/variables";
@import "../../../../../../../assets/css/theme/theme-mode-variables";

::ng-deep p-fileUpload .p-fileupload-content .p-progressbar {
display: none !important;
}

.file-container {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 5px 10px 5px;
}

.form-helper-text {
line-height: 11px;
word-wrap: break-word;
color: var(--status-error-text-color);
font-size: 11px;
font-family: Open Sans;
font-weight: 400;
padding-left: 0px;
justify-content: flex-start;
align-items: flex-start;
gap: 10px;
display: inline-flex
}

.highlight {
background-color: #f0f0f0;
}

.gateway-form-group {
padding: 0px 0px 18px 0px;
}

.flex-row{
display: flex;
align-items: center;
}

.justify-end {
justify-content: end;
}

.private-key-textarea{
margin-top: 5px;
margin-bottom: 5px;
resize: none;
font-family: monospace;
font-size: xx-small;
line-height: 15px;
}

.card {
border-radius: 10px;
margin-bottom: 1rem;
}

.card p-fileupload {
width: 100%;
}

::ng-deep .card p-fileupload .p-fileupload-buttonbar {
display: flex;
justify-content: end;
flex-direction: row !important;
}

::ng-deep .card p-fileupload div {
display: flex;
flex-direction: column-reverse;
}

.file-row {
display: flex;
align-items: center;
justify-content: space-between;
}

::ng-deep .card p-fileupload .p-button {
background-color: white;
border : 1px solid var(--default-btn-bg-color);
color: var(--default-btn-bg-color);
}

::ng-deep .card p-fileupload .p-button:hover {
background-color: var(--default-btn-bg-color);
color: white;
}
Loading

0 comments on commit a884cbb

Please sign in to comment.