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
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,82 @@ describe('LogInPasswordComponent', () => {
});
});

// Standalone login reads the redirect target from the `redirectUrl` query param (set by aai.js).
describe('standalone login redirect (redirectUrl query param)', () => {
let authService: AuthServiceStub;
let setRedirectUrlSpy: jasmine.Spy;
let setRedirectUrlIfNotSetSpy: jasmine.Spy;

const setQueryParams = (queryParams: Record<string, unknown>) => {
(component as any).route = { snapshot: { queryParams } };
};

beforeEach(() => {
authService = TestBed.inject(AuthService) as unknown as AuthServiceStub;
setRedirectUrlSpy = spyOn(authService, 'setRedirectUrl').and.callThrough();
setRedirectUrlIfNotSetSpy = spyOn(authService, 'setRedirectUrlIfNotSet').and.callThrough();
// Avoid scheduling the real DiscoJuice popup timer during ngOnInit.
spyOn(component as any, 'popUpDiscoJuiceLogin');

fixture.detectChanges();
component.form.controls.email.setValue('user');
component.form.controls.password.setValue('password');
});

it('redirects back to the redirectUrl page, reduced to an app-relative path', () => {
setQueryParams({ redirectUrl: 'http://dev-6.pc:8603/repository/search' });

component.submit();

expect(setRedirectUrlSpy).toHaveBeenCalledWith('/repository/search');
expect(setRedirectUrlIfNotSetSpy).not.toHaveBeenCalled();
});

it('keeps the query string of the originating page', () => {
setQueryParams({ redirectUrl: 'http://dev-6.pc:8603/repository/search?query=test' });

component.submit();

expect(setRedirectUrlSpy).toHaveBeenCalledWith('/repository/search?query=test');
});

Comment thread
Kasinhou marked this conversation as resolved.
it('prefers a nested redirectUrl so the login page is not the redirect target', () => {
setQueryParams({
redirectUrl: 'http://dev-6.pc:8603/repository/login?redirectUrl=http://dev-6.pc:8603/repository/items/1',
});

component.submit();

expect(setRedirectUrlSpy).toHaveBeenCalledWith('/repository/items/1');
});

it('passes through an already-relative redirectUrl unchanged', () => {
setQueryParams({ redirectUrl: '/repository/search' });

component.submit();

expect(setRedirectUrlSpy).toHaveBeenCalledWith('/repository/search');
});

it('falls back to setRedirectUrlIfNotSet("/") when no redirectUrl query param is present', () => {
setQueryParams({});

component.submit();

expect(setRedirectUrlIfNotSetSpy).toHaveBeenCalledWith('/');
expect(setRedirectUrlSpy).not.toHaveBeenCalled();
});

it('falls back cleanly when redirectUrl is not a string (repeated query param)', () => {
setQueryParams({ redirectUrl: ['/repository/a', '/repository/b'] });

component.submit();

expect(setRedirectUrlIfNotSetSpy).toHaveBeenCalledWith('/');
expect(setRedirectUrlSpy).not.toHaveBeenCalled();
});
});

});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import {
UntypedFormGroup,
Validators,
} from '@angular/forms';
import { RouterLink } from '@angular/router';
import {
ActivatedRoute,
RouterLink,
} from '@angular/router';
import {
select,
Store,
Expand Down Expand Up @@ -145,6 +148,7 @@ export class LogInPasswordComponent implements OnInit, OnDestroy {
@Inject('isStandalonePage') public isStandalonePage: boolean,
private authService: AuthService,
private hardRedirectService: HardRedirectService,
private route: ActivatedRoute,
private formBuilder: UntypedFormBuilder,
protected store: Store<CoreState>,
protected authorizationService: AuthorizationDataService,
Expand Down Expand Up @@ -239,7 +243,13 @@ export class LogInPasswordComponent implements OnInit, OnDestroy {
if (!this.isStandalonePage) {
this.authService.setRedirectUrl(this.hardRedirectService.getCurrentRoute());
} else {
this.authService.setRedirectUrlIfNotSet('/');
// Standalone login: return to the `redirectUrl` query param set by the aai.js local-auth flow.
const redirectUrl = this.getRedirectUrlFromQueryParams();
if (isNotEmpty(redirectUrl)) {
this.authService.setRedirectUrl(redirectUrl);
} else {
this.authService.setRedirectUrlIfNotSet('/');
}
}

// dispatch AuthenticationAction
Expand All @@ -249,6 +259,26 @@ export class LogInPasswordComponent implements OnInit, OnDestroy {
this.form.reset();
}

/** Post-login redirect target from the `redirectUrl` query param (aai.js), as an app-relative path or null. */
private getRedirectUrlFromQueryParams(): string | null {
// Query params are untyped (can be a string[]); only a non-empty string is usable here.
const rawRedirectUrl = this.route.snapshot.queryParams?.redirectUrl;
if (typeof rawRedirectUrl !== 'string' || isEmpty(rawRedirectUrl)) {
return null;
}

// Prefer a nested `redirectUrl` so login is never the redirect target.
const nestedRedirectUrl = new URLSearchParams(rawRedirectUrl.split('?')[1] ?? '').get('redirectUrl');
const redirectUrl = isNotEmpty(nestedRedirectUrl) ? nestedRedirectUrl : rawRedirectUrl;

return this.toRelativePath(redirectUrl);
}

/** Reduce a possibly-absolute URL to an app-relative path by dropping the scheme+host; relative values pass through. */
private toRelativePath(url: string): string {
return url.replace(/^https?:\/\/[^/]+/i, '');
}

/**
* Toggle Discojuice login. Show it every time except the case when the user click
* on the `local` button in the discojuice box.
Expand Down
Loading