Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@angular/router": "18.2.1",
"@devolutions/icons": "4.0.8",
"@devolutions/iron-remote-desktop": "^0.7.0",
"@devolutions/iron-remote-desktop-rdp": "^0.4.0",
"@devolutions/iron-remote-desktop-rdp": "^0.5.0",
"@devolutions/iron-remote-desktop-vnc": "^0.4.1",
"@devolutions/web-ssh-gui": "0.3.1",
"@devolutions/web-telnet-gui": "0.2.19",
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/client/app/modules/login/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class LoginComponent extends BaseComponent implements OnInit {

private handleLoginResult(success: boolean): void {
if (success) {
void this.navigationService.navigateToNewSession();
void this.navigationService.navigateToReturnUrl();
} else {
this.autoLoginAttempted = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ViewChild,
} from '@angular/core';
import { IronError, SessionEvent, UserInteraction } from '@devolutions/iron-remote-desktop';
import { Backend, displayControl, kdcProxyUrl, preConnectionBlob } from '@devolutions/iron-remote-desktop-rdp';
import { Backend, displayControl, kdcProxyUrl, preConnectionBlob, RdpFile } from '@devolutions/iron-remote-desktop-rdp';
import { WebClientBaseComponent } from '@shared/bases/base-web-client.component';
import { GatewayAlertMessageService } from '@shared/components/gateway-alert-message/gateway-alert-message.service';
import { ScreenScale } from '@shared/enums/screen-scale.enum';
Expand All @@ -27,13 +27,15 @@ import { UtilsService } from '@shared/services/utils.service';
import { WebClientService } from '@shared/services/web-client.service';
import { WebSessionService } from '@shared/services/web-session.service';
import { MessageService } from 'primeng/api';
import { debounceTime, EMPTY, from, Observable, of, Subject, Subscription, throwError } from 'rxjs';
import { debounceTime, EMPTY, from, noop, Observable, of, Subject, Subscription, throwError } from 'rxjs';
import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
import '@devolutions/iron-remote-desktop/iron-remote-desktop.js';
import { ActivatedRoute } from '@angular/router';
import { DVL_RDP_ICON, DVL_WARNING_ICON, JET_RDP_URL } from '@gateway/app.constants';
import { AnalyticService, ProtocolString } from '@gateway/shared/services/analytic.service';
import { WebSession } from '@shared/models/web-session.model';
import { ComponentResizeObserverService } from '@shared/services/component-resize-observer.service';
import { NavigationService } from '@shared/services/navigation.service';

enum UserIronRdpErrorKind {
General = 0,
Expand Down Expand Up @@ -69,6 +71,8 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
dynamicResizeSupported = false;
dynamicResizeEnabled = false;

rdpConfig: string | null;

leftToolbarButtons = [
{
label: 'Start',
Expand Down Expand Up @@ -145,6 +149,8 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
constructor(
private renderer: Renderer2,
protected utils: UtilsService,
private activatedRoute: ActivatedRoute,
private navigation: NavigationService,
protected gatewayAlertMessageService: GatewayAlertMessageService,
private webSessionService: WebSessionService,
private webClientService: WebClientService,
Expand All @@ -160,7 +166,11 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
}

ngOnInit(): void {
console.log('RDP init');
this.removeWebClientGuiElement();
this.setRdpConfig();
// Navigate to /session route to clear query params.
this.navigation.navigateToNewSession().then(noop);
}

ngAfterViewInit(): void {
Expand All @@ -177,6 +187,11 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
super.ngOnDestroy();
}

private setRdpConfig(): void {
const queryParams = this.activatedRoute.snapshot.queryParams;
this.rdpConfig = queryParams.config ?? null;
}

sendWindowsKey(): void {
this.remoteClient.metaKey();
}
Expand Down Expand Up @@ -336,11 +351,16 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
}

private startConnectionProcess(): void {
this.getFormData()
const parameters = this.rdpConfig
? this.parseRdpConfig(this.rdpConfig)
: this.getFormData().pipe(
switchMap(() => this.setScreenSizeScale(this.formData.screenSize)),
switchMap(() => this.fetchParameters(this.formData)),
);

parameters
.pipe(
takeUntil(this.destroyed$),
switchMap(() => this.setScreenSizeScale(this.formData.screenSize)),
switchMap(() => this.fetchParameters(this.formData)),
switchMap((params) => this.fetchTokens(params)),
switchMap((params) => this.webClientService.generateKdcProxyUrl(params)),
catchError((error) => {
Expand Down Expand Up @@ -384,6 +404,37 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
return of(connectionParameters);
}

private parseRdpConfig(config: string): Observable<IronRDPConnectionParameters> {
const rdpFile = new RdpFile();
rdpFile.parse(atob(config));

const host = rdpFile.getStr('full address');
const port = rdpFile.getInt('server port');
const username = rdpFile.getStr('username');
const password = rdpFile.getStr('ClearTextPassword');
const kdcProxyUrl = rdpFile.getStr('kdcproxyurl');

const extractedUsernameDomain: ExtractedUsernameDomain = this.utils.string.extractDomain(username);

// TODO: Parse `DesktopSize` from config.
const screenSize: DesktopSize = this.webSessionService.getWebSessionScreenSizeSnapshot();

const connectionParameters: IronRDPConnectionParameters = {
username: extractedUsernameDomain.username,
password,
host,
port,
domain: extractedUsernameDomain.domain,
gatewayAddress: this.getWebSocketUrl(),
screenSize,
kdcProxyUrl,
// TODO: Parse from config.
enableDisplayControl: true,
};

return of(connectionParameters);
}

fetchTokens(params: IronRDPConnectionParameters): Observable<IronRDPConnectionParameters> {
return this.webClientService
.fetchRdpToken(params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {
ElementRef,
HostListener,
OnDestroy,
OnInit,
Type,
ViewChild,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WebClientRdpComponent } from '@gateway/modules/web-client/rdp/web-client-rdp.component';
import { BaseComponent } from '@shared/bases/base.component';
import { SessionDataTypeMap, SessionType, WebSession } from '@shared/models/web-session.model';
import { ComponentForSession, SessionDataTypeMap, SessionType, WebSession } from '@shared/models/web-session.model';
import { WebSessionService } from '@shared/services/web-session.service';
import { TabView } from 'primeng/tabview';
import { takeUntil } from 'rxjs/operators';
Expand All @@ -20,7 +22,7 @@ import { MainPanelComponent } from '../main-panel/main-panel.component';
templateUrl: './tab-view.component.html',
styleUrls: ['./tab-view.component.scss'],
})
export class TabViewComponent extends BaseComponent implements OnInit, OnDestroy, AfterViewInit {
export class TabViewComponent extends BaseComponent implements OnDestroy, AfterViewInit {
@ViewChild('tabView') tabView: TabView;
@ViewChild('sessionsContainer') sessionsContainerRef: ElementRef;

Expand All @@ -30,6 +32,7 @@ export class TabViewComponent extends BaseComponent implements OnInit, OnDestroy
constructor(
private webSessionService: WebSessionService,
private readonly cdr: ChangeDetectorRef,
private activatedRoute: ActivatedRoute,
) {
super();
}
Expand All @@ -44,13 +47,12 @@ export class TabViewComponent extends BaseComponent implements OnInit, OnDestroy
}
}

ngOnInit(): void {
this.loadFormTab();
ngAfterViewInit(): void {
// Load tabs after the component's view is initialized because we need the `@ViewChild` references to be populated.
this.loadTabs();
this.subscribeToTabMenuArray();
this.subscribeToTabActiveIndex();
}

ngAfterViewInit(): void {
this.measureSize();
}

Expand All @@ -73,13 +75,40 @@ export class TabViewComponent extends BaseComponent implements OnInit, OnDestroy
this.tabView.activeIndex = this.currentTabIndex;
}

private loadTabs(): void {
const queryParams = this.activatedRoute.snapshot.queryParams;

// Autoconnect only if the `config` parameter exists and `autoconnect` is `true`.
const autoconnect: boolean = !!queryParams.config && queryParams.autoconnect === 'true';

// TODO: Fill the form with the configuration from config parameter.
this.loadFormTab();

if (autoconnect) {
const protocol: string = queryParams.protocol?.toLowerCase() ?? 'rdp';

if (protocol === 'rdp') {
// Unfortunately, the hostname cannot be retrieved at this point, so we will use a placeholder
// for the session tab name.
// TODO(Improvement): Rename the session tab after the config will be parsed.
this.loadWebSessionTab('RDP Session', WebClientRdpComponent);
}
// TODO: Support more protocols.
}
}

private loadFormTab(): void {
if (!this.isSessionTabExists('New Session')) {
const newSessionTab = this.createNewSessionTab('New Session') as WebSession<keyof SessionDataTypeMap>;
this.webSessionService.addSession(newSessionTab);
}
}

private loadWebSessionTab(name: string, component: Type<ComponentForSession<keyof SessionDataTypeMap>>): void {
const newSessionTab = new WebSession(name, component);
this.webSessionService.addSession(newSessionTab);
}

private isSessionTabExists(tabName: string): boolean {
return this.webSessionService.getWebSessionSnapshot().some((webSession) => webSession.name === tabName);
}
Expand Down
7 changes: 4 additions & 3 deletions webapp/src/client/app/shared/guards/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/ro

import { AuthService } from '@shared/services/auth.service';

export function authGuard(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean {
export function authGuard(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const router: Router = inject(Router);
const authService = inject(AuthService);

if (authService.isAuthenticated) {
return true;
}

//TODO Add when standalone has more feature pages: { queryParams: { returnUrl: state.url } }
void router.navigate(['login']);
void router.navigate(['login'], {
queryParams: { returnUrl: state.url },
});
return false;
}
9 changes: 9 additions & 0 deletions webapp/src/client/app/shared/services/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ export class NavigationService {
return this.router.navigateByUrl(NavigationService.SESSION_KEY);
}

navigateToReturnUrl(): Promise<boolean> {
const returnUrl = this.activatedRoute.snapshot.queryParams.returnUrl;
if (returnUrl) {
return this.router.navigateByUrl(returnUrl);
}
// Navigate to a new session as a fallback.
return this.navigateToNewSession();
}

navigateToRDPSession(connectionTypeRoute: WebClientSection, queryParams?: string) {
const webClientUrl = `session/${connectionTypeRoute}` + (queryParams ?? '');
return this.router.navigateByUrl(webClientUrl);
Expand Down