Skip to content

Tito-backed magic-link auth, admin UI, and brand styling#4

Merged
Kitkatnik merged 7 commits into
mainfrom
Kitkatnik/tito-magic-link-auth
Apr 22, 2026
Merged

Tito-backed magic-link auth, admin UI, and brand styling#4
Kitkatnik merged 7 commits into
mainfrom
Kitkatnik/tito-magic-link-auth

Conversation

@Kitkatnik

Copy link
Copy Markdown
Collaborator

Summary

  • Adds magic-link auth (ashevillagers pattern) where users enter their email, we check our DB first and fall back to the Tito API, auto-create from their ticket, then email a 30-day signed login link — no passwords, with a clear "no ticket found" page that routes visitors to blueridgeruby.com/#register.
  • Adds an admin area at /admin with user CRUD, bulk Tito sync, and a DB-backed Configuration store for the Tito API token/slugs and sender address (admins edit live, no redeploys); Jeremy Smith is seeded as the sole admin and admin role is never overwritten by syncs.
  • Styles every auth and admin page with the Blue Ridge Ruby brand — navbar/footer/ridge background pulled from the template branch, extended application.css with card/button/form/alert/table/badge utilities, navy + red-gradient palette throughout.
  • Wires Postmark in production and letter_opener in development via the existing email_from config, and adds a justified config/brakeman.ignore entry for the admin-gated role mass-assignment so CI is green.

Test plan

  • Visit /session/new, enter jeremy@blueridgeruby.com, click the magic link from tmp/letter_opener, land on /dashboard with the Admin badge
  • Enter an unknown email on /session/new → see the "no ticket found" page with the Register CTA (requires Tito creds configured; otherwise "try again later" alert is expected)
  • Visit /admin/configurations as admin, set tito_api_token / tito_account_slug / tito_event_slug / email_from, then click "Sync from Tito" on /admin/users and confirm attendees are imported
  • Visit /dashboard and /admin/* unauthenticated → redirected to /session/new
  • Run bin/brakeman --no-pager && bin/bundler-audit && bin/importmap audit && bin/rubocop -f github && bin/rails db:test:prepare test test:system — all green

Port the ashevillagers auth flow: users enter their email, we check our DB
first, fall back to Tito API, auto-create a user from their ticket, then
email a 30-day magic link. No passwords. Single User model with three
roles (attendee, volunteer, admin); admins are seeded in db/seeds.rb and
never overwritten by Tito sync. Admin UI at /admin for user CRUD, bulk
Tito sync, and DB-backed Configuration management for Tito credentials
and email_from.

Ruby bumped to 3.4.4 for Rails 8.1.3 compatibility.
Pull the template branch's navbar/footer/ridge-bg layout, logo, colfax
font, and the blueridgeruby.com class-name system. Restyle all session
views (new, callback, not_registered) and the dashboard to use the
brand palette (navy text, red gradient CTAs, ridge background). Give
the admin area a matching navy top bar and brand-consistent forms,
tables, and role badges. Extend application.css with form/card/button/
alert/table/badge utilities on top of the template's base.

Also add test/system/ scaffolding so bin/rails test:system runs cleanly
and a brakeman.ignore entry for the admin-only role mass-assignment
(guarded by require_admin! on every action).
…link-auth

# Conflicts:
#	.ruby-version
#	app/assets/stylesheets/application.css
#	config/routes.rb
#	db/schema.rb
@railway-app

railway-app Bot commented Apr 22, 2026

Copy link
Copy Markdown

🚅 Deployed to the ruby-embassy-pr-4 environment in ruby-embassy

Service Status Web Updated (UTC)
ruby-embassy ✅ Success (View Logs) Web Apr 22, 2026 at 10:14 pm

@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-4 April 22, 2026 21:09 Destroyed
The Dockerfile has `# check=error=true`, which promotes BuildKit lint
warnings to hard errors. The shell-form CMD from the Railway setup
tripped JSONArgsRecommended and blocked the build.

Use JSON-form CMD with `sh -c` so `${PORT}` still expands at runtime,
and `exec` so SIGTERM is forwarded directly to the Rails process
instead of being swallowed by the shell. Defaults PORT to 3000 for
local `docker run`.
@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-4 April 22, 2026 21:12 Destroyed
Railway deploys booted into a PG::UndefinedTable error: the app eager-
loaded ApplicationMailer, which reads email_from from the configurations
table at class-load time, but the table didn't exist yet because
migrations had never run.

The root cause was the CMD shape. bin/docker-entrypoint only runs
db:prepare when the last two args are exactly `./bin/rails server`, and
the previous `sh -c "..."` form hid those args behind the shell. Switch
to the Rails 8 default `CMD ["./bin/thrust", "./bin/rails", "server"]`,
which:

- is JSON form, so the Dockerfile linter passes
- matches the entrypoint pattern, so db:prepare runs before Rails boots
- lets Thruster read Railway's $PORT natively (no shell expansion needed)
- keeps signal forwarding correct (Thruster is PID 1)
@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-4 April 22, 2026 21:17 Destroyed
Thruster 0.1.20 binds to $HTTP_PORT (default 80), but Railway routes
traffic to whatever it set $PORT to. The deploy was healthy (migrations
ran, Puma was up on 3000 as Thruster's upstream) but Railway's router
got 502s because no one was listening on $PORT.

Propagate PORT → HTTP_PORT in the server-startup branch of the
entrypoint. Respects an explicit HTTP_PORT if the caller set one, and
falls back to 80 (Thruster's default) otherwise — so local `docker run`
still works without either var set.
@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-4 April 22, 2026 21:23 Destroyed
@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-4 April 22, 2026 21:51 Destroyed
Hardcode the mailer sender to hello@blueridgeruby.com and read
tito_api_token / tito_account_slug / tito_event_slug from ENV instead
of a DB-backed Configuration table. Deletes ~200 lines of
metaprogramming (Configurable concern, CurrentAttributes loader,
secret masking, admin CRUD, views) we were maintaining to manage
four string values.

Removes the production boot hazard: ApplicationMailer no longer
queries the DB at class-load time, so eager loading succeeds on a
fresh database before migrations have run.

Keeps the per-user tito_account_slug / tito_event_slug columns and
the memorialize_tito_config before_save callback. ENV holds the
*current* event's slugs (used by Tito API calls); each user's row
snapshots the slugs that were in force when their ticket was linked
(used by admin_ticket_url). That preserves correct historical
dashboard links across year-over-year redeploys.

Railway needs: TITO_API_TOKEN, TITO_ACCOUNT_SLUG, TITO_EVENT_SLUG
(plus the existing POSTMARK_API_TOKEN).
@Kitkatnik Kitkatnik force-pushed the Kitkatnik/tito-magic-link-auth branch from 3f93c42 to b12e27c Compare April 22, 2026 22:09
@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-4 April 22, 2026 22:09 Destroyed
@Kitkatnik Kitkatnik merged commit 7546e70 into main Apr 22, 2026
6 checks passed
@Kitkatnik Kitkatnik deleted the Kitkatnik/tito-magic-link-auth branch April 26, 2026 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant