diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index c7a1bdb89..a95f898dd 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -389,12 +389,14 @@ export class WindowManager { WindowManager._resolveAuthToken = resolve; }); + const existingToken = StoreManager.store.get('authToken') || ''; + const loginWindow = WindowManager.createPopupWindowWithRouting({ width: 420, height: 500, title: 'Login', modal: false, - url: '#/headless/login', + url: `#/headless/login?authToken=${encodeURIComponent(existingToken)}`, }); // Resolve with null if the user closes the window before authenticating diff --git a/gui-js/libs/core/src/lib/services/clerk/clerk.service.ts b/gui-js/libs/core/src/lib/services/clerk/clerk.service.ts index e7544829c..e79e5d9ab 100644 --- a/gui-js/libs/core/src/lib/services/clerk/clerk.service.ts +++ b/gui-js/libs/core/src/lib/services/clerk/clerk.service.ts @@ -59,4 +59,18 @@ export class ClerkService { const token = await this.getToken(); await this.electronService.invoke(events.SET_AUTH_TOKEN, token); } + + async setSession(token: string): Promise { + if (!this.clerk) throw new Error('Clerk not initialized'); + // clerk.load() in initialize() restores the session from browser storage if still valid. + // If no session is active but sessions exist on the client, activate the first available one. + // The token parameter is a hint that the user previously authenticated. + if (!token) return; + if (!this.clerk.session && this.clerk.client?.sessions?.length > 0) { + await this.clerk.setActive({ session: this.clerk.client.sessions[0].id }); + } + if (!this.clerk.session) { + throw new Error('Session expired or invalid'); + } + } } diff --git a/gui-js/libs/ui-components/src/lib/login/login.component.ts b/gui-js/libs/ui-components/src/lib/login/login.component.ts index ecb275383..f9109f21e 100644 --- a/gui-js/libs/ui-components/src/lib/login/login.component.ts +++ b/gui-js/libs/ui-components/src/lib/login/login.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -7,6 +7,8 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { ClerkService } from '@minsky/core'; import { ElectronService } from '@minsky/core'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, take } from 'rxjs'; @Component({ selector: 'minsky-login', @@ -22,7 +24,7 @@ import { ElectronService } from '@minsky/core'; MatProgressSpinnerModule, ], }) -export class LoginComponent implements OnInit { +export class LoginComponent implements OnInit, OnDestroy { loginForm = new FormGroup({ email: new FormControl('', [Validators.required, Validators.email]), password: new FormControl('', [Validators.required]), @@ -32,17 +34,40 @@ export class LoginComponent implements OnInit { errorMessage = ''; isAuthenticated = false; - constructor(private clerkService: ClerkService, private electronService: ElectronService) {} + private destroy$ = new Subject(); + + constructor( + private clerkService: ClerkService, + private electronService: ElectronService, + private route: ActivatedRoute + ) {} async ngOnInit() { + this.route.queryParams.pipe(take(1)).subscribe((params) => { + this.initializeSession(params['authToken']); + }); + } + + private async initializeSession(authToken: string | undefined) { try { await this.clerkService.initialize(); + + if (authToken) { + await this.clerkService.setSession(authToken); + } + this.isAuthenticated = await this.clerkService.isSignedIn(); } catch (err) { - this.errorMessage = 'Failed to initialize authentication.'; + this.errorMessage = 'Session expired. Please sign in again.'; + this.isAuthenticated = false; } } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + get email() { return this.loginForm.get('email'); }