fix: Platzhalter-Keys nicht als echt werten + Crash-Schutz
- env.js: '…CHANGE-ME'-Platzhalter aus .env.example zählen als NICHT gesetzt. Vorher galt sk_test_CHANGE-ME als echter Stripe-Key → echter API-Call mit ungültigem Key → 401 → unhandledRejection → Server-Crash. - billing.js: /checkout in try/catch → 502 statt Empty-Reply/Crash. - index.js: globaler Express-Error-Handler + unhandledRejection-Guard, damit ein einzelner async-Fehler nie den ganzen Prozess killt. E2E verifiziert (Mock): register→checkout→instance, idempotent (1 sub/1 inst), 401 bei falschem PW, Server lebt nach allen Requests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -46,8 +46,12 @@ export const env = {
|
|||||||
|
|
||||||
// MOCK-Modus: ohne echte Stripe-/Rapport-Keys läuft alles lokal simuliert,
|
// MOCK-Modus: ohne echte Stripe-/Rapport-Keys läuft alles lokal simuliert,
|
||||||
// damit der End-to-End-Flow ohne externe Dienste testbar ist.
|
// damit der End-to-End-Flow ohne externe Dienste testbar ist.
|
||||||
export const stripeEnabled = !!env.stripe.secretKey && env.stripe.secretKey.startsWith("sk_");
|
// WICHTIG: Platzhalter-Werte aus .env.example (…CHANGE-ME) zählen als NICHT
|
||||||
export const provisioningMock = !env.rapport.apiUrl || !env.rapport.serviceKey;
|
// gesetzt — sonst würde ein sk_test_CHANGE-ME als echter Key durchgehen und
|
||||||
|
// echte Stripe-Calls mit ungültigem Key abfeuern (401 → Crash).
|
||||||
|
const isPlaceholder = (v) => !v || v.includes("CHANGE-ME");
|
||||||
|
export const stripeEnabled = !isPlaceholder(env.stripe.secretKey) && env.stripe.secretKey.startsWith("sk_");
|
||||||
|
export const provisioningMock = isPlaceholder(env.rapport.apiUrl) || isPlaceholder(env.rapport.serviceKey);
|
||||||
|
|
||||||
if (env.jwtSecret === "dev-insecure-secret-change-me") {
|
if (env.jwtSecret === "dev-insecure-secret-change-me") {
|
||||||
console.warn("⚠ JWT_SECRET nicht gesetzt — unsicheres Dev-Secret in Verwendung.");
|
console.warn("⚠ JWT_SECRET nicht gesetzt — unsicheres Dev-Secret in Verwendung.");
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ app.get("*", (req, res, next) => {
|
|||||||
res.sendFile(path.join(dist, "index.html"), (err) => err && next());
|
res.sendFile(path.join(dist, "index.html"), (err) => err && next());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Express-Fehlerhandler: fängt synchron geworfene Fehler aus Routen → 500
|
||||||
|
// statt stiller Empty-Reply.
|
||||||
|
app.use((err, _req, res, _next) => {
|
||||||
|
console.error("Unbehandelter Routen-Fehler:", err);
|
||||||
|
if (!res.headersSent) res.status(500).json({ error: "Interner Fehler." });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Letzte Verteidigungslinie: ein einzelner async-Fehler darf den Prozess nicht
|
||||||
|
// killen (im Dev-Test sonst "connection refused" für alle Folge-Requests).
|
||||||
|
process.on("unhandledRejection", (reason) => console.error("unhandledRejection:", reason));
|
||||||
|
|
||||||
app.listen(env.port, () => {
|
app.listen(env.port, () => {
|
||||||
console.log(`RAPPORT-HOST API läuft auf :${env.port} (Base: ${env.publicBaseUrl})`);
|
console.log(`RAPPORT-HOST API läuft auf :${env.port} (Base: ${env.publicBaseUrl})`);
|
||||||
});
|
});
|
||||||
|
|||||||
+29
-24
@@ -50,31 +50,36 @@ billingRouter.post("/checkout", requireAuth, async (req, res) => {
|
|||||||
const plan = getPlan(req.body?.planId);
|
const plan = getPlan(req.body?.planId);
|
||||||
if (!plan) return res.status(400).json({ error: "Unbekannter Plan." });
|
if (!plan) return res.status(400).json({ error: "Unbekannter Plan." });
|
||||||
|
|
||||||
// ── MOCK: sofort freischalten ──────────────────────────────────────────────
|
try {
|
||||||
if (!stripeEnabled) {
|
// ── MOCK: sofort freischalten ────────────────────────────────────────────
|
||||||
await fulfill({
|
if (!stripeEnabled) {
|
||||||
accountId: req.account.id,
|
await fulfill({
|
||||||
plan,
|
accountId: req.account.id,
|
||||||
stripeCustomerId: "mock_cus_" + req.account.id,
|
plan,
|
||||||
stripeSubscriptionId: "mock_sub_" + req.account.id + "_" + plan.id,
|
stripeCustomerId: "mock_cus_" + req.account.id,
|
||||||
status: "active",
|
stripeSubscriptionId: "mock_sub_" + req.account.id + "_" + plan.id,
|
||||||
periodEnd: new Date(Date.now() + 30 * 864e5),
|
status: "active",
|
||||||
});
|
periodEnd: new Date(Date.now() + 30 * 864e5),
|
||||||
return res.json({ mock: true, url: `${env.publicBaseUrl}/dashboard?provisioned=1` });
|
});
|
||||||
}
|
return res.json({ mock: true, url: `${env.publicBaseUrl}/dashboard?provisioned=1` });
|
||||||
|
}
|
||||||
|
|
||||||
// ── Stripe-Checkout-Session ────────────────────────────────────────────────
|
// ── Stripe-Checkout-Session ──────────────────────────────────────────────
|
||||||
if (!plan.stripePriceId) return res.status(500).json({ error: "Kein Stripe-Price für diesen Plan konfiguriert." });
|
if (!plan.stripePriceId) return res.status(500).json({ error: "Kein Stripe-Price für diesen Plan konfiguriert." });
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
mode: "subscription",
|
mode: "subscription",
|
||||||
line_items: [{ price: plan.stripePriceId, quantity: 1 }],
|
line_items: [{ price: plan.stripePriceId, quantity: 1 }],
|
||||||
customer_email: req.account.email,
|
customer_email: req.account.email,
|
||||||
client_reference_id: req.account.id,
|
client_reference_id: req.account.id,
|
||||||
metadata: { accountId: req.account.id, planId: plan.id },
|
metadata: { accountId: req.account.id, planId: plan.id },
|
||||||
success_url: `${env.publicBaseUrl}/dashboard?provisioned=1`,
|
success_url: `${env.publicBaseUrl}/dashboard?provisioned=1`,
|
||||||
cancel_url: `${env.publicBaseUrl}/plans?canceled=1`,
|
cancel_url: `${env.publicBaseUrl}/plans?canceled=1`,
|
||||||
});
|
});
|
||||||
res.json({ url: session.url });
|
res.json({ url: session.url });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("checkout fehlgeschlagen:", err.message);
|
||||||
|
res.status(502).json({ error: "Checkout fehlgeschlagen: " + err.message });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stripe-Webhook. Braucht RAW Body (Signaturprüfung) — in index.js so verdrahtet.
|
// Stripe-Webhook. Braucht RAW Body (Signaturprüfung) — in index.js so verdrahtet.
|
||||||
|
|||||||
Reference in New Issue
Block a user