mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
79700: Auto-refreshing the token & Needed config
This commit is contained in:
@@ -130,7 +130,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
console.info(environment);
|
||||
}
|
||||
this.storeCSSVariables();
|
||||
|
||||
this.authService.trackTokenExpiration();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@@ -28,7 +28,7 @@ import { AuthMethodType } from './models/auth.method-type';
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
// Intercetor is called twice per request,
|
||||
// Interceptor is called twice per request,
|
||||
// so to prevent RefreshTokenAction is dispatched twice
|
||||
// we're creating a refresh token request list
|
||||
protected refreshTokenRequestUrls = [];
|
||||
@@ -216,23 +216,8 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
let authorization: string;
|
||||
|
||||
if (authService.isTokenExpired()) {
|
||||
authService.setRedirectUrl(this.router.url);
|
||||
// The access token is expired
|
||||
// Redirect to the login route
|
||||
this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired'));
|
||||
return observableOf(null);
|
||||
} else if ((!this.isAuthRequest(req) || this.isLogoutResponse(req)) && isNotEmpty(token)) {
|
||||
// Intercept a request that is not to the authentication endpoint
|
||||
authService.isTokenExpiring().pipe(
|
||||
filter((isExpiring) => isExpiring))
|
||||
.subscribe(() => {
|
||||
// If the current request url is already in the refresh token request list, skip it
|
||||
if (isUndefined(find(this.refreshTokenRequestUrls, req.url))) {
|
||||
// When a token is about to expire, refresh it
|
||||
this.store.dispatch(new RefreshTokenAction(token));
|
||||
this.refreshTokenRequestUrls.push(req.url);
|
||||
}
|
||||
});
|
||||
// Get the auth header from the service.
|
||||
authorization = authService.buildAuthHeader(token);
|
||||
let newHeaders = req.headers.set('authorization', authorization);
|
||||
|
@@ -33,7 +33,7 @@ import {
|
||||
} from './selectors';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import {
|
||||
CheckAuthenticationTokenAction,
|
||||
CheckAuthenticationTokenAction, RefreshTokenAction,
|
||||
ResetAuthenticationMessagesAction,
|
||||
RetrieveAuthMethodsAction,
|
||||
SetRedirectUrlAction
|
||||
@@ -46,6 +46,9 @@ import { getAllSucceededRemoteDataPayload } from '../shared/operators';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
import { HardRedirectService } from '../services/hard-redirect.service';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export const LOGIN_ROUTE = '/login';
|
||||
export const LOGOUT_ROUTE = '/logout';
|
||||
@@ -64,6 +67,11 @@ export class AuthService {
|
||||
*/
|
||||
protected _authenticated: boolean;
|
||||
|
||||
/**
|
||||
* Timer to track time until token refresh
|
||||
*/
|
||||
private tokenRefreshTimer;
|
||||
|
||||
constructor(@Inject(REQUEST) protected req: any,
|
||||
@Inject(NativeWindowService) protected _window: NativeWindowRef,
|
||||
@Optional() @Inject(RESPONSE) private response: any,
|
||||
@@ -73,7 +81,9 @@ export class AuthService {
|
||||
protected routeService: RouteService,
|
||||
protected storage: CookieService,
|
||||
protected store: Store<AppState>,
|
||||
protected hardRedirectService: HardRedirectService
|
||||
protected hardRedirectService: HardRedirectService,
|
||||
private notificationService: NotificationsService,
|
||||
private translateService: TranslateService
|
||||
) {
|
||||
this.store.pipe(
|
||||
select(isAuthenticated),
|
||||
@@ -298,7 +308,7 @@ export class AuthService {
|
||||
*/
|
||||
public getToken(): AuthTokenInfo {
|
||||
let token: AuthTokenInfo;
|
||||
this.store.pipe(select(getAuthenticationToken))
|
||||
this.store.pipe(take(1), select(getAuthenticationToken))
|
||||
.subscribe((authTokenInfo: AuthTokenInfo) => {
|
||||
// Retrieve authentication token info and check if is valid
|
||||
token = authTokenInfo || null;
|
||||
@@ -306,6 +316,44 @@ export class AuthService {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that checks when the session token from store expires and refreshes it when needed
|
||||
*/
|
||||
public trackTokenExpiration(): void {
|
||||
let token: AuthTokenInfo;
|
||||
let currentlyRefreshingToken = false;
|
||||
this.store.pipe(select(getAuthenticationToken)).subscribe((authTokenInfo: AuthTokenInfo) => {
|
||||
// If new token is undefined an it wasn't previously => Refresh failed
|
||||
if (currentlyRefreshingToken && token != undefined && authTokenInfo == undefined) {
|
||||
// Token refresh failed => Error notification => 10 second wait => Page reloads & user logged out
|
||||
this.notificationService.error(this.translateService.get('auth.messages.token-refresh-failed'));
|
||||
setTimeout(() => this.navigateToRedirectUrl(this.hardRedirectService.getCurrentRoute()), 10000);
|
||||
currentlyRefreshingToken = false;
|
||||
}
|
||||
// If new token.expires is different => Refresh succeeded
|
||||
if (currentlyRefreshingToken && authTokenInfo != undefined && token.expires != authTokenInfo.expires) {
|
||||
currentlyRefreshingToken = false;
|
||||
}
|
||||
// Check if/when token needs to be refreshed
|
||||
if (!currentlyRefreshingToken) {
|
||||
token = authTokenInfo || null;
|
||||
if (token != undefined && token != null) {
|
||||
let timeLeftBeforeRefresh = token.expires - new Date().getTime() - environment.auth.rest.timeLeftBeforeTokenRefresh;
|
||||
if (timeLeftBeforeRefresh < 0) {
|
||||
timeLeftBeforeRefresh = 0;
|
||||
}
|
||||
if (hasValue(this.tokenRefreshTimer)) {
|
||||
clearTimeout(this.tokenRefreshTimer);
|
||||
}
|
||||
this.tokenRefreshTimer = setTimeout(() => {
|
||||
this.store.dispatch(new RefreshTokenAction(token));
|
||||
currentlyRefreshingToken = true;
|
||||
}, timeLeftBeforeRefresh);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a token is next to be expired
|
||||
* @returns {boolean}
|
||||
|
@@ -526,6 +526,8 @@
|
||||
|
||||
"auth.messages.expired": "Your session has expired. Please log in again.",
|
||||
|
||||
"auth.messages.token-refresh-failed": "Refreshing your session token failed. Please log in again.",
|
||||
|
||||
|
||||
|
||||
"bitstream.download.page": "Now downloading {{bitstream}}..." ,
|
||||
|
@@ -6,5 +6,18 @@ export interface AuthTarget {
|
||||
}
|
||||
|
||||
export interface AuthConfig extends Config {
|
||||
target: AuthTarget;
|
||||
target?: AuthTarget;
|
||||
|
||||
ui: {
|
||||
// The amount of time before the idle warning is shown
|
||||
timeUntilIdle: number;
|
||||
// The amount of time the user has to react after the idle warning is shown before they are logged out.
|
||||
idleGracePeriod: number;
|
||||
};
|
||||
|
||||
rest: {
|
||||
// If the rest token expires in less than this amount of time, it will be refreshed automatically.
|
||||
// This is independent from the idle warning.
|
||||
timeLeftBeforeTokenRefresh: number;
|
||||
};
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import { GlobalConfig } from '../config/global-config.interface';
|
||||
import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type';
|
||||
import { BrowseByType } from '../app/+browse-by/+browse-by-switcher/browse-by-decorator';
|
||||
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
||||
import { BASE_THEME_NAME } from '../app/shared/theme-support/theme.constants';
|
||||
|
||||
export const environment: GlobalConfig = {
|
||||
production: true,
|
||||
@@ -43,6 +42,25 @@ export const environment: GlobalConfig = {
|
||||
timePerMethod: {[RestRequestMethod.PATCH]: 3} as any // time in seconds
|
||||
}
|
||||
},
|
||||
// Authority settings
|
||||
auth: {
|
||||
// Authority UI settings
|
||||
ui: {
|
||||
// the amount of time before the idle warning is shown
|
||||
// timeUntilIdle: 15 * 60 * 1000, // 15 minutes
|
||||
timeUntilIdle: 1 * 60 * 1000, // 1 minutes
|
||||
// the amount of time the user has to react after the idle warning is shown before they are logged out.
|
||||
// idleGracePeriod: 5 * 60 * 1000, // 5 minutes
|
||||
idleGracePeriod: 1 * 60 * 1000, // 1 minutes
|
||||
},
|
||||
// Authority REST settings
|
||||
rest: {
|
||||
// If the rest token expires in less than this amount of time, it will be refreshed automatically.
|
||||
// This is independent from the idle warning.
|
||||
// timeLeftBeforeTokenRefresh: 2 * 60 * 1000, // 2 minutes
|
||||
timeLeftBeforeTokenRefresh: 0.25 * 60 * 1000, // 25 seconds
|
||||
},
|
||||
},
|
||||
// Form settings
|
||||
form: {
|
||||
// NOTE: Map server-side validators to comparative Angular form validators
|
||||
@@ -267,8 +285,8 @@ export const environment: GlobalConfig = {
|
||||
],
|
||||
// Whether the UI should rewrite file download URLs to match its domain. Only necessary to enable when running UI and REST API on separate domains
|
||||
rewriteDownloadUrls: false,
|
||||
// Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video").
|
||||
// For images, this enables a gallery viewer where you can zoom or page through images.
|
||||
// Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video").
|
||||
// For images, this enables a gallery viewer where you can zoom or page through images.
|
||||
// For videos, this enables embedded video streaming
|
||||
mediaViewer: {
|
||||
image: false,
|
||||
|
Reference in New Issue
Block a user