altcha-org / altcha-lib

A JavaScript library for creating and verifying ALTCHA challenges.
https://altcha.org
MIT License
25 stars 5 forks source link

Integration with Firebase Appcheck #10

Open andrevferreiraa opened 2 days ago

andrevferreiraa commented 2 days ago

Hey everyone. I'm trying to implement altcha with Firebase Appcheck on a Nuxt3 application using Firebase Functions as the BE. The functions themselves work well standalone, but they stop responding once they are integrated with the CustomProvider getToken function:

/// plugins/appcheck.ts on Client side

import { initializeAppCheck, CustomProvider } from 'firebase/app-check'
import { getFunctions, httpsCallable } from 'firebase/functions'
import { solveChallenge } from 'altcha-lib'

export default defineNuxtPlugin(async (nuxtApp) => {
  if (!process.client) return

  const firebaseApp = nuxtApp.vueApp.$nuxt.$firebaseApp
  const functions = getFunctions(firebaseApp)
  const fetchAppCheckToken = httpsCallable(functions, 'fetchAppCheckToken')
  const createAppCheckChallenge = httpsCallable(functions, 'createAppCheckChallenge')
  const appCheckProvider = new CustomProvider({
    getToken: async () => {
      const challengeResponse = await createAppCheckChallenge()

      // Solve the challenge
      const { promise } = solveChallenge(challengeResponse.data.challenge, challengeResponse.data.salt)
      const solution = await promise

      // Submit the solution
      const token = await fetchAppCheckToken({
        solution,
      })
      return token
    },
  })
  initializeAppCheck(firebaseApp, {
    provider: appCheckProvider,
  })
})

// FIrebase functions

const hmacKey = '...';

exports.createAppCheckChallenge = functions.https.onCall(async () => {
    functions.logger.log('Creating Altcha challenge');
    try {
        const challengeResponse = await createChallenge({
            hmacKey,
            ttlMillis: 60000,
        });
        functions.logger.log(challengeResponse);
        return challengeResponse;
    } catch (error) {
        functions.logger.error(error);
        throw new functions.https.HttpsError(
            error.errorInfo.code,
            error.errorInfo.message
        );
    }
});

exports.fetchAppCheckToken = functions.https.onCall(async (data) => {
    functions.logger.log('Verifying Altcha challenge');
    functions.logger.log(data.solution)
    try {
        // Verify the Altcha challenge response
        const isValid = await verifySolution(data.solution, hmacKey);

        functions.logger.log('Altcha verification result:', isValid);

        if (isValid) {
            // Create an App Check token
            const token = await createAppCheckToken(data.solution);
            return { token };
        } else {
            functions.logger.error('Altcha verification failed');
            throw new functions.https.HttpsError(
                'permission-denied',
                'Altcha verification failed'
            );
        }
    } catch (error) {
        functions.logger.error(error);
        throw new functions.https.HttpsError(
            'permission-denied',
            'Altcha verification failed'
        );
    }
});

async function createAppCheckToken(altchaData) {
    functions.logger.log('Altcha verification successful');
    try {
        const appCheck = admin.appCheck();
        const ttlMillis = 3600 * 1000;
        const appId = admin.instanceId().app.options.appId;
        const token = await appCheck.createToken(appId, {
            ttlMillis,
            customClaims: {
                altcha_verified: true,
                altcha_response_id: altchaData.response_id,
                origin: altchaData.origin,
            },
        });
        functions.logger.log(
            'App Check token created successfully:',
            token.token
        )
        return token.token;
    } catch (error) {
        functions.logger.error('Altcha verification failed');
        throw new functions.https.HttpsError(
            'App Check token created failed',
            'Altcha verification failed'
        );
    }
}

Am i doing something wrong here? I believe an example of how to correctly implement this would help out a lot of people.

Appreciate your help

ovx commented 21 hours ago

Hi, what do you mean by "stop responding"? Do you get some error?

At the moment, there's no example on usage with Firebase, there's a related feature request (https://github.com/altcha-org/altcha/issues/69) I'm still investigating and planning to create an official firebase extensions with examples.

andrevferreiraa commented 9 hours ago

Hey @ovx!

There is no errors, simply a timeout. I know that createAppCheckChallenge is being called but it never returns anything or even throws an error. It simply stops working.

An official firebase extension would be greatly welcomed!