All files / src/routes roadmap-auth.js

100% Statements 44/44
96.29% Branches 26/27
100% Functions 4/4
100% Lines 40/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109              1x 1x 1x     1x   1x           50x 8x 8x   42x       25x 25x 100x   25x         1x 45x 45x   38x     38x 30x 28x     20x         36x 16x         16x 6x 7x   7x   6x 6x         5x 5x             36x 11x               25x   2x     2x           1x 5x     4x     1x  
/**
 * Roadmap page authentication routes.
 *
 * GET  /roadmap/me      → check if ShyTalk account exists for authenticated user
 * POST /roadmap/signout → sign out acknowledgment
 */
 
const router = require('express').Router();
const { db } = require('../utils/firebase');
const log = require('../utils/log');
 
// Fields safe to expose on the roadmap profile
const SAFE_FIELDS = ['uniqueId', 'displayName', 'avatarUrl', 'profilePhotoUrl'];
 
const DOWNLOAD_LINKS = {
  android: 'https://play.google.com/store/apps/details?id=com.shyden.shytalk',
  ios: 'https://apps.apple.com/app/shytalk/id6741488545',
};
 
function requireAuth(req, res) {
  if (!req.auth || (!req.auth.uniqueId && !req.auth.uid)) {
    res.status(401).json({ error: 'Authentication required' });
    return true;
  }
  return false;
}
 
function pickSafeFields(data) {
  const safe = {};
  for (const key of SAFE_FIELDS) {
    Eif (key in data) safe[key] = data[key];
  }
  return safe;
}
 
// ─── GET /roadmap/me ────────────────────────────────────────────
 
router.get('/roadmap/me', async (req, res) => {
  try {
    if (requireAuth(req, res)) return;
 
    let userData = null;
 
    // Try direct lookup by uniqueId
    if (req.auth.uniqueId) {
      const userDoc = await db.doc(`users/${req.auth.uniqueId}`).get();
      if (userDoc.exists) {
        // Trust the authenticated uniqueId, NOT whatever the user-doc payload
        // claims — payload spread comes first so the trusted value wins.
        userData = { ...userDoc.data(), uniqueId: Number(req.auth.uniqueId) };
      }
    }
 
    // Fallback: lookup via identityMap using Firebase UID
    if (!userData && req.auth.uid) {
      const idSnap = await db
        .collection('identityMap')
        .where('firebaseUid', '==', req.auth.uid)
        .get();
 
      if (!idSnap.empty) {
        for (const idDoc of idSnap.docs) {
          const idData = idDoc.data();
          // Skip unlinked entries
          if (idData.unlinked) continue;
 
          const userDoc = await db.doc(`users/${idData.uniqueId}`).get();
          if (userDoc.exists) {
            // Same identity-pinning + numeric coercion as the direct path
            // above. `Number(...)` keeps the response shape consistent
            // across both auth paths (identityMap may legacy-store the FK
            // as a string).
            userData = { ...userDoc.data(), uniqueId: Number(idData.uniqueId) };
            break;
          }
        }
      }
    }
 
    // No ShyTalk account found
    if (!userData) {
      return res.status(404).json({
        error:
          'No ShyTalk account found. Download the app to create an account, then come back to login.',
        downloadLinks: DOWNLOAD_LINKS,
      });
    }
 
    // Return safe profile fields only
    res.json(pickSafeFields(userData));
  } catch (err) {
    log.error('roadmap-auth', 'Failed to check account', {
      error: err.message,
    });
    res.status(500).json({ error: 'Internal server error' });
  }
});
 
// ─── POST /roadmap/signout ──────────────────────────────────────
 
router.post('/roadmap/signout', (req, res) => {
  if (requireAuth(req, res)) return;
  // Sign out is handled client-side (Firebase Auth).
  // This endpoint acknowledges the sign out.
  res.json({ success: true });
});
 
module.exports = router;