mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-02 11:32:50 +00:00
Compare commits
3 Commits
@openstapp
...
@openstapp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
436e1471a7 | ||
|
|
4c9d330c88 | ||
|
|
580ebee362 |
@@ -1,5 +1,11 @@
|
||||
# @openstapps/app
|
||||
|
||||
## 3.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4c9d330c: fix user logout when token expires
|
||||
|
||||
## 3.3.4
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@openstapps/app",
|
||||
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
|
||||
"version": "3.3.4",
|
||||
"version": "3.3.5",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
|
||||
@@ -97,7 +97,7 @@
|
||||
"form-data": "4.0.0",
|
||||
"geojson": "0.5.0",
|
||||
"ionic-appauth": "0.9.0",
|
||||
"jsonpath-plus": "10.0.6",
|
||||
"jsonpath-plus": "10.0.7",
|
||||
"maplibre-gl": "4.0.2",
|
||||
"material-symbols": "0.17.1",
|
||||
"moment": "2.30.1",
|
||||
|
||||
@@ -86,6 +86,8 @@ export abstract class AuthService implements IAuthService {
|
||||
|
||||
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private _loggedInSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private _initComplete = new BehaviorSubject<boolean>(false);
|
||||
|
||||
protected tokenHandler: TokenRequestHandler;
|
||||
@@ -133,6 +135,13 @@ export abstract class AuthService implements IAuthService {
|
||||
return this._authenticatedSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to isAuthenticated$, but will also return true if the token is expired
|
||||
*/
|
||||
get isLoggedIn$(): Observable<boolean> {
|
||||
return this._loggedInSubject.asObservable();
|
||||
}
|
||||
|
||||
get initComplete$(): Observable<boolean> {
|
||||
return this._initComplete.asObservable();
|
||||
}
|
||||
@@ -183,19 +192,20 @@ export abstract class AuthService implements IAuthService {
|
||||
protected notifyActionListers(action: IAuthAction) {
|
||||
/* eslint-disable unicorn/no-useless-undefined */
|
||||
switch (action.action) {
|
||||
case AuthActions.RefreshFailed:
|
||||
case AuthActions.SignInFailed:
|
||||
case AuthActions.SignOutSuccess:
|
||||
case AuthActions.SignOutFailed: {
|
||||
this._tokenSubject.next(undefined);
|
||||
this._userSubject.next(undefined);
|
||||
this._authenticatedSubject.next(false);
|
||||
this._loggedInSubject.next(false);
|
||||
break;
|
||||
}
|
||||
case AuthActions.LoadTokenFromStorageFailed: {
|
||||
this._tokenSubject.next(undefined);
|
||||
this._userSubject.next(undefined);
|
||||
this._authenticatedSubject.next(false);
|
||||
this._loggedInSubject.next(false);
|
||||
this._initComplete.next(true);
|
||||
break;
|
||||
}
|
||||
@@ -203,11 +213,13 @@ export abstract class AuthService implements IAuthService {
|
||||
case AuthActions.RefreshSuccess: {
|
||||
this._tokenSubject.next(action.tokenResponse);
|
||||
this._authenticatedSubject.next(true);
|
||||
this._loggedInSubject.next(true);
|
||||
break;
|
||||
}
|
||||
case AuthActions.LoadTokenFromStorageSuccess: {
|
||||
this._tokenSubject.next(action.tokenResponse);
|
||||
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
|
||||
this._loggedInSubject.next(true);
|
||||
this._initComplete.next(true);
|
||||
break;
|
||||
}
|
||||
@@ -442,7 +454,6 @@ export abstract class AuthService implements IAuthService {
|
||||
|
||||
public async refreshToken() {
|
||||
await this.requestTokenRefresh().catch(error => {
|
||||
this.storage.removeItem(TOKEN_RESPONSE_KEY);
|
||||
this.notifyActionListers(AuthActionBuilder.RefreshFailed(error));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ export class PAIAAuthService {
|
||||
|
||||
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private _loggedIn = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private _initComplete = new BehaviorSubject<boolean>(false);
|
||||
|
||||
protected tokenHandler: PAIATokenRequestHandler;
|
||||
@@ -118,6 +120,13 @@ export class PAIAAuthService {
|
||||
return this._authenticatedSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to isAuthenticated$, but will also return true if the token is expired
|
||||
*/
|
||||
get isLoggedIn$(): Observable<boolean> {
|
||||
return this._loggedIn.asObservable();
|
||||
}
|
||||
|
||||
get initComplete$(): Observable<boolean> {
|
||||
return this._initComplete.asObservable();
|
||||
}
|
||||
@@ -170,23 +179,27 @@ export class PAIAAuthService {
|
||||
this._tokenSubject.next(undefined);
|
||||
this._userSubject.next(undefined);
|
||||
this._authenticatedSubject.next(false);
|
||||
this._loggedIn.next(false);
|
||||
break;
|
||||
}
|
||||
case AuthActions.LoadTokenFromStorageFailed: {
|
||||
this._tokenSubject.next(undefined);
|
||||
this._userSubject.next(undefined);
|
||||
this._authenticatedSubject.next(false);
|
||||
this._loggedIn.next(false);
|
||||
this._initComplete.next(true);
|
||||
break;
|
||||
}
|
||||
case AuthActions.SignInSuccess: {
|
||||
this._tokenSubject.next(action.tokenResponse);
|
||||
this._authenticatedSubject.next(true);
|
||||
this._loggedIn.next(true);
|
||||
break;
|
||||
}
|
||||
case AuthActions.LoadTokenFromStorageSuccess: {
|
||||
this._tokenSubject.next(action.tokenResponse);
|
||||
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
|
||||
this._loggedIn.next(true);
|
||||
this._initComplete.next(true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ export class IdCardsProvider {
|
||||
this.encryptedStorageProvider.get<SCIdCard[]>('id-cards') as Promise<SCIdCard[]>,
|
||||
).pipe(filter(it => it !== undefined));
|
||||
|
||||
return auth.isAuthenticated$.pipe(
|
||||
mergeMap(isAuthenticated =>
|
||||
isAuthenticated
|
||||
return auth.isLoggedIn$.pipe(
|
||||
mergeMap(isLoggedIn =>
|
||||
isLoggedIn
|
||||
? feature
|
||||
? storedIdCards.pipe(
|
||||
concatWith(
|
||||
|
||||
@@ -7,7 +7,7 @@ import {SCAuthorizationProviderType} from '@openstapps/core';
|
||||
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
|
||||
|
||||
class FakeAuth {
|
||||
isAuthenticated$ = new BehaviorSubject(false);
|
||||
isLoggedIn$ = new BehaviorSubject(false);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
getValidToken() {}
|
||||
@@ -42,7 +42,7 @@ describe('IdCards', () => {
|
||||
});
|
||||
|
||||
it('should emit network result when logged in', async () => {
|
||||
fakeAuth.isAuthenticated$.next(true);
|
||||
fakeAuth.isLoggedIn$.next(true);
|
||||
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
||||
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
||||
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
|
||||
@@ -63,7 +63,7 @@ describe('IdCards', () => {
|
||||
expect(await firstValueFrom(observable)).toEqual([]);
|
||||
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
||||
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
||||
fakeAuth.isAuthenticated$.next(true);
|
||||
fakeAuth.isLoggedIn$.next(true);
|
||||
// this is counter-intuitive, but because we unsubscribed above the first value
|
||||
// will now contain the network result.
|
||||
expect(await firstValueFrom(observable)).toEqual(['abc' as never]);
|
||||
|
||||
@@ -6,74 +6,56 @@
|
||||
*
|
||||
* 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
|
||||
* 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/>.
|
||||
* 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 {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {Component, inject, input} from '@angular/core';
|
||||
import {SCSection} from '../../../../config/profile-page-sections';
|
||||
import {AuthHelperService} from '../../auth/auth-helper.service';
|
||||
import {Observable} from 'rxjs';
|
||||
import {SCAuthorizationProviderType} from '@openstapps/core';
|
||||
import {mergeMap, of} from 'rxjs';
|
||||
import Swiper from 'swiper';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {toObservable, toSignal} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-profile-page-section',
|
||||
templateUrl: 'profile-page-section.html',
|
||||
styleUrls: ['profile-page-section.scss'],
|
||||
})
|
||||
export class ProfilePageSectionComponent implements OnInit {
|
||||
@Input() item: SCSection;
|
||||
export class ProfilePageSectionComponent {
|
||||
item = input.required<SCSection>();
|
||||
|
||||
@Input() minSlideWidth = 110;
|
||||
minSlideWidth = input(110);
|
||||
|
||||
isLoggedIn: boolean;
|
||||
authHelper = inject(AuthHelperService);
|
||||
|
||||
loggedIn = toSignal(
|
||||
toObservable(this.item).pipe(
|
||||
mergeMap(item =>
|
||||
item.authProvider ? this.authHelper.getProvider(item.authProvider).isLoggedIn$ : of(false),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
isEnd = false;
|
||||
|
||||
isBeginning = true;
|
||||
|
||||
slidesPerView: number;
|
||||
slidesPerView?: number;
|
||||
|
||||
slidesFillScreen = false;
|
||||
|
||||
data: {
|
||||
[key in SCAuthorizationProviderType]: {loggedIn$: Observable<boolean>};
|
||||
} = {
|
||||
default: {
|
||||
loggedIn$: this.authHelper.getProvider('default').isAuthenticated$,
|
||||
},
|
||||
paia: {
|
||||
loggedIn$: this.authHelper.getProvider('paia').isAuthenticated$,
|
||||
},
|
||||
};
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(private authHelper: AuthHelperService) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.item.authProvider) {
|
||||
this.data[this.item.authProvider].loggedIn$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(loggedIn => {
|
||||
this.isLoggedIn = loggedIn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
activeIndexChange(swiper: Swiper) {
|
||||
this.isBeginning = swiper.isBeginning;
|
||||
this.isEnd = swiper.isEnd;
|
||||
this.slidesFillScreen = this.slidesPerView >= swiper.slides.length;
|
||||
this.slidesFillScreen = this.slidesPerView! >= swiper.slides.length;
|
||||
}
|
||||
|
||||
resizeSwiper(resizeEvent: ResizeObserverEntry, swiper: Swiper) {
|
||||
const slidesPerView =
|
||||
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth / 2) / this.minSlideWidth) + 0.5;
|
||||
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth() / 2) / this.minSlideWidth()) + 0.5;
|
||||
|
||||
if (slidesPerView > 1 && slidesPerView !== this.slidesPerView) {
|
||||
this.slidesPerView = slidesPerView;
|
||||
@@ -84,16 +66,13 @@ export class ProfilePageSectionComponent implements OnInit {
|
||||
}
|
||||
|
||||
async toggleLogIn() {
|
||||
if (!this.item.authProvider) return;
|
||||
await (this.isLoggedIn ? this.signOut(this.item.authProvider) : this.signIn(this.item.authProvider));
|
||||
}
|
||||
|
||||
async signIn(providerType: SCAuthorizationProviderType) {
|
||||
await this.authHelper.getProvider(providerType).signIn();
|
||||
}
|
||||
|
||||
async signOut(providerType: SCAuthorizationProviderType) {
|
||||
await this.authHelper.getProvider(providerType).signOut();
|
||||
await this.authHelper.endBrowserSession(providerType);
|
||||
const providerType = this.item().authProvider;
|
||||
if (!providerType) return;
|
||||
if (this.loggedIn()) {
|
||||
await this.authHelper.getProvider(providerType).signOut();
|
||||
await this.authHelper.endBrowserSession(providerType);
|
||||
} else {
|
||||
await this.authHelper.getProvider(providerType).signIn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,24 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<stapps-section [title]="'name' | translateSimple: item">
|
||||
@if (item.authProvider) {
|
||||
<stapps-section [title]="'name' | translateSimple: item()">
|
||||
@if (item().authProvider) {
|
||||
<ion-button slot="button-end" fill="clear" color="dark" (click)="toggleLogIn()">
|
||||
@if (isLoggedIn) {
|
||||
@if (loggedIn()) {
|
||||
<ion-icon slot="end" name="logout"></ion-icon>
|
||||
} @else {
|
||||
<ion-icon slot="end" name="login"></ion-icon>
|
||||
}
|
||||
<ion-label>{{ 'profile.buttons.default.log_' + (isLoggedIn ? 'out' : 'in') | translate }}</ion-label>
|
||||
<ion-label>{{ 'profile.buttons.default.log_' + (loggedIn() ? 'out' : 'in') | translate }}</ion-label>
|
||||
</ion-button>
|
||||
}
|
||||
|
||||
<simple-swiper>
|
||||
@for (link of item.links; track link) {
|
||||
@for (link of item().links; track link) {
|
||||
<ion-item
|
||||
lines="none"
|
||||
[routerLink]="link.link"
|
||||
[disabled]="link.needsAuth && !isLoggedIn"
|
||||
[disabled]="link.needsAuth && !loggedIn()"
|
||||
[detail]="false"
|
||||
>
|
||||
<div>
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -837,8 +837,8 @@ importers:
|
||||
specifier: 0.9.0
|
||||
version: 0.9.0(rxjs@7.8.1)
|
||||
jsonpath-plus:
|
||||
specifier: 10.0.6
|
||||
version: 10.0.6
|
||||
specifier: 10.0.7
|
||||
version: 10.0.7
|
||||
maplibre-gl:
|
||||
specifier: 4.0.2
|
||||
version: 4.0.2
|
||||
@@ -15039,8 +15039,8 @@ packages:
|
||||
engines: {'0': node >= 0.2.0}
|
||||
dev: true
|
||||
|
||||
/jsonpath-plus@10.0.6:
|
||||
resolution: {integrity: sha512-Q0KCash90S0WQnPnE/W0uVXQSww4NkO34COfs+gbq0fk+Kv03FYpZ+uU2I7soLLaS4d/ywsm9PxplZsTMmfBmg==}
|
||||
/jsonpath-plus@10.0.7:
|
||||
resolution: {integrity: sha512-GDA8d8fu9+s4QzAzo5LMGiLL/9YjecAX+ytlnqdeXYpU55qME57StDgaHt9R2pA7Dr8U31nwzxNJMJiHkrkRgw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user