supersam/docs/operations/supabase-email-otp-auth.md

88 lines
2.8 KiB
Markdown

# Supabase Email OTP Auth
This guide covers the whitelist-only email OTP login flow for the app.
## What changes in this flow
- Users sign in with a one-time code sent to email.
- The app only accepts accounts that already exist in `auth.users`.
- The app reads the app role from `public.users -> public.roles`.
- Self-signup should be disabled if you want whitelist-only access.
## Supabase dashboard settings
1. Open your Supabase project.
2. Go to `Authentication` and confirm email OTP / email login is enabled.
3. Check the email provider settings so OTP messages are deliverable.
4. Disable self-signup if the project should stay whitelist-only.
5. Make sure the two allowed emails exist in Supabase Auth before testing login.
## Frontend environment
The frontend only needs these variables in `.env.local`:
```bash
VITE_SUPABASE_URL=...
VITE_SUPABASE_ANON_KEY=...
```
Do not put `SUPABASE_SERVICE_ROLE_KEY` in the frontend. If an Edge Function needs secrets, add them in the Supabase dashboard or via `supabase secrets`.
## Seed the allowed users
Create these Auth users first:
- `skylanguage@yandex.ru`
- `mk7029953@yandex.ru`
The `public.handle_new_user()` trigger will create the matching row in `public.users` when a new `auth.users` row is inserted. It already reads `raw_user_meta_data ->> 'role'`, so if you set metadata during user creation, the role can be applied automatically.
If the user already exists in `auth.users`, bind the role in `public.users` with SQL:
```sql
update public.users u
set role_id = r.id
from public.roles r
where u.email = 'skylanguage@yandex.ru'
and r.name = 'admin';
update public.users u
set role_id = r.id
from public.roles r
where u.email = 'mk7029953@yandex.ru'
and r.name = 'logistician';
```
If a user exists in Auth but the profile row is missing in `public.users`, recreate it manually:
```sql
insert into public.users (id, email, name, role_id, last_login)
select
au.id,
au.email,
coalesce(au.raw_user_meta_data ->> 'name', split_part(au.email, '@', 1)),
r.id,
timezone('utc', now())
from auth.users au
join public.roles r on r.name = case
when au.email = 'skylanguage@yandex.ru' then 'admin'
when au.email = 'mk7029953@yandex.ru' then 'logistician'
else 'manager'
end
where au.email in ('skylanguage@yandex.ru', 'mk7029953@yandex.ru')
on conflict (id) do update
set email = excluded.email,
name = excluded.name,
role_id = excluded.role_id,
last_login = excluded.last_login;
```
## Sanity checks
- Send an OTP to `skylanguage@yandex.ru`.
- Send an OTP to `mk7029953@yandex.ru`.
- Confirm `admin` reaches the admin area after sign-in.
- Confirm `logistician` reaches the logistics area after sign-in.
- Confirm an email outside the whitelist is rejected.
- Confirm the app shows an error if Auth has a session but `public.users` has no matching profile row.