Ctrl K
ComunidadGuías…

config/reset-password.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
const path = require('path');
const bcrypt = require('bcryptjs');
const readline = require('readline');
const mongoose = require('mongoose');
const { createPasswordResetUpdate, restampRetirementCutoff } = require('@librechat/api');
const { createModels, createMethods } = require('@librechat/data-schemas');
const { User, Passkey, Session } = createModels(mongoose);
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
const getLogStores = require('~/cache/getLogStores');
const { askSilentQuestion } = require('./helpers');
const connect = require('./connect');

const methods = createMethods(mongoose, { getCache: getLogStores });

const question = (query) => {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  return new Promise((resolve) =>
    rl.question(query, (answer) => {
      rl.close();
      resolve(answer);
    }),
  );
};

const resetPassword = async () => {
  try {
    await connect();

    const email = await question('Enter user email: ');
    const user = await User.findOne({ email });

    if (!user) {
      console.error('User not found!');
      process.exit(1);
    }

    let validPassword = false;
    let newPassword;

    while (!validPassword) {
      newPassword = await askSilentQuestion('Enter new password: ');
      if (newPassword.length < 8) {
        console.log('Password must be at least 8 characters! Please try again.');
        continue;
      }

      const confirmPassword = await askSilentQuestion('Confirm new password: ');
      if (newPassword !== confirmPassword) {
        console.log('Passwords do not match! Please try again.');
        continue;
      }

      validPassword = true;
    }

    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(newPassword, salt);

    /**
     * Routes the write through the data-schemas method so the auth user doc
     * cache entry for this user is invalidated; a raw updateOne would leave a
     * cached document that keeps pre-reset access tokens verifying for the
     * cache TTL.
     */
    const userId = user._id.toString();
    /** Stamps credentialsChangedAt and drops any staged enrollment, as the web reset does. */
    const update = createPasswordResetUpdate(hashedPassword);
    const updated = await methods.updateUser(userId, update, {}, { preserveExpiresAt: true });

    if (!updated) {
      console.error('User not found during update!');
      process.exit(1);
    }

    /**
     * A passkey signs in on its own, so leaving one in place would keep an attacker
     * logged in after an administrator believes the account has been recovered.
     */
    let deletedCount;
    let failure;
    /**
     * The password is already changed, so every step runs and the first failure exits non-zero.
     * The cache settles last: an eviction before the re-stamp could leave a document carrying the
     * earlier cutoff, which keeps accepting a credential minted during the password write.
     */
    for (const step of [
      async () => {
        ({ deletedCount } = await Passkey.deleteMany({ user: user._id }));
      },
      /** A refresh session outlives the stamp: refreshing mints a token issued after it. */
      () => Session.deleteMany({ user: user._id }),
      () =>
        restampRetirementCutoff(
          'credentialsChangedAt',
          userId,
          update.credentialsChangedAt,
          (cutoff, expectedState) =>
            methods.updateUser(userId, cutoff, expectedState, { preserveExpiresAt: true }),
        ),
      () => methods.awaitAuthUserDocEviction(userId),
    ]) {
      try {
        await step();
      } catch (err) {
        console.error('Password reset step failed:', err);
        failure ??= err;
      }
    }
    if (failure) {
      throw failure;
    }

    console.log('Password successfully reset!');
    if (deletedCount > 0) {
      console.log(`Removed ${deletedCount} passkey(s); the user must enroll again.`);
    }
    process.exit(0);
  } catch (err) {
    console.error('Error resetting password:', err);
    process.exit(1);
  }
};

resetPassword();