Background
The send-invite-email edge function (#355, PR #372) must currently be deployed with --no-verify-jwt. It's invoked by a database trigger via pg_net, which authenticates with a shared INVITE_HOOK_SECRET in the Authorization: Bearer … header rather than a JWT. With Supabase's default platform JWT gate enabled, the request is rejected before the function runs:
401 | UNAUTHORIZED_INVALID_JWT_FORMAT
So today the platform gate is disabled and auth relies entirely on the in-function INVITE_HOOK_SECRET check. cleanup-avatars uses the same shared-secret pattern and is presumably in the same situation.
Task
Investigate whether we can keep the platform JWT gate enabled and still let the trigger call the function, e.g.:
- Have the trigger send the Supabase service-role key (or a signed JWT) in the
Authorization header instead of the shared secret, sourced from Vault. Then the platform accepts it and we can drop --no-verify-jwt.
- Evaluate whether that's actually more secure than the current shared-secret model, or just different (the service-role key is a very sensitive credential to place in a DB trigger / Vault).
- Consider a
supabase/config.toml with per-function verify_jwt settings so the deploy flag isn't a manual step that's easy to forget.
Decision to reach
Either:
- Document that
--no-verify-jwt + shared secret is the intended, acceptable design (and codify it in config.toml), or
- Switch to a JWT/service-role-key approach and re-enable the gate.
References
supabase/functions/send-invite-email/index.ts
supabase/functions/cleanup-avatars/index.ts (same pattern)
supabase/migrations/20260707_invite_email_notification.sql (trigger builds the Authorization header from Vault)
Background
The
send-invite-emailedge function (#355, PR #372) must currently be deployed with--no-verify-jwt. It's invoked by a database trigger viapg_net, which authenticates with a sharedINVITE_HOOK_SECRETin theAuthorization: Bearer …header rather than a JWT. With Supabase's default platform JWT gate enabled, the request is rejected before the function runs:So today the platform gate is disabled and auth relies entirely on the in-function
INVITE_HOOK_SECRETcheck.cleanup-avatarsuses the same shared-secret pattern and is presumably in the same situation.Task
Investigate whether we can keep the platform JWT gate enabled and still let the trigger call the function, e.g.:
Authorizationheader instead of the shared secret, sourced from Vault. Then the platform accepts it and we can drop--no-verify-jwt.supabase/config.tomlwith per-functionverify_jwtsettings so the deploy flag isn't a manual step that's easy to forget.Decision to reach
Either:
--no-verify-jwt+ shared secret is the intended, acceptable design (and codify it inconfig.toml), orReferences
supabase/functions/send-invite-email/index.tssupabase/functions/cleanup-avatars/index.ts(same pattern)supabase/migrations/20260707_invite_email_notification.sql(trigger builds theAuthorizationheader from Vault)