From 930ef317f771ea88875596dff58c37f0d73e77c1 Mon Sep 17 00:00:00 2001 From: priyachawla11 Date: Wed, 5 Apr 2023 15:21:34 +0530 Subject: [PATCH 1/2] Nonce security updates --- dist/Provider/Provider.js | 16 +++++++++++++--- dist/Utils/Auth.js | 14 ++++++++++---- dist/Utils/Database.js | 2 +- dist/Utils/Request.js | 4 ++-- src/Provider/Provider.js | 9 ++++++++- src/Utils/Auth.js | 12 +++++++----- src/Utils/Database.js | 2 +- src/Utils/Request.js | 4 ++-- 8 files changed, 44 insertions(+), 19 deletions(-) diff --git a/dist/Provider/Provider.js b/dist/Provider/Provider.js index 5a7f8255..987f3e74 100644 --- a/dist/Provider/Provider.js +++ b/dist/Provider/Provider.js @@ -637,10 +637,20 @@ class Provider { // Setting up validation info const cookieOptions = JSON.parse(JSON.stringify((0, _classPrivateFieldGet2.default)(this, _cookieOptions))); cookieOptions.maxAge = 60 * 1000; // Adding max age to state cookie = 1min - res.cookie('state' + state, iss, cookieOptions); + res.cookie('state' + state, iss, cookieOptions); // Create nonce parameter used to prevent replay attack - // Redirect to authentication endpoint - const query = await Request.ltiAdvantageLogin(params, platform, state); + const nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``); // Check if nonce is unique + + while (await this.Database.Get(false, 'nonce', { + nonce: nonce + })) nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``); //Store nonce + + + await this.Database.Insert(false, 'nonce', { + nonce + }); // Redirect to authentication endpoint + + const query = await Request.ltiAdvantageLogin(params, platform, state, nonce); provMainDebug('Login request: '); provMainDebug(query); res.redirect(url.format({ diff --git a/dist/Utils/Auth.js b/dist/Utils/Auth.js index c44e1dc2..f1216d39 100644 --- a/dist/Utils/Auth.js +++ b/dist/Utils/Auth.js @@ -242,11 +242,17 @@ class Auth { static async validateNonce(token, Database) { provAuthDebug('Validating nonce'); provAuthDebug('Nonce: ' + token.nonce); - if (await Database.Get(false, 'nonce', { + const savedNonce = await Database.Get(false, 'nonce', { nonce: token.nonce - })) throw new Error('NONCE_ALREADY_RECEIVED'); - provAuthDebug('Storing nonce'); - await Database.Insert(false, 'nonce', { + }); + + if (!savedNonce) { + provAuthDebug('Nonce have been Deleted Before, nonce not found in Database'); + throw new Error('NONCE_ALREADY_RECEIVED'); + } + + provAuthDebug('Deleting validated nonce'); + Database.Delete('nonce', { nonce: token.nonce }); return true; diff --git a/dist/Utils/Database.js b/dist/Utils/Database.js index fcf30902..d4e22c22 100644 --- a/dist/Utils/Database.js +++ b/dist/Utils/Database.js @@ -171,7 +171,7 @@ class Database { nonce: String, createdAt: { type: Date, - expires: 10, + expires: 3600, default: Date.now } }); diff --git a/dist/Utils/Request.js b/dist/Utils/Request.js index 3e0b8e45..5f69ec90 100644 --- a/dist/Utils/Request.js +++ b/dist/Utils/Request.js @@ -8,7 +8,7 @@ class Request { * @param {object} platform - Platform Object. * @param {String} state - State parameter, used to validate the response. */ - static async ltiAdvantageLogin(request, platform, state) { + static async ltiAdvantageLogin(request, platform, state, nonce) { const query = { response_type: 'id_token', response_mode: 'form_post', @@ -17,7 +17,7 @@ class Request { client_id: request.client_id || (await platform.platformClientId()), redirect_uri: request.target_link_uri, login_hint: request.login_hint, - nonce: encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``), + nonce: nonce, prompt: 'none', state }; diff --git a/src/Provider/Provider.js b/src/Provider/Provider.js index c792838a..692699e1 100644 --- a/src/Provider/Provider.js +++ b/src/Provider/Provider.js @@ -521,8 +521,15 @@ class Provider { cookieOptions.maxAge = 60 * 1000 // Adding max age to state cookie = 1min res.cookie('state' + state, iss, cookieOptions) + // Create nonce parameter used to prevent replay attack + const nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``) + // Check if nonce is unique + while (await this.Database.Get(false, 'nonce', { nonce: nonce })) nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``) + //Store nonce + await this.Database.Insert(false, 'nonce', { nonce }) + // Redirect to authentication endpoint - const query = await Request.ltiAdvantageLogin(params, platform, state) + const query = await Request.ltiAdvantageLogin(params, platform, state, nonce) provMainDebug('Login request: ') provMainDebug(query) res.redirect(url.format({ diff --git a/src/Utils/Auth.js b/src/Utils/Auth.js index f59ae3bc..097188f6 100644 --- a/src/Utils/Auth.js +++ b/src/Utils/Auth.js @@ -222,12 +222,14 @@ class Auth { * @param {Object} token - Id token you wish to validate. */ static async validateNonce (token, Database) { - provAuthDebug('Validating nonce') - provAuthDebug('Nonce: ' + token.nonce) + const savedNonce = await Database.Get(false, 'nonce', { nonce: token.nonce }) + if (!savedNonce) { + provAuthDebug('Nonce have been Deleted Before, nonce not found in Database') + throw new Error('NONCE_ALREADY_RECEIVED') + } - if (await Database.Get(false, 'nonce', { nonce: token.nonce })) throw new Error('NONCE_ALREADY_RECEIVED') - provAuthDebug('Storing nonce') - await Database.Insert(false, 'nonce', { nonce: token.nonce }) + provAuthDebug('Deleting validated nonce') + Database.Delete('nonce', { nonce: token.nonce }) return true } diff --git a/src/Utils/Database.js b/src/Utils/Database.js index 26df8318..f94455df 100644 --- a/src/Utils/Database.js +++ b/src/Utils/Database.js @@ -112,7 +112,7 @@ class Database { const nonceSchema = new Schema({ nonce: String, - createdAt: { type: Date, expires: 10, default: Date.now } + createdAt: { type: Date, expires: 3600, default: Date.now } }) nonceSchema.index({ nonce: 1 }) diff --git a/src/Utils/Request.js b/src/Utils/Request.js index b3c5c087..ceb76277 100644 --- a/src/Utils/Request.js +++ b/src/Utils/Request.js @@ -6,7 +6,7 @@ class Request { * @param {object} platform - Platform Object. * @param {String} state - State parameter, used to validate the response. */ - static async ltiAdvantageLogin (request, platform, state) { + static async ltiAdvantageLogin (request, platform, state, nonce) { const query = { response_type: 'id_token', response_mode: 'form_post', @@ -15,7 +15,7 @@ class Request { client_id: request.client_id || await platform.platformClientId(), redirect_uri: request.target_link_uri, login_hint: request.login_hint, - nonce: encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``), + nonce: nonce, prompt: 'none', state } From e6181081c049afd6b32164cee6f1bd819917829a Mon Sep 17 00:00:00 2001 From: priyachawla11 Date: Wed, 5 Apr 2023 15:30:57 +0530 Subject: [PATCH 2/2] Nonce security updates --- dist/Provider/Provider.js | 15 ++++++++------- dist/Utils/Auth.js | 2 -- src/Utils/Auth.js | 2 ++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dist/Provider/Provider.js b/dist/Provider/Provider.js index 987f3e74..3b7c9945 100644 --- a/dist/Provider/Provider.js +++ b/dist/Provider/Provider.js @@ -637,19 +637,20 @@ class Provider { // Setting up validation info const cookieOptions = JSON.parse(JSON.stringify((0, _classPrivateFieldGet2.default)(this, _cookieOptions))); cookieOptions.maxAge = 60 * 1000; // Adding max age to state cookie = 1min - res.cookie('state' + state, iss, cookieOptions); // Create nonce parameter used to prevent replay attack - - const nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``); // Check if nonce is unique + res.cookie('state' + state, iss, cookieOptions); + // Create nonce parameter used to prevent replay attack + const nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``); + // Check if nonce is unique while (await this.Database.Get(false, 'nonce', { nonce: nonce - })) nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``); //Store nonce - - + })) nonce = encodeURIComponent([...Array(25)].map(_ => (Math.random() * 36 | 0).toString(36)).join``); + //Store nonce await this.Database.Insert(false, 'nonce', { nonce - }); // Redirect to authentication endpoint + }); + // Redirect to authentication endpoint const query = await Request.ltiAdvantageLogin(params, platform, state, nonce); provMainDebug('Login request: '); provMainDebug(query); diff --git a/dist/Utils/Auth.js b/dist/Utils/Auth.js index f1216d39..28cc8464 100644 --- a/dist/Utils/Auth.js +++ b/dist/Utils/Auth.js @@ -245,12 +245,10 @@ class Auth { const savedNonce = await Database.Get(false, 'nonce', { nonce: token.nonce }); - if (!savedNonce) { provAuthDebug('Nonce have been Deleted Before, nonce not found in Database'); throw new Error('NONCE_ALREADY_RECEIVED'); } - provAuthDebug('Deleting validated nonce'); Database.Delete('nonce', { nonce: token.nonce diff --git a/src/Utils/Auth.js b/src/Utils/Auth.js index 097188f6..109f980b 100644 --- a/src/Utils/Auth.js +++ b/src/Utils/Auth.js @@ -222,6 +222,8 @@ class Auth { * @param {Object} token - Id token you wish to validate. */ static async validateNonce (token, Database) { + provAuthDebug('Validating nonce'); + provAuthDebug('Nonce: ' + token.nonce); const savedNonce = await Database.Get(false, 'nonce', { nonce: token.nonce }) if (!savedNonce) { provAuthDebug('Nonce have been Deleted Before, nonce not found in Database')