From df60232afd82b2d0c88234eb7d817ff4aae53993 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Wed, 28 Feb 2018 17:22:37 +0100 Subject: [PATCH 01/16] "Access_token - Expiration Support", "Config for test" Add access_token expiration support Squash commit Fix Trailing spaces on src/model/index.js Delete "return false;" on src/model/index.js Add a config file for testing and add generateRefreshToken() generateRefreshToken() return a base(32) token Add default test/config.js Add "No Password" Support Re-Add test/config.js Change config.js and use of config.js Change on validateAccessToken() and improve config.js use change aDate var name to expirationDate and add refreshTokenExpiration var --- src/model/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index 556b4d1..c22a76b 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -17,6 +17,7 @@ export class ZOAuthModel { } this.config = config; this.tokenExpiration = this.config.tokenExpiration || 3600; + this.refreshTokenExpiration = this.config.refreshTokenExpiration || 86400; } async open() { @@ -43,6 +44,10 @@ export class ZOAuthModel { return this.database.generateToken(48); } + generateRefreshToken() { + return this.database.generateToken(32); + } + generateId() { return this.database.generateToken(32); } @@ -340,8 +345,11 @@ export class ZOAuthModel { if (accessToken) { await sessions.nextItem((a) => { if (a.access_token === accessToken) { - access = a; - return true; + const expirationDate = a.created + (a.expires_in * 1000); + if (expirationDate > new Date().getTime()) { + access = a; + return true; + } } return false; }); From a0a3b53b36afe3daf6c84577919808d0ab3600c5 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Sat, 3 Mar 2018 16:43:48 +0100 Subject: [PATCH 02/16] Establishment of Grant Type, test ok --- src/zoauthServer.js | 93 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/src/zoauthServer.js b/src/zoauthServer.js index 205fb89..f0bdbb2 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -406,6 +406,8 @@ export class ZOAuthServer { /** * Request an access token + * Inspired by Offical Doc of OAuth2, resume here : + * https://docs.google.com/document/d/1yEzRcvOlHXoMmBmV49G4HxAPEmkWuW7CFGo2cuDYdfo/edit?usp=sharing */ async requestAccessToken(params) { const { @@ -416,44 +418,97 @@ export class ZOAuthServer { client_id: clientId, /* ...extras */ } = params; + const refreshToken = null; + let response = {}; + if (grantType === "password") { + response = this.requestGrantTypePassword(clientId, username, password); + } else if (grantType === "refresh_token") { + response = this.requestGrantTypeRefreshToken(clientId, refreshToken); + } else if (grantType === "client_credential") { + response = this.requestGrantTypeClientCredential(clientId); + } else { + response.result = { error: `Unknown grant type: ${grantType}` }; + } + return response; + } + + /** + * requestGrantTypePassword() used in requestAccessToken() for GrantType Password + * + * The Password grant type is used to obtain additional access tokens + * in order to prolong the client’s authorization of a user’s resources. + * + * Password Grant require : client_id, client_secret, "redirect_uri", username, password + */ + async requestGrantTypePassword(clientId, username, password) { const response = {}; let authentication = null; let user = null; - // Only grantType = password for now - if (grantType === "password") { - // validate user - user = await this.model.validateCredentials(username, password); - if (user) { - // validate authentication - authentication = await this.model.getAuthentication(clientId, user.id); - if (!authentication) { - response.result = { error: "Not authentified" }; - } - // TODO extras, redirectUri - } else { - response.result = { error: "Can't authenticate" }; + // validate user + user = await this.model.validateCredentials(username, password); + if (user) { + // validate authentication + authentication = await this.model.getAuthentication( + clientId, + user.id, + ); + if (!authentication) { + response.result = { error: "Not authentified" }; } + // TODO extras, redirectUri } else { - response.result = { error: `Unknown grant type: ${grantType}` }; + response.result = { error: "Can't authenticate" }; } - if (user && authentication) { // generate accessToken const { scope } = authentication; - const accessToken = await this.model.getAccessToken( + const session = await this.model.getAccessToken( clientId, user.id, scope, ); response.result = { - access_token: accessToken.access_token, - expires_in: accessToken.expires_in, - scope: accessToken.scope, + access_token: session.access_token, + expires_in: session.expires_in, + scope: session.scope, }; } return response; } + /* eslint-disable no-unused-vars */ + /* eslint-disable class-methods-use-this */ + /** + * requestGrantTypeRefreshToken() used in requestAccessToken() for GrantType Refresh Token + * + * The Refresh Token grant type is used to obtain a new access token + * without setting a password. + * + * Refresh Token Grant require : client_id, client_secret, refresh_token + */ + async requestGrantTypeRefreshToken(clientId, username, password) { + const response = {}; + response.result = { error: "Function Empty" }; + return response; + } + + /* eslint-disable no-unused-vars */ + /* eslint-disable class-methods-use-this */ + /** + * requestGrantTypeRefreshClientCredential() used in requestAccessToken() + * for GrantType Client Credential + * + * The Client Credentials grant type is used when the client is + * requesting access to protected resources under its control + * + * Refresh Token Grant require : client_id, client_secret + */ + async requestGrantTypeClientCredential(clientId, clientSecret) { + const response = {}; + response.result = { error: "Function Empty" }; + return response; + } + /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ /** From 641201655d51f754d911d7c9a20ab54344928715 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Sat, 3 Mar 2018 17:04:05 +0100 Subject: [PATCH 03/16] Improve Syntax --- src/zoauthServer.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/zoauthServer.js b/src/zoauthServer.js index f0bdbb2..ce33829 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -487,9 +487,11 @@ export class ZOAuthServer { * Refresh Token Grant require : client_id, client_secret, refresh_token */ async requestGrantTypeRefreshToken(clientId, username, password) { - const response = {}; - response.result = { error: "Function Empty" }; - return response; + return { + result: { + error: "Function Empty", + }, + }; } /* eslint-disable no-unused-vars */ @@ -504,9 +506,11 @@ export class ZOAuthServer { * Refresh Token Grant require : client_id, client_secret */ async requestGrantTypeClientCredential(clientId, clientSecret) { - const response = {}; - response.result = { error: "Function Empty" }; - return response; + return { + result: { + error: "Function Empty", + }, + }; } /* eslint-disable no-unused-vars */ From 807a6ceada75173704dc664c7183e96796ed18a9 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 8 Mar 2018 17:00:29 +0100 Subject: [PATCH 04/16] [WIP] Add Refresh_Token support to GrantType Password --- src/model/descriptor.js | 12 ++++++++++ src/model/index.js | 52 ++++++++++++++++++++++++++++++++++++++--- src/zoauthServer.js | 2 ++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/model/descriptor.js b/src/model/descriptor.js index e694ade..5d2f5a4 100644 --- a/src/model/descriptor.js +++ b/src/model/descriptor.js @@ -119,9 +119,21 @@ const descriptor = { access_token: { type: "string", }, + access_created: { + type: "#DateTime", + }, expires_in: { type: "integer", }, + refresh_token: { + type: "string", + }, + refresh_expires_in: { + type: "integer", + }, + refresh_created: { + type: "#DateTime", + }, scope: { type: "array", arraytype: "string", diff --git a/src/model/index.js b/src/model/index.js index c22a76b..ab29657 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -308,15 +308,21 @@ export class ZOAuthModel { clientId, userId, scope, + grantType = "password", // by defaut, other grant type are not supported for moment expiration = this.tokenExpiration, sessions = this.getSessions(), ) { + let actualSession = null; let accessToken = null; + let refreshToken = null; if (clientId && userId) { const time = Date.now(); let id = `${clientId}-${userId}`; - accessToken = await sessions.getItem(id); - if (!accessToken) { + actualSession = await sessions.getItem(id); + if (!actualSession) { + if (grantType === "password") { + refreshToken = await this.getRefreshToken(actualSession); + } accessToken = { access_token: this.generateAccessToken(), expires_in: expiration, @@ -324,7 +330,9 @@ export class ZOAuthModel { client_id: clientId, user_id: userId, id, + access_created: time, created: time, + // Add Refresh_Token here, but promise is null }; id = null; } else { @@ -334,18 +342,39 @@ export class ZOAuthModel { accessToken.scope = scope; } } + if (refreshToken !== null) { + await sessions.setItem(id, refreshToken); + } await sessions.setItem(id, accessToken); // this.database.flush(); } return accessToken; } + async getRefreshToken( + sessions, + expiration = this.refreshTokenExpiration, + ) { + let refreshToken = null; + const time = Date.now(); + if (!sessions) { + refreshToken = null; + } else { + refreshToken = { + refresh_token: this.generateRefreshToken(), + refresh_expires_in: expiration, + refresh_created: time, + }; + } + return refreshToken; + } + async validateAccessToken(accessToken, sessions = this.getSessions()) { let access = null; if (accessToken) { await sessions.nextItem((a) => { if (a.access_token === accessToken) { - const expirationDate = a.created + (a.expires_in * 1000); + const expirationDate = a.access_created + (a.expires_in * 1000); if (expirationDate > new Date().getTime()) { access = a; return true; @@ -357,6 +386,23 @@ export class ZOAuthModel { return access; } + async validateRefreshToken(refreshToken, sessions = this.getSessions()) { + let refresh = null; + if (refreshToken) { + await sessions.nextItem((a) => { + if (a.refresh_token === refreshToken) { + const expirationDate = a.created + (a.refresh_expires_in * 1000); + if (expirationDate > new Date().getTime()) { + refresh = a; + return true; + } + } + return false; + }); + } + return refresh; + } + /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ validateScope(scope) { diff --git a/src/zoauthServer.js b/src/zoauthServer.js index ce33829..0c3fe78 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -467,9 +467,11 @@ export class ZOAuthServer { user.id, scope, ); + const refreshToken = this.model.getRefreshToken(clientId, user.id); response.result = { access_token: session.access_token, expires_in: session.expires_in, + refresh_token: refreshToken, scope: session.scope, }; } From 462a56abf41ade9c31cc02142a220b1a4f9ae6ff Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Fri, 9 Mar 2018 10:40:48 +0100 Subject: [PATCH 05/16] Patch refresh_token return in getAccessToken() --- src/model/index.js | 58 +++++++++++++++++++-------------------------- src/zoauthServer.js | 3 +-- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index ab29657..d85cadc 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -313,7 +313,6 @@ export class ZOAuthModel { sessions = this.getSessions(), ) { let actualSession = null; - let accessToken = null; let refreshToken = null; if (clientId && userId) { const time = Date.now(); @@ -321,51 +320,42 @@ export class ZOAuthModel { actualSession = await sessions.getItem(id); if (!actualSession) { if (grantType === "password") { - refreshToken = await this.getRefreshToken(actualSession); + refreshToken = await this.getRefreshToken(); + actualSession = { + access_token: this.generateAccessToken(), + expires_in: expiration, + scope, + client_id: clientId, + user_id: userId, + id, + access_created: time, + created: time, + refresh_token: refreshToken.refresh_token, + refresh_expires_in: refreshToken.refresh_expires_in, + refresh_created: refreshToken.refresh_created, + }; } - accessToken = { - access_token: this.generateAccessToken(), - expires_in: expiration, - scope, - client_id: clientId, - user_id: userId, - id, - access_created: time, - created: time, - // Add Refresh_Token here, but promise is null - }; id = null; } else { - accessToken.last = time; + actualSession.last = time; // TODO handle token expiration if (scope) { - accessToken.scope = scope; + actualSession.scope = scope; } } - if (refreshToken !== null) { - await sessions.setItem(id, refreshToken); - } - await sessions.setItem(id, accessToken); + await sessions.setItem(id, actualSession); // this.database.flush(); } - return accessToken; + return actualSession; } - async getRefreshToken( - sessions, - expiration = this.refreshTokenExpiration, - ) { - let refreshToken = null; + async getRefreshToken(expiration = this.refreshTokenExpiration) { const time = Date.now(); - if (!sessions) { - refreshToken = null; - } else { - refreshToken = { - refresh_token: this.generateRefreshToken(), - refresh_expires_in: expiration, - refresh_created: time, - }; - } + const refreshToken = { + refresh_token: this.generateRefreshToken(), + refresh_expires_in: expiration, + refresh_created: time, + }; return refreshToken; } diff --git a/src/zoauthServer.js b/src/zoauthServer.js index 0c3fe78..51d0cac 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -467,11 +467,10 @@ export class ZOAuthServer { user.id, scope, ); - const refreshToken = this.model.getRefreshToken(clientId, user.id); response.result = { access_token: session.access_token, expires_in: session.expires_in, - refresh_token: refreshToken, + refresh_token: session.refresh_token, scope: session.scope, }; } From 28ce46eb17a2bd1c66ddba90051e1abd6133f66c Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Fri, 9 Mar 2018 11:10:43 +0100 Subject: [PATCH 06/16] Add Const for Grant_Type --- src/zoauthServer.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/zoauthServer.js b/src/zoauthServer.js index 51d0cac..7250b7d 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -8,6 +8,10 @@ import { StringTools, Password } from "zoapp-core"; import createModel from "./model"; import Route from "./model/route"; +const GRANT_TYPE_PASSWORD = "password"; +const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; +const GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; + export class ZOAuthServer { /* static ErrorsMessages = { CANT_REGISTER: "Can't register this application name", @@ -15,7 +19,6 @@ export class ZOAuthServer { WRONG_NAME: "Wrong name sent", CANT_SAVE_APP: "Can't save application", }; */ - constructor(config = {}, database = null) { this.config = { ...config }; this.model = createModel(this.config.database, database); @@ -420,12 +423,12 @@ export class ZOAuthServer { } = params; const refreshToken = null; let response = {}; - if (grantType === "password") { + if (grantType === GRANT_TYPE_PASSWORD) { response = this.requestGrantTypePassword(clientId, username, password); - } else if (grantType === "refresh_token") { + } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { response = this.requestGrantTypeRefreshToken(clientId, refreshToken); - } else if (grantType === "client_credential") { - response = this.requestGrantTypeClientCredential(clientId); + } else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) { + response = this.requestGrantTypeClientCredentials(clientId); } else { response.result = { error: `Unknown grant type: ${grantType}` }; } @@ -506,7 +509,7 @@ export class ZOAuthServer { * * Refresh Token Grant require : client_id, client_secret */ - async requestGrantTypeClientCredential(clientId, clientSecret) { + async requestGrantTypeClientCredentials(clientId, clientSecret) { return { result: { error: "Function Empty", From dcc327134e2eeb49aee717cf4a8e0485765c55ca Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Fri, 9 Mar 2018 12:37:13 +0100 Subject: [PATCH 07/16] Add createResultResponse() --- src/zoauthServer.js | 115 +++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 65 deletions(-) diff --git a/src/zoauthServer.js b/src/zoauthServer.js index 7250b7d..354aabe 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -25,7 +25,12 @@ export class ZOAuthServer { this.permissionRoutes = []; } - static errorMessages() {} + /* eslint-disable class-methods-use-this */ + createResultResponse(message) { + return { + result: message, + }; + } async start() { await this.model.open(); @@ -64,14 +69,9 @@ export class ZOAuthServer { return route; } - async grantAccess( - routeName, - method = "GET", - accessToken = null, - appCredentials = null, - ) { - const response = {}; + async grantAccess(routeName, method = "GET", accessToken = null, appCredentials = null) { const route = this.findRoute(routeName, method); + let response = {}; let access = null; if (route && accessToken) { access = await this.model.validateAccessToken(accessToken); @@ -98,40 +98,35 @@ export class ZOAuthServer { }; /* eslint-enable camelcase */ } else { - response.result = { error: "Not allowed" }; + response = this.createResultResponse({ error: "Not allowed" }); } } else { - response.result = { error: "Not valid user account" }; + response = this.createResultResponse({ error: "Not valid user account" }); } } else { - response.result = { error: "Not valid access token" }; + response = this.createResultResponse({ error: "Not valid access token" }); } } else if (route && route.isOpen()) { - response.result = { access: "open" }; - } else if ( - route && - route.isScopeValid("application") && - (await this.validateApplicationCredentials(appCredentials)) - ) { - response.result = { - client_id: appCredentials.id, - scope: "application", - }; + response = this.createResultResponse({ access: "open" }); + } else if (route && route.isScopeValid("application") && (await this.validateApplicationCredentials(appCredentials))) { + response = this.createResultResponse({ + client_id: appCredentials.id, scope: "application", + }); } else { - response.result = { error: "No permission route" }; + response = this.createResultResponse({ error: "No permission route" }); } return response; } static validatePassword(params) { const { password } = params; - const response = {}; const strength = Password.strength(password); + let response = {}; if (strength > 0) { const hash = Password.generateSaltHash(password); response.result = { hash, strength }; } else { - response.result = { error: "Empty password" }; + response = this.createResultResponse({ error: "Empty password" }); } return response; } @@ -167,7 +162,7 @@ export class ZOAuthServer { domains, } = params; // logger.info("registerApplication"); - const response = {}; + let response = {}; let app = null; const wrongEmail = !StringTools.isEmail(email); if (ZOAuthServer.validateApplicationName(name) && !wrongEmail) { @@ -185,21 +180,21 @@ export class ZOAuthServer { } else { // logger.info("app exist !"); app = null; - response.result = { error: "Can't register this application name" }; + response = this.createResultResponse({ error: "Can't register this application name" }); } } else if (wrongEmail) { - response.result = { error: "Wrong email sent" }; + response = this.createResultResponse({ error: "Wrong email sent" }); } else { - response.result = { error: "Wrong name sent" }; + response = this.createResultResponse({ error: "Wrong name sent" }); } if (app) { app = await this.model.setApplication(app); // logger.info("app=", app); if (app) { - response.result = { client_id: app.id, client_secret: app.secret }; + response = this.createResultResponse({ client_id: app.id, client_secret: app.secret }); } else { - response.result = { error: "Can't save application" }; + response = this.createResultResponse({ error: "Can't save application" }); } } // TODO authorizedIps CORS params @@ -268,7 +263,7 @@ export class ZOAuthServer { } } if (!response) { - response = { error: "No client found" }; + response = this.createResultResponse({ error: "No client found" }); } return response; } @@ -288,10 +283,10 @@ export class ZOAuthServer { if (clientId) { app = await this.model.getApplication(clientId); } - const response = {}; + let response = {}; if (!app) { - return { error: "No client found" }; + return this.createResultResponse({ error: "No client found" }); } let user = null; const policies = app.policies || { userNeedEmail: true }; // TODO remove this default policies @@ -315,7 +310,7 @@ export class ZOAuthServer { anonymous_secret: extras.anonymous_secret, }; } else { - response.result = { error: "Wrong parameters sent" }; + response = this.createResultResponse({ error: "Wrong parameters sent" }); } } else if ( ZOAuthServer.validateCredentialsValue(username, email, password, policies) @@ -332,23 +327,23 @@ export class ZOAuthServer { } } else { user = null; - response.result = { error: `User exist: ${username}` }; + response = this.createResultResponse({ error: `User exist: ${username}` }); } } else { - response.result = { error: "Wrong parameters sent" }; + response = this.createResultResponse({ error: "Wrong parameters sent" }); } if (user) { user = await this.model.setUser(user); if (user) { - response.result = { + response = this.createResultResponse({ id: user.id, username: user.username, - }; + }); if (user.email) { response.result.email = user.email; } } else { - response.result = { error: "Can't save user" }; + response = this.createResultResponse({ error: "Can't save user" }); } } return response; @@ -367,8 +362,8 @@ export class ZOAuthServer { redirect_uri: redirectUri, /* ...extras */ } = params; - const response = {}; const authentication = {}; + let response = {}; let app = null; let user = null; let storedAuth = null; @@ -391,18 +386,18 @@ export class ZOAuthServer { // TODO save extra params storedAuth = await this.model.setAuthentication(authentication); if (storedAuth) { - response.result = { redirect_uri: authentication.redirect_uri }; + response = this.createResultResponse({ redirect_uri: authentication.redirect_uri }); } else { - response.result = { error: "Can't authenticate" }; + response = this.createResultResponse({ error: "Can't authenticate" }); } } else if (!app) { - response.result = { error: "No valid client_id" }; + response = this.createResultResponse({ error: "No valid client_id" }); } else if (user == null && userId) { - response.result = { error: "No valid user_id" }; + response = this.createResultResponse({ error: "No valid user_id" }); } else if (user == null && username && password) { - response.result = { error: "Wrong credentials" }; + response = this.createResultResponse({ error: "Wrong credentials" }); } else { - response.result = { error: "Not valid" }; + response = this.createResultResponse({ error: "Not valid" }); } return response; } @@ -424,13 +419,13 @@ export class ZOAuthServer { const refreshToken = null; let response = {}; if (grantType === GRANT_TYPE_PASSWORD) { - response = this.requestGrantTypePassword(clientId, username, password); + response = await this.requestGrantTypePassword(clientId, username, password); } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { - response = this.requestGrantTypeRefreshToken(clientId, refreshToken); + response = await this.requestGrantTypeRefreshToken(clientId, refreshToken); } else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) { - response = this.requestGrantTypeClientCredentials(clientId); + response = await this.requestGrantTypeClientCredentials(clientId); } else { - response.result = { error: `Unknown grant type: ${grantType}` }; + response = this.createResultResponse({ error: `Unknown grant type: ${grantType}` }); } return response; } @@ -444,7 +439,7 @@ export class ZOAuthServer { * Password Grant require : client_id, client_secret, "redirect_uri", username, password */ async requestGrantTypePassword(clientId, username, password) { - const response = {}; + let response = {}; let authentication = null; let user = null; // validate user @@ -456,11 +451,11 @@ export class ZOAuthServer { user.id, ); if (!authentication) { - response.result = { error: "Not authentified" }; + response = this.createResultResponse({ error: "Not authentified" }); } // TODO extras, redirectUri } else { - response.result = { error: "Can't authenticate" }; + response = this.createResultResponse({ error: "Can't authenticate" }); } if (user && authentication) { // generate accessToken @@ -481,7 +476,6 @@ export class ZOAuthServer { } /* eslint-disable no-unused-vars */ - /* eslint-disable class-methods-use-this */ /** * requestGrantTypeRefreshToken() used in requestAccessToken() for GrantType Refresh Token * @@ -491,15 +485,10 @@ export class ZOAuthServer { * Refresh Token Grant require : client_id, client_secret, refresh_token */ async requestGrantTypeRefreshToken(clientId, username, password) { - return { - result: { - error: "Function Empty", - }, - }; + return this.createResultResponse({ error: "Function Empty" }); } /* eslint-disable no-unused-vars */ - /* eslint-disable class-methods-use-this */ /** * requestGrantTypeRefreshClientCredential() used in requestAccessToken() * for GrantType Client Credential @@ -510,11 +499,7 @@ export class ZOAuthServer { * Refresh Token Grant require : client_id, client_secret */ async requestGrantTypeClientCredentials(clientId, clientSecret) { - return { - result: { - error: "Function Empty", - }, - }; + return this.createResultResponse({ error: "Function Empty" }); } /* eslint-disable no-unused-vars */ From eb2d8a691fd34207c27078ae0b7dfcd106b81101 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 15 Mar 2018 10:35:30 +0100 Subject: [PATCH 08/16] Adding Grant_type constant --- src/model/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index d85cadc..05fc05d 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -7,6 +7,10 @@ import { StringTools, dbCreate } from "zoapp-core"; import descriptor from "./descriptor"; +const GRANT_TYPE_PASSWORD = "password"; +const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; +const GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; + export class ZOAuthModel { constructor(config = {}, database = null) { this.database = database; @@ -308,7 +312,7 @@ export class ZOAuthModel { clientId, userId, scope, - grantType = "password", // by defaut, other grant type are not supported for moment + grantType = GRANT_TYPE_PASSWORD, // by defaut, other grant type are not supported for moment expiration = this.tokenExpiration, sessions = this.getSessions(), ) { @@ -319,7 +323,7 @@ export class ZOAuthModel { let id = `${clientId}-${userId}`; actualSession = await sessions.getItem(id); if (!actualSession) { - if (grantType === "password") { + if (grantType === GRANT_TYPE_PASSWORD) { refreshToken = await this.getRefreshToken(); actualSession = { access_token: this.generateAccessToken(), From 0e2b622ef6882edc5111253001d7cc83e6f087aa Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 15 Mar 2018 11:48:18 +0100 Subject: [PATCH 09/16] Adding usage Grant_Type constant, need to make test on it --- src/model/index.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/model/index.js b/src/model/index.js index 05fc05d..b879424 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -338,9 +338,35 @@ export class ZOAuthModel { refresh_expires_in: refreshToken.refresh_expires_in, refresh_created: refreshToken.refresh_created, }; + } else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) { + actualSession = { + access_token: this.generateAccessToken(), + expires_in: expiration, + scope, + client_id: clientId, + user_id: userId, + id, + access_created: time, + created: time, + }; } id = null; } else { + if (grantType === GRANT_TYPE_REFRESH_TOKEN) { + refreshToken = await this.getRefreshToken(); + actualSession = { + access_token: this.generateAccessToken(), + expires_in: expiration, + scope, + client_id: clientId, + user_id: userId, + id, + access_created: time, + refresh_token: refreshToken.refresh_token, + refresh_expires_in: refreshToken.refresh_expires_in, + refresh_created: refreshToken.refresh_created, + }; + } actualSession.last = time; // TODO handle token expiration if (scope) { From 006325f79149531d18c052570fcedd80f0eab20d Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Fri, 16 Mar 2018 16:38:02 +0100 Subject: [PATCH 10/16] Remove client_credentials and refactor some functions --- src/model/index.js | 77 ++++++++++++++++++++++----------------------- src/zoauthServer.js | 39 ++++++++++++----------- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index b879424..d0e49c7 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -9,7 +9,6 @@ import descriptor from "./descriptor"; const GRANT_TYPE_PASSWORD = "password"; const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; -const GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; export class ZOAuthModel { constructor(config = {}, database = null) { @@ -318,12 +317,13 @@ export class ZOAuthModel { ) { let actualSession = null; let refreshToken = null; - if (clientId && userId) { - const time = Date.now(); - let id = `${clientId}-${userId}`; - actualSession = await sessions.getItem(id); - if (!actualSession) { - if (grantType === GRANT_TYPE_PASSWORD) { + const time = Date.now(); + if (grantType === GRANT_TYPE_PASSWORD) { + // GRANT_TYPE_PASSWORD we can get session by his ID + if (clientId && userId) { + let id = `${clientId}-${userId}`; + actualSession = await sessions.getItem(id); + if (!actualSession) { refreshToken = await this.getRefreshToken(); actualSession = { access_token: this.generateAccessToken(), @@ -338,43 +338,40 @@ export class ZOAuthModel { refresh_expires_in: refreshToken.refresh_expires_in, refresh_created: refreshToken.refresh_created, }; - } else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) { - actualSession = { - access_token: this.generateAccessToken(), - expires_in: expiration, - scope, - client_id: clientId, - user_id: userId, - id, - access_created: time, - created: time, - }; + id = null; + } else { + actualSession.last = time; + // TODO handle token expiration + if (scope) { + actualSession.scope = scope; + } } - id = null; + await sessions.setItem(id, actualSession); + // this.database.flush(); } else { - if (grantType === GRANT_TYPE_REFRESH_TOKEN) { - refreshToken = await this.getRefreshToken(); - actualSession = { - access_token: this.generateAccessToken(), - expires_in: expiration, - scope, - client_id: clientId, - user_id: userId, - id, - access_created: time, - refresh_token: refreshToken.refresh_token, - refresh_expires_in: refreshToken.refresh_expires_in, - refresh_created: refreshToken.refresh_created, - }; - } - actualSession.last = time; - // TODO handle token expiration - if (scope) { - actualSession.scope = scope; - } + // Error Response } - await sessions.setItem(id, actualSession); + } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { + // We can get session with the refresh_token + let updatedSession = {}; + actualSession = await sessions.getItem(refreshToken); + if (actualSession) { + refreshToken = await this.getRefreshToken(); + updatedSession = { + access_token: this.generateAccessToken(), + expires_in: expiration, + access_created: time, + refresh_token: refreshToken.refresh_token, + refresh_expires_in: refreshToken.refresh_expires_in, + refresh_created: refreshToken.refresh_created, + }; + await sessions.setItem(actualSession.id, updatedSession); // this.database.flush(); + } else { + // Error Response + } + } else { + actualSession = { error: "Request Failed" }; } return actualSession; } diff --git a/src/zoauthServer.js b/src/zoauthServer.js index 354aabe..d068328 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -10,7 +10,6 @@ import Route from "./model/route"; const GRANT_TYPE_PASSWORD = "password"; const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; -const GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; export class ZOAuthServer { /* static ErrorsMessages = { @@ -422,8 +421,6 @@ export class ZOAuthServer { response = await this.requestGrantTypePassword(clientId, username, password); } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { response = await this.requestGrantTypeRefreshToken(clientId, refreshToken); - } else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) { - response = await this.requestGrantTypeClientCredentials(clientId); } else { response = this.createResultResponse({ error: `Unknown grant type: ${grantType}` }); } @@ -484,22 +481,26 @@ export class ZOAuthServer { * * Refresh Token Grant require : client_id, client_secret, refresh_token */ - async requestGrantTypeRefreshToken(clientId, username, password) { - return this.createResultResponse({ error: "Function Empty" }); - } - - /* eslint-disable no-unused-vars */ - /** - * requestGrantTypeRefreshClientCredential() used in requestAccessToken() - * for GrantType Client Credential - * - * The Client Credentials grant type is used when the client is - * requesting access to protected resources under its control - * - * Refresh Token Grant require : client_id, client_secret - */ - async requestGrantTypeClientCredentials(clientId, clientSecret) { - return this.createResultResponse({ error: "Function Empty" }); + async requestGrantTypeRefreshToken(clientId, refreshToken) { + let response = {}; + // no need to validate user we have refreshToken + if (refreshToken) { + // generate accessToken, scope is stock in refresh + const session = await this.model.getAccessToken( + clientId, + refreshToken, + ); + response = this.createResultResponse({ + access_token: session.access_token, + expires_in: session.expires_in, + refresh_token: session.refresh_token, + scope: session.scope, + }); + // TODO extras, redirectUri + } else { + response = this.createResultResponse({ error: "Can't use GRANT_TYPE_REFRESH_TOKEN without refresh_token" }); + } + return response; } /* eslint-disable no-unused-vars */ From f69b2b273933ba670330b50406f627dc0628b87b Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 22 Mar 2018 10:42:35 +0100 Subject: [PATCH 11/16] Rebase manually and apply new standards to new code. OK local test --- .gitignore | 1 + src/model/index.js | 6 +- src/zoauthServer.js | 130 ++++++++++++++++++++++++++----------- tests/config.js | 10 +++ tests/zoauthRouter.test.js | 14 ++-- tests/zoauthServer.test.js | 13 +--- 6 files changed, 112 insertions(+), 62 deletions(-) create mode 100644 tests/config.js diff --git a/.gitignore b/.gitignore index 18f267f..83fb59b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ lib *.DS_Store config yarn.lock +yarn-error.log diff --git a/src/model/index.js b/src/model/index.js index d0e49c7..c9890f8 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -366,7 +366,7 @@ export class ZOAuthModel { refresh_created: refreshToken.refresh_created, }; await sessions.setItem(actualSession.id, updatedSession); - // this.database.flush(); + // this.database.flush(); } else { // Error Response } @@ -391,7 +391,7 @@ export class ZOAuthModel { if (accessToken) { await sessions.nextItem((a) => { if (a.access_token === accessToken) { - const expirationDate = a.access_created + (a.expires_in * 1000); + const expirationDate = a.access_created + a.expires_in * 1000; if (expirationDate > new Date().getTime()) { access = a; return true; @@ -408,7 +408,7 @@ export class ZOAuthModel { if (refreshToken) { await sessions.nextItem((a) => { if (a.refresh_token === refreshToken) { - const expirationDate = a.created + (a.refresh_expires_in * 1000); + const expirationDate = a.created + a.refresh_expires_in * 1000; if (expirationDate > new Date().getTime()) { refresh = a; return true; diff --git a/src/zoauthServer.js b/src/zoauthServer.js index d068328..f3d688d 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -68,7 +68,12 @@ export class ZOAuthServer { return route; } - async grantAccess(routeName, method = "GET", accessToken = null, appCredentials = null) { + async grantAccess( + routeName, + method = "GET", + accessToken = null, + appCredentials = null, + ) { const route = this.findRoute(routeName, method); let response = {}; let access = null; @@ -85,6 +90,7 @@ export class ZOAuthServer { access_token, client_id, expires_in, + refresh_token, scope, user_id, } = access; @@ -92,27 +98,43 @@ export class ZOAuthServer { access_token, client_id, expires_in, + refresh_token, scope, user_id, }; /* eslint-enable camelcase */ } else { - response = this.createResultResponse({ error: "Not allowed" }); + response = this.createResultResponse({ + error: "Not allowed", + }); } } else { - response = this.createResultResponse({ error: "Not valid user account" }); + response = this.createResultResponse({ + error: "Not valid user account", + }); } } else { - response = this.createResultResponse({ error: "Not valid access token" }); + response = this.createResultResponse({ + error: "Not valid access token", + }); } } else if (route && route.isOpen()) { - response = this.createResultResponse({ access: "open" }); - } else if (route && route.isScopeValid("application") && (await this.validateApplicationCredentials(appCredentials))) { response = this.createResultResponse({ - client_id: appCredentials.id, scope: "application", + access: "open", + }); + } else if ( + route && + route.isScopeValid("application") && + (await this.validateApplicationCredentials(appCredentials)) + ) { + response = this.createResultResponse({ + client_id: appCredentials.id, + scope: "application", }); } else { - response = this.createResultResponse({ error: "No permission route" }); + response = this.createResultResponse({ + error: "No permission route", + }); } return response; } @@ -179,21 +201,32 @@ export class ZOAuthServer { } else { // logger.info("app exist !"); app = null; - response = this.createResultResponse({ error: "Can't register this application name" }); + response = this.createResultResponse({ + error: "Can't register this application name", + }); } } else if (wrongEmail) { - response = this.createResultResponse({ error: "Wrong email sent" }); + response = this.createResultResponse({ + error: "Wrong email sent", + }); } else { - response = this.createResultResponse({ error: "Wrong name sent" }); + response = this.createResultResponse({ + error: "Wrong name sent", + }); } if (app) { app = await this.model.setApplication(app); // logger.info("app=", app); if (app) { - response = this.createResultResponse({ client_id: app.id, client_secret: app.secret }); + response = this.createResultResponse({ + client_id: app.id, + client_secret: app.secret, + }); } else { - response = this.createResultResponse({ error: "Can't save application" }); + response = this.createResultResponse({ + error: "Can't save application", + }); } } // TODO authorizedIps CORS params @@ -309,7 +342,9 @@ export class ZOAuthServer { anonymous_secret: extras.anonymous_secret, }; } else { - response = this.createResultResponse({ error: "Wrong parameters sent" }); + response = this.createResultResponse({ + error: "Wrong parameters sent", + }); } } else if ( ZOAuthServer.validateCredentialsValue(username, email, password, policies) @@ -326,10 +361,14 @@ export class ZOAuthServer { } } else { user = null; - response = this.createResultResponse({ error: `User exist: ${username}` }); + response = this.createResultResponse({ + error: `User exist: ${username}`, + }); } } else { - response = this.createResultResponse({ error: "Wrong parameters sent" }); + response = this.createResultResponse({ + error: "Wrong parameters sent", + }); } if (user) { user = await this.model.setUser(user); @@ -385,18 +424,30 @@ export class ZOAuthServer { // TODO save extra params storedAuth = await this.model.setAuthentication(authentication); if (storedAuth) { - response = this.createResultResponse({ redirect_uri: authentication.redirect_uri }); + response = this.createResultResponse({ + redirect_uri: authentication.redirect_uri, + }); } else { - response = this.createResultResponse({ error: "Can't authenticate" }); + response = this.createResultResponse({ + error: "Can't authenticate", + }); } } else if (!app) { - response = this.createResultResponse({ error: "No valid client_id" }); + response = this.createResultResponse({ + error: "No valid client_id", + }); } else if (user == null && userId) { - response = this.createResultResponse({ error: "No valid user_id" }); + response = this.createResultResponse({ + error: "No valid user_id", + }); } else if (user == null && username && password) { - response = this.createResultResponse({ error: "Wrong credentials" }); + response = this.createResultResponse({ + error: "Wrong credentials", + }); } else { - response = this.createResultResponse({ error: "Not valid" }); + response = this.createResultResponse({ + error: "Not valid", + }); } return response; } @@ -418,11 +469,20 @@ export class ZOAuthServer { const refreshToken = null; let response = {}; if (grantType === GRANT_TYPE_PASSWORD) { - response = await this.requestGrantTypePassword(clientId, username, password); + response = await this.requestGrantTypePassword( + clientId, + username, + password, + ); } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { - response = await this.requestGrantTypeRefreshToken(clientId, refreshToken); + response = await this.requestGrantTypeRefreshToken( + clientId, + refreshToken, + ); } else { - response = this.createResultResponse({ error: `Unknown grant type: ${grantType}` }); + response = this.createResultResponse({ + error: `Unknown grant type: ${grantType}`, + }); } return response; } @@ -443,10 +503,7 @@ export class ZOAuthServer { user = await this.model.validateCredentials(username, password); if (user) { // validate authentication - authentication = await this.model.getAuthentication( - clientId, - user.id, - ); + authentication = await this.model.getAuthentication(clientId, user.id); if (!authentication) { response = this.createResultResponse({ error: "Not authentified" }); } @@ -457,11 +514,7 @@ export class ZOAuthServer { if (user && authentication) { // generate accessToken const { scope } = authentication; - const session = await this.model.getAccessToken( - clientId, - user.id, - scope, - ); + const session = await this.model.getAccessToken(clientId, user.id, scope); response.result = { access_token: session.access_token, expires_in: session.expires_in, @@ -486,10 +539,7 @@ export class ZOAuthServer { // no need to validate user we have refreshToken if (refreshToken) { // generate accessToken, scope is stock in refresh - const session = await this.model.getAccessToken( - clientId, - refreshToken, - ); + const session = await this.model.getAccessToken(clientId, refreshToken); response = this.createResultResponse({ access_token: session.access_token, expires_in: session.expires_in, @@ -498,7 +548,9 @@ export class ZOAuthServer { }); // TODO extras, redirectUri } else { - response = this.createResultResponse({ error: "Can't use GRANT_TYPE_REFRESH_TOKEN without refresh_token" }); + response = this.createResultResponse({ + error: "Can't use GRANT_TYPE_REFRESH_TOKEN without refresh_token", + }); } return response; } diff --git a/tests/config.js b/tests/config.js new file mode 100644 index 0000000..1357b52 --- /dev/null +++ b/tests/config.js @@ -0,0 +1,10 @@ +/* eslint import/prefer-default-export: 0 */ +export const mysqlConfig = { + database: { + datatype: "mysql", + host: "localhost", + name: "test", + user: "root", + }, + endpoint: "/auth", +}; diff --git a/tests/zoauthRouter.test.js b/tests/zoauthRouter.test.js index db32bb9..c2c01ff 100644 --- a/tests/zoauthRouter.test.js +++ b/tests/zoauthRouter.test.js @@ -6,16 +6,7 @@ */ import zoauthServer from "zoauth/zoauthServer"; import ZOAuthRouter, { send } from "zoauth/zoauthRouter"; - -const mysqlConfig = { - database: { - datatype: "mysql", - host: "localhost", - name: "auth_test", - user: "root", - }, - endpoint: "/auth", -}; +import { mysqlConfig } from "./config"; const describeParams = (name, params, func) => { params.forEach((p) => { @@ -89,6 +80,7 @@ describeParams( expect(Object.keys(result)).toEqual([ "access_token", "expires_in", + "refresh_token", "scope", ]); accessToken = result.access_token; @@ -177,6 +169,7 @@ describeParams( expect(Object.keys(result)).toEqual([ "access_token", "expires_in", + "refresh_token", "scope", ]); accessToken = result.access_token; @@ -198,6 +191,7 @@ describeParams( "access_token", "client_id", "expires_in", + "refresh_token", "scope", "user_id", ]); diff --git a/tests/zoauthServer.test.js b/tests/zoauthServer.test.js index 9b57b88..0d416fe 100644 --- a/tests/zoauthServer.test.js +++ b/tests/zoauthServer.test.js @@ -5,16 +5,7 @@ * LICENSE file in the root directory of this source tree. */ import zoauthServer from "zoauth/zoauthServer"; - -const mysqlConfig = { - database: { - datatype: "mysql", - host: "localhost", - name: "auth_test", - user: "root", - }, - endpoint: "/auth", -}; +import { mysqlConfig } from "./config"; const describeParams = (name, params, func) => { params.forEach((p) => { @@ -241,6 +232,7 @@ describeParams( expect(Object.keys(result)).toEqual([ "access_token", "expires_in", + "refresh_token", "scope", "username", "user_id", @@ -424,6 +416,7 @@ describeParams( expect(Object.keys(result)).toEqual([ "access_token", "expires_in", + "refresh_token", "scope", ]); expect(result.access_token).toHaveLength(48); From 764ab599c2c9985c3f8aeffb0ee8ce17f0a91539 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 22 Mar 2018 12:22:08 +0100 Subject: [PATCH 12/16] Make getAccessToken more clear --- src/model/index.js | 81 ++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index c9890f8..0c28459 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -319,25 +319,18 @@ export class ZOAuthModel { let refreshToken = null; const time = Date.now(); if (grantType === GRANT_TYPE_PASSWORD) { - // GRANT_TYPE_PASSWORD we can get session by his ID if (clientId && userId) { let id = `${clientId}-${userId}`; actualSession = await sessions.getItem(id); if (!actualSession) { refreshToken = await this.getRefreshToken(); - actualSession = { - access_token: this.generateAccessToken(), - expires_in: expiration, - scope, - client_id: clientId, - user_id: userId, - id, - access_created: time, - created: time, - refresh_token: refreshToken.refresh_token, - refresh_expires_in: refreshToken.refresh_expires_in, - refresh_created: refreshToken.refresh_created, - }; + actualSession = await this.createSession(id, scope, { + expiration, + clientId, + userId, + refreshToken, + time, + }); id = null; } else { actualSession.last = time; @@ -347,35 +340,60 @@ export class ZOAuthModel { } } await sessions.setItem(id, actualSession); - // this.database.flush(); } else { - // Error Response + actualSession = { error: "Require credentials" }; } } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { - // We can get session with the refresh_token - let updatedSession = {}; actualSession = await sessions.getItem(refreshToken); if (actualSession) { + const sessionId = actualSession.id; refreshToken = await this.getRefreshToken(); - updatedSession = { - access_token: this.generateAccessToken(), - expires_in: expiration, - access_created: time, - refresh_token: refreshToken.refresh_token, - refresh_expires_in: refreshToken.refresh_expires_in, - refresh_created: refreshToken.refresh_created, - }; - await sessions.setItem(actualSession.id, updatedSession); - // this.database.flush(); + actualSession = await this.refreshSession({ + expiration, + refreshToken, + time, + }); + await sessions.setItem(sessionId, actualSession); } else { - // Error Response + actualSession = { error: "Require refresh_token" }; } } else { - actualSession = { error: "Request Failed" }; + actualSession = { error: "Request Failed, unknown grant_type" }; } return actualSession; } + async createSession(id, scope, params) { + let session = {}; + session = { + access_token: this.generateAccessToken(), + expires_in: params.expiration, + scope, + client_id: params.clientId, + user_id: params.userId, + id, + access_created: params.time, + created: params.time, + refresh_token: params.refreshToken.refresh_token, + refresh_expires_in: params.refreshToken.refresh_expires_in, + refresh_created: params.refreshToken.refresh_created, + }; + return session; + } + + async refreshSession(param) { + let session = {}; + session = { + access_token: this.generateAccessToken(), + expires_in: param.expiration, + access_created: param.time, + refresh_token: param.refreshToken.refresh_token, + refresh_expires_in: param.refreshToken.refresh_expires_in, + refresh_created: param.refreshToken.refresh_created, + }; + return session; + } + async getRefreshToken(expiration = this.refreshTokenExpiration) { const time = Date.now(); const refreshToken = { @@ -391,7 +409,8 @@ export class ZOAuthModel { if (accessToken) { await sessions.nextItem((a) => { if (a.access_token === accessToken) { - const expirationDate = a.access_created + a.expires_in * 1000; + const expireTime = a.expires_in * 1000; + const expirationDate = a.access_created + expireTime; if (expirationDate > new Date().getTime()) { access = a; return true; From 3ad06872034fa21feb10d0c8cd0b3dc3e700567c Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 22 Mar 2018 15:47:20 +0100 Subject: [PATCH 13/16] Add Refresh_Token methods --- src/model/index.js | 44 +++++++++++++++------------- src/zoauthServer.js | 70 +++++++++++++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index 0c28459..4629e21 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -308,27 +308,27 @@ export class ZOAuthModel { } async getAccessToken( + grantType, + refreshToken, clientId, userId, scope, - grantType = GRANT_TYPE_PASSWORD, // by defaut, other grant type are not supported for moment expiration = this.tokenExpiration, sessions = this.getSessions(), ) { let actualSession = null; - let refreshToken = null; const time = Date.now(); if (grantType === GRANT_TYPE_PASSWORD) { if (clientId && userId) { let id = `${clientId}-${userId}`; actualSession = await sessions.getItem(id); if (!actualSession) { - refreshToken = await this.getRefreshToken(); + const newRefreshToken = await this.getRefreshToken(); actualSession = await this.createSession(id, scope, { expiration, clientId, userId, - refreshToken, + newRefreshToken, time, }); id = null; @@ -344,16 +344,20 @@ export class ZOAuthModel { actualSession = { error: "Require credentials" }; } } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { - actualSession = await sessions.getItem(refreshToken); - if (actualSession) { - const sessionId = actualSession.id; - refreshToken = await this.getRefreshToken(); - actualSession = await this.refreshSession({ - expiration, - refreshToken, - time, - }); - await sessions.setItem(sessionId, actualSession); + if (refreshToken) { + actualSession = await sessions.getItem(`refresh_token=${refreshToken}`); + if (actualSession !== null) { + const sessionId = actualSession.id; + const newRefreshToken = await this.getRefreshToken(); + actualSession = await this.refreshSession({ + expiration, + newRefreshToken, + time, + }); + await sessions.setItem(sessionId, actualSession); + } else { + actualSession = { error: "Wrong refresh_token" }; + } } else { actualSession = { error: "Require refresh_token" }; } @@ -374,9 +378,9 @@ export class ZOAuthModel { id, access_created: params.time, created: params.time, - refresh_token: params.refreshToken.refresh_token, - refresh_expires_in: params.refreshToken.refresh_expires_in, - refresh_created: params.refreshToken.refresh_created, + refresh_token: params.newRefreshToken.refresh_token, + refresh_expires_in: params.newRefreshToken.refresh_expires_in, + refresh_created: params.newRefreshToken.refresh_created, }; return session; } @@ -387,9 +391,9 @@ export class ZOAuthModel { access_token: this.generateAccessToken(), expires_in: param.expiration, access_created: param.time, - refresh_token: param.refreshToken.refresh_token, - refresh_expires_in: param.refreshToken.refresh_expires_in, - refresh_created: param.refreshToken.refresh_created, + refresh_token: param.newRefreshToken.refresh_token, + refresh_expires_in: param.newRefreshToken.refresh_expires_in, + refresh_created: param.newRefreshToken.refresh_created, }; return session; } diff --git a/src/zoauthServer.js b/src/zoauthServer.js index f3d688d..befae0b 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -459,29 +459,35 @@ export class ZOAuthServer { */ async requestAccessToken(params) { const { + grant_type: grantType, + refresh_token: refreshToken, + client_id: clientId, username, password, - grant_type: grantType, /* redirect_uri: redirectUri, */ - client_id: clientId, /* ...extras */ } = params; - const refreshToken = null; let response = {}; - if (grantType === GRANT_TYPE_PASSWORD) { - response = await this.requestGrantTypePassword( - clientId, - username, - password, - ); - } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { - response = await this.requestGrantTypeRefreshToken( - clientId, - refreshToken, - ); + if (clientId) { + if (grantType === GRANT_TYPE_PASSWORD) { + response = await this.requestGrantTypePassword( + clientId, + username, + password, + ); + } else if (grantType === GRANT_TYPE_REFRESH_TOKEN) { + response = await this.requestGrantTypeRefreshToken( + clientId, + refreshToken, + ); + } else { + response = this.createResultResponse({ + error: `Unknown grant type: ${grantType}`, + }); + } } else { response = this.createResultResponse({ - error: `Unknown grant type: ${grantType}`, + error: "Require client_id", }); } return response; @@ -514,7 +520,13 @@ export class ZOAuthServer { if (user && authentication) { // generate accessToken const { scope } = authentication; - const session = await this.model.getAccessToken(clientId, user.id, scope); + const session = await this.model.getAccessToken( + GRANT_TYPE_PASSWORD, + null, + clientId, + user.id, + scope, + ); response.result = { access_token: session.access_token, expires_in: session.expires_in, @@ -539,17 +551,27 @@ export class ZOAuthServer { // no need to validate user we have refreshToken if (refreshToken) { // generate accessToken, scope is stock in refresh - const session = await this.model.getAccessToken(clientId, refreshToken); - response = this.createResultResponse({ - access_token: session.access_token, - expires_in: session.expires_in, - refresh_token: session.refresh_token, - scope: session.scope, - }); + const session = await this.model.getAccessToken( + GRANT_TYPE_REFRESH_TOKEN, + refreshToken, + clientId, + ); + if (session.error === undefined) { + response = this.createResultResponse({ + access_token: session.access_token, + expires_in: session.expires_in, + refresh_token: session.refresh_token, + scope: session.scope, + }); + } else { + response = this.createResultResponse({ + error: session.error, + }); + } // TODO extras, redirectUri } else { response = this.createResultResponse({ - error: "Can't use GRANT_TYPE_REFRESH_TOKEN without refresh_token", + error: "Use GRANT_TYPE_REFRESH_TOKEN without refresh_token", }); } return response; From 11440c50ac1eb93a298b9dbac1d7b16d4d5ed030 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 22 Mar 2018 15:48:07 +0100 Subject: [PATCH 14/16] Add tests for refresh_token --- tests/zoauthServer.test.js | 148 ++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/tests/zoauthServer.test.js b/tests/zoauthServer.test.js index 0d416fe..58136bf 100644 --- a/tests/zoauthServer.test.js +++ b/tests/zoauthServer.test.js @@ -500,7 +500,153 @@ describeParams( }; response = await authServer.requestAccessToken(params); ({ result } = response); - expect(result.error).toEqual("Not authentified"); + expect(result.error).toEqual("Require client_id"); + }); + }); + + describe("requestAccessTokenWithRefreshToken", () => { + it("should get accessToken", async () => { + let params = { + name: "Zoapp", + grant_type: "password", + redirect_uri: "localhost", + email: "toto@test.com", + }; + const authServer = zoauthServer(config); + await authServer.reset(); + await authServer.start(); + + let response = await authServer.registerApplication(params); + let { result } = response; + expect(Object.keys(result)).toEqual(["client_id", "client_secret"]); + expect(result.client_id).toHaveLength(64); + const clientId = result.client_id; + + params = { + client_id: clientId, + username: "toto", + password: "12345", + email: "toto@test.com", + }; + response = await authServer.registerUser(params); + ({ result } = response); + expect(Object.keys(result)).toEqual(["id", "username", "email"]); + expect(result.id).toHaveLength(32); + + params = { + client_id: clientId, + username: "toto", + password: "12345", + redirect_uri: "localhost", + }; + response = await authServer.authorizeAccess(params); + ({ result } = response); + expect(Object.keys(result)).toEqual(["redirect_uri"]); + expect(result.redirect_uri).toEqual("localhost"); + + params = { + client_id: clientId, + username: "toto", + password: "12345", + redirect_uri: "localhost", + grant_type: "password", + }; + response = await authServer.requestAccessToken(params); + ({ result } = response); + expect(Object.keys(result)).toEqual([ + "access_token", + "expires_in", + "refresh_token", + "scope", + ]); + expect(result.access_token).toHaveLength(48); + const oldToken = { + access_token: result.access_token, + refresh_token: result.refresh_token, + }; + + params = { + grant_type: "refresh_token", + refresh_token: result.refresh_token, + client_id: clientId, + }; + response = await authServer.requestAccessToken(params); + ({ result } = response); + expect(Object.keys(result)).toEqual([ + "access_token", + "expires_in", + "refresh_token", + "scope", + ]); + expect(result.access_token).toHaveLength(48); + const newToken = { + access_token: result.access_token, + refresh_token: result.refresh_token, + }; + expect(newToken).not.toBe(oldToken); + }); + + it("should not get accessToken", async () => { + let params = { + name: "Zoapp", + grant_type: "password", + redirect_uri: "localhost", + email: "toto@test.com", + }; + const authServer = zoauthServer(config); + await authServer.reset(); + await authServer.start(); + + let response = await authServer.registerApplication(params); + let { result } = response; + expect(Object.keys(result)).toEqual(["client_id", "client_secret"]); + expect(result.client_id).toHaveLength(64); + const clientId = result.client_id; + + params = { + client_id: clientId, + username: "toto", + password: "12345", + email: "toto@test.com", + }; + response = await authServer.registerUser(params); + ({ result } = response); + expect(Object.keys(result)).toEqual(["id", "username", "email"]); + expect(result.id).toHaveLength(32); + + params = { + client_id: clientId, + }; + response = await authServer.requestAccessToken(params); + ({ result } = response); + expect(result.error).toEqual("Unknown grant type: undefined"); + + params = { + client_id: clientId, + grant_type: "refresh_token", + }; + response = await authServer.requestAccessToken(params); + ({ result } = response); + expect(result.error).toEqual( + "Use GRANT_TYPE_REFRESH_TOKEN without refresh_token", + ); + + params = { + grant_type: "refresh_token", + refresh_token: "bypass", + client_id: clientId, + }; + response = await authServer.requestAccessToken(params); + ({ result } = response); + expect(result.error).toEqual("Wrong refresh_token"); + + params = { + grant_type: "refresh_token", + refresh_token: "hacked", + }; + response = await authServer.requestAccessToken(params); + ({ result } = response); + expect(result.error).toEqual("Require client_id"); }); }); }, From 186065bd9707f665f354a022247f62ea3a9cafac Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 12 Apr 2018 10:21:15 +0200 Subject: [PATCH 15/16] Add constants.js --- src/constants.js | 7 +++++++ src/model/index.js | 5 +++-- src/zoauthServer.js | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 src/constants.js diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..36baefd --- /dev/null +++ b/src/constants.js @@ -0,0 +1,7 @@ +/* eslint import/prefer-default-export: 0 */ +export const constants = { + grant_type: { + password: "password", + refresh_token: "refresh_token", + }, +}; diff --git a/src/model/index.js b/src/model/index.js index 4629e21..7b56375 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -6,9 +6,10 @@ */ import { StringTools, dbCreate } from "zoapp-core"; import descriptor from "./descriptor"; +import { constants } from "../constants"; -const GRANT_TYPE_PASSWORD = "password"; -const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; +const GRANT_TYPE_PASSWORD = constants.grant_type.password; +const GRANT_TYPE_REFRESH_TOKEN = constants.grant_type.refresh_token; export class ZOAuthModel { constructor(config = {}, database = null) { diff --git a/src/zoauthServer.js b/src/zoauthServer.js index befae0b..4630d0a 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -7,9 +7,10 @@ import { StringTools, Password } from "zoapp-core"; import createModel from "./model"; import Route from "./model/route"; +import { constants } from "./constants"; -const GRANT_TYPE_PASSWORD = "password"; -const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; +const GRANT_TYPE_PASSWORD = constants.grant_type.password; +const GRANT_TYPE_REFRESH_TOKEN = constants.grant_type.refresh_token; export class ZOAuthServer { /* static ErrorsMessages = { From e18c6d3b1672b540a1fc9c4b41621cbd131ce967 Mon Sep 17 00:00:00 2001 From: Guilmain Dorian Date: Thu, 12 Apr 2018 11:55:00 +0200 Subject: [PATCH 16/16] Change constants.js --- src/constants.js | 8 ++------ src/model/index.js | 5 +---- src/zoauthServer.js | 5 +---- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/constants.js b/src/constants.js index 36baefd..89cac31 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,7 +1,3 @@ /* eslint import/prefer-default-export: 0 */ -export const constants = { - grant_type: { - password: "password", - refresh_token: "refresh_token", - }, -}; +export const GRANT_TYPE_PASSWORD = "password"; +export const GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; diff --git a/src/model/index.js b/src/model/index.js index 7b56375..7dfc3db 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -6,10 +6,7 @@ */ import { StringTools, dbCreate } from "zoapp-core"; import descriptor from "./descriptor"; -import { constants } from "../constants"; - -const GRANT_TYPE_PASSWORD = constants.grant_type.password; -const GRANT_TYPE_REFRESH_TOKEN = constants.grant_type.refresh_token; +import { GRANT_TYPE_PASSWORD, GRANT_TYPE_REFRESH_TOKEN } from "../constants"; export class ZOAuthModel { constructor(config = {}, database = null) { diff --git a/src/zoauthServer.js b/src/zoauthServer.js index 4630d0a..00a6f7c 100644 --- a/src/zoauthServer.js +++ b/src/zoauthServer.js @@ -7,10 +7,7 @@ import { StringTools, Password } from "zoapp-core"; import createModel from "./model"; import Route from "./model/route"; -import { constants } from "./constants"; - -const GRANT_TYPE_PASSWORD = constants.grant_type.password; -const GRANT_TYPE_REFRESH_TOKEN = constants.grant_type.refresh_token; +import { GRANT_TYPE_PASSWORD, GRANT_TYPE_REFRESH_TOKEN } from "./constants"; export class ZOAuthServer { /* static ErrorsMessages = {