kakakararara
kakakararara

Reputation: 13

2FA Authentication - QR Code and Generated Secret Key has Different Key (Google Authenticator)

If I manually input the Secret Key into the front end, it matches the token to the expected token. However, if I scan the QR code, it doesn't match the token.

/Controller

const setupTwoFactor = async (req, res) => {
  try {
    const patient = await Patient.findById(req.params.id);
    if (!patient) {
      return res.status(404).json({ message: 'Patient not found' });
    }

    let secret;
    if (!patient.twoFactorSecret || req.body.regenerate) {
      // Generate a new secret key if the patient does not have one or if regenerate flag is true
      secret = speakeasy.generateSecret({ length: 30 });
      patient.twoFactorSecret = secret.base32;
      patient.twoFactorEnabled = true; // Enable 2FA for this patient
      await patient.save(); // Save the changes
    } else {
      // Use the existing secret key
      secret = { base32: patient.twoFactorSecret };
    }

    // Log the stored secret key for debugging
    console.log('Stored Secret in DB:', patient.twoFactorSecret);

    // Generate the QR code with the saved secret key using the method from the schema
    const qrCode = await patient.generateQRCode();

    console.log('Generated Secret:', secret.base32); // Log the secret

    res.json({ qrCode, secret: secret.base32 });
  } catch (error) {
    console.error('Error generating 2FA secret:', error); // Log the error
    res.status(500).json({ message: 'Error generating 2FA secret', error });
  }
};

// Verify Two-Factor Function
const verifyTwoFactor = async (req, res) => {
  const { userId, token } = req.body;

  try {
    const patient = await Patient.findById(userId);
    if (!patient) {
      return res.status(404).json({ message: 'Patient not found' });
    }

    if (!patient.twoFactorEnabled) {
      return res.status(400).json({ message: '2FA not enabled for this patient' });
    }

    if (!token) {
      return res.status(400).json({ message: '2FA token is required' });
    }

    console.log(`Verifying token: ${token} for user ${userId}`);
    console.log(`Secret key: ${patient.twoFactorSecret}`);

    const verified = speakeasy.totp.verify({
      secret: patient.twoFactorSecret,
      encoding: 'base32',
      token,
      window: 2 // Increase the window size if necessary
    });

    console.log(`Verified: ${verified}`);

    if (verified) {
      res.json({ verified: true });
    } else {
      res.status(400).json({ verified: false, message: 'Invalid 2FA token' });
    }
  } catch (error) {
    console.error('Error verifying 2FA token:', error);
    res.status(500).json({ message: 'Error verifying 2FA token', error });
  }
};

//Model

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const { Schema, model } = mongoose;

const PatientSchema = new Schema({
    // Personal info
    patient_ID: {
        type: String,
        unique: true
    },
    patient_firstName: {
        type: String,
        minlength: 3,
        maxlength: 20
    },
    patient_middleInitial: {
        type: String,
        maxlength: 1
    },
    patient_lastName: {
        type: String,
        minlength: 2,
        maxlength: 20
    },
    patient_email: {
        type: String,
        unique: true,
   
        
    },
    patient_password: {
        type: String,
        minlength: 6,
    },
    patient_dob: {
        type: Date,
    },
    patient_age:{
        type:String
    },
    patient_contactNumber: {
        type: String,
        unique: true,
        validate: {
            validator: function (v) {
                return v.length === 11;
            },
            message: props => `${props.value} has to be 11 characters long.`
        }
    },
    patient_gender: {
        type: String,
        enum: ['Male', 'Female', 'Other']
    },
    patient_appointments: [{
        type: Schema.Types.ObjectId,
        ref: 'Appointment'
    }],
    medicalHistory: {
        type: Schema.Types.ObjectId,
        ref: 'MedicalHistory'
    },
    prescriptions: [{
        type: Schema.Types.ObjectId,
        ref: 'Prescription'
    }],
    notifications: [{
        type: Schema.Types.ObjectId,
        ref: 'Notification'
    }],
    twoFactorSecret: { type: String },
    twoFactorEnabled: { type: Boolean, default: false }
}, { timestamps: true });

// Pre-save hook for generating the patient ID
PatientSchema.pre('save', async function (next) {
    if (!this.isNew) {
        return next();
    }
    const currentYear = new Date().getFullYear();

    try {
        const highestPatient = await this.constructor.findOne({ patient_ID: new RegExp(`^P${currentYear}`, 'i') })
            .sort({ patient_ID: -1 })
            .limit(1);
        let nextNumber = 1;
        if (highestPatient) {
            const lastNumber = parseInt(highestPatient.patient_ID.split('-')[1]);
            nextNumber = lastNumber + 1;
        }
        const paddedNumber = nextNumber.toString().padStart(6, '0');
        this.patient_ID = `P${currentYear}-${paddedNumber}`;
        next();
    } catch (error) {
        next(error);
    }
});

const QRCode = require('qrcode');
const speakeasy = require('speakeasy');

PatientSchema.methods.generateQRCode = async function() {
  const otpAuthUrl = speakeasy.otpauthURL({ 
    secret: this.twoFactorSecret, 
    label: `Landagan Kids Clinic:${this.patient_email}`, 
    issuer: 'Landagan Kids Clinic',
    algorithm: 'SHA1' // Ensure the algorithm is specified
  });
  return await QRCode.toDataURL(otpAuthUrl);
};

const Patient = model('Patient', PatientSchema);

module.exports = Patient;

I am expecting that the generated key, has the same value of key also in the qr code, but it turns out different.

//Output Verifying token: 893236 for user 66533caad18ff8bf1f76fd11 Secret key: KB3HAOLLJVCDQ7JSO5JWE33JIFEW26TEO5JTCZZ2FY6DYVKB Verified: false

Upvotes: 0

Views: 212

Answers (0)

Related Questions