MasterKale / SimpleWebAuthn

WebAuthn, Simplified. A collection of TypeScript-first libraries for simpler WebAuthn integration. Supports modern browsers, Node, Deno, and more.
https://simplewebauthn.dev
MIT License
1.49k stars 127 forks source link

Error: No date at Object.decodePartialCBOR (C:\Users\siste\Desktop\web-authentication-main\node_modules\@levischuck\tiny-cbor\script\cbor\cbor.js:355:15) #589

Closed JimmyDevPasto closed 1 month ago

JimmyDevPasto commented 1 month ago

Describe the issue

Best regards, when doing the same code managing a database with moongose ​​​​it gives an error in the login-verify function when saving the access key in the user and when returning it the following error appears

Error verifying login: Error: No date
    at Object.decodePartialCBOR (C:\Users\siste\Desktop\web-authentication-main\node_modules\@levischuck\tiny-cbor\script\cbor\cbor.js:355:15)
    in Object.decodeFirst (C:\Users\siste\Desktop\web-authentication-main\node_modules\@simplewebauthn\server\script\helpers\iso\isoCBOR.js:25:40)
    at decodeCredentialPublicKey (C:\Users\siste\Desktop\web-authentication-main\node_modules\@simplewebauthn\server\script\helpers\decodeCredentialPublicKey.js:6:84)
    in verifySignature (C:\Users\siste\Desktop\web-authentication-main\node_modules\@simplewebauthn\server\script\helpers\verifySignature.js:20:86)
    in verifyAuthenticationResponse (C:\Users\siste\Desktop\web-authentication-main\node_modules\@simplewebauthn\server\script\authentication\verifyAuthenticationResponse.js:157:66)
    at C:\Users\siste\Desktop\web-authentication-main\index.js:165:24 asynchronous

I don't know if it's a library bug or if I'm saving my user schema with the wrong password.

import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
   name: String,
   password: String,
   passkey: Object
});

const User = mongoose.model('User', userSchema);

export default User; 

I don't know if I'm saving the passkeys wrong in my mongo schema

Reproduction Steps

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior

Code Samples + WebAuthn Options and Responses

import Challenge from '../model/challengeModel.js';

import User from '../model/userModel.js'; // Asegúrate de que la ruta es correcta
import {
    generateAuthenticationOptions,
    generateRegistrationOptions,
    verifyAuthenticationResponse,
    verifyRegistrationResponse
  } from '@simplewebauthn/server';

const register = async (req, res) => {
    const { username, password } = req.body;

    try {
        // Crear un nuevo usuario utilizando el modelo de Mongoose
        const newUser = new User({
            nombre: username,
            passwor: password // Asegúrate de que la propiedad coincide con el esquema
        });

        // Guardar el usuario en la base de datos
        await newUser.save();

      //  console.log("Registro exitoso", newUser); 

        // Devolver el ID de Mongoose
        return res.json({ id: newUser._id });
    } catch (error) {
        console.error("Error al registrar el usuario:", error);
        return res.status(500).json({ error: 'Error al registrar el usuario' });
    }
}

const registerChallenge = async (req, res) => {
    const { userId } = req.body;

    try {
       // console.log('Request body:', req.body); // Verificar el cuerpo de la solicitud

        const user = await User.findById(userId);
        //console.log('Usuario encontrado:', user); // Verificar si se encontró al usuario

        if (!user) {
         //   console.log('Usuario no encontrado');
            return res.status(404).json({ error: 'Usuario no encontrado' });
        }

        const challengePayload = await generateRegistrationOptions({
            rpID: 'localhost',
            rpName: 'My localhost maquina',
            userName: user.nombre,
        });
       // console.log('Payload de desafío generado:', challengePayload); // Verificar el payload de desafío generado

        // Crear un nuevo documento de desafío en la base de datos
        const challengeDoc = new Challenge({
            userId: user._id,
            challenge: challengePayload.challenge,
        });
        await challengeDoc.save();
        //console.log('Documento de desafío guardado:', challengeDoc); // Verificar si se guardó correctamente el documento

        return res.json({ options: challengePayload });

    } catch (error) {
        console.error('Error al generar el desafío de registro:', error);
        return res.status(500).json({ message: 'Error interno del servidor challenge' });
    }
};

const registerVerify = async (req, res) => {
    try {
        const { userId, cred } = req.body;

       // console.log('cred:', cred); // Verificar el cuerpo de la solicitud

        // Buscar al usuario por su ID
        const user = await User.findById(userId);
        //console.log('Usuario encontrado:', user); // Verificar si se encontró al usuario

        if (!user) {
            console.log('Usuario no encontrado');
            return res.status(404).json({ error: 'Usuario no encontrado' });
        }

        // Buscar el desafío en la base de datos (asumiendo que tienes una colección 'challenges')
        const challengeDoc = await Challenge.findOne({ userId: userId });
        if (!challengeDoc) {
            console.log('Desafío no encontrado para el usuario');
            return res.status(404).json({ error: 'Desafío no encontrado para el usuario' });
        }

        // Verificar la respuesta de registro
        const verificationResult = await verifyRegistrationResponse({
            expectedChallenge: challengeDoc.challenge, // Usar el desafío almacenado en la base de datos
            expectedOrigin: 'http://localhost:5173', // Cambiado a la URL correcta
            expectedRPID: 'localhost',
            response: cred // La credencial de autenticación recibida del cliente
        });

       // console.log('Resultado de la verificación:', verificationResult);

        if (!verificationResult.verified) {
            return res.status(400).json({ error: 'La verificación de la autenticación ha fallado' });
        }

        // //console.log('verificationResult: aqui dabe haber un registrationInfo', verificationResult.registrationInfo);
        //    console.log(typeof(verificationResult.registrationInfo));
        // Actualizar la propiedad 'passkey' del usuario con la información de 'registrationinfo'
        user.passkey = verificationResult.registrationInfo;
      //  console.log('user passkey generada de registervery:',user.passkey);
        // Guardar los cambios en la base de datos
        await user.save();

        // Devolver una respuesta al cliente
        return res.json({ verified: true });

    } catch (error) {
        console.error('Error al verificar la autenticación:', error);
        return res.status(500).json({ message: 'Error interno del servidor' });
    }
};

const loginChallenge = async (req, res) => {

    try {

        const { userId } = req.body;
        console.log(userId);
        // Buscar al usuario por su ID
        const user = await User.findById(userId);

        if (!user) {
            console.log('Usuario no encontrado');
            return res.status(404).json({ error: 'Usuario no encontrado' });
        }

        const options =  await generateAuthenticationOptions({
            rpID: 'localhost',

        })

         // Buscar el desafío en la base de datos (asumiendo que tienes una colección 'challenges')
         const challengeDoc = await Challenge.findOne({ userId: userId });
         if (!challengeDoc) {
             console.log('Desafío no encontrado para el usuario');
             return res.status(404).json({ error: 'Desafío no encontrado para el usuario' });
         }

         // Guardar el desafío generado en la base de datos o en alguna estructura adecuada
        challengeDoc.challenge = options.challenge; // Actualiza el desafío en el documento de desafío

        // Guardar los cambios en el desafío
        await challengeDoc.save();

        // Devolver las opciones de autenticación al cliente
        return res.json({options});

    } catch (error) {
        console.error('Error al generar el desafío de autenticación loginchallegnte:', error);
        return res.status(500).json({ message: 'Error interno del servidor loginchallegnte' });

    }
}

const loginVerify = async (req, res) => {
    const { userId, cred } = req.body;

    try {
        // Buscar al usuario por su ID
        const user = await User.findById(userId);
        if (!user) {
            console.log('Usuario no encontrado');
            return res.status(404).json({ error: 'Usuario no encontrado' });
        }

        // Buscar el desafío en la base de datos (asumiendo que tienes una colección 'challenges')
        const challengeDoc = await Challenge.findOne({ userId: userId });
        if (!challengeDoc) {
            console.log('Desafío no encontrado para el usuario');
            return res.status(404).json({ error: 'Desafío no encontrado para el usuario' });
        }

        // Verificar la respuesta de autenticación
        const result = await verifyAuthenticationResponse({
            expectedChallenge: challengeDoc.challenge,
            expectedOrigin: 'http://localhost:5173',
            expectedRPID: 'localhost',
            response: cred, // La credencial de autenticación recibida del cliente
            authenticator: user.passkey // El autenticador almacenado del usuario
        });

        if (!result.verified) {
            return res.json({ error: 'La verificación de la autenticación ha fallado' });
        }

        // Aquí puedes proceder con el inicio de sesión del usuario, como establecer una sesión, generar un token JWT, etc.
        // Por ejemplo, podrías manejar el inicio de sesión con cookies o JWT

        return res.json({ success: true, userId });
    } catch (error) {
        console.error('Error en loginVerify:', error);
        return res.status(500).json({ error: 'Error interno del servidor' });
    }
};

export  {
    register,
    registerChallenge,
    registerVerify,
    loginChallenge, 
    loginVerify
};

Dependencies

"dependencies": {
    "@simplewebauthn/server": "^10.0.0",
    "cors": "^2.8.5",
    "crypto-js": "^4.2.0",
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "mongoose": "^8.4.5"
}

SimpleWebAuthn Libraries

$ npm list --depth=0 | grep @simplewebauthn
├── @simplewebauthn/browser@_._._
├── @simplewebauthn/server@_._._
# ...

Additional context

MasterKale commented 1 month ago

Hello @JimmyDevPasto, sorry for the delayed response. Can you take a look at this comment in #577 and see if it's not the solution to your problem?

https://github.com/MasterKale/SimpleWebAuthn/issues/577#issuecomment-2141773422

People have had issues with credential public keys stored as Binary values in Mongo that required converting them to Uint8Array's as SimpleWebAuthn expects. If that comment fixes your issue then I'll add it to the docs as official guidance for other MongoDB users.

JimmyDevPasto commented 1 month ago

Best regards, I had not had time to correct the code with your suggestion, apparently it was an error in the way buffers are handled in mongo,

it was fixed with credentialPublicKey: new Uint8Array(passkey.publicKey), credentialID: new Uint8Array(passkey.id),

yes, it would be good if you put it in the documentation, but in a clearer way and with an example for mongo, since some of us use that database engine, I appreciate that you answered me and With that I was able to solve my problem, I congratulate you on your library, greetings.

El lun, 15 jul 2024 a la(s) 11:49 p.m., Matthew Miller ( @.***) escribió:

Hello @JimmyDevPasto https://github.com/JimmyDevPasto, sorry for the delayed response. Can you take a look at this comment in #577 https://github.com/MasterKale/SimpleWebAuthn/issues/577 and see if it's not the solution to your problem?

577 (comment)

https://github.com/MasterKale/SimpleWebAuthn/issues/577#issuecomment-2141773422

People have had issues with credential public keys stored as Binary values in Mongo that required converting them to Uint8Array's as SimpleWebAuthn expects. If that comment fixes your issue then I'll add it to the docs as official guidance for other MongoDB users.

— Reply to this email directly, view it on GitHub https://github.com/MasterKale/SimpleWebAuthn/issues/589#issuecomment-2230014522, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATDXOJKP54FTPUHK7E6HWETZMSQ5ZAVCNFSM6AAAAABKSAZBR6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMZQGAYTINJSGI . You are receiving this because you were mentioned.Message ID: @.***>

MasterKale commented 1 month ago

Thank you for confirming @JimmyDevPasto, I've gone ahead and added troubleshooting for this error to the docs because it seems to be a common pain point for SimpleWebAuthn users adding it to their Mongo-backed projects:

https://simplewebauthn.dev/docs/packages/server#error-no-data