// This file was modified by Oracle on July 5, 2021.
// Errors generated by asynchronous authentication plugins are now being
// handled and subsequently emitted at the command level.
// Modifications copyright (c) 2021, Oracle and/or its affiliates.

'use strict';

const Packets = require('../packets/index.js');
const sha256_password = require('../auth_plugins/sha256_password');
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');

// 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(
    'WARNING! authSwitchHandler api is deprecated, please use new authPlugins api'
  );
}

function authSwitchPluginError(error, command) {
  // Authentication errors are fatal
  error.code = 'AUTH_SWITCH_PLUGIN_ERROR';
  error.fatal = true;

  command.emit('error', error);
}

function authSwitchRequest(packet, connection, command) {
  const { pluginName, pluginData } =
    Packets.AuthSwitchRequest.fromPacket(packet);

  // legacy plugin api don't allow to override mysql_native_password
  // if pluginName is mysql_native_password it's using standard auth4.1 auth
  if (
    connection.config.authSwitchHandler &&
    pluginName !== 'mysql_native_password'
  ) {
    const legacySwitchHandler = connection.config.authSwitchHandler;
    warnLegacyAuthSwitch();
    legacySwitchHandler({ pluginName, pluginData }, (err, data) => {
      if (err) {
        return authSwitchPluginError(err, command);
      }
      connection.writePacket(new Packets.AuthSwitchResponse(data).toPacket());
    });
    return;
  }

  if (pluginName === 'mysql_clear_password') {
    const hasCustomPlugin =
      connection.config.authPlugins &&
      Object.prototype.hasOwnProperty.call(
        connection.config.authPlugins,
        'mysql_clear_password'
      );
    if (!hasCustomPlugin && !connection.config.enableCleartextPlugin) {
      const err = new Error(
        'Server requested authentication using mysql_clear_password, ' +
          'which sends the password in plaintext over the network and is ' +
          'disabled by default. To enable it, set the `enableCleartextPlugin` ' +
          'option to `true` in your connection configuration, or provide a ' +
          'custom `mysql_clear_password` auth plugin via the `authPlugins` ' +
          'option. Only use this over a secure connection (TLS/SSL).'
      );
      err.code = 'MYSQL_CLEAR_PASSWORD_NOT_ENABLED';
      err.fatal = true;
      throw err;
    }
  }

  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.`
    );
  }
  connection._authPlugin = authPlugin({ connection, command });
  Promise.resolve(connection._authPlugin(pluginData))
    .then((data) => {
      if (data) {
        connection.writePacket(new Packets.AuthSwitchResponse(data).toPacket());
      }
    })
    .catch((err) => {
      authSwitchPluginError(err, command);
    });
}

function authSwitchRequestMoreData(packet, connection, command) {
  const { data } = Packets.AuthSwitchRequestMoreData.fromPacket(packet);

  if (connection.config.authSwitchHandler) {
    const legacySwitchHandler = connection.config.authSwitchHandler;
    warnLegacyAuthSwitch();
    legacySwitchHandler({ pluginData: data }, (err, data) => {
      if (err) {
        return authSwitchPluginError(err, command);
      }
      connection.writePacket(new Packets.AuthSwitchResponse(data).toPacket());
    });
    return;
  }

  if (!connection._authPlugin) {
    throw new Error(
      'AuthPluginMoreData received but no auth plugin instance found'
    );
  }
  Promise.resolve(connection._authPlugin(data))
    .then((data) => {
      if (data) {
        connection.writePacket(new Packets.AuthSwitchResponse(data).toPacket());
      }
    })
    .catch((err) => {
      authSwitchPluginError(err, command);
    });
}

module.exports = {
  authSwitchRequest,
  authSwitchRequestMoreData,
  getAuthPlugin,
  standardAuthPlugins,
};
