ReviewOS

stacks/analyticshq

Register social auth apps and fill provider credentials

#61
Open glennmichael123 opened this 9 days ago ·
9 days ago

The framework side already exists, so this is registration plus secrets: create the OAuth app per provider, then fill config.services.<provider> in each app with clientId, clientSecret and redirectUrl. Apple is the exception and takes teamId, keyId and privateKey instead of a secret.

Providers supported today: Google, GitHub, Facebook, X, Apple.

Current state across the HQ apps

Surveyed 2026-09-28 from each app's config/services.ts and app/Actions/Auth/:

appproviders declared in configsocial auth actions
analyticshqgoogle, github, facebook, apple2
statushqgoogle, github, facebook, apple2
bughqgoogle, github, facebook, apple2
loghqgoogle, github, facebook, apple2
commshqgoogle, github, facebook, apple0
reportshqgoogle, github, facebook, apple0

Two things fall out of that:

  1. X is declared nowhere. All six apps declare google, github, facebook and apple; none declares x. If X is in scope, its config block has to be added to each app, not just credentialed.
  2. commshq and reportshq have no social auth actions at all. For those two this is not "registration plus secrets" - the sign-in path itself is missing, so they need the actions wired before credentials mean anything.

Scope

  • Register the OAuth app per provider per environment, and note the exact redirectUrl each one is registered with
  • Fill config.services.<provider> in analyticshq, statushq, bughq, loghq
  • Add the x provider block where X is wanted
  • Wire social auth actions in commshq and reportshq, or decide explicitly that those two stay password-only
  • Verify a real sign-in per provider per app after the credentials land

Note on storing the secrets

buddy env:set writes values that cannot be decrypted later - see the existing note in this workspace. Provider secrets must not go through it. Apple's privateKey in particular is multi-line and needs care in whatever store is chosen.

8 days ago

Correction and the concrete plan

Two things in the issue body above are wrong, and one new finding is the thing most likely to break this in production.

1. X is declared - as twitter

I grepped for a provider key named x and reported it missing. It is declared in all six apps as twitter:, reading TWITTER_CLIENT_ID / TWITTER_CLIENT_SECRET / TWITTER_REDIRECT_URL. Nothing to add.

2. The four wired apps need credentials only - the code is complete

analyticshq, statushq, bughq and loghq each have redirect + callback actions, routes reaching them, and provider buttons on the login page. The audit's old finding that SocialCallback issued no session cookie is fixed: all four now call loginUsingId + buildAuthCookie.

One nuance: only loghq passes sessionExpiryMinutes. The other three call loginUsingId with no explicit expiry, so a social sign-in falls back to config.auth.tokenExpiry (7 days) and can never reach the 30-day remembered tier. Worth aligning, not a blocker.

3. The redirect URL defaults do not match the routes

This is the one that matters. Register the wrong callback URL with a provider and the flow dies at the last step, after everything looks correct.

Actual routes:

appcallback route
analyticshq, bughq, loghq/api/auth/{provider}/callback
statushq/api/auth/sso/{provider}/callback

So the URLs to register are:

https://analyticshq.org/api/auth/<provider>/callback
https://bughq.org/api/auth/<provider>/callback
https://loghq.org/api/auth/<provider>/callback
https://statushq.org/api/auth/sso/<provider>/callback

Two config defaults are wrong today:

  • analyticshq, bughq, loghq default to http://localhost:3000/auth/<provider>/callback - missing /api, so local development against the default 404s at the callback.
  • statushq's facebook defaults to http://localhost:3000/auth/facebook/callback while its github, google and apple defaults correctly say /api/auth/sso/.... Facebook is the odd one out and is wrong.

Since redirectUrl is env-driven, setting <PROVIDER>_REDIRECT_URL correctly per app makes production work regardless - but the defaults should be fixed so local development is not a trap.

4. Env vars per app

Same names in every app, different values (the redirect URL differs per domain):

GITHUB_CLIENT_ID      GITHUB_CLIENT_SECRET      GITHUB_REDIRECT_URL
GOOGLE_CLIENT_ID      GOOGLE_CLIENT_SECRET      GOOGLE_REDIRECT_URL
FACEBOOK_CLIENT_ID    FACEBOOK_CLIENT_SECRET    FACEBOOK_REDIRECT_URL
TWITTER_CLIENT_ID     TWITTER_CLIENT_SECRET     TWITTER_REDIRECT_URL
APPLE_CLIENT_ID       APPLE_TEAM_ID  APPLE_KEY_ID  APPLE_PRIVATE_KEY  APPLE_REDIRECT_URL

Apple takes the Services ID as clientId (e.g. org.example.web) and signs an ES256 JWT from teamId + keyId + privateKey; literal \n newlines are accepted in the key.

Order of work

  1. Fix the three (four counting statushq's facebook) wrong redirectUrl defaults - a one-line change per app, no credentials needed
  2. Register one provider (GitHub is the least friction) for one app, set its three env vars, and prove the round trip end to end
  3. Repeat per provider per app
  4. Align the three apps' social callback with sessionExpiryMinutes so a social session matches a password session
  5. Only then decide whether commshq and reportshq get social auth at all - they have no actions, no routes and no buttons, so for them this is a port, not a credential task
8 days ago

Live probe of production, 2026-09-28

Checked every provider endpoint on every deployed app. No credentials needed for this - the redirect leg reveals its own configuration.

appproviderresult
statushqgithub302 → github.com/login/oauth/authorize ✅
analyticshqgithub, google503 sign-in is not configured yet
analyticshqapple, facebook, twitter404 - no route exists
bughqgithub503 not configured
loghqgithub503 not configured

statushq already works, and is the reference

redirect_uri = https://statushq.org/api/auth/sso/github/callback
scope        = read:user user:email
client_id    = present

That redirect_uri matches the route exactly, which is what the default fix in this issue was about.

analyticshq only wires two providers, not five

apple, facebook and twitter return 404 - the routes do not exist. routes/auth.ts registers github and google only, and the comment above them says as much: "Social sign-in (GitHub, Google) via the native @stacksjs/socials drivers". So for analyticshq this issue is a two-provider task unless someone adds the other three routes. Same for bughq and loghq.

The 503 is not a missing-credentials problem in the obvious sense

.env.production for analyticshq already contains GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET and GITHUB_REDIRECT_URL as real encrypted:v2 blobs (255-345 chars each), plus the Google pair. So credentials were configured at some point and are not taking effect.

SocialRedirectAction returns that 503 from:

if (!socialConfigured(provider) || !state)
  return response.json({ error: `${provider} sign-in is not configured yet.` }, 503)

Two candidate causes, and they need the private key to tell apart:

  1. The values do not decrypt, so @stacksjs/env drops the keys and socialConfigured() sees empty strings. This matches the known buddy env:set hazard in this workspace - it writes values that cannot be decrypted later. If these were set with it, that is the explanation.
  2. signState() is returning falsy, which would be an APP_KEY problem rather than a credentials one, and would affect statushq too - except statushq works, so this is the less likely of the two.

Next step: with DOTENV_PRIVATE_KEY_PRODUCTION set, run ./buddy env:decrypt GITHUB_CLIENT_ID against .env.production. If it fails or returns empty, cause 1 is confirmed and the fix is to re-set all three with a method that round-trips, then redeploy.

Re-verify afterwards with:

curl -sS -o /dev/null -D - https://analyticshq.org/api/auth/github/redirect | grep -i '^location:'

A 302 to github.com/login/oauth/authorize means it works. A 503 means it is still dropping the values.

8 days ago

GitHub sign-in: done on all four wired apps (2026-09-28)

analyticshq  302  LIVE
statushq     302  LIVE   (already working before this work)
bughq        302  LIVE
loghq        302  LIVE

Each verified the same way - the redirect leg returns 302 to github.com/login/oauth/authorize with a redirect_uri matching its registered callback:

appredirect_uri
analyticshqhttps://analyticshq.org/api/auth/github/callback
statushqhttps://statushq.org/api/auth/sso/github/callback
bughqhttps://bughq.org/api/auth/github/callback
loghqhttps://loghq.org/api/auth/github/callback

Scope is read:user user:email in every case. loghq shipped through its production branch (loghqorg/loghq#9), which also promoted three commits that had been sitting unshipped on main since 2026-09-25.

Deferred: Google

analyticshq  503
bughq        503
loghq        503
statushq     302  LIVE

analyticshq is the awkward one: it already holds Google credentials that do not work, the same way its GitHub pair did not. Re-encrypting will not help - it needs a fresh client secret from the Google Cloud console, which means a project and an OAuth consent screen. Deferred by decision, not blocked.

Incident during this work, for the record

While configuring bughq, buddy env:decrypt was run against GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET as a verification step. That command is destructive: it decrypts the named key in place, writing plaintext into the file, and strips the DOTENV_PUBLIC_KEY_PRODUCTION header. The result was committed and pushed, exposing a live client secret. It has been rotated.

Two things limited the damage:

  • GITHUB_REDIRECT_URL, set but never decrypted, stayed encrypted - the asymmetry that identified the cause
  • With the header gone nothing in the file decrypted at deploy time, so DB_CONNECTION fell back to sqlite and the dialect guard refused the deploy (migrations cannot run on sqlite). Production never received the broken state. A green deploy would have been worse.

Audited afterwards: analyticshq, statushq, loghq, commshq and reportshq all have intact headers and zero plaintext values. The exposure was confined to bughq.

Rule going forward: verify an encrypted write with dotenvx get (prints to stdout, leaves the file alone) or by shape plus a live endpoint check. Never with buddy env:decrypt.

Still open: whether to scrub the plaintext secret from bughq's git history. It is revoked, so this is hygiene.

Sign in to comment on this issue.