Security-only release per ADR-0001. Closes all six ship-blockers from the post-v1.5.0 audit: PSEC-001 (no rate limiting), PSEC-002 (account enumeration via separate error messages), PSEC-003 (registration auto-login with no email verification), PSEC-004 (ungated "My Data" templates), PSEC-005 (CSV formula injection on subscriber export), PSEC-006 (no honeypot/CAPTCHA on public forms). Adds inc/config.php (single source of truth for all security thresholds, filterable via peptide_starter_security_config), inc/rate-limiter.php (Peptide_Starter_Rate_Limiter + Cloudflare-aware client IP resolver, transient-backed, never stores raw IPs), and inc/email-verification.php (43-char token, 24h TTL, /verify rewrite, rate-limited resend endpoint). Auth handlers rewritten to: unify all failure messages, rate-limit before any expensive check, drop registrations with a filled honeypot silently, stop auto-login on register, send a verification email instead. The require_login() helper lives in inc/helpers.php and is applied at the top of page-tracker.php, page-subject-log.php, and page-protocol-builder.php; page-calculator.php stays open (no PII, no writes). Newsletter signup gains: consent checkbox, privacy link, honeypot, autoload=false on the wp_options blob, unsubscribe token stored per row, /newsletter-unsubscribe route, merged success+duplicate into a single "ok" state. Admin export runs every cell through peptide_starter_csv_safe() which prefixes leading =/+/-/@/tab/CR with a quote. Contact handler hardens against email-header injection (rejects name values with \r \n , < >), rate-limits, honors honeypot, tags mail failures with a request ID for correlation without logging message content. Adds Tools → Mail Test (inc/mail-diagnostic.php): a permanent wp_mail probe so we can check Hostinger outbound deliverability any time it looks flaky. Adds a one-time admin_init migration that enrols pre-v1.5.1 subscribers into the verification flow (currently a no-op — zero users — but guarded for the future). Other cleanup from the audit: fallback primary menu moved out of header.php into inc/helpers.php (redeclaration risk), placeholder footer language links replaced with "Translations coming soon", footer copyright now reads through the customizer getter, settings-panel gains a proper Tab/Shift+Tab focus trap and saves/restores body overflow, documentation heading IDs slugified from heading text with collision counters, JS files standardized on const/let. Six new PHPUnit suites cover rate limiter lifecycle, auth handler error unification and rate limits, email verification token generation and expiry, contact handler header injection + rate limit + honeypot, newsletter signup consent + CSV safety, and require_login gate behavior. ARCHITECTURE.md and CONVENTIONS.md updated in the same commit — new module layout, data flows, security patterns for rate-limiting, honeypotting, gating, and CSV-safe export. Version bumped to 1.5.1 in style.css and functions.php. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
Peptide Starter Theme — Architecture
Overview
Peptide Starter (slug: peptide-starter) is a premium classic WordPress
theme for peptiderepo.com — a scientific peptide reference database. It
assembles a modern, accessible frontend from a monolithic stylesheet, a
handful of vanilla-JS modules, and a collection of PHP modules under
inc/. The theme integrates with six companion plugins through
shortcodes (graceful degradation via shortcode_exists), coordinates
dark mode via CSS custom properties + a data-theme attribute, and — as
of v1.5.1 — runs a self-contained abuse-control layer (rate limiting,
honeypots, email verification) over its frontend auth surface.
Directory Structure
peptide-starter/
├── style.css # All theme CSS. Section 26 = v1.5.1 additions.
├── functions.php # Theme setup entry point (<250 lines).
│ # - Constants, feature module loading (order matters)
│ # - Theme support + nav menus
│ # - Script/style enqueue (conditional per template)
│ # - Newsletter signup POST handler
│ # - Inline dark mode flash preventer in wp_head
│
├── inc/
│ ├── config.php # [v1.5.1] Security config — single source of truth.
│ │ # Every threshold lives here; filterable.
│ ├── helpers.php # Nav walker + menu fallback, logo, getters,
│ │ # pagination, honeypot renderer, csv_safe,
│ │ # require_login gate, safe referer.
│ ├── rate-limiter.php # [v1.5.1] Peptide_Starter_Rate_Limiter +
│ │ # peptide_starter_get_client_ip (CF-aware).
│ ├── email-verification.php # [v1.5.1] Token send + verify route + resend
│ │ # endpoint + user_is_verified().
│ ├── customizer.php # Customizer sections/settings/controls.
│ ├── auth-handlers.php # AJAX login + register — unified errors,
│ │ # honeypot, rate limit, no auto-login.
│ ├── contact-handler.php # AJAX contact — header-injection guard,
│ │ # honeypot, rate limit, request IDs in mail.
│ ├── newsletter-admin.php # Subscriber viewer + CSV-safe export +
│ │ # /newsletter-unsubscribe route.
│ ├── mail-diagnostic.php # [v1.5.1] Tools → Mail Test — wp_mail probe.
│ └── page-setup.php # Auto-create pages on activation +
│ # v1.5.0 user migration (enrol into verify).
│
├── header.php # Site header, nav, icons, search overlay.
├── footer.php # Widget grid + copyright + settings panel.
├── front-page.php # Hero → module cards → plugin feeds.
│
├── page-calculator.php # OPEN — reconstitution calculator (no PII).
├── page-protocol-builder.php # GATED — require_login (builds saved protocols).
├── page-tracker.php # GATED — require_login (per-user tracking).
├── page-subject-log.php # GATED — require_login (user lab data).
├── page-directory.php # Peptide directory.
├── page-documentation.php # Two-column docs with ToC sidebar.
├── page-science-feed.php # Science feed + newsletter signup.
├── page-profile.php # Profile + verify-required banner + resend.
├── page-auth.php # Sign In / Register forms (honeypotted).
├── page.php, index.php, 404.php # Standard fallbacks.
├── single-peptide.php # Peptide CPT detail.
├── archive-peptide.php # Peptide CPT archive.
│
├── template-parts/
│ ├── content.php, content-*.php # Loop / CPT / empty state.
│ ├── module-cards.php # Front-page 6-card grid.
│ ├── newsletter-signup.php # [v1.5.1] Signup + consent + honeypot.
│ └── settings-panel.php # Slide-out support/contact panel + honeypot.
│
├── assets/js/
│ ├── navigation.js # Mobile menu + body-overflow save/restore.
│ ├── theme.js # Dark mode + search overlay + plugin API.
│ ├── documentation.js # ToC builder (text-slug IDs) + scroll spy.
│ ├── auth.js # Form toggle + AJAX + XSS-safe status.
│ └── settings-panel.js # Open/close + focus trap + AJAX contact.
│
├── tests/
│ ├── bootstrap.php # PHPUnit bootstrap.
│ ├── test-functions.php # Legacy helper tests.
│ ├── test-theme-setup.php # Theme setup tests.
│ ├── test-rate-limiter.php # [v1.5.1] Rate limiter lifecycle + IP resolver.
│ ├── test-auth-handlers.php # [v1.5.1] Login + register unified errors.
│ ├── test-email-verification.php# [v1.5.1] Token send/verify/reject.
│ ├── test-contact-handler.php # [v1.5.1] Header injection + rate limit + honeypot.
│ ├── test-newsletter.php # [v1.5.1] Signup + consent + CSV-safe.
│ └── test-auth-gate.php # [v1.5.1] require_login redirects.
│
├── .phpcs.xml.dist # PHPCS config (WordPress-Core).
├── phpunit.xml # PHPUnit config (theme suite).
├── composer.json # Dev-only dependencies.
├── CHANGELOG.md, CONVENTIONS.md, README.md, CLAUDE.md
└── screenshot.png
Navigation Structure
Home | Tools ▾ | My Data ▾ | Resources ▾ | [Sign In / Username ▾] ⚙ 🌐 🔍 🌙
Tools: Calculator (open) · Protocol Builder · Tracker
My Data: Peptides · Subject Log
Resources: Documentation · Science Feed
User menu: My Profile · Tracker · Subject Log · Sign Out
Page Template Map
| Page Title | Slug | Template | Gated | Shortcode |
|---|---|---|---|---|
| Calculator | calculator | page-calculator.php | open | [peptide_tools_calculator] |
| Protocol Builder | protocol-builder | page-protocol-builder.php | login+verify | [peptide_tools_protocol_builder] |
| Tracker | tracker | page-tracker.php | login+verify | [peptide_tracker] |
| Subject Log | subject-log | page-subject-log.php | login+verify | [peptide_tracker_subject_log] |
| Documentation | documentation | page-documentation.php | open | — |
| Peptide Directory | peptides | page-directory.php | open | [peptide_directory] |
| Science Feed | news | page-science-feed.php | open | [peptide_news] |
| Profile | profile | page-profile.php | login | [peptide_community_profile] |
| Sign In | auth | page-auth.php | open | (custom forms) |
Pages auto-created via inc/page-setup.php on theme activation.
"Gated" templates call peptide_starter_require_login() before get_header().
Data Flow
Authentication (v1.5.1)
page-auth.php
├─ Sign In form → auth.js AJAX → inc/auth-handlers.php::peptide_starter_ajax_login
│ ├─ Nonce check
│ ├─ Honeypot check (fake success on trip)
│ ├─ Rate-limit check (login bucket, IP+email hash)
│ ├─ wp_signon (all failures → unified message)
│ └─ Success → reset limiter → redirect
│ (unverified users → /profile?verify_required=1)
│
└─ Register form → auth.js AJAX → inc/auth-handlers.php::peptide_starter_ajax_register
├─ Nonce check
├─ Honeypot check (fake success on trip)
├─ Rate-limit check (register bucket, IP only)
├─ Validation (all failures → unified message)
├─ wp_create_user
├─ peptide_starter_send_verification_email
│ ├─ Generate 43-char token
│ ├─ Set ps_pending_verification=1,
│ │ ps_verify_token, ps_verify_expires
│ └─ wp_mail with /verify?uid=&token= link
└─ Respond with "check your inbox" (no redirect, no auto-login)
/verify?uid=&token=
→ inc/email-verification.php::peptide_starter_handle_verify_request
├─ hash_equals() token match + TTL check
├─ Clear verify meta on success
├─ wp_set_auth_cookie
└─ Redirect /profile?verified=1 (failure → /auth?verify_error=1)
Abuse controls (v1.5.1)
Every public form submit:
1. Nonce verify → 403-equivalent security error if missing/invalid
2. Honeypot check → Fake-success drop on non-empty hidden field
3. Peptide_Starter_Rate_Limiter → Silent generic error on budget exhaustion
4. Business logic → Unified error on any validation failure
Rate-limit state:
Storage: wp_options transients, key ps_rl_{action}_{hash}
Hash: substr( wp_hash( ip . '|' . identifier ), 0, 16 )
Never stores raw IPs or identifiers.
Front page assembly
front-page.php
├─ Hero (title, subtitle, search CTA)
│ └─ [peptide_search] (Peptide Search AI)
├─ Research Modules grid (template-parts/module-cards.php)
├─ [prautoblogger_posts] (PRAutoBlogger)
└─ [peptide_news] (Peptide News)
Dark mode coordination
wp_head inline script sets html[data-theme] from localStorage → system pref → customizer default
CSS :root vs html[data-theme="dark"] colour tokens swap
theme.js toggle writes localStorage, dispatches 'themechange' DOM event
External Integrations
Shortcodes (plugins), all wrapped in shortcode_exists():
- Peptide Search AI (
[peptide_search]) —.psa-* - Peptide News (
[peptide_news]) —.pn-* - PRAutoBlogger (
[prautoblogger_posts]) —.prab-* - Peptide Tools (
[peptide_tools_calculator],[peptide_tools_protocol_builder]) - Peptide Tracker (
[peptide_tracker],[peptide_tracker_subject_log]) - Peptide Community (
[peptide_community_profile],[peptide_directory])
Key Architectural Decisions
- Classic theme (not FSE) — stability, plugin compatibility.
- Monolithic CSS — no build step, CSS custom properties for tokens.
- Vanilla JavaScript — no bundler;
node --checkin CI. - Shortcode integration — plugins decoupled; theme degrades gracefully.
- Split
functions.phpintoinc/modules — every file < 300 lines. - Self-contained auth hardening (v1.5.1 / ADR-0001) — rate limiter,
honeypots, email verification all live in the theme. No plugin
dependencies, no paid services. Swappable via two small interfaces
(
Peptide_Starter_Rate_Limiter,peptide_starter_require_login). - Transient-backed rate limits — auto-expire, no custom table, no uninstall cleanup. If the site ever goes multi-server, swap to the shared object cache without caller changes.
Security Summary
- CSRF nonces on every form (auth, newsletter, contact, mail-test, unsubscribe).
- Unified error messages on login + registration (no enumeration).
- Rate limiting on login / register / contact / newsletter / verify-resend.
- Honeypots on the four public forms (login, register, contact, newsletter).
require_login()gate on every template that owns user data.- Email verification required for write access to user-data tools.
- CSV export formula-injection safe.
- All input sanitized at the boundary; all output escaped.
- Login redirect validated with
wp_validate_redirect. - Rate-limit storage keys are hashed — no raw IPs persisted.
- Contact sender names reject header-injection characters before
wp_mail.
Version
Current: v1.5.1 (2026-04-14)
Bump style.css header line 7 and PEPTIDE_STARTER_VERSION in
functions.php together on release.