🔧 ProgrammierungGitHub Release: dependabot/dependabot-core v0.395.0 (07.09.2026)(07.09.2026 um 15:21 Uhr)
🔧 ProgrammierungGitHub Release: dependabot/dependabot-core v0.396.0 (14.09.2026)(14.09.2026 um 19:05 Uhr)
🔧 ProgrammierungGitHub Release: langwatch/scenario vpython/v1.4.0 (02.09.2026)(02.09.2026 um 04:47 Uhr)
🔧 ProgrammierungGitHub Release: langwatch/scenario vjavascript/v1.5.0 (02.09.2026)(02.09.2026 um 04:47 Uhr)
🔧 ProgrammierungGitHub Release: langwatch/scenario vjavascript/v1.6.0 (06.09.2026)(06.09.2026 um 17:45 Uhr)
🔧 Programmierungclawpatrol v0.5.10(13.09.2026 um 02:54 Uhr)
⚠️ Malware / Trojaner / VirenCAPE-parsers v0.1.69(13.09.2026 um 04:14 Uhr)
⚠️ Malware / Trojaner / Virendarknet-mcp-server(13.09.2026 um 04:55 Uhr)
🐧 Linux Tippsazurelinux v3.0.20260909-3.0(13.09.2026 um 09:51 Uhr)
🕵️ Sicherheitslückenatomicvulns(13.09.2026 um 10:36 Uhr)
🔧 ProgrammierungGitHub Release: dependabot/dependabot-core v0.395.0 (07.09.2026)(07.09.2026 um 15:21 Uhr)
🔧 ProgrammierungGitHub Release: dependabot/dependabot-core v0.396.0 (14.09.2026)(14.09.2026 um 19:05 Uhr)
🔧 ProgrammierungGitHub Release: langwatch/scenario vpython/v1.4.0 (02.09.2026)(02.09.2026 um 04:47 Uhr)
🔧 ProgrammierungGitHub Release: langwatch/scenario vjavascript/v1.5.0 (02.09.2026)(02.09.2026 um 04:47 Uhr)
🔧 ProgrammierungGitHub Release: langwatch/scenario vjavascript/v1.6.0 (06.09.2026)(06.09.2026 um 17:45 Uhr)
🔧 Programmierungclawpatrol v0.5.10(13.09.2026 um 02:54 Uhr)
⚠️ Malware / Trojaner / VirenCAPE-parsers v0.1.69(13.09.2026 um 04:14 Uhr)
⚠️ Malware / Trojaner / Virendarknet-mcp-server(13.09.2026 um 04:55 Uhr)
🐧 Linux Tippsazurelinux v3.0.20260909-3.0(13.09.2026 um 09:51 Uhr)
🕵️ Sicherheitslückenatomicvulns(13.09.2026 um 10:36 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 8 Min Lesezeit
0

Custom e-commerce on Firebase: catalog, atomic orders and admin panel

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

How I built a fully custom e-commerce from scratch using Firebase Realtime Database and Netlify — no off-the-shelf platforms: per-product pricing variants, an admin panel protected by a PBKDF2 hash with no password in the source code, duplicate-proof atomic order numbering, and automatic vendor identification via a cross-app token.









The context



A POS terminal reseller needed a showcase site with an integrated e-commerce. The requirements were clear: no payment gateway to integrate (checkout with ready-made payment links or bank transfer details), a catalog of around seven products with selectable pricing variants, and the ability for their sales team to place orders directly from the site — with the salesperson’s name automatically saved on each order, read from their identity already authenticated in the internal management panel.



The decision to skip off-the-shelf e-commerce platforms was deliberate: something fully custom was needed, integrable with the Firebase ecosystem already in use for the management panel, and controllable in every detail. Resulting stack: vanilla HTML/CSS/JS, Firebase Realtime Database for catalog and orders, Firebase Storage for images and PDFs, Netlify for hosting and serverless functions.









Architecture: two separate Firebase projects



The first key decision was keeping the e-commerce database completely separate from the internal management panel’s database. With around forty orders per day (about fifteen thousand a year), merging them into the same Firebase project would have created risk of contamination between commercial data and administrative data (salaries, budgets, employee records).



The e-commerce Realtime Database structure:




CODE
{
"products": {
"{id}": {
"name": "Product A",
"price": 0,
"image": "https://...",
"activeTariffs": ["tariff_id_1", "tariff_id_2"]
}
},
"tariffs": {
"{id}": {
"name": "Plan A", "code": "PLAN-CODE",
"fee": "€0/month", "commission": "1.00%",
"pdfUrl": "https://firebasestorage..."
}
},
"orders": {
"{pushKey}": {
"number": 1, "vendor": "Alice",
"customer": { "name": "...", "email": "..." },
"items": [{ "product": "Product A", "tariff": "Plan A" }]
}
},
"counters": {
"lastOrderNumber": 42
}
}












Pricing variants: global archive + per-product assignment



The most interesting data modeling challenge was handling pricing plans. Each POS terminal can be paired with multiple pricing tiers, but the same plans repeat across different products. Embedding tariffs inside each product would mean duplicating data and updating multiple nodes every time a plan changes.



The solution was a global archive at /tariffs with all plans defined once, and per product an activeTariffs array holding the IDs of applicable plans. The admin can check or uncheck plans per product with a checkbox, without touching the plan data itself. Products without plans (e.g. a printer) use the noTariffs: true flag instead.



On the frontend, when the user selects a plan from the product sheet, a detail box updates in real time showing all information: commissions, monthly fee, linked account and card, economic conditions, included services, and — highlighted in monospace — the provider plan code to communicate to the customer.









Order numbering: Firebase atomic transaction



Progressive order numbering sounds trivial, but with multiple vendors placing orders concurrently it becomes a classic race condition problem. If two vendors press “Confirm” at the same instant, both read lastOrderNumber = 41 and write 42 — one order vanishes.



Firebase Realtime Database solves this with atomic transactions: the runTransaction() function attempts the update, and if the value changed on the server in the meantime, it automatically retries until it succeeds with the correct value.




CODE
// Atomic counter increment — zero duplicates even with concurrent vendors
async function submitOrder(orderData) {
const counterRef = ref(db, 'counters/lastOrderNumber');

const { snapshot } = await runTransaction(counterRef, currentValue => {
// currentValue is null on the first order
return (currentValue || 0) + 1;
});

const orderNumber = snapshot.val(); // guaranteed unique

const ordersRef = ref(db, 'orders');
await push(ordersRef, {
number: orderNumber,
vendor: orderData.vendorName || 'Anonymous',
customer: orderData.customer,
items: orderData.items,
method: orderData.paymentMethod,
ts: Date.now()
});
}







Note: The /counters node in Firebase rules has ".read": false — it’s not readable by the client. Only the transaction writes to that node; the number comes back in the transaction response itself.










Admin panel: PBKDF2 with no password in the source



The admin panel lets you manage products (name, price, images, assigned plans) and the global pricing archive (provider codes, fees, conditions, T&C PDFs). It must be accessible only to those who know the password, but without a dedicated backend.



Storing the password in plain text in the source code is obviously out of the question — anyone reading the HTML file sees it. But even hashing isn’t enough with plain MD5 or SHA-256: they’re fast to brute-force. The solution is PBKDF2 (Password-Based Key Derivation Function 2), which applies the hash function thousands of times making it computationally expensive to attack.



The browser’s Web Crypto API exposes PBKDF2 natively. At first setup, the hash is generated outside the site (or with a small Node script) and only the hash and salt are put in the code — never the original password.




CODE
// In the source: only salt and hash. The password is never there.
const STORED_SALT = "a7f3..."; // randomly generated at setup
const STORED_HASH = "e9b2..."; // PBKDF2 of the real password

async function verifyPassword(input) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw', enc.encode(input), 'PBKDF2', false, ['deriveBits']
);
const derived = await crypto.subtle.deriveBits({
name: 'PBKDF2',
salt: hexToBuffer(STORED_SALT),
iterations: 310000, // expensive to brute-force
hash: 'SHA-256'
}, keyMaterial, 256);

return bufferToHex(derived) === STORED_HASH;
}






After a successful verification, the admin performs an anonymous Firebase sign-in in the background: this allows the database Security Rules to recognize the user as authenticated (auth != null) and authorize writes on products and plans. The catalog remains publicly readable, orders are protected.









The zero-value trap in JavaScript



During development I encountered a subtle bug worth documenting: a terminal’s price was zero (included in the monthly fee), but the site kept showing “price to be defined” even after saving it correctly.



The problem was JavaScript’s falsy evaluation. In two places in the code I was using patterns like:




CODE
// ❌ WRONG: 0 is falsy, treated as absent
const price = p.price || null; // 0 → null → Firebase deletes the field
const display = p.price ? ... : 'price to be defined'; // 0 → else branch

// In the admin HTML template:
`value="${p.price || ''}"` // 0 || '' → '', field appears empty on reload

// ✅ CORRECT: != null is true for any number, including zero
const price = p.price != null ? p.price : null;
const display = p.price != null ? ... : 'price to be defined';
`value="${p.price != null ? p.price : ''}"`






The bug manifested in three different places with different effects: during save (the field was deleted by Firebase because it received null), in the admin template (the field appeared empty on reload and the next save wrote null again), and in the frontend display (showing the fallback instead of the price). The fix is always the same: use != null instead of || or a ternary without a guard.









Content Security Policy and Firebase: dynamic subdomains



Adding a Content Security Policy is good security practice, but Firebase Realtime Database uses long-polling as a fallback channel alongside WebSockets, and the subdomains it generates are dynamic and unpredictable (e.g. s-gke-euw1-nssi1-1.europe-west1.firebasedatabase.app). A CSP in an HTML <meta> tag doesn’t support subdomain wildcards, so Firebase was silently blocked.



The solution is moving the CSP from HTML to Netlify’s HTTP headers in netlify.toml, where wildcards work correctly:




CODE
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = """
default-src 'self';
script-src 'self' 'unsafe-inline'
https://www.gstatic.com
https://cdnjs.cloudflare.com;
connect-src 'self'
https://*.firebasedatabase.app
wss://*.firebasedatabase.app
https://*.googleapis.com
https://firebasestorage.googleapis.com;
img-src 'self' data: blob:
https://firebasestorage.googleapis.com;
"""

X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Strict-Transport-Security = "max-age=31536000; includeSubDomains"







⚠️ <meta http-equiv="Content-Security-Policy"> tags don’t support subdomain wildcards in script-src. For Firebase — and any service using dynamic subdomains — always configure the CSP in HTTP server headers. On Netlify, that means netlify.toml.










Security: Firebase rules and architecture



Real protection isn’t about hiding buttons in JavaScript, but in Firebase Security Rules. The final structure:




CODE
{
"rules": {
"products": {
".read": true,
".write": "auth != null"
},
"tariffs": {
".read": true,
".write": "auth != null"
},
"orders": {
".read": "auth != null",
".write": "auth != null"
},
"counters": {
".read": false,
".write": "auth != null"
}
}
}






The next step is replacing the generic auth != null on the orders section with a Firebase custom claim (role: 'vendor') assigned by the Netlify Function that validates the management panel token. This way only vendors with a valid panel-issued token will have access to orders, not just any authenticated user.









The result



A fully custom e-commerce, without off-the-shelf platforms, built on a familiar stack: Firebase for data, Netlify for deployment, vanilla JS for everything else. The vendor opens the site from the management panel with a token in the URL, gets recognized automatically, selects the terminal and plan, fills in customer details, and the order lands on Firebase with a guaranteed-unique sequential number and their name attached.



The most instructive part of the project wasn’t the technical complexity, but how many architectural decisions depend on seemingly simple requirements: separating databases, keeping plans global, using transactions instead of simple writes, moving the CSP from HTML headers to HTTP headers. Each choice prevented a problem that would have surfaced in production.






Originally published on roversia.it

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
GitHub Release: crowdsecurity/crowdsec v1.8.0-rc2 (25.08.2026)
1 Quelle
clawpatrol v0.5.10
1 Quelle
CAPE-parsers v0.1.69
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Custom e-commerce on Firebase: catalog, atomic orders and admin panel

Thematisch verwandte Begriffe: Custom, ecommerce, Firebase, catalog · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...