flatrate / wiki-supabase-oauth
FlatRate Wiki Supabase OAuth 2.1 SSO provider and public-auth gate for Flarum 1.8.
Package info
github.com/mrkcntrmn/flatrate-wiki-supabase-oauth
Type:flarum-extension
pkg:composer/flatrate/wiki-supabase-oauth
Requires
- php: >=8.1
- flarum/core: ^1.8.1
- flarum/nicknames: ^1.8.3
- fof/extend: ^1.3.4
- fof/oauth: ^1.7.4
- league/oauth2-client: ^2.7
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.2.8
- v0.2.7
- v0.2.6
- v0.2.5
- v0.2.4
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- dev-feat/forum-identity-001-r3d-oauth
- dev-fix/forum-identity-001-r3de
- dev-fix/forum-identity-001-r3-reservation
- dev-feat/rep001fa1-activity-emitter
- dev-feat/forum-ia015-persistent-grouped-navigation
- dev-feat/forum-sub001-family-follow-notifications
- dev-fix/forum-sub000d-frontend-js-registration
- dev-fix/forum-email002-delivery-promotion
- dev-feat/forum-sub000a-follow-tags-email-policy
- dev-feat/forum-ia013-grouped-navigation
- dev-feat/brands-presentation-nav-tree
- dev-feat/forum-email-001-teaser-only
- dev-fix/mobile-brand-drawer-order-filter
- dev-fix/mobile-brand-drawer-placement
- dev-feat/forum-email-teaser-policy
- dev-feat/mobile-brand-sidebar-links
- dev-fix/affiliated-brand-username-itemlist-get
- dev-fix/affiliated-brand-header-inline-stack
- dev-fix/affiliated-brand-header-display-contents
- dev-fix/affiliated-brand-author-header-layout
- dev-feat/affiliated-brand-post-user
- dev-fix/meta001b-taglabel-header-alignment
- dev-fix/meta001b-reply-taglabel-persistence
- dev-fix/meta001a-flarum1-compat
- dev-codex/meta001-reply-job-breakdown-marker
- dev-fix-forum-direct-login-bundle
- dev-hotfix/forum-frontend-boot
- dev-cleanup/release-v0.2.7-workflow
- dev-release/v0.2.7
- dev-fix/forum-oauth-sequential-nickname
- dev-feat/direct-forum-login-redirect
- dev-feat/forum-sequential-public-nickname
- dev-chore/release-v0.2.6
- dev-fix/sso006-csrf-exemption
- dev-chore/release-v0.2.5
- dev-fix/sso005-managed-host-secret
- dev-chore/release-v0.2.4
- dev-feat/sso004-session-bootstrap
- dev-fix/top-level-community-settings-oauth
- dev-feat/sso-003-silent-provisioning
- dev-feat/public-nickname-model
- dev-feat/login-cta-icon
- dev-feat/login-button-polish
This package is auto-updated.
Last update: 2026-09-11 21:16:11 UTC
README
Flarum 1.8 extension for FlatRate Wiki identity integration and forum metadata.
Supabase Auth remains the canonical credential/account authority. Flarum keeps only local community identity/state linked to the immutable Supabase/OIDC sub.
The production target has two complementary paths:
- Primary product path: FlatRate.wiki provisions the linked Flarum identity server-to-server and enters Community with a short-lived, opaque, one-time session ticket. Users do not see an OAuth consent/callback flow when they click Community.
- Rollback/fallback path: the existing OAuth 2.1 authorization-code flow with PKCE
S256remains available during migration and for explicit legacy-account linking.
Security and identity contract
- Supabase
subis the only cross-system identity key. - Email is a private attribute and is never used to infer or auto-link an unrelated Flarum account.
- Flarum routing usernames are deterministic opaque handles:
tech_<stable-hash(sub)>. - Flarum Nicknames is the user-editable public display-name layer.
- New forum identities require a non-empty verified email attribute, but linkage remains keyed to
sub. - If a verified email is already owned by an unrelated local Flarum account, provisioning fails closed with
existing_account_requires_explicit_link. - Ordinary native Flarum password login/signup are blocked server-side; native administrator password login remains an unadvertised recovery path.
- FlatRate.wiki and Flarum do not share authentication cookies.
- Supabase access tokens, refresh tokens, passwords, and service-role credentials never appear in forum-entry URLs.
- Internal bridge requests require a deployment-only HMAC secret, timestamp, and nonce.
- Forum-entry tickets are cryptographically random, hashed at rest, expire after 45 seconds, and are atomically single-use.
Reserved internal email namespace (FORUM-EMAIL-002)
@users.flatrate.wiki is a reserved Flarum-internal namespace. FlatRate uses deterministic placeholder addresses there only to satisfy Flarum's unique email-shaped field while a verified phone user's real email remains unconfirmed.
Hard rules:
- the entire
users.flatrate.wikidomain isINTERNAL=trueandOUTBOUND_DELIVERABLE=false; - SSO
email_verified=truedoes not imply outbound email deliverability; - FlatRate replaces only Flarum's
emailnotification driver so placeholder-backed users still receive browser/on-site alerts; - do not use a global
Notification::beforeSending()recipient filter; - when a later SSO call carries a confirmed real email for the same
sub, the already-linked Flarum user is promoted one-way from the placeholder to that real address; - promotion never changes Flarum user id,
login_providersidentifier, Supabasesub, nickname, preferences, or discussion/post ownership; - never automatically downgrade a real email back to a placeholder, and never auto-replace real email A with real email B;
subremains authoritative; email remains a mutable attribute;- DNS for
users.flatrate.wikimust not be created as a workaround (DNS_CHANGE_REQUIRED=false).
Identity fields
| Concern | Source of truth | Example | Public? |
|---|---|---|---|
| Authentication identity | Supabase sub |
UUID-like subject | No |
| Login/account address | Supabase/Flarum email | tech@example.com |
No |
| Internal Flarum schema email | FlatRate placeholder | forum-<hash>@users.flatrate.wiki |
No |
| Flarum routing username | Derived from sub |
tech_a1b2c3d4 |
Yes |
| FlatRate tech number | Supabase assignment (forward-only; R3) | 20031 |
No |
| System nickname | Derived from tech number | tech_20031 |
Yes (initial) |
| Display name / nickname | Flarum Nicknames (editable) | EV Tech / DieselDan |
Yes |
Reserved numeric nickname namespace (FORUM-IDENTITY-001-R3-A)
^tech_[0-9]+$ (case-insensitive) is reserved for system technician IDs.
- Human nickname edits and direct Flarum signup that set
attributes.nicknameto a reserved value are rejected. - Grandfathered users who already store
tech_Nare untouched; unrelated profile saves withoutattributes.nicknameare not rejected. - FlatRate SSO RegistrationToken nicknames are applied on the user model (not via request
attributes.nickname), so system registration remains allowed. - R3-A reservation remains live. R3-D candidate source removes
count()+1and requires a signedtech_number(≥ 20031) for new unlinked identities only; existing linked users never allocate.
Requirements
- PHP
>=8.1 - Flarum
^1.8.1 flarum/nicknames:^1.8.3fof/oauth:^1.7.4fof/extend:^1.3.4league/oauth2-client:^2.7
Install
composer require flatrate/wiki-supabase-oauth:^0.2
For the managed PikaPods/Flarum image, persist the package in /data/extensions/list so it is restored after restart.
Enable dependencies in this order:
- Nicknames
- FoF OAuth
- FlatRate Wiki Login
Seamless product-to-forum flow
The normal user journey is intentionally not a browser OAuth flow:
FlatRate.wiki signup / confirmation
-> authenticated Supabase user
-> POST forum /api/flatrate-sso/provision (server-to-server)
-> Flarum user + flatrate LoginProvider keyed by sub
User clicks Community
-> FlatRate.wiki verifies/refreshes its Supabase session
-> POST forum /api/flatrate-sso/ticket (server-to-server)
-> short-lived opaque one-time ticket
-> browser GET forum /auth/flatrate/session?ticket=<opaque>
-> Flarum consumes ticket atomically
-> Flarum issues its own normal remember/session cookie
-> redirect directly to requested Community path
The single top-level request to forum.flatrate.wiki is necessary so the forum can issue its own host-scoped cookie. There is no second password, popup, consent page, authorization-code callback page, or shared parent-domain cookie.
Internal bridge configuration
Set the same high-entropy deployment secret on both the FlatRate.wiki server and the Flarum/PikaPods runtime.
Environment configuration is preferred when the host exposes arbitrary environment variables:
FORUM_SSO_SHARED_SECRET=<at-least-32-random-characters>
On a managed host that does not expose that environment variable, open the FlatRate Wiki provider settings under FoF OAuth and enter the same value in Community SSO Shared Secret. The extension reads the environment variable first and otherwise falls back to the private FlatRate provider setting fof-oauth.flatrate.sso_shared_secret.
The provider setting is intended only as a managed-host deployment fallback. Do not reuse the OAuth client secret, and do not commit either secret to GitHub or public Flarum assets.
Internal requests use these headers:
X-FlatRate-Timestamp: <unix-seconds>
X-FlatRate-Nonce: <random-base64url-or-hex>
X-FlatRate-Signature: v1=<hex-hmac-sha256>
Canonical signing input:
<timestamp>\n
<nonce>\n
<METHOD>\n
<request-path>\n
<sha256(raw-request-body)>
The receiver rejects stale timestamps, malformed signatures, and duplicate nonces. Nonce hashes are stored only long enough to enforce replay protection.
Internal endpoints
POST /api/flatrate-sso/provision
Authenticated server-to-server only.
Request body:
{
"sub": "immutable-supabase-sub",
"email": "private@example.com",
"email_verified": true
}
Behavior is idempotent:
- return the user already linked by
login_providers(provider=flatrate, identifier=sub); or - create exactly one Flarum user with deterministic routing username and nickname
tech_<tech_number>from the signed SSO body; - create the
flatrateprovider link keyed tosub; - never join an unrelated account solely because email matches.
The provisioner uses Flarum's own RegistrationToken + RegisterUserHandler path so core validation/events, nickname persistence, email activation, and provider-link persistence remain intact.
POST /api/flatrate-sso/ticket
Authenticated server-to-server only. It accepts the same identity fields plus a relative return_to path. It idempotently ensures the linked forum user exists and returns an opaque entry path with a 45-second TTL.
Example response shape:
{
"ok": true,
"entry_path": "/auth/flatrate/session?ticket=<opaque>",
"expires_in": 45
}
GET /auth/flatrate/session?ticket=<opaque>
Browser entry endpoint. It:
- hashes and looks up the ticket;
- locks the row and confirms it is unexpired/unconsumed;
- marks it consumed in the same transaction;
- creates Flarum's normal
RememberAccessToken; - sets the normal Flarum remember cookie;
- redirects to the ticket-bound relative forum path.
Responses use Cache-Control: no-store and Referrer-Policy: no-referrer. Tickets contain no email, JWT, refresh token, or other PII.
Signup and self-healing provisioning
FlatRate.wiki should call /provision whenever a Supabase account becomes verified/authenticated:
- immediately after signup if Supabase returns a session;
- after email-confirmation verification;
- after accepting a confirmed callback session;
- on ordinary login as an idempotent repair path.
Community entry should call /ticket, which also runs the same idempotent provisioner. This means a missed signup webhook/callback cannot permanently strand the account.
Existing accounts
Existing login_providers(provider=flatrate, identifier=sub) rows created by the OAuth flow are reused unchanged by the new bridge. No migration to a new identity key is required.
Do not automatically link an existing Flarum-native account because its email matches a Supabase account. Use explicit linking for legacy accounts.
OAuth rollback/fallback path
The OAuth provider remains configured during rollout. It still uses:
/auth/v1/oauth/authorize
/auth/v1/oauth/token
/auth/v1/oauth/userinfo
with scopes:
openid email profile
and requires:
response_type=code
code_challenge=<non-empty value>
code_challenge_method=S256
The Flarum callback remains:
https://forum.flatrate.wiki/auth/flatrate
The Supabase OAuth client is confidential and uses client_secret_post.
A new identity arriving through this fallback path delegates to the same reusable FlatRateUserProvisioner, so OAuth and the ticket bridge cannot create divergent forum identities.
Explicit legacy account linking
While signed into the target native Flarum account, use:
https://forum.flatrate.wiki/auth/flatrate?linkTo=<FLARUM_USER_ID>
FoF OAuth verifies that the authenticated actor matches linkTo before creating the provider record. Keep this primarily for migration/recovery; ordinary product navigation should use the seamless ticket bridge.
Public-auth behavior
The extension:
- hides public native username/password login controls;
- hides public signup and forgot-password affordances;
- hides local Change Password / Change Email controls for ordinary users;
- exposes Nicknames for public identity management;
- rejects ordinary native password authentication server-side;
- rejects native public user creation without an OAuth registration token;
- preserves native administrator password login for recovery.
Reply Job Breakdown marker
Reply classification is stored as FlatRate-owned post metadata because Flarum tags are discussion-level relationships. The extension does not attach native Flarum tags to individual posts.
When marked, the reply reuses the existing Flarum Job Breakdown secondary tag's TagLabel presentation (name, color, icon) without modifying the discussion's tag relationship.
- The
flatrate_post_markerstable stores the controlledjob-breakdownmarker by post ID. - API post payloads expose the marker as
attributes.flatRateJobBreakdown. - Reply and edit composers show a compact Job Breakdown checkbox for replies.
- Marked replies resolve the canonical Flarum tag by slug
job-breakdownand render Flarum's owntags/helpers/tagLabeloutput in the post header. - Discussion starters are not valid marker targets, and the backend fails closed if a request tries to mark one.
- Deleting a post deletes its local marker rows.
- Marking a reply never adds or removes
discussion.tags().
Optional Affiliated Brand presentation
When FoF Masquerade is installed and enabled, the forum bundle can render one optional self-declared profile value directly beneath the author's username in discussion posts and replies.
- Masquerade field name:
Affiliated Brand(Dropdown /select, optional). - The renderer resolves the unique active Masquerade field by exact name and type from the already-loaded
masquerade-fieldstore; it does not hardcode production field IDs. - User answers are read from the loaded
user.masqueradeAnswers()relationship; the bundle does not issue per-post API requests. - Presentation is plain text (
span.FlatRateAffiliatedBrand) insidePostUser-name, not a TagLabel, badge, or OEM logo. - Row 1 preserves native nickname +
PostMetainline; row 2 renders affiliation beneath the nickname via scopedinline-grid(not avatar-edge offsets). - Blank or missing values render nothing (no spacer line).
- Masquerade is optional at runtime: if the extension or field is absent, SSO and other forum behavior continue unchanged.
This value is self-declared profile metadata only. It does not indicate employment, certification, dealership status, or OEM verification; it is not mirrored to Supabase; and it does not mutate discussion vehicle-make tags or Job Breakdown metadata.
FoF Masquerade stores dropdown option lists in fof_masquerade_fields.validation as a comma-separated in: rule. The upstream default column is VARCHAR(255), which truncates long brand lists. This extension widens that column to TEXT when Masquerade is present so the full Affiliated Brand vocabulary can be saved.
Brands navigation presentation
Mobile Brands navigation renders from the shared js/dist/brands-navigation.js presentation contract. GM and CDJR are top-level Brand links with Buick/Cadillac/Chevrolet/GMC and Chrysler/Dodge/Jeep/Ram nested beneath them for presentation only. The extension does not infer Brand membership from Flarum root tags, parent(), isChild(), or tag position.
Production proof gate
Before removing the OAuth product path, verify:
- a new confirmed FlatRate.wiki account creates exactly one linked Flarum identity without opening the forum;
- repeat provisioning creates no duplicates;
- existing linked users resolve the same Flarum row;
- clicking Community lands already authenticated at the requested forum path;
- clicking Community Settings lands at
/settingsalready authenticated; - reused, expired, malformed, and tampered tickets fail closed;
- replayed/stale HMAC requests fail closed;
- changing the Supabase email does not create a second forum identity;
- Flarum bans/suspensions still apply;
- PikaPods restart restores the extension and migrations;
- reply Job Breakdown markers can be created, edited, rendered, and deleted without changing forum tags;
- no bridge secret, Supabase token, password, or PII appears in browser URLs, logs, GitHub, or public assets.
Development
Run static contract tests:
node --test test/*.test.mjs
Run PHP syntax validation:
find . -name '*.php' -print0 | xargs -0 -n1 php -l
License
MIT.