Commit 22fb85b1 authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Mejora lógica de sesión, añade uso refresh_token

Implementa la actualización del token de acceso mediante el uso del
token de actualización, todavía en pruebas. El componente Session se
encarga de gestionar el estado de validez de los mismos, haciendo
peticiones sólo cuando es necesario.

Simplifica la lógica del componente Session, aprovechando la inclusión
de la nueva lógica de actualización de tokens.

Añade endpoint del lado del servidor para gestionar la renovación de
manera independiente (aunque usa el mismo endpoint de OpenID realmente),
refactorizando también la lógica asociada.
parent d6ec038d
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ OID_CLIENT_ID=app \
OID_CLIENT_SECRET=secretKey \
API_URL=https://api.ecomarcan.grafcan.es/api \
CONFIG_URL=https://s3.eu-west-1.amazonaws.com/mediastorage.redmicdev/public/config.json \
PRODUCTION=0 \
npm start
```

+17 −41
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ define([
	, 'dojo/_base/lang'
	, 'src/app/component/session/_Login'
	, 'src/app/component/session/_Logout'
	, 'src/app/component/session/_Refresh'
	, 'src/component/base/_Module'
	, 'src/component/base/_Store'
], function(
@@ -10,11 +11,12 @@ define([
	, lang
	, _Login
	, _Logout
	, _Refresh
	, _Module
	, _Store
) {

	return declare([_Module, _Store, _Login, _Logout], {
	return declare([_Module, _Store, _Login, _Logout, _Refresh], {
		//	summary:
		//		Módulo para gestionar el inicio y fin de sesión del usuario, así como mantenerla activa.

@@ -24,9 +26,11 @@ define([
				ownChannel: 'session',
				events: {
					USER_LOGGED_IN: 'userLoggedIn',
					USER_LOGGED_OUT: 'userLoggedOut',
					USER_LOGIN_ERROR: 'userLoginError',
					USER_LOGOUT_ERROR: 'userLogoutError'
					USER_LOGGED_OUT: 'userLoggedOut',
					USER_LOGOUT_ERROR: 'userLogoutError',
					USER_TOKEN_REFRESHED: 'userTokenRefreshed',
					USER_REFRESH_ERROR: 'userRefreshError'
				},
				actions: {
					USER_LOGIN: 'userLogin',
@@ -34,7 +38,9 @@ define([
					USER_LOGIN_ERROR: 'userLoginError',
					USER_LOGOUT: 'userLogout',
					USER_LOGGED_OUT: 'userLoggedOut',
					USER_LOGOUT_ERROR: 'userLogoutError'
					USER_LOGOUT_ERROR: 'userLogoutError',
					USER_TOKEN_REFRESHED: 'userTokenRefreshed',
					USER_REFRESH_ERROR: 'userRefreshError'
				}
			};

@@ -60,28 +66,24 @@ define([
			},{
				event: 'USER_LOGGED_OUT',
				channel: this.getChannel('USER_LOGGED_OUT')
			},{
				event: 'USER_TOKEN_REFRESHED',
				channel: this.getChannel('USER_TOKEN_REFRESHED')
			},{
				event: 'USER_LOGIN_ERROR',
				channel: this.getChannel('USER_LOGIN_ERROR')
			},{
				event: 'USER_LOGOUT_ERROR',
				channel: this.getChannel('USER_LOGOUT_ERROR')
			},{
				event: 'USER_REFRESH_ERROR',
				channel: this.getChannel('USER_REFRESH_ERROR')
			});
		},

		_initialize: function() {

			this._loginTargets = [
				this._oauthLoginTarget,
				this._oidLoginTarget
			];

			this._logoutTargets = [
				this._oauthLogoutTarget,
				this._oidLogoutTarget
			];

			this.target = this._loginTargets.concat(this._logoutTargets);
			this.target = [];
		},

		_subUserLogin: function(req) {
@@ -92,32 +94,6 @@ define([
		_subUserLogout: function() {

			this._userLogout();
		},

		_dataAvailable: function(res, resWrapper) {

			const target = resWrapper.target;

			if (this._loginTargets.includes(target)) {
				this._loginDataAvailable(res, resWrapper);
			} else if (this._logoutTargets.includes(target)) {
				this._logoutDataAvailable(res, resWrapper);
			} else {
				console.error('Received data from unknown target:', target);
			}
		},

		_errorAvailable: function(error, status, resWrapper) {

			const target = resWrapper.target;

			if (this._loginTargets.includes(target)) {
				this._loginErrorAvailable(error, status, resWrapper);
			} else if (this._logoutTargets.includes(target)) {
				this._logoutErrorAvailable(error, status, resWrapper);
			} else {
				console.error('Received error from unknown target:', target);
			}
		}
	});
});
+41 −20
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/aspect'
	, 'dojo/Deferred'
	, 'dojo/promise/all'
	, 'src/redmicConfig'
@@ -8,6 +9,7 @@ define([
], function(
	declare
	, lang
	, aspect
	, Deferred
	, PromiseAll
	, redmicConfig
@@ -26,6 +28,15 @@ define([
			};

			lang.mixin(this, this.config, args);

			aspect.after(this, '_initialize', lang.hitch(this, this._loginInitialize));
			aspect.before(this, '_dataAvailable', lang.hitch(this, this._loginDataAvailable));
			aspect.before(this, '_errorAvailable', lang.hitch(this, this._loginErrorAvailable));
		},

		_loginInitialize: function() {

			this.target.push(this._oauthLoginTarget, this._oidLoginTarget);
		},

		_userLogin: function(loginData) {
@@ -42,8 +53,8 @@ define([
			this._getTokenDfds[this._oidLoginTarget] = new Deferred();

			PromiseAll(Object.values(this._getTokenDfds)).then(
				lang.hitch(this, this._onGotTokensSuccess),
				lang.hitch(this, this._onGotTokensFailure));
				lang.hitch(this, this._onLoginSuccess),
				lang.hitch(this, this._onLoginFailure));
		},

		_getToken: function(loginData) {
@@ -63,59 +74,69 @@ define([
			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._oauthLoginTarget,
				options: options
				options: options,
				requesterId: this.getOwnChannel()
			});

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._oidLoginTarget,
				options: options
				options: options,
				requesterId: this.getOwnChannel()
			});
		},

		_loginDataAvailable: function(res, resWrapper) {

			const target = resWrapper.target,
				tokenData = res.data;
			const target = resWrapper.target;

			if (this._oauthLoginTarget !== target && this._oidLoginTarget !== target) {
				return;
			}

			this._getTokenDfds[target].resolve(tokenData);
			this._getTokenDfds[target].resolve(res.data);
		},

		_loginErrorAvailable: function(error, status, resWrapper) {

			const target = resWrapper.target;

			if (this._oauthLoginTarget !== target && this._oidLoginTarget !== target) {
				return;
			}

			this._getTokenDfds[target].reject({ error, status });
		},

		_onGotTokensSuccess: function(tokensData) {

			const oauthTokenData = tokensData[0],
				oidTokenData = tokensData[1];
		_onLoginSuccess: function(tokensData) {

			this._emitEvt('USER_LOGGED_IN', tokensData);

			this._addUserOidData(oidTokenData);
			this._addUserOauthData(oauthTokenData);
			this._updateUserSessionData(tokensData);
		},

		_onGotTokensFailure: function(errorData) {
		_onLoginFailure: function(errorData) {

			this._emitEvt('USER_LOGIN_ERROR', errorData);
		},

		_addUserOidData: function(data) {
		_updateUserSessionData: function(tokensData) {

			const oauthTokenData = tokensData[0],
				oidTokenData = tokensData[1];

			const oidAccessToken = data.access_token;
			this._addUserOidData(oidTokenData);
			this._addUserOauthData(oauthTokenData);
		},

			Credentials.set('oidAccessToken', oidAccessToken);
		_addUserOidData: function(data) {

			Credentials.set('oidAccessToken', data.access_token);
		},

		_addUserOauthData: function(data) {

			const oauthAccessToken = data.access_token;

			Credentials.set('accessToken', oauthAccessToken);
			Credentials.set('accessToken', data.access_token);
		}
	});
});
+25 −12
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/aspect'
	, 'dojo/Deferred'
	, 'dojo/promise/all'
	, 'src/redmicConfig'
@@ -8,6 +9,7 @@ define([
], function(
	declare
	, lang
	, aspect
	, Deferred
	, PromiseAll
	, redmicConfig
@@ -26,6 +28,15 @@ define([
			};

			lang.mixin(this, this.config, args);

			aspect.after(this, '_initialize', lang.hitch(this, this._logoutInitialize));
			aspect.before(this, '_dataAvailable', lang.hitch(this, this._logoutDataAvailable));
			aspect.before(this, '_errorAvailable', lang.hitch(this, this._logoutErrorAvailable));
		},

		_logoutInitialize: function() {

			this.target.push(this._oauthLogoutTarget, this._oidLogoutTarget);
		},

		_userLogout: function() {
@@ -86,16 +97,23 @@ define([

		_logoutDataAvailable: function(res, resWrapper) {

			const target = resWrapper.target,
				logoutData = res.data;
			const target = resWrapper.target;

			if (this._oauthLogoutTarget !== target && this._oidLogoutTarget !== target) {
				return;
			}

			this._logoutDfds[target].resolve(logoutData);
			this._logoutDfds[target].resolve(res.data);
		},

		_logoutErrorAvailable: function(error, status, resWrapper) {

			const target = resWrapper.target;

			if (this._oauthLogoutTarget !== target && this._oidLogoutTarget !== target) {
				return;
			}

			this._logoutDfds[target].reject({ error, status });
		},

@@ -103,8 +121,7 @@ define([

			this._emitEvt('USER_LOGGED_OUT', logoutData);

			this._removeUserOidData();
			this._removeUserOauthData();
			this._removeUserSessionData();
		},

		_onLogoutFailure: function(errorData) {
@@ -112,14 +129,10 @@ define([
			this._emitEvt('USER_LOGOUT_ERROR', errorData);
		},

		_removeUserOidData: function() {

			Credentials.set('oidAccessToken', null);
		},

		_removeUserOauthData: function() {
		_removeUserSessionData: function() {

			Credentials.set('accessToken', null);
			Credentials.remove('oidAccessToken');
			Credentials.remove('accessToken');
		},

		_userIsLoggedIn: function() {
+145 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/aspect'
	, 'src/redmicConfig'
	, 'src/util/Credentials'
], function(
	declare
	, lang
	, aspect
	, redmicConfig
	, Credentials
) {

	return declare(null, {
		//	summary:
		//		Lógica relativa a la renovación de la sesión del usuario.

		constructor: function(args) {

			this.config = {
				_refreshTarget: redmicConfig.services.refreshToken,
				_sessionCheckInterval: 10000
			};

			lang.mixin(this, this.config, args);

			aspect.after(this, '_initialize', lang.hitch(this, this._refreshInitialize));
			aspect.before(this, '_dataAvailable', lang.hitch(this, this._refreshDataAvailable));
			aspect.before(this, '_errorAvailable', lang.hitch(this, this._refreshErrorAvailable));
			aspect.before(this, '_updateUserSessionData',
				lang.hitch(this, this._updateRefreshSessionDataOnUpdateUserSessionData));
			aspect.before(this, '_removeUserSessionData', lang.hitch(this,
				this._removeRefreshSessionDataOnRemoveUserSessionData));
		},

		_refreshInitialize: function() {

			this.target.push(this._refreshTarget);

			setInterval(lang.hitch(this, this._checkSessionValidity), this._sessionCheckInterval);
		},

		_checkSessionValidity: function() {

			const accessTokenExpiresAt = Credentials.get('expiresAt'),
				currentTime = Math.floor(Date.now() / 1000);

			if (accessTokenExpiresAt && currentTime < accessTokenExpiresAt) {
				console.log('  access token is still valid');
				return;
			}

			const refreshTokenExpiresAt = Credentials.get('refreshExpiresAt');

			if (refreshTokenExpiresAt && currentTime < refreshTokenExpiresAt) {
				console.log('  refresh token is still valid, refreshing!!');
				this._refreshToken();
			} else {
				console.log('  refresh token is invalid, logging out!!');
				this._removeUserSessionData();
			}
		},

		_refreshToken: function() {

			const data = {
				refresh_token: Credentials.get('refreshToken')
			};

			this._emitEvt('REQUEST', {
				method: 'POST',
				target: this._refreshTarget,
				query: data,
				requesterId: this.getOwnChannel()
			});
		},

		_refreshDataAvailable: function(res, resWrapper) {

			const target = resWrapper.target;

			if (this._refreshTarget !== target) {
				return;
			}

			this._onRefreshSuccess(res.data);
		},

		_refreshErrorAvailable: function(error, status, resWrapper) {

			const target = resWrapper.target;

			if (this._refreshTarget !== target) {
				return;
			}

			this._onRefreshFailure({ error, status });
		},

		_onRefreshSuccess: function(tokenData) {

			this._emitEvt('USER_TOKEN_REFRESHED', tokenData);

			this._updateRefreshSessionData(tokenData);
		},

		_onRefreshFailure: function(errorData) {

			this._emitEvt('USER_REFRESH_ERROR', errorData);
		},

		_updateRefreshSessionDataOnUpdateUserSessionData: function(tokensData) {

			this._updateRefreshSessionData(tokensData[1]);
		},

		_updateRefreshSessionData: function(data) {

			const expiresAt = this._getExpiryDate(data.expires_in),
				refreshExpiresAt = this._getExpiryDate(data.refresh_expires_in);

			Credentials.set('expiresAt', expiresAt);
			Credentials.set('refreshToken', data.refresh_token);
			Credentials.set('refreshExpiresAt', refreshExpiresAt);
		},

		_getExpiryDate: function(expiresIn) {

			return Math.floor(Date.now() / 1000) + expiresIn;
		},

		_removeRefreshSessionDataOnRemoveUserSessionData: function() {

			this._removeRefreshSessionData();
		},

		_removeRefreshSessionData: function() {

			Credentials.remove('expiresAt');
			Credentials.remove('refreshToken');
			Credentials.remove('refreshExpiresAt');
		}
	});
});
Loading