Update Fix
This commit is contained in:
32
node_modules/mysql2/lib/commands/auth_switch.js
generated
vendored
32
node_modules/mysql2/lib/commands/auth_switch.js
generated
vendored
@@ -11,12 +11,31 @@ const caching_sha2_password = require('../auth_plugins/caching_sha2_password.js'
|
||||
const mysql_native_password = require('../auth_plugins/mysql_native_password.js');
|
||||
const mysql_clear_password = require('../auth_plugins/mysql_clear_password.js');
|
||||
|
||||
const standardAuthPlugins = {
|
||||
// Use Object.create(null) to avoid prototype pollution
|
||||
// This prevents server-controlled pluginName values like "toString" or "__proto__"
|
||||
// from resolving to prototype properties
|
||||
const standardAuthPlugins = Object.assign(Object.create(null), {
|
||||
sha256_password: sha256_password({}),
|
||||
caching_sha2_password: caching_sha2_password({}),
|
||||
mysql_native_password: mysql_native_password({}),
|
||||
mysql_clear_password: mysql_clear_password({}),
|
||||
};
|
||||
});
|
||||
|
||||
// Helper function to get auth plugin (custom or standard)
|
||||
function getAuthPlugin(pluginName, connection) {
|
||||
const customPlugins = connection.config.authPlugins;
|
||||
|
||||
// Check custom plugins with hasOwnProperty for safety
|
||||
if (
|
||||
customPlugins &&
|
||||
Object.prototype.hasOwnProperty.call(customPlugins, pluginName)
|
||||
) {
|
||||
return customPlugins[pluginName];
|
||||
}
|
||||
|
||||
// Safe to access standardAuthPlugins directly since it has no prototype
|
||||
return standardAuthPlugins[pluginName];
|
||||
}
|
||||
|
||||
function warnLegacyAuthSwitch() {
|
||||
console.warn(
|
||||
@@ -35,8 +54,6 @@ function authSwitchPluginError(error, command) {
|
||||
function authSwitchRequest(packet, connection, command) {
|
||||
const { pluginName, pluginData } =
|
||||
Packets.AuthSwitchRequest.fromPacket(packet);
|
||||
let authPlugin =
|
||||
connection.config.authPlugins && connection.config.authPlugins[pluginName];
|
||||
|
||||
// legacy plugin api don't allow to override mysql_native_password
|
||||
// if pluginName is mysql_native_password it's using standard auth4.1 auth
|
||||
@@ -54,9 +71,8 @@ function authSwitchRequest(packet, connection, command) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!authPlugin) {
|
||||
authPlugin = standardAuthPlugins[pluginName];
|
||||
}
|
||||
|
||||
const authPlugin = getAuthPlugin(pluginName, connection);
|
||||
if (!authPlugin) {
|
||||
throw new Error(
|
||||
`Server requests authentication using unknown plugin ${pluginName}. See ${'TODO: add plugins doco here'} on how to configure or author authentication plugins.`
|
||||
@@ -108,4 +124,6 @@ function authSwitchRequestMoreData(packet, connection, command) {
|
||||
module.exports = {
|
||||
authSwitchRequest,
|
||||
authSwitchRequestMoreData,
|
||||
getAuthPlugin,
|
||||
standardAuthPlugins,
|
||||
};
|
||||
|
||||
144
node_modules/mysql2/lib/commands/client_handshake.js
generated
vendored
144
node_modules/mysql2/lib/commands/client_handshake.js
generated
vendored
@@ -15,6 +15,10 @@ const Packets = require('../packets/index.js');
|
||||
const ClientConstants = require('../constants/client.js');
|
||||
const CharsetToEncoding = require('../constants/charset_encodings.js');
|
||||
const auth41 = require('../auth_41.js');
|
||||
const { getAuthPlugin } = require('./auth_switch.js');
|
||||
const {
|
||||
calculateToken: calculateSha2Token,
|
||||
} = require('../auth_plugins/caching_sha2_password.js');
|
||||
|
||||
function flagNames(flags) {
|
||||
const res = [];
|
||||
@@ -67,6 +71,61 @@ class ClientHandshake extends Command {
|
||||
this.passwordSha1 = connection.config.passwordSha1;
|
||||
this.database = connection.config.database;
|
||||
this.authPluginName = this.handshake.authPluginName;
|
||||
|
||||
// Optimization: Try to use the server's preferred authentication method
|
||||
// to avoid an unnecessary auth switch roundtrip
|
||||
const serverAuthMethod = this.handshake.authPluginName;
|
||||
const isSecureConnection =
|
||||
connection.config.ssl || connection.config.socketPath;
|
||||
|
||||
// Combine auth plugin data for easier handling
|
||||
// Note: authPluginData2 can include a trailing NUL byte when PLUGIN_AUTH is set
|
||||
// We must ensure exactly 20 bytes for the scramble
|
||||
const authPluginData =
|
||||
this.handshake.authPluginData1 && this.handshake.authPluginData2
|
||||
? Buffer.concat([
|
||||
this.handshake.authPluginData1,
|
||||
this.handshake.authPluginData2,
|
||||
]).slice(0, 20)
|
||||
: Buffer.alloc(20);
|
||||
|
||||
// Check if user has custom auth plugin or legacy handler for the server-advertised method
|
||||
// If so, we must not bypass the auth switch flow with our built-in implementation
|
||||
const hasCustomAuthPlugin =
|
||||
connection.config.authPlugins &&
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
connection.config.authPlugins,
|
||||
serverAuthMethod
|
||||
);
|
||||
const hasLegacyAuthSwitchHandler =
|
||||
typeof connection.config.authSwitchHandler === 'function';
|
||||
|
||||
// Determine which auth method to use
|
||||
// Try to use server's preferred method if we can, otherwise fallback to native
|
||||
const canUseDirectAuth =
|
||||
!hasCustomAuthPlugin &&
|
||||
!hasLegacyAuthSwitchHandler &&
|
||||
this.canUseAuthMethodDirectly(serverAuthMethod, isSecureConnection);
|
||||
|
||||
const clientAuthMethod = canUseDirectAuth
|
||||
? serverAuthMethod
|
||||
: 'mysql_native_password';
|
||||
|
||||
// Calculate the auth token for the chosen method
|
||||
const authToken = this.calculateAuthToken(
|
||||
clientAuthMethod,
|
||||
this.password,
|
||||
authPluginData
|
||||
);
|
||||
|
||||
if (connection.config.debug) {
|
||||
console.log(
|
||||
'Server auth method: %s, Using auth method: %s',
|
||||
serverAuthMethod,
|
||||
clientAuthMethod
|
||||
);
|
||||
}
|
||||
|
||||
const handshakeResponse = new Packets.HandshakeResponse({
|
||||
flags: this.clientFlags,
|
||||
user: this.user,
|
||||
@@ -78,8 +137,17 @@ class ClientHandshake extends Command {
|
||||
authPluginData2: this.handshake.authPluginData2,
|
||||
compress: connection.config.compress,
|
||||
connectAttributes: connection.config.connectAttributes,
|
||||
authToken: authToken,
|
||||
authPluginName: clientAuthMethod,
|
||||
});
|
||||
connection.writePacket(handshakeResponse.toPacket());
|
||||
|
||||
// If we used a non-native auth method in the initial handshake response,
|
||||
// we need to prepare for potential AuthMoreData packets by creating
|
||||
// the appropriate auth plugin instance
|
||||
if (clientAuthMethod !== 'mysql_native_password') {
|
||||
this.initializeAuthPlugin(clientAuthMethod, authPluginData, connection);
|
||||
}
|
||||
}
|
||||
|
||||
calculateNativePasswordAuthToken(authPluginData) {
|
||||
@@ -103,6 +171,82 @@ class ClientHandshake extends Command {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
calculateSha256Token(password, scramble) {
|
||||
// Reuse the token calculation from caching_sha2_password plugin
|
||||
// to avoid code duplication and ensure consistency
|
||||
return calculateSha2Token(password, scramble);
|
||||
}
|
||||
|
||||
// Helper: Calculate auth token for a specific auth method
|
||||
calculateAuthToken(authMethod, password, authPluginData) {
|
||||
switch (authMethod) {
|
||||
case 'mysql_native_password':
|
||||
return this.calculateNativePasswordAuthToken(authPluginData);
|
||||
|
||||
case 'caching_sha2_password':
|
||||
return this.calculateSha256Token(password, authPluginData);
|
||||
|
||||
case 'sha256_password':
|
||||
case 'mysql_clear_password':
|
||||
// These methods send plaintext password over secure connections
|
||||
return password
|
||||
? Buffer.from(`${password}\0`, 'utf8')
|
||||
: Buffer.alloc(0);
|
||||
|
||||
default:
|
||||
// Unknown method - use native password as fallback
|
||||
return this.calculateNativePasswordAuthToken(authPluginData);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Determine if we can use a specific auth method directly
|
||||
canUseAuthMethodDirectly(authMethod, isSecureConnection) {
|
||||
switch (authMethod) {
|
||||
case 'mysql_native_password':
|
||||
case 'caching_sha2_password':
|
||||
// These methods work with or without SSL
|
||||
return true;
|
||||
|
||||
case 'sha256_password':
|
||||
case 'mysql_clear_password':
|
||||
// These methods require secure connection for direct use
|
||||
return isSecureConnection;
|
||||
|
||||
default:
|
||||
// Unknown methods - fallback to native password
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Initialize auth plugin for handling subsequent AuthMoreData packets
|
||||
initializeAuthPlugin(authMethod, authPluginData, connection) {
|
||||
const authPlugin = getAuthPlugin(authMethod, connection);
|
||||
if (!authPlugin) {
|
||||
return; // Plugin not found, will fallback to auth switch if needed
|
||||
}
|
||||
|
||||
// Initialize the plugin with connection and command context
|
||||
const pluginHandler = authPlugin({ connection, command: this });
|
||||
connection._authPlugin = pluginHandler;
|
||||
|
||||
// Prime the plugin by calling it with the scramble data
|
||||
// This advances the plugin's state machine (e.g., to STATE_TOKEN_SENT)
|
||||
// We don't send the result because we already included it in the handshake response
|
||||
try {
|
||||
Promise.resolve(pluginHandler(authPluginData)).catch((err) => {
|
||||
// Ignore errors during initialization since we already sent the token
|
||||
if (connection.config.debug) {
|
||||
console.log('Auth plugin initialization:', err.message);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// Ignore synchronous errors during initialization
|
||||
if (connection.config.debug) {
|
||||
console.log('Auth plugin initialization error:', err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handshakeInit(helloPacket, connection) {
|
||||
this.on('error', (e) => {
|
||||
connection._fatalError = e;
|
||||
|
||||
Reference in New Issue
Block a user