import * as functions from 'firebase-functions' import { GoogleAuth } from 'google-auth-library' const PACKAGE_NAME = 'app.closer' const PLAY_INTEGRITY_URL = `https://playintegrity.googleapis.com/v1/${PACKAGE_NAME}:decodeIntegrityToken` /** * Verifies a Play Integrity API token server-side and returns whether the * device meets basic integrity requirements. * * Called by [PlayIntegrityChecker] on app startup. Requires the caller to be * authenticated — the Firebase Auth token is verified automatically by the * Functions runtime. * * Setup required before this function works in production: * 1. Enable the Play Integrity API in your Google Cloud project. * 2. Link the Cloud project to your Play Console app (Play Console → * Setup → API access → Link to Cloud project). * 3. Grant the Cloud Functions service account the "Play Integrity API User" * role in IAM, or use a dedicated service account key via * GOOGLE_APPLICATION_CREDENTIALS. * * If the API is not yet configured the function fails-open (returns * passed: true) and logs the error, so unconfigured environments don't * block users. Firebase App Check remains the server-side gatekeeper. */ export const checkDeviceIntegrity = functions.https.onCall( async (data: { token?: string }, context) => { if (!context.auth) { throw new functions.https.HttpsError( 'unauthenticated', 'Caller must be authenticated.' ) } const token = data?.token if (!token || typeof token !== 'string') { throw new functions.https.HttpsError( 'invalid-argument', 'token is required.' ) } try { const verdicts = await decodeIntegrityToken(token) const passed = verdicts.includes('MEETS_DEVICE_INTEGRITY') || verdicts.includes('MEETS_STRONG_INTEGRITY') return { passed, verdicts } } catch (err) { console.error('[checkDeviceIntegrity] verification unavailable:', err) // Fail-open: treat as passed if the API is not yet configured. return { passed: true, verdicts: [], error: 'verification_unavailable' } } } ) interface IntegrityResponse { tokenPayloadExternal?: { deviceIntegrity?: { deviceRecognitionVerdict?: string[] } } } async function decodeIntegrityToken(token: string): Promise { const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/playintegrity'], }) const client = await auth.getClient() const response = await client.request({ url: PLAY_INTEGRITY_URL, method: 'POST', data: { integrity_token: token }, }) return ( response.data.tokenPayloadExternal?.deviceIntegrity ?.deviceRecognitionVerdict ?? [] ) }