diff --git a/npm/ng-packs/packages/oauth/src/lib/services/remember-me.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/remember-me.service.ts new file mode 100644 index 0000000000..84d40d982a --- /dev/null +++ b/npm/ng-packs/packages/oauth/src/lib/services/remember-me.service.ts @@ -0,0 +1,27 @@ +import { AbpLocalStorageService } from '@abp/ng.core'; +import { Injectable, inject } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class RememberMeService { + readonly #rememberMe = 'remember_me'; + protected readonly localStorageService = inject(AbpLocalStorageService); + + set(remember: boolean) { + this.localStorageService.setItem(this.#rememberMe, JSON.stringify(remember)); + } + + remove() { + this.localStorageService.removeItem(this.#rememberMe); + } + + get() { + return Boolean(JSON.parse(this.localStorageService.getItem(this.#rememberMe))); + } + + getFromToken(accessToken: string) { + const parsedToken = JSON.parse(atob(accessToken.split('.')[1])); + return Boolean(parsedToken[this.#rememberMe]); + } +} diff --git a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-code-flow-strategy.ts b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-code-flow-strategy.ts index c9708d699e..7b1e0c108a 100644 --- a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-code-flow-strategy.ts +++ b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-code-flow-strategy.ts @@ -2,15 +2,36 @@ import { noop } from '@abp/ng.core'; import { Params } from '@angular/router'; import { from, of } from 'rxjs'; import { AuthFlowStrategy } from './auth-flow-strategy'; +import { isTokenExpired } from '../utils'; export class AuthCodeFlowStrategy extends AuthFlowStrategy { readonly isInternalAuth = false; async init() { + this.checkRememberMeOption(); + return super .init() .then(() => this.oAuthService.tryLogin().catch(noop)) - .then(() => this.oAuthService.setupAutomaticSilentRefresh({}, 'access_token')); + .then(() => this.oAuthService.setupAutomaticSilentRefresh()); + } + + private checkRememberMeOption() { + const accessToken = this.oAuthService.getAccessToken(); + const isTokenExpire = isTokenExpired(this.oAuthService.getAccessTokenExpiration()); + let rememberMe = this.rememberMeService.get(); + + if (accessToken && !rememberMe) { + const rememberMeValue = this.rememberMeService.getFromToken(accessToken); + + this.rememberMeService.set(!!rememberMeValue); + } + + rememberMe = this.rememberMeService.get(); + if (accessToken && isTokenExpire && !rememberMe) { + this.rememberMeService.remove(); + this.oAuthService.logOut(); + } } navigateToLogin(queryParams?: Params) { @@ -29,6 +50,7 @@ export class AuthCodeFlowStrategy extends AuthFlowStrategy { } logout(queryParams?: Params) { + this.rememberMeService.remove(); return from(this.oAuthService.revokeTokenAndLogout(this.getCultureParams(queryParams))); } diff --git a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts index 256b4b38fa..1417fb7825 100644 --- a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts +++ b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts @@ -24,6 +24,8 @@ import { import { clearOAuthStorage } from '../utils/clear-o-auth-storage'; import { oAuthStorage } from '../utils/oauth-storage'; import { OAuthErrorFilterService } from '../services'; +import { isTokenExpired } from '../utils'; +import { RememberMeService } from '../services/remember-me.service'; export abstract class AuthFlowStrategy { abstract readonly isInternalAuth: boolean; @@ -35,6 +37,7 @@ export abstract class AuthFlowStrategy { protected oAuthConfig!: AuthConfig; protected sessionState: SessionStateService; protected localStorageService: AbpLocalStorageService; + protected rememberMeService: RememberMeService; protected tenantKey: string; protected router: Router; @@ -61,6 +64,7 @@ export abstract class AuthFlowStrategy { this.tenantKey = injector.get(TENANT_KEY); this.router = injector.get(Router); this.oAuthErrorFilterService = injector.get(OAuthErrorFilterService); + this.rememberMeService = injector.get(RememberMeService); this.listenToOauthErrors(); } @@ -70,23 +74,20 @@ export abstract class AuthFlowStrategy { const shouldClear = shouldStorageClear(this.oAuthConfig.clientId, oAuthStorage); if (shouldClear) clearOAuthStorage(oAuthStorage); } - this.oAuthService.configure(this.oAuthConfig); - this.oAuthService.events .pipe(filter(event => event.type === 'token_refresh_error')) .subscribe(() => this.navigateToLogin()); - this.navigateToPreviousUrl(); - return this.oAuthService .loadDiscoveryDocument() .then(() => { - if (this.oAuthService.hasValidAccessToken() || !this.oAuthService.getRefreshToken()) { - return Promise.resolve(); + const isTokenExpire = isTokenExpired(this.oAuthService.getAccessTokenExpiration()); + if (!isTokenExpire || this.oAuthService.getRefreshToken()) { + return this.refreshToken(); } - return this.refreshToken(); + return Promise.resolve(); }) .catch(this.catchError); } diff --git a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-password-flow-strategy.ts b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-password-flow-strategy.ts index 1958865e23..9c38c64e27 100644 --- a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-password-flow-strategy.ts +++ b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-password-flow-strategy.ts @@ -1,31 +1,23 @@ import { filter, switchMap, tap } from 'rxjs/operators'; import { OAuthInfoEvent } from 'angular-oauth2-oidc'; import { Params, Router } from '@angular/router'; -import { from, Observable, pipe } from 'rxjs'; +import { from, Observable } from 'rxjs'; import { HttpHeaders } from '@angular/common/http'; import { AuthFlowStrategy } from './auth-flow-strategy'; -import { pipeToLogin, removeRememberMe } from '../utils/auth-utils'; +import { isTokenExpired, pipeToLogin } from '../utils/auth-utils'; import { LoginParams } from '@abp/ng.core'; import { clearOAuthStorage } from '../utils/clear-o-auth-storage'; -function getCookieValueByName(name: string) { - const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); - return match ? match[2] : ''; -} - export class AuthPasswordFlowStrategy extends AuthFlowStrategy { readonly isInternalAuth = true; - private cookieKey = 'rememberMe'; - private storageKey = 'passwordFlow'; private listenToTokenExpiration() { this.oAuthService.events .pipe( filter( - event => - event instanceof OAuthInfoEvent && + event => event instanceof OAuthInfoEvent && event.type === 'token_expires' && - event.info === 'access_token', + event.info === 'access_token' ), ) .subscribe(() => { @@ -33,20 +25,28 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy { this.refreshToken(); } else { this.oAuthService.logOut(); - removeRememberMe(this.localStorageService); + this.rememberMeService.remove(); this.configState.refreshAppState().subscribe(); } }); } async init() { - if (!getCookieValueByName(this.cookieKey) && localStorage.getItem(this.storageKey)) { - this.oAuthService.logOut(); - } + this.checkRememberMeOption(); return super.init().then(() => this.listenToTokenExpiration()); } + private checkRememberMeOption() { + const accessToken = this.oAuthService.getAccessToken(); + const isTokenExpire = isTokenExpired(this.oAuthService.getAccessTokenExpiration()); + const rememberMe = this.rememberMeService.get(); + if (accessToken && isTokenExpire && !rememberMe) { + this.rememberMeService.remove(); + this.oAuthService.logOut(); + } + } + navigateToLogin(queryParams?: Params) { const router = this.injector.get(Router); return router.navigate(['/account/login'], { queryParams }); @@ -67,14 +67,15 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy { ), ).pipe(pipeToLogin(params, this.injector)); } + logout() { const router = this.injector.get(Router); const noRedirectToLogoutUrl = true; return from(this.oAuthService.revokeTokenAndLogout(noRedirectToLogoutUrl)).pipe( switchMap(() => this.configState.refreshAppState()), tap(() => { + this.rememberMeService.remove(); router.navigateByUrl('/'); - removeRememberMe(this.localStorageService); }), ); } @@ -82,7 +83,7 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy { protected refreshToken() { return this.oAuthService.refreshToken().catch(() => { clearOAuthStorage(); - removeRememberMe(this.localStorageService); + this.rememberMeService.remove(); }); } } diff --git a/npm/ng-packs/packages/oauth/src/lib/utils/auth-utils.ts b/npm/ng-packs/packages/oauth/src/lib/utils/auth-utils.ts index bd50e97ee7..bc76609f5a 100644 --- a/npm/ng-packs/packages/oauth/src/lib/utils/auth-utils.ts +++ b/npm/ng-packs/packages/oauth/src/lib/utils/auth-utils.ts @@ -6,11 +6,8 @@ import { ConfigStateService, LoginParams, PipeToLoginFn, - AbpLocalStorageService, } from '@abp/ng.core'; - -const cookieKey = 'rememberMe'; -const storageKey = 'passwordFlow'; +import { RememberMeService } from '../services/remember-me.service'; export const pipeToLogin: PipeToLoginFn = function ( params: Pick, @@ -18,28 +15,18 @@ export const pipeToLogin: PipeToLoginFn = function ( ) { const configState = injector.get(ConfigStateService); const router = injector.get(Router); - const localStorage = injector.get(AbpLocalStorageService); + const rememberMeService = injector.get(RememberMeService); return pipe( switchMap(() => configState.refreshAppState()), tap(() => { - setRememberMe(params.rememberMe, localStorage); + rememberMeService.set(params.rememberMe); if (params.redirectUrl) router.navigate([params.redirectUrl]); }), ); }; -export function setRememberMe( - remember: boolean | undefined, - localStorageService: AbpLocalStorageService, -) { - removeRememberMe(localStorageService); - localStorageService.setItem(storageKey, 'true'); - document.cookie = `${cookieKey}=true; path=/${ - remember ? ' ;expires=Fri, 31 Dec 9999 23:59:59 GMT' : '' - }`; -} - -export function removeRememberMe(localStorageService: AbpLocalStorageService) { - localStorageService.removeItem(storageKey); - document.cookie = cookieKey + '= ; path=/; expires = Thu, 01 Jan 1970 00:00:00 GMT'; +//Ref: https://github.com/manfredsteyer/angular-oauth2-oidc/issues/1214 +export function isTokenExpired(expireDate: number): boolean { + const currentDate = new Date().getTime(); + return expireDate < currentDate; }