Node.js/Express-Server als Ersatz für Cloudflare Pages Functions + D1 (läuft auf eigenem Server statt Cloudflare)
Build & Deploy / deploy (push) Waiting to run

This commit is contained in:
qcigano
2026-08-05 15:06:17 +02:00
parent 53e7bb158a
commit b8b8ec7b7a
19 changed files with 1322 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
/* =====================================================================
lib/oauth-handlers.js — gemeinsame Start-/Callback-Logik für alle OAuth-Anbieter. 1:1 portiert
aus functions/_shared/oauth-handlers.js, auf Express (req/res) statt Cloudflare
Request/Response umgestellt.
===================================================================== */
import {
erzeugeZufallswert,
codeChallengeFuer,
baueAutorisierungsUrl,
istProviderKonfiguriert,
tauscheCodeGegenToken,
holeNutzerprofil,
} from "./oauth.js";
import { signCustomerSession, setCustomerCookie, CUSTOMER_SESSION_TAGE } from "./customer-auth.js";
import { db } from "../db.js";
const STATE_COOKIE = "vandiy_oauth_state";
const VERIFIER_COOKIE = "vandiy_oauth_verifier";
const LANG_COOKIE = "vandiy_oauth_lang";
const OAUTH_TEMP_MAXAGE = 600; // Sekunden
const ERLAUBTE_SPRACHEN = ["", "en", "ch", "fr"];
function langPrefix(lang) {
return ERLAUBTE_SPRACHEN.includes(lang) ? lang : "";
}
function originVon(req) {
return `${req.protocol}://${req.get("host")}`;
}
/** GET /api/auth/:provider/start */
export async function starteOAuth(req, res, providerName) {
const origin = originVon(req);
const lang = langPrefix(req.query.lang || "");
if (!istProviderKonfiguriert(providerName)) {
return res.redirect(302, `${origin}/${lang ? lang + "/" : ""}konto/?login_error=${providerName}_unconfigured`);
}
const state = erzeugeZufallswert(24);
const codeVerifier = erzeugeZufallswert(48);
const codeChallenge = await codeChallengeFuer(codeVerifier);
const redirectUri = `${origin}/api/auth/${providerName}/callback`;
const authUrl = baueAutorisierungsUrl(providerName, { redirectUri, state, codeChallenge });
const cookiePath = `/api/auth/${providerName}`;
const cookieOpts = { path: cookiePath, maxAge: OAUTH_TEMP_MAXAGE * 1000, httpOnly: true, secure: true, sameSite: "lax" };
res.cookie(STATE_COOKIE, state, cookieOpts);
res.cookie(VERIFIER_COOKIE, codeVerifier, cookieOpts);
res.cookie(LANG_COOKIE, lang, cookieOpts);
return res.redirect(302, authUrl);
}
/** GET /api/auth/:provider/callback */
export async function verarbeiteOAuthCallback(req, res, providerName) {
const origin = originVon(req);
const code = req.query.code;
const state = req.query.state;
const errorParam = req.query.error;
const lang = langPrefix(req.cookies?.[LANG_COOKIE] || "");
const kontoPfad = `${origin}/${lang ? lang + "/" : ""}konto/`;
const cookiePath = `/api/auth/${providerName}`;
function loescheTempCookies() {
res.clearCookie(STATE_COOKIE, { path: cookiePath });
res.clearCookie(VERIFIER_COOKIE, { path: cookiePath });
res.clearCookie(LANG_COOKIE, { path: cookiePath });
}
function fehlerRedirect(grund) {
loescheTempCookies();
return res.redirect(302, `${kontoPfad}?login_error=${grund}`);
}
if (errorParam) return fehlerRedirect(`${providerName}_denied`);
if (!code || !state) return fehlerRedirect(`${providerName}_invalid`);
const stateCookie = req.cookies?.[STATE_COOKIE];
const verifierCookie = req.cookies?.[VERIFIER_COOKIE];
if (!stateCookie || stateCookie !== state || !verifierCookie) {
return fehlerRedirect(`${providerName}_state_mismatch`);
}
if (!process.env.CUSTOMER_SESSION_SECRET) {
return fehlerRedirect(`${providerName}_backend_unconfigured`);
}
try {
const redirectUri = `${origin}/api/auth/${providerName}/callback`;
const accessToken = await tauscheCodeGegenToken(providerName, { code, redirectUri, codeVerifier: verifierCookie });
const profil = await holeNutzerprofil(providerName, accessToken);
const now = new Date().toISOString();
const bestehend = db
.prepare(`SELECT id FROM customers WHERE provider = ? AND provider_user_id = ?`)
.get(providerName, profil.providerUserId);
let customerId;
if (bestehend) {
customerId = bestehend.id;
db.prepare(`UPDATE customers SET email = ?, name = ?, updated_at = ?, last_login_at = ? WHERE id = ?`)
.run(profil.email, profil.name, now, now, customerId);
} else {
const insert = db
.prepare(
`INSERT INTO customers (created_at, updated_at, last_login_at, provider, provider_user_id, email, name) VALUES (?,?,?,?,?,?,?)`
)
.run(now, now, now, providerName, profil.providerUserId, profil.email, profil.name);
customerId = insert.lastInsertRowid;
}
const sessionToken = await signCustomerSession(customerId, process.env.CUSTOMER_SESSION_SECRET);
setCustomerCookie(res, sessionToken, CUSTOMER_SESSION_TAGE * 24 * 60 * 60);
loescheTempCookies();
return res.redirect(302, `${origin}/${lang ? lang + "/" : ""}konto/angemeldet/`);
} catch (err) {
return fehlerRedirect(`${providerName}_failed`);
}
}