From a9939350048f9281531f1a966049a93a3ca676db Mon Sep 17 00:00:00 2001 From: ahmedomosanya Date: Wed, 6 May 2026 19:03:12 +0100 Subject: [PATCH] fix(routing): register #/auth so Auth0 callback resolves correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `auth.service.ts` configures Auth0's `callbackUrl` as `${window.location.origin}/#/auth`, and `lfx-header.service.ts` forwards the same value to the LFX header. After interactive login Auth0 redirects the browser back to `#/auth`, but `app-routing.module.ts` only registered the empty path `''` for AuthDashboardComponent — so the callback fell through to the `**` wildcard and rendered PageNotFoundComponent, leaving the user stranded with no in-app path back to the Contributor Console even though they were fully authenticated. This was masked on first login (the LFX header's own Auth0 SPA SDK typically establishes the session via silent auth, so the contributor console never had to do an interactive Auth0 redirect and the user landed straight on /cla/project/...). It surfaced after an explicit header-menu Logout: the next visit forced an interactive login, the post-login redirect landed on /auth, and the missing route bit. Add an explicit auth route mapped to AuthDashboardComponent so its ngOnInit runs, reads PROJECT_ID/USER_ID/REDIRECT from localStorage and router.navigate's the user back to /cla/project/:projectId/user/:userId?redirect=... Also strengthen the AuthDashboardComponent unit test: provide Router / StorageService spies (the existing scaffolding "should create" test was silently failing because ngOnInit was being executed against unmocked services) and add a regression test asserting the storage-driven navigation behaviour that the new route now exercises. Tracks frontend issue: https://github.com/linuxfoundation/easycla-contributor-console/issues/502 Resolves: https://github.com/linuxfoundation/easycla/issues/5032 Assisted by [Cursor](https://cursor.com/) Signed-off-by: ahmedomosanya --- src/app/app-routing.module.ts | 5 +++ .../auth-dashboard.component.spec.ts | 45 +++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b3ca54cd..65e3cc26 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -18,6 +18,11 @@ const routes: Routes = [ pathMatch: 'full', component: AuthDashboardComponent }, + { + path: 'auth', + pathMatch: 'full', + component: AuthDashboardComponent + }, { path: 'cla/project/:projectId/user/:userId', pathMatch: 'full', diff --git a/src/app/modules/dashboard/container/auth-dashboard/auth-dashboard.component.spec.ts b/src/app/modules/dashboard/container/auth-dashboard/auth-dashboard.component.spec.ts index b3551d6d..470f4a9d 100644 --- a/src/app/modules/dashboard/container/auth-dashboard/auth-dashboard.component.spec.ts +++ b/src/app/modules/dashboard/container/auth-dashboard/auth-dashboard.component.spec.ts @@ -2,27 +2,64 @@ // SPDX-License-Identifier: MIT import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { AppSettings } from 'src/app/config/app-settings'; +import { StorageService } from 'src/app/shared/services/storage.service'; import { AuthDashboardComponent } from './auth-dashboard.component'; describe('AuthDashboardComponent', () => { let component: AuthDashboardComponent; let fixture: ComponentFixture; + let routerSpy: jasmine.SpyObj; + let storageSpy: jasmine.SpyObj; beforeEach(async () => { + routerSpy = jasmine.createSpyObj('Router', ['navigate']); + storageSpy = jasmine.createSpyObj('StorageService', ['getItem']); + storageSpy.getItem.and.returnValue(null); + await TestBed.configureTestingModule({ - declarations: [ AuthDashboardComponent ] - }) - .compileComponents(); + declarations: [AuthDashboardComponent], + providers: [ + { provide: Router, useValue: routerSpy }, + { provide: StorageService, useValue: storageSpy } + ] + }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(AuthDashboardComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('navigates to /cla/project/:projectId/user/:userId with redirect query param on init', () => { + storageSpy.getItem.and.callFake(((key: string) => { + switch (key) { + case AppSettings.PROJECT_ID: + return JSON.stringify('proj-123'); + case AppSettings.USER_ID: + return JSON.stringify('user-456'); + case AppSettings.REDIRECT: + return JSON.stringify('https://github.com/foo/bar/pull/42'); + default: + return null; + } + }) as (key: string) => T); + + fixture.detectChanges(); + + expect(storageSpy.getItem).toHaveBeenCalledWith(AppSettings.PROJECT_ID); + expect(storageSpy.getItem).toHaveBeenCalledWith(AppSettings.USER_ID); + expect(storageSpy.getItem).toHaveBeenCalledWith(AppSettings.REDIRECT); + expect(routerSpy.navigate).toHaveBeenCalledOnceWith( + ['/cla/project/proj-123/user/user-456'], + { queryParams: { redirect: 'https://github.com/foo/bar/pull/42' } } + ); + }); });