Browse Source

Merge pull request #18768 from abpframework/sinan/remember_me

Add remember me implementation
pull/18891/head
İsmail ÇAĞDAŞ 3 years ago
committed by GitHub
parent
commit
c5ae1422ed
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 27
      npm/ng-packs/packages/oauth/src/lib/services/remember-me.service.ts
  2. 24
      npm/ng-packs/packages/oauth/src/lib/strategies/auth-code-flow-strategy.ts
  3. 15
      npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts
  4. 37
      npm/ng-packs/packages/oauth/src/lib/strategies/auth-password-flow-strategy.ts
  5. 27
      npm/ng-packs/packages/oauth/src/lib/utils/auth-utils.ts

27
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]);
}
}

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

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

37
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();
});
}
}

27
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<LoginParams, 'redirectUrl' | 'rememberMe'>,
@ -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;
}

Loading…
Cancel
Save