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;
|