Files
Rosary/README.md
T
pguzman c7f1bdd630 Block bot registrations and add cron cleanup for unconfirmed accounts
register.php was a fully open signup form with no bot defenses — the
likely source of the unconfirmed accounts piling up in admin/users.php.

- Add an always-on honeypot field + timing trap to register.php: either
  tripping silently pretends success without creating an account, so a
  bot doesn't learn it was caught. No configuration needed.

- Add optional Google reCAPTCHA v3 support (includes/recaptcha.php,
  recaptcha_enabled()/verify_recaptcha(), no Composer dependency — a
  raw file_get_contents() POST like mailer.php's SMTP socket approach).
  A failed check here shows a real, visible error instead of the silent
  honeypot path, since a legitimate low-score user deserves a retry.

- Configure it through admin/settings.php's new "Bot Protection" section,
  mirroring the existing SMTP pattern exactly: recaptcha_enabled/
  recaptcha_site_key/recaptcha_secret_key in site_settings, secret key
  masked the same way smtp_pass now is (blank submission keeps it
  unchanged). install.php seeds sane defaults so the feature stays off
  until explicitly configured — fully backward compatible.

- Add cron/cleanup_unconfirmed.php: deletes accounts still unconfirmed
  after 3 days. CLI-only (refuses to run over HTTP, and cron/.htaccess
  denies web access to the directory as a second layer) since it's an
  unattended, irreversible deletion. Safe by construction — login.php
  already refuses login to unconfirmed accounts, so these rows can never
  own a session/novena_group/custom_prayer row. Not wired up
  automatically; README documents the Hostinger cron job to schedule it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 10:11:29 -07:00

149 lines
6.2 KiB
Markdown

# Rosary Presenter
A multi-user web app for leading the Rosary, novenas, and the Divine Mercy Chaplet — built for live presentation at prayer services. Live at **[loveandrosary.com](https://loveandrosary.com)**.
## What It Does
- **Slide-based presentation** — navigate prayer-by-prayer with leader/congregation text split on screen
- **Rosary bead ring** — SVG visualization tracks which bead is active in real time
- **Session types** — General Rosary, Memorial Rosary, Deceased Novena, Divine Mercy Chaplet
- **Novena groups** — link 9 daily sessions into one group with a public day-picker page
- **Audio uploads** — attach MP3/audio per session (up to 50 MB)
- **Multi-user** — role hierarchy: `superadmin``admin``superuser``user`
- **Public profiles** — each user gets a `/username` page with their public sessions
- **Donate strip** — optional PayPal / Venmo / Buy Me a Coffee link on public pages
## Stack
- PHP 8 + PDO (no framework, no Composer dependencies)
- MySQL 8 / MariaDB
- Vanilla JS (no build step)
- Apache/Nginx with `.htaccess` rewrite rules
## Setup
### 1. Configure database
```bash
cp config/db.example.php config/db.php
# Edit config/db.php — fill in DB_HOST, DB_NAME, DB_USER, DB_PASS
# Set BASE_URL if deploying to a subdirectory (e.g. '/rosary')
```
### 2. Create the database schema
Visit `install.php` in your browser once — it creates all tables (matching `schema.sql`, kept as the canonical structure reference) and seeds `site_settings` defaults, the superadmin account, and the standard prayer library. **Delete `install.php` immediately after.**
Default superadmin credentials: `supadmin` / `supadmin`**change these immediately**.
`schema.sql` documents the current database structure; there is no separate migration-script chain to run.
### 3. Configure the web server
#### Apache — `.htaccess` is included. Enable `mod_rewrite` and set `AllowOverride All`.
#### Nginx — add to your server block:
```nginx
location / {
try_files $uri $uri/ @php;
}
location @php {
rewrite ^/([^/]+)/([^/]+)$ /present.php?username=$1&slug=$2 last;
rewrite ^/([^/]+)$ /profile.php?username=$1 last;
}
```
### 4. Uploads directory
```bash
chmod 755 uploads/
```
### 5. SMTP (optional)
Configure outbound email in **Admin → Settings** for registration confirmation and password reset emails. If left blank, the app will auto-confirm new users instead.
### 6. Bot protection (optional)
`register.php` always runs a built-in honeypot + timing trap against scripted signups — no setup needed. On top of that, you can enable Google reCAPTCHA v3: register your domain at [google.com/recaptcha](https://www.google.com/recaptcha/admin) (choose **reCAPTCHA v3**), then enter the Site Key and Secret Key in **Admin → Settings → Bot Protection**.
### 7. Scheduled cleanup of unconfirmed accounts (optional)
`cron/cleanup_unconfirmed.php` permanently deletes accounts that are still unconfirmed 3 days after registering — useful for clearing out bot signups that get past the defenses above. It's CLI-only (refuses to run over HTTP) and is not wired up automatically; schedule it yourself as a cron job. In Hostinger's hPanel: **Advanced → Cron Jobs** → run daily:
```bash
php /home/<your-account>/domains/loveandrosary.com/public_html/cron/cleanup_unconfirmed.php
```
(Adjust the path to match your actual hosting account.) Confirmed accounts are never touched — only rows with `email_confirmed = 0`.
## Upgrading an Existing Install
`schema.sql` reflects the current database structure. For a production database that predates the `failed_login_attempts` / `locked_until` login-lockout columns, run this once against it manually — it's not applied automatically since there's no migration runner against a live database:
```sql
ALTER TABLE users
ADD COLUMN failed_login_attempts INT NOT NULL DEFAULT 0,
ADD COLUMN locked_until DATETIME NULL;
```
## Deployment Checklist
- [ ] `config/db.php` filled in with production credentials
- [ ] `install.php` deleted after first run
- [ ] `uploads/` is writable by the web server
- [ ] `BASE_URL` matches your subdirectory path (leave empty for domain root)
- [ ] Superadmin password and email changed
- [ ] SMTP configured in Admin → Settings
## Project Structure
```
Rosary/
├── admin/ # Admin dashboard (auth-gated)
│ ├── index.php # Dashboard home
│ ├── setup.php # Create/edit a session
│ ├── novena_group.php
│ ├── users.php
│ ├── settings.php # Site-wide settings (superadmin only)
│ └── audio.php
├── api/ # JSON endpoints (upload, save, delete)
├── assets/
│ ├── css/ # present.css, public.css, setup.css
│ └── js/ # presenter.js, rosary.js, setup.js
├── config/
│ ├── db.example.php # Copy → db.php and fill in credentials
│ └── db.php # (gitignored — contains real credentials)
├── cron/
│ └── cleanup_unconfirmed.php # CLI-only; schedule via host cron
├── data/
│ └── prayers.php # All prayer text + build_decade_slides()
├── includes/
│ ├── auth.php # require_auth(), current_user(), has_role(), login lockout
│ ├── csrf.php # csrf_token(), csrf_field(), csrf_verify()
│ ├── recaptcha.php # recaptcha_enabled(), verify_recaptcha()
│ ├── build_slides.php
│ ├── donate.php
│ └── mailer.php
├── uploads/ # User-uploaded audio (gitignored)
├── index.php # Public home — card grid of sessions
├── present.php # Presentation player (public)
├── novena_public.php # Novena day-picker (public)
├── schema.sql # Canonical database schema (structure only)
├── install.php # Run once, then delete
└── .htaccess # URL rewriting
```
## URL Routing
| URL | Resolves to |
|-----|-------------|
| `/username/slug` | `present.php?username=X&slug=Y` |
| `/username` | `profile.php?username=X` |
| `/username/novena-slug` | Redirects to `novena_public.php?group_id=X` |
## License
Private project — all rights reserved.