Mongoose: Лучший способ проверить ObjectId

Созданный на 12 мар. 2014  ·  29Комментарии  ·  Источник: Automattic/mongoose

Я вставляю некоторые идентификаторы объектов в качестве ссылок и хочу убедиться, что они являются действительными идентификаторами объектов (сейчас у меня происходит сбой, когда они не являются действительными идентификаторами).

Я проверяю, ссылаются ли они на действительные объекты, просто выполняя вызов find().

Как мне проверить правильность формата строк? Я видел некоторые регулярные выражения, которые проверяют, является ли строка строкой из 24 символов и тому подобное - похоже на взлом. Есть ли внутренний валидатор для ObjectId? Я не мог понять, как его использовать. Большое спасибо!

Самый полезный комментарий

Это работает для меня:

var mongoose = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [Функция: допустима]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// истинный
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// ложный

Все 29 Комментарий

У меня такая же проблема. Я пробовал mongoose.Schema.ObjectId.isValid(), а также mongoose.Types.ObjectId.isValid(), но ни одно из этих свойств не имеет метода isValid. Как вы в итоге решили эту проблему? Я также вижу, что у mongodb есть один, а в качестве другого варианта есть регулярное выражение. Я бы предпочел не использовать регулярное выражение и не требовать ('mongodb')

Это работает для меня:

var mongoose = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [Функция: допустима]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// истинный
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// ложный

Между прочим, метод в mongodb — это метод bson lib, который просто проверяет наличие не нулевой, 12 или 24-символьной шестнадцатеричной строки — это регулярное выражение, подобное этому:

var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");

так что это не может быть слишком хакерским, если оно используется там

sValid() всегда возвращает True, если строка содержит 12 букв.

console.log(mongoose.Types.ObjectId.isValid("zzzzzzzzzzzz")); // истинный

Поскольку «zzzzzzzzzzzz» технически является допустимым ObjectId, единственной определяющей особенностью идентификатора объекта является то, что его длина составляет 12 байт. См. mongodb/js-bson#112. Строка JS длиной 12 имеет 12 байтов по модулю странностей Юникода. Если вы хотите проверить шестнадцатеричную строку длиной 24, просто проверьте, соответствует ли строка /^[a-fA-F0-9]{24}$/

"zzzzzzzzzzzz" не является допустимым идентификатором объекта. Например, список оболочек Mongo (версия mongodb — 3.0.2):

> ObjectId('zzzzzzzzzzzz')
2015-04-29T18:05:20.705+0300 E QUERY    Error: invalid object id: length
    at (shell):1:1
> ObjectId('zzzzzzzzzzzzzzzzzzzzzzzz')
2015-04-29T18:06:09.773+0300 E QUERY    Error: invalid object id: not hex
    at (shell):1:1
> ObjectId('ffffffffffff')
2015-04-29T18:09:17.303+0300 E QUERY    Error: invalid object id: length
    at (shell):1:1
> ObjectId('ffffffffffffffffffffffff')
ObjectId("ffffffffffffffffffffffff")

Поскольку конструктор ObjectId оболочки mongodb написан таким образом, что он принимает только шестнадцатеричные строки. Это ограничение в оболочке mongo для удобства, а не с ObjectId типа BSON. По общему признанию, это несколько нелогичный случай, учитывая, что шестнадцатеричные строки - это то, как обычно представляются ObjectId, но если вам это не нравится, просто используйте регулярное выражение /^[a-fA-F0-9]{24}$/ :)

Почему мы получаем false, когда пытаемся выполнить isValid для самого ObjectId, а не для строки? Разве это не должно возвращать true, поскольку ObjectId является допустимым ObjectId? Это не имеет смысла — может быть, вызвать .toString() , если это объект, который передается в isValid ?

Комментарии @niftylettuce приветствуются в #3365. Прямо сейчас мы просто полагаемся на функцию ObjectId.isValid() пакета bson , которая не совсем соответствует тому, как люди думают об ObjectId в mongoose. Я открою PR для возврата true, если вам дан ObjectId, хотя это кажется вполне разумным.

Возвращаясь к немного старой проблеме здесь ... Решение @atcwells mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943') работает достаточно хорошо для меня, но все еще кажется немного хакерским, чтобы мой контроллер проверял, действителен ли идентификатор объекта - когда конечно, это довольно распространенный вариант использования, чтобы иметь возможность отправлять неправильно сформированный идентификатор на сервер и не допускать его сбоя.

В идеале он просто возвращает что-то в err в обратном вызове, чтобы мы могли правильно обработать его и отправить правильный HTTP-статус с помощью нашего контроллера.

Есть ли вариант использования, когда это не было бы полезной функциональностью в ядре? Если нет, возможно, мы можем сделать плагин. У меня был быстрый поиск, и, похоже, нет ничего, что выполняло бы эту работу — https://github.com/CampbellSoftwareSolutions/mongoose-id-validator предназначено для проверки того, что идентификатор действительно существует, а это не то, что мы хотим сделать здесь - мы просто хотим убедиться, что мы не генерируем неперехваченную ошибку.

Прямо сейчас, в моем контроллере Express, каждый раз, когда я отправляю запрос, в котором есть ObjectId, например, GET для https://myproject/organisations/ {id}, я должен сделать что-то вроде:

if( !mongoose.Types.ObjectId.isValid(id) ){
    return res.sendStatus(400); // They didn't send an object ID
  }

... прежде чем продолжить делать Organisation.findOne();

Кажется довольно шаблонным. Я буду рад написать плагин или что-то в этом роде, если кто-то укажет мне правильное направление, с чего начать. Не похоже на плагин, так как на самом деле это не схема...

@shankiesan вам не нужно этого делать, мангуст отклонит обещание запроса, если идентификатор недействителен.

var assert = require('assert');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');
mongoose.set('debug', true);

var MyModel = mongoose.model('test', new Schema({ name: String }));

MyModel.findOne({ _id: 'invalid' }).exec().catch(error => console.error('error', error));

Выход:

$ node gh-1959.js 
error { CastError: Cast to ObjectId failed for value "invalid" at path "_id"
    at MongooseError.CastError (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/error/cast.js:19:11)
    at ObjectId.cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/schema/objectid.js:147:13)
    at ObjectId.castForQuery (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/schema/objectid.js:187:15)
    at cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/cast.js:174:32)
    at Query.cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2563:10)
    at Query.findOne (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:1239:10)
    at /home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2163:21
    at new Promise.ES6 (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/promise.js:45:3)
    at Query.exec (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2156:10)
    at Object.<anonymous> (/home/val/Workspace/10gen/troubleshoot-mongoose/gh-1959.js:10:37)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
  message: 'Cast to ObjectId failed for value "invalid" at path "_id"',
  name: 'CastError',
  kind: 'ObjectId',
  value: 'invalid',
  path: '_id',
  reason: undefined }
^C
$ 

Ах, какой я была куклой! Конечно, смириться с отказом от обещания... Арг, мой мозг. Спасибо @vkarpov15

Обратные вызовы тоже работают, если промисы — это слишком большая головная боль MyModel.findOne({ _id: 'invalid' }).exec(error => console.error('error', error)); :)

НЕ ИСПОЛЬЗУЙТЕ ObjectId.isValid() , если это еще не ясно из приведенных выше 12 байтов, которые сказал Валери. Просто хорошо обожглась вот этим:
ObjectId.isValid('The Flagship') === true

@atcwells , если бы вы могли обновить свой высоко оцененный комментарий, включив в него эту часть, я думаю, что другие люди могли бы это оценить, поскольку я изначально делал это на основе того, что вы сказали: ObjectId.isValid('The Flagship') === true

Самое смешное:
следующее утверждение возвращает true
mongoose.Types.ObjectId.isValid("Южная Африка")

То, с чем я работаю (на данный момент), — это проверка типа ошибки в улове обещания. Если это «ObjectId», мне возвращается 404. Я возвращаю 404, потому что для потребителя API/веб-сервиса ресурс не найден или не существует.

См. пример:

  Widget.findByIdAndRemove(resourceId)
    .then((result) => {
      if (!result) {
        let error = new Error('Resource not found');
        error.status = 404;
        next(error);
      } else {
        res.redirect(303, '/');
      }
    })
    .catch((error) => {
      if (error.kind === 'ObjectId') {
        let error = new Error('Resource not found');
        error.status = 404;

        next(error);
      } else {
        next(error);
      }
    });

ОБНОВИТЬ:
Вместо того, чтобы добавлять это к каждому обработчику маршрута в контроллере. Я добавляю в глобальный обработчик catch all.

См. пример:

app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  if (err.kind === 'ObjectId') {
    err.status = 404;
  }

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

Можно ли проверить в схеме? Не рекомендуется повторять это, если, но я хочу регистрировать событие ошибки приведения.

Почему мангуст не может реализовать регулярное выражение /^[a-fA-F0-9]{24}$/ для isValid, когда он имеет дело только с MongoDB. Это так сбивает с толку. Мы просто потратили час на отладку проблемы, и это оказалось настолько глупо, что вы никогда не заметите

Я бы рекомендовал использовать этот пакет npm https://www.npmjs.com/package/valid-objectid
Он отлично работает

Я создал обходной путь для этого:

function isObjectId(value) {
  try {
      const { ObjectId } = mongoose.Types;
      const asString = value.toString(); // value is either ObjectId or string or anything
      const asObjectId = new ObjectId(asString);
      const asStringifiedObjectId = asObjectId.toString();
      return asString === asStringifiedObjectId;
    } catch (error) {
      return false;
    }
}

@mstmustisnt Я думаю, что это нужно попробовать / поймать, если значение равно «123», это вызовет ошибку.

На случай, если это кому-то поможет, я адаптировал описанный выше подход ObjectId.isValid() . Потому что в моем случае использования я хочу иметь возможность получить ресурс либо по его идентификатору, либо по его URL-адресу, например:

GET /users/59f5e7257a92d900168ce49a ... или ...
GET /users/andrew-shankie

... Я обнаружил, что это хорошо работает в моем контроллере:

  const { id } = req.params;

  const query = {
    $or: [{ slug: id }],
  };

  if (mongoose.Types.ObjectId.isValid(id)) query.$or.push({ _id: id });

  User.findOne(query)
    .exec((err, user) => { ... }

В этом случае 12-байтовая строка по-прежнему является допустимым идентификатором объекта, и поиск ее просто возвращает массив нулевой длины, а не выдает ошибку. И поскольку я использую запрос $or , он выполняет поиск по URL-адресу (другой возможный вариант).

Возможно, это не самое элегантное решение, но оно работает для меня.

@victorbadila да, точно. Я просто намекнул. Отредактировал мой комментарий, на самом деле просто поместил код, который я действительно использую.

В validator.js есть встроенный метод isMongoId

Всякий раз, когда я использую mongoose, я всегда расширяю его несколькими статическими вспомогательными методами:

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;

//Helper to check if an ID is an object ID
mongoose.isObjectId = function(id) {
  return (id instanceof ObjectId);
};

//Helper to validate a string as object ID
mongoose.isValidObjectId = function(str) {
  if (typeof str !== 'string') {
    return false;
  }
  return str.match(/^[a-f\d]{24}$/i);
};

Вы можете запустить это как часть сценариев инициализации вашей базы данных, чтобы методы всегда были доступны в вашем приложении.

Если ObjectId.isValid(id) истинно, мы можем судить о значении и идентификаторе (new ObjectId(id).toString()) .

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;
const validateObjectId = (id) => ObjectId.isValid(id) && (new ObjectId(id)).toString() === id;
Была ли эта страница полезной?
0 / 5 - 0 рейтинги