// ── /.well-known/* — web-root, non-API endpoints ────────────────────────── // // Android App Links verification file (M9 follow-up, docs/android/APP_LINKS.md). // Served at the web root (outside /api/v1) because Android's Play/verifier fetch // it from a fixed path. It is gated by the `mobile_app_links_enabled` admin // setting: off ⇒ 404 (the app stays on the custom-scheme callback for this shard). // // The asserted package + fingerprint are constants of the ONE published app, not // per-shard: the same binary is verifiable against every shard that opts in. const settings = require('../model/settings/settings.model') const log = require('../utils/logger')('well-known') // Fixed identity of the published app. Overridable via env for a white-label build // that ships under a different package / release cert. const PACKAGE = (process.env.MOBILE_APP_PACKAGE || 'com.runicgateway.app').trim() // Release signing-cert SHA-256 fingerprint(s), comma-separated. Multiple entries // support cert rotation (old + new) and a debug + release cert during testing. // Colons and case are normalized to the upper-cased, colon-separated form the // Digital Asset Links spec expects. function fingerprints() { return (process.env.MOBILE_APP_CERT_SHA256 || '') .split(',') .map((s) => s.trim().toUpperCase()) .filter(Boolean) } let warnedNoFingerprint = false // GET /.well-known/assetlinks.json async function assetlinks(req, res) { // #swagger.ignore = true (web-root verification file, not part of the API surface) const enabled = await settings.isMobileAppLinksEnabled() // fail-closed on any error const fps = fingerprints() // Off, or on-but-unconfigured, both 404 — serving a statement with no fingerprint // asserts nothing and would only mislead the verifier. if (!enabled || fps.length === 0) { if (enabled && fps.length === 0 && !warnedNoFingerprint) { warnedNoFingerprint = true log.warn( 'mobile_app_links_enabled is ON but MOBILE_APP_CERT_SHA256 is unset — assetlinks.json 404s', ) } return res.status(404).json({ message: 'Not found' }) } // The OS/verifier re-fetch this; it changes only on a cert rotation. res.set('Cache-Control', 'public, max-age=3600') return res.json([ { relation: ['delegate_permission/common.handle_all_urls'], target: { namespace: 'android_app', package_name: PACKAGE, sha256_cert_fingerprints: fps, }, }, ]) } module.exports = { assetlinks }