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.62k stars 137 forks source link

Problems with Promise.any #397

Closed lmunozs closed 1 year ago

lmunozs commented 1 year ago

Type Promise.any is not a function

Hi, I am implementing SimpleWebAuthn in my environment. I have NodeJS in version 18.16.1, but I'm having some issues with the script matchExpectedRPID since it has the function Promise.any. I have installed promise.any with: npm i promise.any, but still does not work.

Code Sample

My code looks like:

exports.fido_verify_registration_response = async function (req, res) {
  debug("--> fido_verify_register")

  try {
    const verification = await fido2.verifyRegistrationResponse({
      response: req.body.attResp,
      expectedChallenge: req.body.challenge,
      expectedOrigin: process.env.origin,
      expectedRPID: process.env.rpID,
    });

    console.log(verification);
    const { verified } = verification.verified;
    res.send({ verified: verified });
    return verified 
  } catch (error) {
    console.error(error);
  }
}

And the response of startRegistration (attResp) is:

{
  id: 'TxH_Ul6MgEkw_IKUz2pLSQ',
  rawId: 'TxH_Ul6MgEkw_IKUz2pLSQ',
  response: {
     attestationObject: 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViU58viY2Im_PjtzphLrkyAnsFk9wuhah41se5wChiFhINdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE8R_1JejIBJMPyClM9qS0mlAQIDJiABIVggS4vJpgllFoWGqLVu4LARujYiu4Y0D3ojCKW_TXI7s6YiWCDCRDurTsB1Z_Tjrd5P4KPD1EabyMGKktbqwU6ijLHTzQ',
 clientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYzB0dFluUTBjRjlJZWxoWlQzRk5Tazl6TUY5Zk1XRjNTekZoY1RWd2NtaHJSakJEUnpnd05GWkVVMjB5UVdoTFoyVklRMHRST0hReU9WSlNZVkJpVm1oRGIxQTRZWEJ2TFZKU1dGQkxkR2xGU0ZCMU5qaFNVM0ZWWkZaMFJYcE1VelpxUVVsWkxVTlhjMjg1WjFvelJFczFNWGxPVFhOYU9HeG9WM1Z5WWpoVlgxTnNVVzVTZWtSNVZVNVdNMDFETjFSdllXTmlXWGMzWTNkVlVHNXJibGxWTXpabVNWTXdiM1ZOIiwib3JpZ2luIjoiaHR0cHM6Ly9jeWJlcmxhYi52aWNvbXRlY2gub3JnOjU0NDQiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
  transports: [ 'hybrid', 'internal' ]
    },
  type: 'public-key',
   clientExtensionResults: { credProps: { rk: true } },
   authenticatorAttachment: 'cross-platform'}
}

Dependencies

SimpleWebAuthn Libraries

├── @simplewebauthn/browser@7.2.0
├── @simplewebauthn/server@7.3.1

Thank you for your response!

MasterKale commented 1 year ago

Hello @lmunozs, Promise.any should be available, according to MDN it was introduced in Node 15...you definitely shouldn't need to install anything in Node 18 for this to work 🤔

Can you include a stack trace from the error that Node is raising?

MasterKale commented 1 year ago

I tried to reproduce with the code you provided and Node 18.16.1, but it ran fine:

import { verifyRegistrationResponse } from './src/registration/verifyRegistrationResponse';
import { AuthenticatorTransportFuture } from '../typescript-types';

(async () => {
  const process = {
    env: {
      origin: 'https://cyberlab.vicomtech.org:5444',
      rpID: 'cyberlab.vicomtech.org',
    },
  };
  const req = {
    body: {
      attResp: {
        id: 'TxH_Ul6MgEkw_IKUz2pLSQ',
        rawId: 'TxH_Ul6MgEkw_IKUz2pLSQ',
        response: {
          attestationObject: 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViU58viY2Im_PjtzphLrkyAnsFk9wuhah41se5wChiFhINdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE8R_1JejIBJMPyClM9qS0mlAQIDJiABIVggS4vJpgllFoWGqLVu4LARujYiu4Y0D3ojCKW_TXI7s6YiWCDCRDurTsB1Z_Tjrd5P4KPD1EabyMGKktbqwU6ijLHTzQ',
          clientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYzB0dFluUTBjRjlJZWxoWlQzRk5Tazl6TUY5Zk1XRjNTekZoY1RWd2NtaHJSakJEUnpnd05GWkVVMjB5UVdoTFoyVklRMHRST0hReU9WSlNZVkJpVm1oRGIxQTRZWEJ2TFZKU1dGQkxkR2xGU0ZCMU5qaFNVM0ZWWkZaMFJYcE1VelpxUVVsWkxVTlhjMjg1WjFvelJFczFNWGxPVFhOYU9HeG9WM1Z5WWpoVlgxTnNVVzVTZWtSNVZVNVdNMDFETjFSdllXTmlXWGMzWTNkVlVHNXJibGxWTXpabVNWTXdiM1ZOIiwib3JpZ2luIjoiaHR0cHM6Ly9jeWJlcmxhYi52aWNvbXRlY2gub3JnOjU0NDQiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
          transports: ['hybrid', 'internal'] as AuthenticatorTransportFuture[],
        },
        type: 'public-key' as "public-key",
        clientExtensionResults: { credProps: { rk: true } },
        authenticatorAttachment: 'cross-platform' as AuthenticatorAttachment,
      },
      challenge: 'c0ttYnQ0cF9IelhZT3FNSk9zMF9fMWF3SzFhcTVwcmhrRjBDRzgwNFZEU20yQWhLZ2VIQ0tROHQyOVJSYVBiVmhDb1A4YXBvLVJSWFBLdGlFSFB1NjhSU3FVZFZ0RXpMUzZqQUlZLUNXc285Z1ozREs1MXlOTXNaOGxoV3VyYjhVX1NsUW5SekR5VU5WM01DN1RvYWNiWXc3Y3dVUG5rbllVMzZmSVMwb3VN',
    },
  };
  try {
    const verification = await verifyRegistrationResponse({
      response: req.body.attResp,
      expectedChallenge: req.body.challenge,
      expectedOrigin: process.env.origin,
      expectedRPID: process.env.rpID,
    });

    console.log(verification);
  } catch (error) {
    console.error(error);
  }
})();

And the response verified as expected:

{
  verified: true,
  registrationInfo: {
    fmt: 'none',
    counter: 0,
    aaguid: '00000000-0000-0000-0000-000000000000',
    credentialID: Uint8Array(16) [...],
    credentialPublicKey: Uint8Array(77) [...],
    credentialType: 'public-key',
    attestationObject: Uint8Array(178) [...],
    userVerified: true,
    credentialDeviceType: 'multiDevice',
    credentialBackedUp: true,
    authenticatorExtensionResults: undefined
  }
}

I tested the sample code in Node 16 and it ran fine there too. I tested with Node 14 too and it predictably failed when trying to call matchExpectedRPID ():

TypeError: Promise.any is not a function
    at matchExpectedRPID (.../matchExpectedRPID.ts:12:19)
    ...

One thing you can confirm is that console.log(process.version) from your server's code outputs 'v18.16.1'. If you see a different version that's lower than Node 15 for some reason then that's something to dig into on your end.

lmunozs commented 1 year ago

Hi @MasterKale, Thank you for your help :)

I have made sure that version 18.16 is installed: image

With this code I don't get the error of: Promise.any

exports.fido_verify_registration_response = async function (req, res) {
  debug("--> fido_verify_register")
  //cogemos challenge que hemos enviado al front y debemos recibir en aqui (back)
  const verificationPromises = [
    fido2.verifyRegistrationResponse({
      response: req.body.attResp,
      expectedChallenge: req.body.challenge,
      expectedOrigin: process.env.origin,
      supportedAlgorithmIDs: [-7, -8, -35, -36, -37, -38, -39, -257, -258, -259, -65535],
      requireUserVerification: false
    })
  ];

  try {
    const verificationResults = await Promise.allSettled(verificationPromises);
    const verification = verificationResults.find(result => result.status === 'fulfilled');

    // Manejar el resultado
    console.log(verificationResults)
    if(verification){
      res.send({ verified: verification.verified });
    }
    // ...
    return { verified: verification.verified }
  } catch (error) {
    // Manejar errores
    // ...
    console.log("Error generating registration options", error)
    res.status(500).send({ error: "Internal Server Error" });
  }
}

But the errors persist, I paste to you the stack trace:

[
  {
    status: 'rejected',
    reason: TypeError: Cannot read property 'subtle' of undefined
        at Object.digest (/opt/fiware-idm/node_modules/@simplewebauthn/server/dist/helpers/iso/isoCrypto/digest.js:17:50)
        at toHash (/opt/fiware-idm/node_modules/@simplewebauthn/server/dist/helpers/toHash.js:13:36)
        at Object.verifyRegistrationResponse (/opt/fiware-idm/node_modules/@simplewebauthn/server/dist/registration/verifyRegistrationResponse.js:131:54)
        at exports.fido_verify_registration_response (/opt/fiware-idm/controllers/web/sessions.js:72:11)
        at Layer.handle [as handle_request] (/opt/fiware-idm/node_modules/express/lib/router/layer.js:95:5)
        at next (/opt/fiware-idm/node_modules/express/lib/router/route.js:144:13)
        at Route.dispatch (/opt/fiware-idm/node_modules/express/lib/router/route.js:114:3)
        at Layer.handle [as handle_request] (/opt/fiware-idm/node_modules/express/lib/router/layer.js:95:5)
        at /opt/fiware-idm/node_modules/express/lib/router/index.js:284:15
        at Function.process_params (/opt/fiware-idm/node_modules/express/lib/router/index.js:346:12)
        at next (/opt/fiware-idm/node_modules/express/lib/router/index.js:280:10)
        at Function.handle (/opt/fiware-idm/node_modules/express/lib/router/index.js:175:3)
        at router (/opt/fiware-idm/node_modules/express/lib/router/index.js:47:12)
        at Layer.handle [as handle_request] (/opt/fiware-idm/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/opt/fiware-idm/node_modules/express/lib/router/index.js:328:13)
        at /opt/fiware-idm/node_modules/express/lib/router/index.js:286:9
        at Function.process_params (/opt/fiware-idm/node_modules/express/lib/router/index.js:346:12)
        at next (/opt/fiware-idm/node_modules/express/lib/router/index.js:280:10)
        at exports.login_required (/opt/fiware-idm/controllers/web/sessions.js:169:5)
        at Layer.handle [as handle_request] (/opt/fiware-idm/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/opt/fiware-idm/node_modules/express/lib/router/index.js:328:13)
        at /opt/fiware-idm/node_modules/express/lib/router/index.js:286:9
        at Function.process_params (/opt/fiware-idm/node_modules/express/lib/router/index.js:346:12)
        at next (/opt/fiware-idm/node_modules/express/lib/router/index.js:280:10)
        at /opt/fiware-idm/routes/web/index.js:19:3
        at Layer.handle [as handle_request] (/opt/fiware-idm/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/opt/fiware-idm/node_modules/express/lib/router/index.js:328:13)
        at /opt/fiware-idm/node_modules/express/lib/router/index.js:286:9
        at Function.process_params (/opt/fiware-idm/node_modules/express/lib/router/index.js:346:12)
        at next (/opt/fiware-idm/node_modules/express/lib/router/index.js:280:10)
        at Function.handle (/opt/fiware-idm/node_modules/express/lib/router/index.js:175:3)
        at router (/opt/fiware-idm/node_modules/express/lib/router/index.js:47:12)
        at Layer.handle [as handle_request] (/opt/fiware-idm/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/opt/fiware-idm/node_modules/express/lib/router/index.js:328:13)
  }
]
Error generating registration options TypeError: Cannot read property 'verified' of undefined
    at exports.fido_verify_registration_response (/opt/fiware-idm/controllers/web/sessions.js:91:37)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)

I am sending you the part of the browser in case the error could be there:

 <div class=" ">
                                    <input class="btn-primary" id="inputfido" type="submit"
                                        value="<%=translation.settings.settings.label08%>">
                                    <script type="text/javascript">
                                        const accordion2 = (content) => {
                                            content.addEventListener('click', function () {
                                                event.preventDefault() //para que no recargue la pagina
                                                var url = '/idm/settings/fido_generate_registration_options';
                                                const { startRegistration } = SimpleWebAuthnBrowser
                                                let attResp
                                                const elemError = document.getElementById('error')

                                                const postPromise = new Promise((resolve, reject) => {
                                                    $.post(url, function (data) {
                                                        resolve(data)
                                                    }).fail(function (error) {
                                                        reject(error)
                                                    })
                                                });

                                                postPromise.then(async function (data) {
                                                    if (data) {
                                                        try {
                                                            // Pass the options to the authenticator and wait for a response
                                                            attResp = await startRegistration(data.optionsJSON);
                                                            const dataToSend = {
                                                                attResp: attResp,
                                                                challenge: data.optionsJSON.challenge,
                                                                subtle: data.optionsJSON.pubKeyCredParams
                                                            };

                                                            $.post({
                                                                url: '/idm/settings/fido_verify_register',
                                                                data: JSON.stringify(dataToSend),
                                                                contentType: 'application/json',
                                                                success: function (data) {
                                                                    console.log(data)
                                                                    resolve(data);
                                                                },
                                                                error: function (error) {
                                                                    reject(error);
                                                                }
                                                            });

                                                        } catch (error) {
                                                            // Some basic error handling
                                                            if (error.name === 'InvalidStateError') {
                                                                console.log('Error: Authenticator was probably already registered by user');
                                                            } else {
                                                                console.log(error);
                                                            }

                                                            throw error;
                                                        }
                                                    }
                                                }).catch(function (error) {
                                                    console.error('Error:', error);
                                                });
                                            })
                                        }
                                        content2 = document.getElementById('inputfido')
                                        accordion2(content2);
                                    </script>
                                </div>

Thank you!

MasterKale commented 1 year ago

TypeError: Cannot read property 'subtle' of undefined

This is a different error than the one related to Promise.any, but it's another sign that the Node that's executing your code isn't Node v18.16. SubtleCrypto support wasn't added to Node till v16.

Are you using Docker containers or similar technology to run your server? Without knowing more about your hosting setup I don't know that I'll be able to help you solve this. It's definitely a server issue though so you can focus on that and skip anything related to the front end.

At this moment I don't think this is a SimpleWebAuthn issue.

lmunozs commented 1 year ago

Yes, you are right, the problem is in the Docker container we are using. It is taking node version 14 and we are trying to solve it.

Thanks!