Ensuring Unique Code Usage in React Native and Expo Authentication

I’ve created a mobile app with react-native and expo for my studies. I’m trying to implement an authentication logic where the user has to enter an access code.

If the code complies with the verification rules, it will be stored in a mongodb database and allowed to log in to the app. It’s important to note that the code can only be used once.

I’ve tried all sorts of ways to do this logic, but without any success, I’m managing to use the code more than once, which I don’t want. Below is my backend and frontend code.

– server.js

require('dotenv').config();
const express = require('express');
const cors = require('cors'); // Importar cors
const { MongoClient } = require('mongodb');
const crypto = require('crypto');

const app = express();
const port = process.env.PORT || 3000;

app.use(cors()); // Usar cors
app.use(express.json());

const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });

let db;
client.connect()
  .then(() => {
    db = client.db('pesagem');
    console.log('Connected to MongoDB');
  })
  .catch(err => console.error('Error connecting to MongoDB', err));

const validateCode = (code) => {
  const codeParts = code.split('-');
  if (codeParts.length !== 4) {
    console.log('Invalid code format');
    return { valid: false, message: 'Código inválido.' };
  }

  const [timestamp, random, userType, hash] = codeParts;
  const originalData = `${timestamp}-${random}-${userType}`;
  const generatedHash = crypto.createHash('sha256').update(originalData).digest('hex');

  console.log('Generated Hash:', generatedHash);

  if (hash !== generatedHash) {
    console.log('Invalid code: hash does not match');
    return { valid: false, message: 'Código inválido.' };
  }

  let userTypeText;
  switch (userType) {
    case '0':
      userTypeText = '5';
      break;
    default:
      console.log('Invalid userType');
      return { valid: false, message: 'Código inválido.' };
  }

  return { valid: true, userTypeText };
};

app.post('/check-code', async (req, res) => {
  const { code } = req.body;

  if (!code) {
    console.log('Código não fornecido.');
    return res.status(400).send({ error: 'Código não fornecido' });
  }

  console.log('Received code:', code);

  const validationResult = validateCode(code);
  if (!validationResult.valid) {
    console.log('Validation result:', validationResult);
    return res.status(400).send({ error: validationResult.message });
  }

  try {
    const codesCollection = db.collection('usedCodes');
    const existingCode = await codesCollection.findOne({ code });

    if (existingCode) {
      console.log(`Código ${code} já utilizado.`);
      return res.status(400).send({ error: 'Este código já foi utilizado.' });
    }

    await codesCollection.insertOne({ code });
    console.log(`Código ${code} inserido com sucesso.`);
    return res.status(200).send({ message: 'Código válido e armazenado.', userType: validationResult.userTypeText });
  } catch (error) {
    console.error('Erro ao verificar o código', error);
    return res.status(500).send({ error: 'Erro interno do servidor' });
  }
});

app.listen(port, () => {
  console.log(`Servidor rodando na porta ${port}`);
});

– Login.js

const Login = ({ loginDone }) => {
  const [code, setCode] = useState('');
  const [isAlertVisible, setIsAlertVisible] = useState(false);
  const [alertMessage, setAlertMessage] = useState('');

  const showAlert = (message) => {
    setAlertMessage(message);
    setIsAlertVisible(true);
  };

  const closeAlert = () => {
    setIsAlertVisible(false);
  };

  const checkCode = async () => {
    console.log('checkCode function called');
    try {
      if (!code) {
        console.log('No code provided');
        showAlert('Por favor, insira um código para verificar.');
        return;
      }

      const codeParts = code.split('-');
      if (codeParts.length !== 4) {
        console.log('Invalid code format');
        showAlert('Código inválido.');
        return;
      }

      const [timestamp, random, userType, hash] = codeParts;
      const originalData = `${timestamp}-${random}-${userType}`;
      const generatedHash = await Crypto.digestStringAsync(
        Crypto.CryptoDigestAlgorithm.SHA256,
        originalData
      );

      console.log('Generated Hash:', generatedHash);

      if (hash !== generatedHash) {
        console.log('Invalid code: hash does not match');
        showAlert('Código inválido.');
        return;
      }

      console.log('Sending code to API:', code);
      const response = await axios.post(`${API_URL}/check-code`, { code });

      if (response.data.error) {
        console.log('API returned an error:', response.data.error);
        showAlert(response.data.error);
        loginDone(false, 0);
      } else {
        console.log('Login successful, user type:', response.data.userType);
        loginDone(true, response.data.userType);
        setCode('');
      }
    } catch (error) {
      if (error.response) {
        console.log('Erro na resposta da API:', error.response.data);
        showAlert('Erro ao verificar o código: ' + error.response.data.error);
      } else if (error.request) {
        console.log('Erro na requisição:', error.request);
        showAlert('Erro na conexão com o servidor.');
      } else {
        console.log('Erro desconhecido:', error.message);
        showAlert('Erro ao verificar o código.');
      }
      loginDone(false, 0);
    }
  };