Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
198b07d
stellar: validate signed XDR contents before submit; store expectedHa…
Banx17 Aug 12, 2026
edfb161
feat(stellar): publish Soroban giving-escrow contract id in stellar.toml
zeemscript Aug 13, 2026
0ef4ea2
fix(stellar): resolve Horizon endpoints lazily + network-aware default
zeemscript Aug 13, 2026
cef0b4f
feat(auth): authenticated change-password endpoint
zeemscript Aug 13, 2026
2a98de3
feat(auth): harden authentication against login lockout, breached pas…
abimbolaalabi Aug 16, 2026
16e6b6f
Feat/93 idempotency keys (#100)
Kaycee276 Aug 17, 2026
c7fe869
feat(auth): enforce resource ownership across mutating endpoints (#88…
BountySpaghetti Aug 17, 2026
2895a69
fix(security): stop logging OTP codes and verification tokens in emai…
samjay8 Aug 17, 2026
11c2915
fix(transactions): prevent TTL index from deleting confirmed purchase…
samjay8 Aug 17, 2026
e7fb634
feat(security): implement educator verification pipeline and content-…
abimbolaalabi Aug 17, 2026
92f6b25
feat(auth): signed service-to-service authentication for the AI servi…
Lspnjr1 Aug 17, 2026
da78382
feat(webhooks): signed outbound webhook event system (#45) (#107)
Lspnjr1 Aug 18, 2026
413afff
feat(security): implement TOTP two-factor authentication for admins a…
Unclebaffa Aug 18, 2026
9aa2629
Add scholarship escrow contract foundation (#108)
dotmantissa Aug 18, 2026
cbd640e
Improve application test coverage (#110)
dotmantissa Aug 19, 2026
ba4fe4f
Add dependency health checks (#112)
dotmantissa Aug 19, 2026
c2dae8a
Validate auth and Stellar requests (#109)
dotmantissa Aug 19, 2026
3a115ab
Secure book deletion authorization (#113)
dotmantissa Aug 19, 2026
b7cc4e9
feat(stellar): gift courses/books via claimable balances with expiry …
samjay8 Aug 19, 2026
1791019
feat(stellar): add idempotency protection to the Stellar payment endp…
samjay8 Aug 19, 2026
47fffd2
feat(stellar): validate Stellar config at startup and document the ma…
samjay8 Aug 19, 2026
90f325d
feat(stellar): fee-bump sponsorship with structural whitelist and spe…
ezzy156 Aug 19, 2026
beb6b9c
Merge origin/main into dev
BountySpaghetti Aug 19, 2026
5f17959
feat: add managed course categories (#118)
TS-mfon Aug 22, 2026
d308967
feat: add recurring sadaqah pledges (#119)
TS-mfon Aug 22, 2026
063c346
feat(stellar): real-time payment notifications over Socket.io (closes…
zeemscript Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ SENDLIB_API_URL=https://sendlib.samueltuoyo.com/api/send
# SendLib. Prefer a Workspace address on your own domain (e.g. no-reply@deenbridge.app).
EMAIL_FROM=no-reply@deenbridge.com

# Stellar blockchain network (testnet or mainnet)
# Stellar blockchain network (testnet or mainnet; "public" is accepted as an
# alias for mainnet). Validated fail-fast at boot — a mainnet flag paired
# with a testnet Horizon URL or USDC issuer aborts startup with the exact
# problem named. See docs/MAINNET.md for the full mainnet switch checklist.
STELLAR_NETWORK=testnet

# Resilient Horizon Client Configuration (Optional)
Expand All @@ -53,6 +56,27 @@ DONATION_WALLET_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
PLATFORM_FEE_PERCENT=0
PLATFORM_WALLET_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# ── Fee-bump sponsorship (#30) ────────────────────────────────────────────────
# The platform can pay a user's Stellar network fee by wrapping their signed
# transaction in a fee-bump. A user holding USDC but ~no XLM can then transact.
# All of these are OPTIONAL: with FEE_SPONSOR_ENABLED unset/false (the default)
# the feature is completely inert and the payment/donation flow is unchanged.
#
# Master switch. Leave false to disable sponsorship entirely.
FEE_SPONSOR_ENABLED=false
# DEDICATED fee-source secret (S…) for an account holding a small XLM float used
# ONLY to pay network fees. It MUST NOT be the donation or platform receiving
# wallet, and can never move user funds — it only signs the fee-bump wrapper.
# Required (and validated at boot) when FEE_SPONSOR_ENABLED=true. Never commit a
# real secret; keep it out of source control like the donation secret.
FEE_SPONSOR_SECRET=
# Per-transaction fee ceiling in stroops (default 1000000 = 0.1 XLM).
FEE_SPONSOR_MAX_FEE_STROOPS=1000000
# Total XLM-fee spend allowed per UTC day, in stroops (default 100000000 = 10 XLM).
FEE_SPONSOR_DAILY_CAP_STROOPS=100000000
# Max sponsored transactions per user per UTC day (default 10).
FEE_SPONSOR_PER_USER_DAILY_LIMIT=10

# SEP-1 stellar.toml (/.well-known/stellar.toml) — optional, omitted when blank
STELLAR_PLATFORM_PUBLIC_KEY=
ORG_NAME=
Expand Down Expand Up @@ -84,6 +108,31 @@ GIVING_ESCROW_CONTRACT_ID=
ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=30d

# ── Authentication abuse hardening (issue #89) ──────────────────────────────
# Progressive per-account login lockout. After LOGIN_MAX_ATTEMPTS consecutive
# failures the account is locked for an escalating backoff (base * 2^(excess)),
# capped at LOGIN_LOCKOUT_MAX_MS. Clears automatically on success/backoff.
LOGIN_MAX_ATTEMPTS=5
LOGIN_LOCKOUT_BASE_MS=60000
LOGIN_LOCKOUT_MAX_MS=86400000

# Breached-password check (HaveIBeenPwned range API). Only the 5-char SHA-1
# prefix is sent; fails OPEN on outage so signups/resets never break.
# HIBP_RANGE_URL=https://api.pwnedpasswords.com/range/
# HIBP_TIMEOUT_MS=2000

# Per-email signup / verification-resend throttle (survives IP rotation).
RATE_LIMIT_EMAIL_AUTH_MAX=20
RATE_LIMIT_EMAIL_AUTH_WINDOW_MS=900000
# RATE_LIMIT_EMAIL_AUTH_DISABLE=true

# Optional captcha gate for /register and /resend-verification. No-op when
# unset; fails OPEN on provider outage. Supports hCaptcha (default) and
# Google reCAPTCHA v2/v3 via CAPTCHA_VERIFY_URL override.
# CAPTCHA_SECRET_KEY=
# CAPTCHA_VERIFY_URL=https://hcaptcha.com/siteverify
# CAPTCHA_TIMEOUT_MS=5000

# Redis Configuration (optional - app works without Redis but with reduced performance)
# Option 1: Use REDIS_URL for full connection string (recommended for cloud services)
# REDIS_URL=redis://username:password@host:port
Expand All @@ -106,3 +155,31 @@ JOBS_ENABLED=true
QUEUE_DRIVER=mongo
JOBS_DASHBOARD_TOKEN=replace_with_a_long_random_token

# Service-to-service auth for the AI service (dnb-ai). A JSON array of signed,
# scoped, rotatable keys keyed by `kid`. REQUIRED in production (boot fails
# fast if missing); optional in dev/test. Keep >=1 entry active; to rotate,
# add a new active kid, deploy, switch dnb-ai over, then set the old kid
# "active": false. See docs/service-to-service-auth.md.
# AI_SERVICE_KEYS=[{"kid":"k1","secret":"replace_with_a_long_random_secret","scopes":["ai:read-content"],"active":true}]


# ── Outbound webhooks (issue #45) ────────────────────────────────────────────
# Webhook signing secrets are stored ENCRYPTED at rest (AES-256-GCM). This key
# derives the encryption key (SHA-256). REQUIRED in production (boot fails fast
# if missing); a fixed dev fallback is used in development/test. Rotating this
# invalidates all stored secrets — rotate per-endpoint secrets via the API.
# WEBHOOK_SECRET_ENCRYPTION_KEY=replace_with_a_long_random_value
# Enable the interval delivery worker on this process (like INGESTION_WORKER_ENABLED).
WEBHOOK_WORKER_ENABLED=false
# Delivery loop poll interval (ms).
WEBHOOK_POLL_INTERVAL_MS=5000
# Total attempts before a delivery is dead-lettered.
WEBHOOK_MAX_ATTEMPTS=6
# Consecutive dead deliveries that auto-disable an endpoint.
WEBHOOK_AUTO_DISABLE_THRESHOLD=5
# Max random jitter (ms) added to each backoff delay.
WEBHOOK_BACKOFF_JITTER_MS=30000
# Per-request HTTP timeout for delivery POSTs (ms).
WEBHOOK_HTTP_TIMEOUT_MS=10000
# Payload envelope version advertised to consumers.
WEBHOOK_API_VERSION=2025-01-01
47 changes: 42 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

services:
mongodb:
image: mongo:7
image: mongo:6.0
ports:
- 27017:27017

Expand All @@ -24,12 +24,23 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Wait for MongoDB
run: |
for i in $(seq 1 30); do
if nc -z localhost 27017 2>/dev/null || (exec 6<>/dev/tcp/localhost/27017) 2>/dev/null; then
echo "MongoDB is listening on port 27017"
break
fi
echo "Waiting for MongoDB..."
sleep 1
done

- name: Run tests
run: npm test
env:
Expand All @@ -41,16 +52,19 @@ jobs:
CLOUDINARY_CLOUD_NAME: ci_test_cloud
CLOUDINARY_API_KEY: ci_test_key
CLOUDINARY_API_SECRET: ci_test_secret_that_should_not_leak

build:
name: Syntax and Boot Check
runs-on: ubuntu-latest

services:
mongodb:
image: mongo:7
image: mongo:6.0
ports:
- 27017:27017
redis:
image: redis:7
ports:
- 6379:6379

steps:
- name: Checkout code
Expand All @@ -59,12 +73,34 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Wait for MongoDB
run: |
for i in $(seq 1 30); do
if nc -z localhost 27017 2>/dev/null || (exec 6<>/dev/tcp/localhost/27017) 2>/dev/null; then
echo "MongoDB is listening on port 27017"
break
fi
echo "Waiting for MongoDB..."
sleep 1
done

- name: Wait for Redis
run: |
for i in $(seq 1 30); do
if nc -z localhost 6379 2>/dev/null || (exec 6<>/dev/tcp/localhost/6379) 2>/dev/null; then
echo "Redis is listening on port 6379"
break
fi
echo "Waiting for Redis..."
sleep 1
done

- name: Check syntax of all source files
run: |
find . -name "*.js" -not -path "./node_modules/*" -print0 \
Expand Down Expand Up @@ -94,3 +130,4 @@ jobs:
CLOUDINARY_CLOUD_NAME: ci_test_cloud
CLOUDINARY_API_KEY: ci_test_key
CLOUDINARY_API_SECRET: ci_test_secret_that_should_not_leak
REDIS_URL: redis://localhost:6379
37 changes: 37 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Contracts CI

on:
pull_request:
branches: [main, dev]
paths:
- "contracts/**"
- ".github/workflows/contracts.yml"
push:
branches: [main, dev]
paths:
- "contracts/**"
- ".github/workflows/contracts.yml"

jobs:
rust:
name: Rust contract checks
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none
components: rustfmt, clippy

- name: Check formatting
run: cargo fmt --manifest-path contracts/Cargo.toml -- --check

- name: Run clippy
run: cargo clippy --manifest-path contracts/Cargo.toml --all-targets -- -D warnings

- name: Run contract tests
run: cargo test --manifest-path contracts/Cargo.toml
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The platform is composed of three services:
- 🎓 **Course Management** — create, enroll, review, and track courses
- 📚 **Digital Library** — upload, purchase, and read Islamic books
- ⭐ **Stellar Payments** — USDC payment initialize → sign → submit → on-chain verify flow
- ⛽ **Fee Sponsorship** — optional platform-paid network fees via fee-bump, with a structural whitelist and spend caps ([docs](docs/fee-sponsorship.md))
- 👛 **Wallet Management** — connect Freighter, xBull, or Albedo; balance and trustline checks
- 💬 **Real-time** — Socket.io messaging and notifications
- ☁️ **Media** — Cloudinary uploads for avatars, covers, books, and reels
Expand Down Expand Up @@ -76,12 +77,14 @@ The API runs at `http://localhost:5000`.
| `PORT` | Server port (default `5000`) |
| `MONGO_URI` | MongoDB connection string |
| `JWT_SECRET` | Secret for signing tokens (32+ chars) |
| `STELLAR_NETWORK` | `testnet` or `mainnet` |
| `STELLAR_NETWORK` | `testnet` or `mainnet` (`public` accepted; validated at boot) |
| — | **Switching to mainnet? See [docs/MAINNET.md](docs/MAINNET.md)** — env changes, creator trustlines, smoke-test checklist |
| `CLOUDINARY_*` | Cloudinary credentials for media uploads |
| `QUEUE_DRIVER` | `mongo` (durable production default) or `inline` (tests/CI) |
| `JOBS_ENABLED` | Start background workers; defaults to `true` |
| `JOBS_DASHBOARD_TOKEN` | Bearer token protecting `/admin/jobs` |
| `STELLAR_PLATFORM_PUBLIC_KEY` | Public key published in `stellar.toml` `ACCOUNTS[]` |
| `FEE_SPONSOR_ENABLED` | Turn on platform-paid network fees (fee-bump). Off by default; when on, `FEE_SPONSOR_SECRET` is validated at boot ([docs](docs/fee-sponsorship.md)) |

See `.env.example` for the full list.

Expand Down Expand Up @@ -142,3 +145,6 @@ Read **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full workflow, coding standa
- 🌐 Website: [dnb-frontend.vercel.app](https://dnb-frontend.vercel.app)
- 🐦 X/Twitter: [@deen_bridge](https://x.com/deen_bridge)
- 🏢 Organization: [github.com/Deen-Bridge](https://github.com/Deen-Bridge)
# Course categories

Seed the curated Islamic-discipline taxonomy with `npm run seed:categories`. Existing free-text course and book categories can be linked without removing their legacy string values by running `npm run migrate:categories`. Both commands are idempotent and require `MONGO_URI`.
Loading
Loading