feat: optional logout from identity provider

Closes #372
This commit is contained in:
Jovan Krunić
2023-03-09 08:12:32 +00:00
committed by Rainer Killinger
parent a8c7d5ab59
commit 8cd2d777ab
15 changed files with 103 additions and 36 deletions

View File

@@ -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<ScheduleSyncService>;
let platformIsSpy;
let storageProvider: jasmine.SpyObj<StorageProvider>;
let simpleBrowser: jasmine.SpyObj<SimpleBrowser>;
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();

View File

@@ -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],
},

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
};

View File

@@ -1,2 +1 @@
export * from './browser.factory';
export * from './storage.factory';

View File

@@ -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,

View File

@@ -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)

View File

@@ -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

View File

@@ -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() {

View File

@@ -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<ProfilePageComponent>;
let configProvider: ConfigProvider;
let storageProvider: jasmine.SpyObj<StorageProvider>;
let simpleBrowser: jasmine.SpyObj<SimpleBrowser>;
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,
],

View File

@@ -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> | void;
}

View File

@@ -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\"."
}
}
}
},

View File

@@ -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\"."
}
}
}
},

View File

@@ -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);
}
}