diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 15d718ce..073828a1 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -31,6 +31,7 @@ import {RouterTestingModule} from '@angular/router/testing'; import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service'; import {sampleAuthConfiguration} from './_helpers/data/sample-configuration'; import {StorageProvider} from './modules/storage/storage.provider'; +import {SimpleBrowser} from './util/browser.factory'; describe('AppComponent', () => { let platformReadySpy: any; @@ -43,6 +44,7 @@ describe('AppComponent', () => { let scheduleSyncServiceSpy: jasmine.SpyObj; let platformIsSpy; let storageProvider: jasmine.SpyObj; + let simpleBrowser: jasmine.SpyObj; beforeEach(() => { platformReadySpy = Promise.resolve(); @@ -68,6 +70,7 @@ describe('AppComponent', () => { }); ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']); storageProvider = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']); + simpleBrowser = jasmine.createSpyObj('SimpleBrowser', ['open']); TestBed.configureTestingModule({ imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule], @@ -81,6 +84,7 @@ describe('AppComponent', () => { {provide: ConfigProvider, useValue: configProvider}, {provide: NGXLogger, useValue: ngxLogger}, {provide: StorageProvider, useValue: storageProvider}, + {provide: SimpleBrowser, useValue: simpleBrowser}, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a0e23c9a..4c2b97bf 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -51,7 +51,6 @@ import {FavoritesModule} from './modules/favorites/favorites.module'; import {ProfilePageModule} from './modules/profile/profile.module'; import {FeedbackModule} from './modules/feedback/feedback.module'; import {DebugDataCollectorService} from './modules/data/debug-data-collector.service'; -import {Browser, browserFactory} from './util/browser.factory'; import {AuthModule} from './modules/auth/auth.module'; import {BackgroundModule} from './modules/background/background.module'; import {LibraryModule} from './modules/library/library.module'; @@ -64,6 +63,7 @@ import {DefaultAuthService} from './modules/auth/default-auth.service'; import {PAIAAuthService} from './modules/auth/paia/paia-auth.service'; import {IonIconModule} from './util/ion-icon/ion-icon.module'; import {NavigationModule} from './modules/menu/navigation/navigation.module'; +import {browserFactory, SimpleBrowser} from './util/browser.factory'; registerLocaleData(localeDe); @@ -184,7 +184,7 @@ export function createTranslateLoader(http: HttpClient) { useClass: PathLocationStrategy, }, { - provide: Browser, + provide: SimpleBrowser, useFactory: browserFactory, deps: [Platform], }, diff --git a/src/app/modules/auth/auth-helper.service.spec.ts b/src/app/modules/auth/auth-helper.service.spec.ts index f7fd55c3..17699f94 100644 --- a/src/app/modules/auth/auth-helper.service.spec.ts +++ b/src/app/modules/auth/auth-helper.service.spec.ts @@ -24,6 +24,7 @@ import {PAIAAuthService} from './paia/paia-auth.service'; import {LoggerConfig, LoggerModule, NGXLogger} from 'ngx-logger'; import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; import {HttpClientModule} from '@angular/common/http'; +import {SimpleBrowser} from '../../util/browser.factory'; describe('AuthHelperService', () => { let authHelperService: AuthHelperService; @@ -32,6 +33,7 @@ describe('AuthHelperService', () => { const defaultAuthServiceMock = jasmine.createSpyObj('DefaultAuthService', ['init', 'setupConfiguration']); const paiaAuthServiceMock = jasmine.createSpyObj('PAIAAuthService', ['init', 'setupConfiguration']); const authHelperServiceMock = jasmine.createSpyObj('AuthHelperService', ['constructor']); + const simpleBrowserMock = jasmine.createSpyObj('SimpleBrowser', ['open']); const configProvider = jasmine.createSpyObj('ConfigProvider', { getAnyValue: { default: { @@ -84,6 +86,10 @@ describe('AuthHelperService', () => { provider: AuthHelperService, useValue: authHelperServiceMock, }, + { + provide: SimpleBrowser, + useValue: simpleBrowserMock, + }, ], }); authHelperService = TestBed.inject(AuthHelperService); diff --git a/src/app/modules/auth/auth-helper.service.ts b/src/app/modules/auth/auth-helper.service.ts index c06fe11d..0de61a59 100644 --- a/src/app/modules/auth/auth-helper.service.ts +++ b/src/app/modules/auth/auth-helper.service.ts @@ -28,6 +28,7 @@ import {ConfigProvider} from '../config/config.provider'; import {StorageProvider} from '../storage/storage.provider'; import {DefaultAuthService} from './default-auth.service'; import {PAIAAuthService} from './paia/paia-auth.service'; +import {SimpleBrowser} from '../../util/browser.factory'; const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path'; @@ -43,6 +44,7 @@ export class AuthHelperService { private storageProvider: StorageProvider, private defaultAuth: DefaultAuthService, private paiaAuth: PAIAAuthService, + private browser: SimpleBrowser, ) { this.userConfigurationMap = ( this.configProvider.getAnyValue('auth') as { @@ -113,4 +115,14 @@ export class AuthHelperService { ? (this.paiaAuth as PAIAAuthService) : (this.defaultAuth as DefaultAuthService); } + + /** + * Ends browser session by opening endSessionEndpoint URL of the provider + */ + async endBrowserSession(providerType: SCAuthorizationProviderType) { + const endSessionEndpoint = (await this.getProvider(providerType).configuration).endSessionEndpoint ?? ''; + if (endSessionEndpoint.length > 0) { + this.browser.open(new URL(endSessionEndpoint).href); + } + } } diff --git a/src/app/modules/auth/factories/browser.factory.ts b/src/app/modules/auth/factories/browser.factory.ts deleted file mode 100644 index cea6dba2..00000000 --- a/src/app/modules/auth/factories/browser.factory.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022 StApps - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ - -import {Platform} from '@ionic/angular'; -import {DefaultBrowser} from 'ionic-appauth'; -import {CapacitorBrowser} from 'ionic-appauth/lib/capacitor'; - -export const browserFactory = (platform: Platform) => { - return platform.is('capacitor') ? new CapacitorBrowser() : new DefaultBrowser(); -}; diff --git a/src/app/modules/auth/factories/index.ts b/src/app/modules/auth/factories/index.ts index 817c59f5..716bf452 100644 --- a/src/app/modules/auth/factories/index.ts +++ b/src/app/modules/auth/factories/index.ts @@ -1,2 +1 @@ -export * from './browser.factory'; export * from './storage.factory'; diff --git a/src/app/modules/data/data.module.ts b/src/app/modules/data/data.module.ts index 532d3910..6c28d405 100644 --- a/src/app/modules/data/data.module.ts +++ b/src/app/modules/data/data.module.ts @@ -17,7 +17,7 @@ import {CommonModule} from '@angular/common'; import {HttpClientModule} from '@angular/common/http'; import {NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; -import {IonicModule} from '@ionic/angular'; +import {IonicModule, Platform} from '@ionic/angular'; import {TranslateModule} from '@ngx-translate/core'; import {MarkdownModule} from 'ngx-markdown'; import {MomentModule} from 'ngx-moment'; @@ -98,6 +98,7 @@ import {PeriodicalDetailContentComponent} from './types/periodical/periodical-de import {SCThingListItemVirtualScrollStrategyDirective} from './list/sc-thing-list-item-virtual-scroll-strategy.directive'; import {DataListItemHostDirective} from './list/data-list-item-host.directive'; import {DataListItemHostDefaultComponent} from './list/data-list-item-host-default.component'; +import {browserFactory, SimpleBrowser} from '../../util/browser.factory'; /** * Module for handling data @@ -199,6 +200,11 @@ import {DataListItemHostDefaultComponent} from './list/data-list-item-host-defau CalendarService, RoutingStackService, SettingsProvider, + { + provide: SimpleBrowser, + useFactory: browserFactory, + deps: [Platform], + }, ], exports: [ SCThingListItemVirtualScrollStrategyDirective, diff --git a/src/app/modules/data/elements/external-link.component.ts b/src/app/modules/data/elements/external-link.component.ts index 454d2708..7e463ed6 100644 --- a/src/app/modules/data/elements/external-link.component.ts +++ b/src/app/modules/data/elements/external-link.component.ts @@ -14,7 +14,7 @@ */ import {Component, Input} from '@angular/core'; -import {Browser} from '../../../util/browser.factory'; +import {SimpleBrowser} from '../../../util/browser.factory'; @Component({ selector: 'stapps-external-link', @@ -26,7 +26,7 @@ export class ExternalLinkComponent { @Input() text: string; - constructor(private browser: Browser) {} + constructor(private browser: SimpleBrowser) {} onLinkClick(url: string) { // make sure if the url is valid and then open it in the browser (prevent problem in iOS) diff --git a/src/app/modules/data/types/message/message-detail-content.component.ts b/src/app/modules/data/types/message/message-detail-content.component.ts index a2ca9342..5315d5d3 100644 --- a/src/app/modules/data/types/message/message-detail-content.component.ts +++ b/src/app/modules/data/types/message/message-detail-content.component.ts @@ -14,7 +14,7 @@ */ import {Component, Input} from '@angular/core'; import {SCMessage} from '@openstapps/core'; -import {Browser} from '../../../../util/browser.factory'; +import {SimpleBrowser} from '../../../../util/browser.factory'; /** * TODO @@ -25,7 +25,7 @@ import {Browser} from '../../../../util/browser.factory'; styleUrls: ['message-detail-content.scss'], }) export class MessageDetailContentComponent { - constructor(private browser: Browser) {} + constructor(private browser: SimpleBrowser) {} /** * TODO diff --git a/src/app/modules/profile/page/profile-page-section.component.ts b/src/app/modules/profile/page/profile-page-section.component.ts index defcf9ce..eacd025a 100644 --- a/src/app/modules/profile/page/profile-page-section.component.ts +++ b/src/app/modules/profile/page/profile-page-section.component.ts @@ -19,6 +19,8 @@ import {AuthHelperService} from '../../auth/auth-helper.service'; import {Observable, Subscription} from 'rxjs'; import {SCAuthorizationProviderType} from '@openstapps/core'; import Swiper from 'swiper'; +import {AlertController} from '@ionic/angular'; +import {TranslateService} from '@ngx-translate/core'; @Component({ selector: 'stapps-profile-page-section', @@ -53,7 +55,11 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy { }, }; - constructor(private authHelper: AuthHelperService) {} + constructor( + private authHelper: AuthHelperService, + private alertController: AlertController, + private translateService: TranslateService, + ) {} ngOnInit() { if (this.item.authProvider) { @@ -94,6 +100,27 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy { async signOut(providerType: SCAuthorizationProviderType) { await this.authHelper.getProvider(providerType).signOut(); + + const alert: HTMLIonAlertElement = await this.alertController.create({ + header: this.translateService.instant(`auth.messages.${providerType}.log_out_alert.header`), + message: this.translateService.instant(`auth.messages.${providerType}.log_out_alert.message`), + buttons: [ + { + text: this.translateService.instant('no'), + cssClass: 'default', + }, + { + text: this.translateService.instant('yes'), + role: 'confirm', + cssClass: 'preferred', + handler: () => { + this.authHelper.endBrowserSession(providerType); + }, + }, + ], + }); + + await alert.present(); } ngOnDestroy() { diff --git a/src/app/modules/profile/page/profile-page.spec.ts b/src/app/modules/profile/page/profile-page.spec.ts index 98b79178..f2a60ace 100644 --- a/src/app/modules/profile/page/profile-page.spec.ts +++ b/src/app/modules/profile/page/profile-page.spec.ts @@ -26,16 +26,19 @@ import {StorageProvider} from '../../storage/storage.provider'; import {ScheduleProvider} from '../../calendar/schedule.provider'; import {DataProvider} from '../../data/data.provider'; import {StAppsWebHttpClient} from '../../data/stapps-web-http-client.provider'; +import {SimpleBrowser} from '../../../util/browser.factory'; describe('ProfilePage', () => { let component: ProfilePageComponent; let fixture: ComponentFixture; let configProvider: ConfigProvider; let storageProvider: jasmine.SpyObj; + let simpleBrowser: jasmine.SpyObj; beforeEach(() => { configProvider = jasmine.createSpyObj('ConfigProvider', ['init', 'getAnyValue']); storageProvider = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']); + simpleBrowser = jasmine.createSpyObj('SimpleBrowser', ['open']); configProvider.getAnyValue = jasmine.createSpy().and.callFake(function () { return sampleAuthConfiguration; }); @@ -49,6 +52,7 @@ describe('ProfilePage', () => { {provide: ConfigProvider, useValue: configProvider}, {provide: StorageProvider, useValue: storageProvider}, {provide: StAppsWebHttpClient, useValue: webHttpClientMethodSpy}, + {provide: SimpleBrowser, useValue: simpleBrowser}, ScheduleProvider, DataProvider, ], diff --git a/src/app/util/browser.factory.ts b/src/app/util/browser.factory.ts index 79016dcd..799668ec 100644 --- a/src/app/util/browser.factory.ts +++ b/src/app/util/browser.factory.ts @@ -15,7 +15,7 @@ import {Platform} from '@ionic/angular'; import {Browser as BrowserPlugin} from '@capacitor/browser'; -export abstract class Browser { +export abstract class SimpleBrowser { abstract open(url: string): Promise | void; } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index f0e00c12..3ce22d59 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1,5 +1,7 @@ { "ok": "Ok", + "yes": "Ja", + "no": "Nein", "abort": "Abbrechen", "save": "Speichern", "back": "Zurück", @@ -45,12 +47,20 @@ "default": { "authorizing": "Autorisierung läuft...", "logged_in_success": "Erfolgreich eingeloggt.", - "logged_out_success": "Erfolgreich ausgeloggt." + "logged_out_success": "Erfolgreich ausgeloggt.", + "log_out_alert": { + "header": "Komplett ausloggen", + "message": "Du wurdest innerhalb der App ausgeloggt. Soll zudem die Browser Session beendet werden? Wähle im Zweifelsfall \"Ja\"." + } }, "paia": { "authorizing": "Autorisierung (Bibliothek) läuft...", "logged_in_success": "Erfolgreich ins Bibliothekskonto eingeloggt.", - "logged_out_success": "Erfolgreich aus dem Bibliothekskonto ausgeloggt." + "logged_out_success": "Erfolgreich aus dem Bibliothekskonto ausgeloggt.", + "log_out_alert": { + "header": "Ausgeloggt...", + "message": "Du wurdest aus der App ausgeloggt. Möchtest du Dich dazu vom Identitätsanbieter der Bibliothek in Deinem Browser ausloggen? Falls unsicher, bitte wähle \"Ja\"." + } } } }, diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index f50874ec..4075ccf1 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,5 +1,7 @@ { "ok": "Ok", + "yes": "Yes", + "no": "No", "abort": "Abort", "save": "Save", "back": "back", @@ -45,12 +47,20 @@ "default": { "authorizing": "Authorizing...", "logged_in_success": "Successfully logged in.", - "logged_out_success": "Successfully logged out." + "logged_out_success": "Successfully logged out.", + "log_out_alert": { + "header": "Complete logout", + "message": "You are now logged out in your app. Do you want to log out from your identity provider as well? If unsure, please choose \"Yes\"." + } }, "paia": { "authorizing": "Authorizing (library)...", "logged_in_success": "Successfully logged in to library.", - "logged_out_success": "Successfully logged out from library." + "logged_out_success": "Successfully logged out from library.", + "log_out_alert": { + "header": "Logged out...", + "message": "You are now logged out within the app. Do you also want to end the browser session? If in doubt, choose \"Yes\"." + } } } }, diff --git a/src/global.scss b/src/global.scss index 95aa8c29..a63bea62 100644 --- a/src/global.scss +++ b/src/global.scss @@ -127,3 +127,14 @@ ion-header { .ion-content-parallax { @include ion-content-parallax(); } + +ion-alert { + button.alert-button.preferred { + background-color: var(--ion-color-primary); + color: var(--ion-color-primary-contrast); + } + button.alert-button.default { + background-color: transparent; + color: var(--ion-color-primary); + } +}