Mongoose: Meilleur moyen de valider un ObjectId

Créé le 12 mars 2014  ·  29Commentaires  ·  Source: Automattic/mongoose

J'insère des objectIds en tant que références et je veux m'assurer qu'ils sont des objectids valides (en ce moment, je reçois un plantage lorsqu'ils ne sont pas des identifiants valides).

Je vérifie s'ils font référence à des objets valides en faisant simplement un appel find().

Comment vérifier si les chaînes sont au bon format ? J'ai vu des regex qui vérifient si la chaîne est une chaîne de 24 caractères et des trucs comme ça - ça ressemble à un hack. Existe-t-il un validateur interne sur l'ObjectId ? Je n'ai pas compris comment l'utiliser. Merci beaucoup!

Commentaire le plus utile

Cela fonctionne pour moi:

var mangouste = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [Fonction : est valide]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// vrai
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// faux

Tous les 29 commentaires

J'ai le même problème. J'ai essayé mongoose.Schema.ObjectId.isValid() ainsi que mongoose.Types.ObjectId.isValid() mais aucune de ces propriétés n'a de méthode isValid. Comment avez-vous fini par résoudre ce problème ? Je vois aussi que mongodb en a un et il y a aussi regex comme autre option. Je préférerais ne pas utiliser regex ni avoir à exiger ('mongodb')

Cela fonctionne pour moi:

var mangouste = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [Fonction : est valide]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// vrai
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// faux

Incidemment, la méthode dans mongodb est la méthode bson lib, qui vérifie simplement la chaîne hexadécimale non nulle, 12 ou 24 caractères - c'est une expression régulière comme celle-ci :

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

donc ça ne peut pas être trop hacky s'il est utilisé là-dedans

sValid() renvoie toujours True si la chaîne contient 12 lettres

console.log(mongoose.Types.ObjectId.isValid("zzzzzzzzzzzz")); // vrai

Parce que "zzzzzzzzzzzz" est techniquement un ObjectId valide - la seule caractéristique déterminante d'un ID d'objet est sa longueur de 12 octets. Voir mongodb/js-bson#112. Une chaîne JS de longueur 12 a 12 octets, bizarreries modulo unicode. Si vous souhaitez rechercher une chaîne hexadécimale de longueur 24, vérifiez simplement que la chaîne correspond /^[a-fA-F0-9]{24}$/

"zzzzzzzzzzzz" n'est pas un ObjectId valide. Par exemple Mongo shell listiong (mongodb version - 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")

Parce que le constructeur ObjectId du shell mongodb est écrit de telle sorte qu'il n'accepte que les chaînes hexadécimales. C'est une restriction dans le shell mongo pour plus de commodité, pas avec l'ObjectId de type BSON. Certes, il s'agit d'un cas quelque peu contre-intuitif étant donné que les chaînes hexadécimales sont la façon dont les ObjectId sont généralement représentés, mais si vous ne l'aimez pas, utilisez simplement la regex /^[a-fA-F0-9]{24}$/ :)

Pourquoi obtenons-nous false lorsque nous essayons de faire isValid sur un ObjectId lui-même et non sur une chaîne ? Cela ne devrait-il pas renvoyer true puisque ObjectId est un ObjectId valide ? Cela n'a pas de sens - peut-être appeler .toString() s'il s'agit d'un objet passé à isValid ?

Les commentaires de @niftylettuce sont les bienvenus au #3365. Pour le moment, nous nous en remettons à la fonction ObjectId.isValid() du package bson , qui ne correspond pas exactement à la façon dont les gens pensent des ObjectIds dans mangouste. J'ouvrirai un PR pour retourner true si on vous donne un ObjectId, cela semble parfaitement raisonnable.

Revenons à un vieux problème ici ... La solution @atcwells mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943') fonctionne assez bien pour moi, mais il semble toujours un peu hacky d'avoir à ce que mon contrôleur vérifie si un ID d'objet est valide - quand c'est sûrement un cas d'utilisation assez courant de vouloir pouvoir envoyer un identifiant mal formé au serveur et ne pas le faire planter.

Idéalement, il renverrait simplement quelque chose dans le err du rappel afin que nous puissions le gérer correctement et envoyer le statut HTTP correct à l'aide de notre contrôleur.

Existe-t-il un cas d'utilisation où cette fonctionnalité ne serait pas utile dans le noyau ? Sinon, nous pouvons peut-être créer un plugin. J'ai effectué une recherche rapide et rien ne semble faire le travail - https://github.com/CampbellSoftwareSolutions/mongoose-id-validator sert à valider que l'ID existe réellement, ce qui n'est pas le cas nous voulons faire ici - nous voulons simplement nous assurer que nous ne générons pas d'erreur non détectée.

À l'heure actuelle, dans mon contrôleur Express, chaque fois que je lance une requête contenant un ObjectId, par exemple GET to https://myproject/organisations/ {id}, je dois faire quelque chose comme :

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

... avant de continuer à faire Organisation.findOne();

Semble assez passe-partout. Je suis heureux d'écrire un plugin ou quelque chose si quelqu'un peut me diriger dans la bonne direction par où commencer. Cela ne ressemble pas à un plugin car ce n'est pas vraiment une chose de schéma ...

@shankiesan vous n'avez pas besoin de le faire, la mangouste rejettera la promesse de requête si l'identifiant n'est pas valide.

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));

Sortir:

$ 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
$ 

Ah quelle marionnette j'ai été ! Bien sûr, gérer la promesse rejetée... arg, mon cerveau. Merci @vkarpov15

Les rappels fonctionnent aussi si les promesses sont trop casse-tête MyModel.findOne({ _id: 'invalid' }).exec(error => console.error('error', error)); :)

N'UTILISEZ PAS ObjectId.isValid() si ce n'est pas déjà clair à partir des 12 octets ci-dessus dit par Valeri. Je viens de me faire bien brûler par celui-ci :
ObjectId.isValid('The Flagship') === true

@atcwells si vous pouviez mettre à jour votre commentaire très apprécié pour inclure ce morceau, je pense que d'autres personnes pourraient l'apprécier puisque je le faisais initialement sur la base de ce que vous avez dit : ObjectId.isValid('The Flagship') === true

La partie amusante est :
l'instruction suivante renvoie true
mangouste.Types.ObjectId.isValid("Afrique du Sud")

Ce que je fais (pour l'instant) est de vérifier le type d'erreur dans la capture de la promesse. Si c'est 'ObjectId', je reçois un 404. Je renvoie un 404 car pour le consommateur du service API/Web, la ressource n'a pas été trouvée ou n'existe pas.

Voir exemple :

  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);
      }
    });

METTRE À JOUR:
Au lieu d'ajouter ceci à chaque gestionnaire d'itinéraire dans le contrôleur. J'ajoute au global catch all handler.

Voir exemple :

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');
});

Est-il possible de valider dans le schéma ? N'est-ce pas une meilleure pratique de répéter cela si, mais je veux enregistrer l'événement d'erreur de diffusion.

Pourquoi la mangouste ne peut-elle pas implémenter la regex /^[a-fA-F0-9]{24}$/ pour isValid alors qu'elle ne traite que de MongoDB. C'est tellement déroutant. Nous venons de passer une heure à déboguer le problème et il s'est avéré être une chose tellement stupide que vous ne le remarquerez jamais

Je recommanderais d'utiliser ce package npm https://www.npmjs.com/package/valid-objectid
Cela fonctionne parfaitement

J'ai créé une solution de contournement pour cela:

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 Je pense que cela doit être essayé/attrapé, si la valeur est "123", cela générera une erreur.

Au cas où cela aiderait quelqu'un, j'ai fait une adaptation de l'approche ObjectId.isValid() ci-dessus. Parce que dans mon cas d'utilisation, je veux pouvoir obtenir une ressource soit par son ID, soit par son url-slug, par exemple :

GET /users/59f5e7257a92d900168ce49a ... ou ...
GET /users/andrew-shankie

... J'ai trouvé que cela fonctionne bien dans mon contrôleur :

  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) => { ... }

Dans ce cas, une chaîne de 12 octets est toujours un ID d'objet valide, sa recherche renvoie simplement un tableau de longueur nulle, plutôt que de générer une erreur. Et parce que j'utilise une requête $or , elle recherche ensuite par le slug d'URL (mon autre option possible).

Ce n'est peut-être pas la solution la plus élégante, mais cela fonctionne pour moi.

@victorbadila oui, exactement. J'ai juste donné un indice. Edité mon commentaire, place en fait juste le code que j'utilise réellement.

validator.js a une méthode intégrée isMongoId

Chaque fois que j'utilise mangouste, je l'étends toujours avec quelques méthodes d'assistance statique :

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);
};

Vous pouvez l'exécuter dans le cadre de vos scripts d'initialisation de base de données, de sorte que les méthodes soient toujours disponibles dans votre application.

Si ObjectId.isValid(id) est vrai, nous pouvons juger de la valeur et de l'id de (new ObjectId(id).toString()).

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;
const validateObjectId = (id) => ObjectId.isValid(id) && (new ObjectId(id)).toString() === id;
Cette page vous a été utile?
0 / 5 - 0 notes