fix(auth): replace fixed-salt bcrypt with HMAC-SHA256 for API key hashing

bcrypt with a fixed salt is deterministic but defeats the purpose of
bcrypt salting. API keys are high-entropy UUIDs (128 bits), making
brute-force resistance unnecessary — HMAC-SHA256 with the app secret
is the correct primitive for deterministic, secure key hashing.

Also fixes application.author.id → application.authorId in the API key
auth path, which was silently setting req.payload.id to undefined.
This commit is contained in:
2026-04-26 20:31:06 +02:00
parent b3a31e4725
commit 6245d1eabd
2 changed files with 16 additions and 20 deletions
+10 -12
View File
@@ -1,5 +1,5 @@
var secret = require('.').secret, var secret = require('.').secret;
bcrypt = require('bcrypt'); const crypto = require('crypto');
const passport = require('passport'); const passport = require('passport');
var HeaderAPIKeyStrategy = require('passport-headerapikey').HeaderAPIKeyStrategy; var HeaderAPIKeyStrategy = require('passport-headerapikey').HeaderAPIKeyStrategy;
const DB = require('../database/mysql'); const DB = require('../database/mysql');
@@ -8,15 +8,13 @@ passport.use('headerapikey', new HeaderAPIKeyStrategy(
{ header: 'Authorization', prefix: `${process.env.AUTHORIZATION_PREFIX} ` }, { header: 'Authorization', prefix: `${process.env.AUTHORIZATION_PREFIX} ` },
false, false,
function (apikey, done) { function (apikey, done) {
const salt = secret; const encryptedKey = crypto.createHmac('sha256', secret).update(apikey).digest('hex');
Promise.resolve(bcrypt.hash(apikey, salt)).then(function (encryptedKey) { DB.Application.findOne({ where: { apikey: encryptedKey } })
DB.Application.findOne({ where: { apikey: encryptedKey } }) .then(function (application) {
.then(function (application) { if (!application) {
if (!application) { return done({ message: "Application can't be found.", status: 404 }, false, false);
return done({ message: "Application can't be found.", status: 404 }, false, false); }
} return done(null, application);
return done(null, application); }).catch(done);
});
}).catch(done);
} }
)); ));
+6 -8
View File
@@ -2,7 +2,7 @@
//var jwt = require('express-jwt'); //var jwt = require('express-jwt');
var { expressjwt: jwt } = require("express-jwt"), var { expressjwt: jwt } = require("express-jwt"),
secret = require('../config').secret, secret = require('../config').secret,
bcrypt = require('bcrypt'); crypto = require('crypto');
const { v4 } = require('uuid'), const { v4 } = require('uuid'),
passport = require('passport'), passport = require('passport'),
auths = ['Api-Key', 'Basic', 'Bearer', 'Token']; auths = ['Api-Key', 'Basic', 'Bearer', 'Token'];
@@ -38,7 +38,7 @@ const auth = {
return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info}); return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info});
} }
req.application = application; req.application = application;
req.payload = {id: application.author.id}; req.payload = { id: application.authorId };
return next(); return next();
})(req, res, next); })(req, res, next);
} else { } else {
@@ -57,13 +57,11 @@ const auth = {
} }
return null; return null;
}, },
generateAPIKey: async () => { generateAPIKey: () => {
const key = v4().replace(/-/g, ''); const key = v4().replace(/-/g, '');
const salt = secret; const maskedKey = `${key.slice(0, 4)}...${key.slice(-4)}`;
// Génération d'un salt aléatoire : const salt = await Promise.resolve(bcrypt.genSalt(10)); const encryptedKey = crypto.createHmac('sha256', secret).update(key).digest('hex');
const maskedKey = `${key.slice(0, 4)}...${key.slice(-4)}`; return { key, masked: maskedKey, encrypted: encryptedKey };
const encryptedKey = await Promise.resolve(bcrypt.hash(key, salt));
return { key: key, masked: maskedKey, encrypted: encryptedKey};
}, },
authenticateKey: async (req, res, next) => { authenticateKey: async (req, res, next) => {
passport.authenticate('headerapikey', { session: false }, function (err, application, info) { passport.authenticate('headerapikey', { session: false }, function (err, application, info) {