diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts index 5cee3c2b69..47132f26e7 100644 --- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -344,11 +344,25 @@ describe('dashboard (authenticated)', () => { }) }) -describe('when the user is not logged in', () => { - beforeEach(() => { - cy.visit('/catalog/search') +describe('Logging in and out', () => { + describe('when the user is not logged in', () => { + beforeEach(() => { + cy.visit('/catalog/search') + }) + it('redirects to the login page', () => { + cy.url().should('include', '/catalog.signin?redirect=') + }) }) - it('redirects to the login page', () => { - cy.url().should('include', '/catalog.signin?redirect=') + describe('Logging out', () => { + beforeEach(() => { + cy.login('admin', 'admin', false) + cy.visit('/catalog/search') + }) + it('logs out the user', () => { + cy.get('gn-ui-avatar').should('be.visible') + cy.get('md-editor-sidebar').find('gn-ui-button').eq(1).click() + cy.url().should('include', '/catalog.signin?redirect=') + cy.get('gn-ui-avatar').should('not.exist') + }) }) }) diff --git a/apps/metadata-editor/src/app/app.module.ts b/apps/metadata-editor/src/app/app.module.ts index 87354808bc..c2a3784725 100644 --- a/apps/metadata-editor/src/app/app.module.ts +++ b/apps/metadata-editor/src/app/app.module.ts @@ -26,6 +26,7 @@ import { DashboardPageComponent } from './dashboard/dashboard-page.component' import { EditorRouterService } from './router.service' import { LOGIN_URL, + LOGOUT_URL, provideGn4, provideRepositoryUrl, } from '@geonetwork-ui/api/repository' @@ -70,6 +71,10 @@ import { FeatureEditorModule } from '@geonetwork-ui/feature/editor' provide: LOGIN_URL, useFactory: () => getGlobalConfig().LOGIN_URL, }, + { + provide: LOGOUT_URL, + useFactory: () => getGlobalConfig().LOGOUT_URL, + }, ], bootstrap: [AppComponent], }) diff --git a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html index 0c81c789b8..fc99769cb1 100644 --- a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html +++ b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.html @@ -31,8 +31,14 @@ - Log out + diff --git a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts index 50982f3777..8743381d52 100644 --- a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts +++ b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.spec.ts @@ -1,19 +1,27 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { AvatarServiceInterface } from '@geonetwork-ui/api/repository' +import { + AuthService, + AvatarServiceInterface, +} from '@geonetwork-ui/api/repository' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' import { TranslateModule } from '@ngx-translate/core' -import { MockBuilder, MockProviders } from 'ng-mocks' +import { MockBuilder, MockProvider, MockProviders } from 'ng-mocks' import { SidebarComponent } from './sidebar.component' describe('SidebarComponent', () => { let component: SidebarComponent let fixture: ComponentFixture + let service: AuthService beforeEach(() => { return MockBuilder(SidebarComponent) }) + afterEach(() => { + jest.resetAllMocks() + }) + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [SidebarComponent, TranslateModule.forRoot()], @@ -23,9 +31,13 @@ describe('SidebarComponent', () => { AvatarServiceInterface, OrganizationsServiceInterface ), + MockProvider(AuthService, { + logoutUrl: 'http://logout.com/bla?', + }), ], }).compileComponents() + service = TestBed.inject(AuthService) fixture = TestBed.createComponent(SidebarComponent) component = fixture.componentInstance fixture.detectChanges() @@ -34,4 +46,21 @@ describe('SidebarComponent', () => { it('should create', () => { expect(component).toBeTruthy() }) + + describe('logOut', () => { + it('should log out', async () => { + jest.spyOn(window, 'fetch').mockResolvedValue({ + ok: true, + } as Response) + + const originalUrl = window.origin + + await component.logOut() + + expect(window.fetch).toHaveBeenCalledWith(service.logoutUrl, { + method: 'GET', + }) + expect(window.location.href.slice(0, -1)).toBe(originalUrl) + }) + }) }) diff --git a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.ts b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.ts index 0aeb4afca0..8b88be4411 100644 --- a/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.ts +++ b/apps/metadata-editor/src/app/dashboard/sidebar/sidebar.component.ts @@ -3,7 +3,10 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' import { TranslateModule } from '@ngx-translate/core' import { DashboardMenuComponent } from '../dashboard-menu/dashboard-menu.component' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' -import { AvatarServiceInterface } from '@geonetwork-ui/api/repository' +import { + AuthService, + AvatarServiceInterface, +} from '@geonetwork-ui/api/repository' import { LetDirective } from '@ngrx/component' import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' @@ -31,7 +34,8 @@ export class SidebarComponent implements OnInit { constructor( public platformService: PlatformServiceInterface, private avatarService: AvatarServiceInterface, - public organisationsService: OrganizationsServiceInterface + public organisationsService: OrganizationsServiceInterface, + private authService: AuthService ) {} ngOnInit(): void { @@ -41,4 +45,21 @@ export class SidebarComponent implements OnInit { (orgs, me) => orgs.filter((org) => org.name === me?.organisation) ) } + + logOut() { + const current_url = window.origin.toString() + fetch(this.authService.logoutUrl, { + method: 'GET', + }) + .then((response) => { + if (response.ok) { + window.location.href = current_url + } else { + console.error('Logout failed') + } + }) + .catch((error) => { + console.error('Error during logout request:', error) + }) + } } diff --git a/conf/default.toml b/conf/default.toml index 0cd7d309d6..bae70b32dc 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -23,6 +23,7 @@ proxy_path = "" # - ${lang2}, ${lang3}: indicates if and where the current language should be part of the login URL in language 2 or 3 letter code # Example to use the georchestra login page: # login_url = "/cas/login?service=${current_url}" +# logout_url = "/geonetwork/signout" # This optional URL should point to the static html page wc-embedder.html which allows to display a web component (like chart and table) via a permalink. # URLs can be indicated from the root of the same server starting with a "/" or as an external URL. Be conscious of potential CORS issues when using an external URL. # The default location in the dockerized datahub app for example is "/datahub/wc-embedder.html". diff --git a/libs/api/repository/src/lib/gn4/auth/auth.service.spec.ts b/libs/api/repository/src/lib/gn4/auth/auth.service.spec.ts index f604788788..dabc434e34 100644 --- a/libs/api/repository/src/lib/gn4/auth/auth.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/auth/auth.service.spec.ts @@ -82,4 +82,13 @@ describe('AuthService', () => { ) }) }) + + describe('Logout', () => { + beforeEach(() => { + service = TestBed.inject(AuthService) + }) + it('should return the logout url', () => { + expect(service.logoutUrl).toEqual('/geonetwork/signout') + }) + }) }) diff --git a/libs/api/repository/src/lib/gn4/auth/auth.service.ts b/libs/api/repository/src/lib/gn4/auth/auth.service.ts index fae24500ba..667e817aa3 100644 --- a/libs/api/repository/src/lib/gn4/auth/auth.service.ts +++ b/libs/api/repository/src/lib/gn4/auth/auth.service.ts @@ -5,11 +5,15 @@ import { TranslateService } from '@ngx-translate/core' export const DEFAULT_GN4_LOGIN_URL = `/geonetwork/srv/\${lang3}/catalog.signin?redirect=\${current_url}` export const LOGIN_URL = new InjectionToken('loginUrl') +export const DEFAULT_GN4_LOGOUT_URL = `/geonetwork/signout` +export const LOGOUT_URL = new InjectionToken('logoutUrl') + @Injectable({ providedIn: 'root', }) export class AuthService { baseLoginUrl = this.baseLoginUrlToken || DEFAULT_GN4_LOGIN_URL + baseLogoutUrl = this.baseLogoutUrlToken || DEFAULT_GN4_LOGOUT_URL get loginUrl() { let baseUrl = this.baseLoginUrl const locationHasQueryParams = !!window.location.search @@ -25,10 +29,14 @@ export class AuthService { LANG_2_TO_3_MAPPER[this.translateService.currentLang] ) } + + get logoutUrl() { + return this.baseLogoutUrl + } + constructor( - @Optional() - @Inject(LOGIN_URL) - private baseLoginUrlToken: string, + @Optional() @Inject(LOGIN_URL) private baseLoginUrlToken: string, + @Optional() @Inject(LOGOUT_URL) private baseLogoutUrlToken: string, private translateService: TranslateService ) {} } diff --git a/libs/util/app-config/src/lib/app-config.spec.ts b/libs/util/app-config/src/lib/app-config.spec.ts index e590d8a9ad..53b05d6bfa 100644 --- a/libs/util/app-config/src/lib/app-config.spec.ts +++ b/libs/util/app-config/src/lib/app-config.spec.ts @@ -133,6 +133,7 @@ describe('app config utils', () => { PROXY_PATH: '/proxy/?url=', METADATA_LANGUAGE: 'fre', LOGIN_URL: '/cas/login?service=', + LOGOUT_URL: '/geonetwork/signout', WEB_COMPONENT_EMBEDDER_URL: '/datahub/wc-embedder.html', }) }) diff --git a/libs/util/app-config/src/lib/app-config.ts b/libs/util/app-config/src/lib/app-config.ts index 04ca6b87fd..540073903c 100644 --- a/libs/util/app-config/src/lib/app-config.ts +++ b/libs/util/app-config/src/lib/app-config.ts @@ -98,6 +98,7 @@ export function loadAppConfig() { 'proxy_path', 'metadata_language', 'login_url', + 'logout_url', 'web_component_embedder_url', 'languages', 'contact_email', @@ -124,6 +125,7 @@ export function loadAppConfig() { ).toLowerCase() : undefined, LOGIN_URL: parsedGlobalSection.login_url, + LOGOUT_URL: parsedGlobalSection.logout_url, WEB_COMPONENT_EMBEDDER_URL: parsedGlobalSection.web_component_embedder_url, LANGUAGES: parsedGlobalSection.languages, diff --git a/libs/util/app-config/src/lib/fixtures.ts b/libs/util/app-config/src/lib/fixtures.ts index 2a7cec35d4..3126cb0492 100644 --- a/libs/util/app-config/src/lib/fixtures.ts +++ b/libs/util/app-config/src/lib/fixtures.ts @@ -6,6 +6,7 @@ geonetwork4_api_url = "/geonetwork/srv/api" proxy_path = "/proxy/?url=" metadata_language = "fre" login_url = "/cas/login?service=" +logout_url = "/geonetwork/signout" web_component_embedder_url = "/datahub/wc-embedder.html" [map] diff --git a/libs/util/app-config/src/lib/model.ts b/libs/util/app-config/src/lib/model.ts index 84682b6eee..742aa7fc4b 100644 --- a/libs/util/app-config/src/lib/model.ts +++ b/libs/util/app-config/src/lib/model.ts @@ -6,6 +6,7 @@ export interface GlobalConfig { PROXY_PATH?: string METADATA_LANGUAGE?: string LOGIN_URL?: string + LOGOUT_URL?: string WEB_COMPONENT_EMBEDDER_URL?: string LANGUAGES?: string[] CONTACT_EMAIL?: string diff --git a/translations/de.json b/translations/de.json index 63d5ec7762..00ae56f652 100644 --- a/translations/de.json +++ b/translations/de.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "", "editor.record.undo.tooltip.enabled": "", "editor.record.upToDate": "Dieser Datensatz ist auf dem neuesten Stand", + "editor.sidebar.logout": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "", "externalviewer.dataset.unnamed": "Datensatz aus dem Datahub", diff --git a/translations/en.json b/translations/en.json index 3f15cfe258..f4c7e19558 100644 --- a/translations/en.json +++ b/translations/en.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "There are no pending changes on this record", "editor.record.undo.tooltip.enabled": "Clicking this button will cancel the pending changes on this record.", "editor.record.upToDate": "This record is up to date", + "editor.sidebar.logout": "Log out", "editor.sidebar.menu.editor": "Editor", "editor.temporary.disabled": "Not implemented yet", "externalviewer.dataset.unnamed": "Datahub layer", diff --git a/translations/es.json b/translations/es.json index 10e1c8cb55..12e2cba9ce 100644 --- a/translations/es.json +++ b/translations/es.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "", "editor.record.undo.tooltip.enabled": "", "editor.record.upToDate": "", + "editor.sidebar.logout": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "", "externalviewer.dataset.unnamed": "", diff --git a/translations/fr.json b/translations/fr.json index a12538009c..ba4da4eef8 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "Il n'y a pas de modifications en cours sur cette fiche", "editor.record.undo.tooltip.enabled": "Cliquez sur ce bouton pour annuler les modifications apportées à cette fiche", "editor.record.upToDate": "", + "editor.sidebar.logout": "Se déconnecter", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "Pas encore implémenté", "externalviewer.dataset.unnamed": "Couche du datahub", diff --git a/translations/it.json b/translations/it.json index ed95d6c683..272d8d2da0 100644 --- a/translations/it.json +++ b/translations/it.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "", "editor.record.undo.tooltip.enabled": "", "editor.record.upToDate": "", + "editor.sidebar.logout": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "", "externalviewer.dataset.unnamed": "Layer del datahub", diff --git a/translations/nl.json b/translations/nl.json index 1c50db16cd..fc38f38858 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "", "editor.record.undo.tooltip.enabled": "", "editor.record.upToDate": "", + "editor.sidebar.logout": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "", "externalviewer.dataset.unnamed": "", diff --git a/translations/pt.json b/translations/pt.json index 3d48db46d9..4a412c2691 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "", "editor.record.undo.tooltip.enabled": "", "editor.record.upToDate": "", + "editor.sidebar.logout": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "", "externalviewer.dataset.unnamed": "", diff --git a/translations/sk.json b/translations/sk.json index cb11870799..bef5dc3a42 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -296,6 +296,7 @@ "editor.record.undo.tooltip.disabled": "", "editor.record.undo.tooltip.enabled": "", "editor.record.upToDate": "", + "editor.sidebar.logout": "", "editor.sidebar.menu.editor": "", "editor.temporary.disabled": "", "externalviewer.dataset.unnamed": "",