@@ -27,6 +27,7 @@ import { FormsModule } from '@angular/forms';
2727import { PrimeTemplate , SelectItem , TreeNode } from 'primeng/api' ;
2828import { Button , ButtonDirective } from 'primeng/button' ;
2929import { Dialog } from 'primeng/dialog' ;
30+ import { InputTextModule } from 'primeng/inputtext' ;
3031import { ProgressSpinnerModule } from 'primeng/progressspinner' ;
3132import { Select } from 'primeng/select' ;
3233import { Tree } from 'primeng/tree' ;
@@ -60,6 +61,7 @@ import { SubscriptionManager } from '../shared/types';
6061 ButtonDirective ,
6162 Dialog ,
6263 FormsModule ,
64+ InputTextModule ,
6365 Select ,
6466 TreeTableModule ,
6567 PrimeTemplate ,
@@ -114,6 +116,9 @@ export class DownloadComponent
114116 done = false ;
115117 datasetUrl = '' ;
116118 showGuestLoginPopup = false ;
119+ previewUrlInput = '' ;
120+ isPreviewUrlMode = false ;
121+ isGuestMode = false ;
117122
118123 // globus
119124 token ?: string ;
@@ -149,34 +154,11 @@ export class DownloadComponent
149154 // eslint-disable-next-line no-console
150155 console . debug ( '[DownloadComponent] Config loaded' ) ;
151156
152- // Check if user is logged in and show popup if not
153- // eslint-disable-next-line no-console
154- console . debug ( '[DownloadComponent] Checking user info for popup...' ) ;
155- try {
156- const userInfo = await firstValueFrom ( this . dataService . getUserInfo ( ) ) ;
157- // eslint-disable-next-line no-console
158- console . debug ( '[DownloadComponent] getUserInfo response:' , userInfo ) ;
159- if ( ! userInfo . loggedIn ) {
160- // eslint-disable-next-line no-console
161- console . debug ( '[DownloadComponent] User not logged in, showing popup' ) ;
162- this . showGuestLoginPopup = true ;
163- } else {
164- // eslint-disable-next-line no-console
165- console . debug ( '[DownloadComponent] User is logged in, no popup needed' ) ;
166- }
167- } catch ( err ) {
168- // eslint-disable-next-line no-console
169- console . error ( '[DownloadComponent] getUserInfo error:' , err ) ;
170- // eslint-disable-next-line no-console
171- console . debug ( '[DownloadComponent] Assuming not logged in, showing popup' ) ;
172- this . showGuestLoginPopup = true ;
173- }
174-
175157 const dvToken = localStorage . getItem ( 'dataverseToken' ) ;
176158 if ( dvToken !== null ) {
177159 this . dataverseToken = dvToken ;
178160 }
179- this . route . queryParams . subscribe ( ( params ) => {
161+ this . route . queryParams . subscribe ( async ( params ) => {
180162 const apiToken = params [ 'apiToken' ] ;
181163 if ( apiToken ) {
182164 this . dataverseToken = apiToken ;
@@ -189,17 +171,27 @@ export class DownloadComponent
189171 this . downloadId = params [ 'downloadId' ] ;
190172 const code = params [ 'code' ] ;
191173 if ( code !== undefined ) {
174+ // Returning from OAuth - parse state
192175 const loginState : LoginState = JSON . parse ( params [ 'state' ] ) ;
193176 if ( loginState . nonce ) {
194177 const doi = loginState . datasetId ?. value
195178 ? loginState . datasetId ?. value
196179 : '?' ;
197180 this . doiItems = [ { label : doi , value : doi } ] ;
198181 this . datasetId = doi ;
199- // Only try to load files if we have a valid dataset ID
200- if ( doi && doi !== '?' && doi !== 'undefined' ) {
201- this . onDatasetChange ( ) ;
182+
183+ // Restore state from OAuth redirect
184+ if ( loginState . downloadId ) {
185+ this . downloadId = loginState . downloadId ;
186+ }
187+ if ( loginState . isGuestMode ) {
188+ this . isGuestMode = true ;
202189 }
190+ if ( loginState . isPreviewUrlMode && loginState . previewUrlToken ) {
191+ this . isPreviewUrlMode = true ;
192+ this . dataverseToken = loginState . previewUrlToken ;
193+ }
194+
203195 const tokenSubscription = this . oauth
204196 . getToken ( 'globus' , code , loginState . nonce )
205197 . subscribe ( ( x ) => {
@@ -211,9 +203,15 @@ export class DownloadComponent
211203 } ) ;
212204 }
213205 this . globusPlugin = this . pluginService . getGlobusPlugin ( ) ;
206+
207+ // After OAuth return, load files (user already made their choice before redirect)
208+ if ( this . datasetId && this . datasetId !== '?' && this . datasetId !== 'undefined' ) {
209+ this . onDatasetChange ( ) ;
210+ }
214211 } else {
212+ // First arrival - check if user is logged in and show popup if not
215213 this . globusPlugin = this . pluginService . getGlobusPlugin ( ) ;
216- this . getRepoToken ( ) ;
214+ await this . checkUserAndShowPopupOrRedirect ( ) ;
217215 }
218216 } ) ;
219217 this . datasetSearchResultsSubscription =
@@ -277,9 +275,82 @@ export class DownloadComponent
277275 }
278276
279277 continueAsGuest ( ) : void {
280- // User chose to continue without Dataverse login
281- // They will still need to authenticate with Globus OAuth
278+ // User chose to continue as guest - strip session_required_single_domain and redirect to OAuth
279+ this . isGuestMode = true ;
280+ this . showGuestLoginPopup = false ;
281+ this . getRepoToken ( ) ;
282+ }
283+
284+ continueAsLoggedInUser ( ) : void {
285+ // User chose to log in with KU Leuven account - keep session_required_single_domain
286+ this . isGuestMode = false ;
282287 this . showGuestLoginPopup = false ;
288+ this . getRepoToken ( ) ;
289+ }
290+
291+ async checkUserAndShowPopupOrRedirect ( ) : Promise < void > {
292+ // Check if user is logged in; show popup if not, otherwise redirect to OAuth
293+ // eslint-disable-next-line no-console
294+ console . debug ( '[DownloadComponent] Checking user info on first arrival...' ) ;
295+ try {
296+ const userInfo = await firstValueFrom ( this . dataService . getUserInfo ( ) ) ;
297+ // eslint-disable-next-line no-console
298+ console . debug ( '[DownloadComponent] getUserInfo response:' , userInfo ) ;
299+ if ( ! userInfo . loggedIn ) {
300+ // eslint-disable-next-line no-console
301+ console . debug ( '[DownloadComponent] User not logged in, showing popup' ) ;
302+ this . showGuestLoginPopup = true ;
303+ } else {
304+ // eslint-disable-next-line no-console
305+ console . debug ( '[DownloadComponent] User is logged in, redirecting to OAuth' ) ;
306+ this . getRepoToken ( ) ;
307+ }
308+ } catch ( err ) {
309+ // eslint-disable-next-line no-console
310+ console . error ( '[DownloadComponent] getUserInfo error:' , err ) ;
311+ // eslint-disable-next-line no-console
312+ console . debug ( '[DownloadComponent] Assuming not logged in, showing popup' ) ;
313+ this . showGuestLoginPopup = true ;
314+ }
315+ }
316+
317+ continueWithPreviewUrl ( ) : void {
318+ // Extract token from pasted preview URL and redirect to OAuth
319+ const token = this . extractPreviewUrlToken ( this . previewUrlInput ) ;
320+ if ( token ) {
321+ this . dataverseToken = token ;
322+ this . isPreviewUrlMode = true ;
323+ this . isGuestMode = true ; // Preview users also need guest Globus access
324+ this . showGuestLoginPopup = false ;
325+ // eslint-disable-next-line no-console
326+ console . debug ( '[DownloadComponent] Preview URL token extracted, redirecting to OAuth' ) ;
327+ this . getRepoToken ( ) ;
328+ } else {
329+ // eslint-disable-next-line no-console
330+ console . error ( '[DownloadComponent] Could not extract token from preview URL' ) ;
331+ }
332+ }
333+
334+ extractPreviewUrlToken ( input : string ) : string | null {
335+ if ( ! input ) return null ;
336+ const trimmed = input . trim ( ) ;
337+ // If it's just a UUID-like token, return it directly
338+ const uuidPattern = / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i;
339+ if ( uuidPattern . test ( trimmed ) ) {
340+ return trimmed ;
341+ }
342+ // Try to extract token from URL like https://.../previewurl.xhtml?token=...
343+ try {
344+ const url = new URL ( trimmed ) ;
345+ const token = url . searchParams . get ( 'token' ) ;
346+ if ( token ) return token ;
347+ } catch {
348+ // Not a valid URL, ignore
349+ }
350+ // Try regex fallback for token= in any string
351+ const tokenMatch = trimmed . match ( / t o k e n = ( [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } ) / i) ;
352+ if ( tokenMatch ) return tokenMatch [ 1 ] ;
353+ return null ;
283354 }
284355
285356 onUserChange ( ) {
@@ -685,20 +756,24 @@ export class DownloadComponent
685756 if ( tg . URL ?. includes ( '://' ) ) {
686757 url = tg . URL ;
687758 }
688- // For guest users, strip session_required_single_domain to allow any Globus identity
689- if ( this . showGuestLoginPopup ) {
759+ // For guest/preview users, strip session_required_single_domain to allow any Globus identity
760+ if ( this . isGuestMode ) {
690761 url = url . replace ( / [ & ? ] s e s s i o n _ r e q u i r e d _ s i n g l e _ d o m a i n = [ ^ & ] * / g, '' ) ;
691762 }
692763 if ( tg . oauth_client_id !== undefined && tg . oauth_client_id !== '' ) {
693764 const nonce = this . newNonce ( 44 ) ;
694- // Only include datasetId if one is actually selected
765+ // Include all state to preserve across OAuth redirect
695766 const loginState : LoginState = {
696767 datasetId :
697768 this . datasetId && this . datasetId !== '?'
698769 ? { value : this . datasetId , label : this . datasetId }
699770 : undefined ,
700771 nonce : nonce ,
701772 download : true ,
773+ downloadId : this . downloadId ,
774+ isGuestMode : this . isGuestMode ,
775+ isPreviewUrlMode : this . isPreviewUrlMode ,
776+ previewUrlToken : this . isPreviewUrlMode ? this . dataverseToken : undefined ,
702777 } ;
703778 let clId = '?client_id=' ;
704779 if ( url . includes ( '?' ) ) {
0 commit comments