Res.on("searchEntry") из Ldap.js не возвращает результат в рабочем режиме Next.js

Нужна помощь и совет по моей проблеме. В своем приложении Next.js (14.1) я делал авторизацию с помощью next-auth (5.0.0) и ldap.js (3.0.7). Процесс происходит следующим образом:

  • Пользователь вводит учетные данные.
  • Функция действия сервера вызывает SignIn из next-auth.
  • Внутри SingIn=>CredentialsProvider при авторизации обратного вызова я вызываю свой создал функцию входа в систему.
  • Функция входа вызывает другую функцию с именем «call», которая использует ldap.js. to: сначала привязать, а затем выполнить поиск пользователя и его данных в AD. В return Мне нужно получить все данные пользователя.

Важно: в режиме разработки все работает нормально. Но когда сборка и запуск npm run start, «привязка» проходит нормально, а затем во время процесса client.search останавливается. Последний файл console.info я получаю от res.on("searchRequest") и ничего после этого.

Кто-нибудь знает, почему? Почему это работает в режиме разработки, но не в режиме prod?

Действия сервера:

export const logInAction = async (prevState, formData) => {
  const { username, password } = Object.fromEntries(formData);
  if (username === "" || password === "") {
    return { error: "Wrong" };
  }
  try {
    await signIn("credentials", { username, password });
  } catch (error) {
    if (error.type?.includes("CredentialsSignin")) {
      return { error: "Wrong" };
    }

    throw error;
  }
};

логика следующей аутентификации:

const login = async (credentials) => {
  try {
    const userInfo = await call(credentials.username, credentials.password);
    console.info(userInfo);

    return user;
  } catch (error) {
    console.info("error in login function", error);
    throw new Error("Failed to login");
  }
};

export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
} = NextAuth({
  ...authConfig,
  providers: [
    CredentialsProvider({
      async authorize(credentials) {
        try {
          const user = await login(credentials);

          return user;
        } catch (error) {
          console.info("error in autorize:", error);
          return null;
        }
      },
    }),
  ],
  callbacks: {
    async signIn({ user, account, profile }) {
      //doing some logic
    },
    ...authConfig.callbacks,
  },
})

логика ldpajs:

const ldap = require("ldapjs");

const createClient = () =>
  ldap.createClient({
    url: process.env.URL,
    tlsOptions: { rejectUnauthorized: false }, 
    reconnect: true, 
  });

let client;

const ADLoginUserCheck = async (user, password) => {
  return new Promise((resolve, reject) => {
    client = createClient();
    client.bind(`${user}${process.env.URL}`, password, (err, auth) => {
      if (err) {
        console.info("Reject error...");
        reject(err);
      } else {
        console.info("all ok...");
        resolve(true);
      }
    });
  });
};

const getUserDetails = async (user) => {
  const opts = {
    filter: `(sAMAccountName=${user})`,
    scope: "sub",
     attributes: [],
  };
  return new Promise((resolve, reject) => {
    let userData;
    client.search(`${process.env.URL}`, opts, (err, res) => {
      if (err) {
        console.info("Error in search:", err);
        reject(err);
      } else {
        res.on("searchRequest", (searchRequest) => {
          console.info("65", searchRequest.messageId);
        });
        res.on("searchEntry", (entry) => {
          console.info("68", entry.pojo.attributes);
          userData = entry.pojo.attributes;
        });
        res.on("searchReference", (referral) => {
          console.info("72", referral);
        });
        res.on("error", (err) => {
          console.info("75", err)
        });
        res.on("end", async (result) => {
          //   console.info("userDAta", userData);
          resolve({ userData });
          client.destroy();
        });
      }
    });
  });
};

const top = async (user, password) => {
  const isCorrenctCredentials = await ADLoginUserCheck(user, password);
  let userDetails;
  if (isCorrenctCredentials) {
    userDetails = await getUserDetails(user);
  }
  return userDetails;
};

const call = async (user, password) => {
  top(user, password).then((res) => {
    return res;
  });
};
module.exports = call

Во время сборки я обнаружил одну ошибку: «Обещание» не определено. Может быть, в этом причина? Должен ли я как-то заменить ту часть, где я создаю новое обещание?

Pavel_Bu 08.05.2024 16:04
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
2
1
139
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуйте решить проблему, добавив этот параметр в следующую конфигурацию (у меня тоже работает):

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverComponentsExternalPackages: ["ldapjs"],
  },
};

module.exports = nextConfig;

На случай, если это не поможет, я также вставляю некоторые мысли другого разработчика (https://github.com/ldapjs/node-ldapjs/issues/967) Функция поиска работала неожиданно, не возвращая никаких результатов из события searchEntry.

После некоторого устранения неполадок я начал подозревать, что проблема может быть связана с процессом производственной сборки. Кажется возможным, что имена классов изменяются во время минимизации, что потенциально влияет на способность библиотеки ldapjs правильно выполнять проверки.

В своих журналах я заметил изменение имени класса в конечном событии с SearchResultDone в разработке на уменьшенную версию, например s в рабочей версии, что указывает на переименование классов в процессе сборки.

В частности, в client.js проблема, похоже, связана с тем, как генерируются и используются имена событий:

if (msg instanceof SearchEntry || msg instanceof SearchReference) {
  let event = msg.constructor.name
  // Generate the event name for the event emitter, i.e., "searchEntry"
  // and "searchReference".
  event = (event[0].toLowerCase() + event.slice(1)).replaceAll('Result', '')
  return sendResult(event, msg)
} else {
  tracker.remove(message.messageId)
  // Potentially mark client as idle
  self._updateIdle()
  if (msg instanceof LDAPResult) {
    if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
      return sendResult('error', errors.getError(msg))
    }
    return sendResult('end', msg)
  } else if (msg instanceof Error) {
    return sendResult('error', msg)
  } else {
    return sendResult('error', new errors.ProtocolError(msg.type))
  }
}

Такое поведение предполагает, что процесс создания имен событий на основе имен классов (которые изменяются в рабочей сборке) может привести к отправке событий с неожиданными именами. В качестве временного решения я изменил метод Client.prototype._sendSocket напрямую, чтобы гарантировать, что событие searchEntry генерируется без использования имени класса.

const ldap = require("ldapjs");

ldap.Client.prototype._sendSocket = function (
  message,
  expect,
  emitter,
  callback
) {
  const conn = this._socket;
  const tracker = this._tracker;
  const log = this.log;
  const self = this;
  let timer = false;
  let sentEmitter = false;

  function sendResult(event, obj) {
    if (event === "error") {
      self.emit("resultError", obj);
    }
    if (emitter) {
      if (event === "error") {
        // Error will go unhandled if emitter hasn't been sent via callback.
        // Execute callback with the error instead.
        if (!sentEmitter) {
          return callback(obj);
        }
      }
      return emitter.emit(event, obj);
    }

    if (event === "error") {
      return callback(obj);
    }

    return callback(null, obj);
  }

  function messageCallback(msg) {
    if (timer) {
      clearTimeout(timer);
    }

    log.trace({ msg: msg ? msg.pojo : null }, "response received");

    if (expect === "abandon") {
      return sendResult("end", null);
    }

    if (msg instanceof ldap.SearchEntry) {
      return sendResult("searchEntry", msg);
    } else if (msg instanceof ldap.SearchReference) {
      return sendResult("searchReference", msg);
    } else {
      tracker.remove(message.messageId);
      // Potentially mark client as idle
      self._updateIdle();

      if (msg instanceof ldap.LDAPResult) {
        if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
          return sendResult("error", ldap.getError(msg));
        }
        return sendResult("end", msg);
      } else if (msg instanceof Error) {
        return sendResult("error", msg);
      } else {
        return sendResult("error", new ldap.ProtocolError(msg.type));
      }
    }
  }

  function onRequestTimeout() {
    self.emit("timeout", message);
    const { callback: cb } = tracker.fetch(message.messageId);
    if (cb) {
      // FIXME: the timed-out request should be abandoned
      cb(new errors.TimeoutError("request timeout (client interrupt)"));
    }
  }

  function writeCallback() {
    if (expect === "abandon") {
      // Mark the messageId specified as abandoned
      tracker.abandon(message.abandonId);
      // No need to track the abandon request itself
      tracker.remove(message.id);
      return callback(null);
    } else if (expect === "unbind") {
      conn.unbindMessageID = message.id;
      // Mark client as disconnected once unbind clears the socket
      self.connected = false;
      // Some servers will RST the connection after receiving an unbind.
      // Socket errors are blackholed since the connection is being closed.
      conn.removeAllListeners("error");
      conn.on("error", function () {});
      conn.end();
    } else if (emitter) {
      sentEmitter = true;
      callback(null, emitter);
      emitter.emit("searchRequest", message);
      return;
    }
    return false;
  }

  // Start actually doing something...
  tracker.track(message, messageCallback);
  // Mark client as active
  this._updateIdle(true);

  if (self.timeout) {
    log.trace("Setting timeout to %d", self.timeout);
    timer = setTimeout(onRequestTimeout, self.timeout);
  }

  log.trace("sending request %j", message.pojo);

  try {
    const messageBer = message.toBer();
    return conn.write(messageBer.buffer, writeCallback);
  } catch (e) {
    if (timer) {
      clearTimeout(timer);
    }

    log.trace({ err: e }, "Error writing message to socket");
    return callback(e);
  }
};

Другие вопросы по теме