File size: 2,555 Bytes
45dc2a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Single shared firebase_admin App instance. backend/firestore_client.py (real Firestore
branch) and backend/auth.py (real token verification) both use this instead of each
initializing their own — firebase_admin.initialize_app() raises if called twice for the
same app name, so this is a real requirement, not just tidiness.

Only imported/called when LOCAL_MODE=false or REAL_AUTH_ENABLED=true — never during the
test suite (backend/tests/conftest.py forces both off), so tests never need real credentials.
"""

from __future__ import annotations

import json
import os
import re

import firebase_admin
from firebase_admin import credentials

_app: firebase_admin.App | None = None

_PEM_HEAD = "-----BEGIN PRIVATE KEY-----"
_PEM_TAIL = "-----END PRIVATE KEY-----"
_PEM = re.compile(r"-----BEGIN\s*PRIVATE\s*KEY-----(.*?)-----END\s*PRIVATE\s*KEY-----", re.S)


def service_account_info(raw: str) -> dict:
    """
    Reads the key from the text of FIREBASE_SERVICE_ACCOUNT_JSON. Pasting a key into a host's
    secret box often damages the line breaks inside "private_key" (they become the two characters
    \\n, or spaces), and the key then cannot be read at all. Repair that here instead of failing.
    """
    text = raw.strip()
    if len(text) > 1 and text[0] == text[-1] and text[0] in "'\"":
        text = text[1:-1]
    info = json.loads(text, strict=False)
    key = str(info.get("private_key", "")).replace("\\n", "\n")
    # Rebuild the key from its body. The header may have lost its spaces too
    # ("-----BEGINPRIVATEKEY-----") when every space was stripped from the pasted text.
    found = _PEM.search(key)
    if found:
        body = "".join(found.group(1).split())
        lines = [body[i:i + 64] for i in range(0, len(body), 64)]
        key = "\n".join([_PEM_HEAD, *lines, _PEM_TAIL]) + "\n"
    info["private_key"] = key
    return info


def get_app() -> firebase_admin.App:
    global _app
    if _app is None:
        # A host that only offers environment variables (the Hugging Face Space) passes the key's
        # content in FIREBASE_SERVICE_ACCOUNT_JSON; a developer's machine points at the file.
        key_json = os.getenv("FIREBASE_SERVICE_ACCOUNT_JSON", "").strip()
        if key_json:
            cred = credentials.Certificate(service_account_info(key_json))
        else:
            cred = credentials.Certificate(
                os.getenv("GOOGLE_APPLICATION_CREDENTIALS", "./secrets/firebase-service-account.dev.json")
            )
        _app = firebase_admin.initialize_app(cred)
    return _app