initial
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import type { SupportedLocale } from '../i18n/types.js';
|
||||
import type { AppSupabaseClient } from '../supabase/client.js';
|
||||
|
||||
export interface SignupParams {
|
||||
email: string;
|
||||
// Login handle. Lowercased server-side; must match ^[a-z0-9_]{3,32}$ once lowercased.
|
||||
username: string;
|
||||
// Optional human-readable name. Defaults to username server-side if omitted.
|
||||
displayName?: string;
|
||||
// Invite code is mandatory (enforced by the handle_new_user trigger).
|
||||
inviteCode: string;
|
||||
// Deep link the magic-link email should bounce back to.
|
||||
// e.g. `chatapp://auth/callback` on mobile, `http://localhost:1420/auth/callback` on desktop dev.
|
||||
redirectTo: string;
|
||||
// Optional initial locale for the profile row. Defaults to 'en' server-side.
|
||||
locale?: SupportedLocale;
|
||||
}
|
||||
|
||||
// Trigger signup. User receives a magic link email. Completion happens in
|
||||
// `completeSessionFromUrl` below once the callback fires.
|
||||
export async function signUpWithMagicLink(
|
||||
client: AppSupabaseClient,
|
||||
params: SignupParams,
|
||||
): Promise<void> {
|
||||
const { error } = await client.auth.signInWithOtp({
|
||||
email: params.email,
|
||||
options: {
|
||||
emailRedirectTo: params.redirectTo,
|
||||
shouldCreateUser: true,
|
||||
data: {
|
||||
invite_code: params.inviteCode,
|
||||
username: params.username.toLowerCase(),
|
||||
display_name: params.displayName ?? params.username,
|
||||
...(params.locale ? { locale: params.locale } : {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
// Trigger magic-link login for an existing user. No invite code needed.
|
||||
export async function loginWithMagicLink(
|
||||
client: AppSupabaseClient,
|
||||
email: string,
|
||||
redirectTo: string,
|
||||
): Promise<void> {
|
||||
const { error } = await client.auth.signInWithOtp({
|
||||
email,
|
||||
options: {
|
||||
emailRedirectTo: redirectTo,
|
||||
shouldCreateUser: false,
|
||||
},
|
||||
});
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
// Parse a callback URL produced by the magic-link email and establish a session.
|
||||
// Works for both PKCE (`?code=…`) and legacy token-hash (`#access_token=…`).
|
||||
export async function completeSessionFromUrl(
|
||||
client: AppSupabaseClient,
|
||||
url: string,
|
||||
): Promise<void> {
|
||||
const u = new URL(url);
|
||||
|
||||
// PKCE flow: exchange the code.
|
||||
const code = u.searchParams.get('code');
|
||||
if (code) {
|
||||
const { error } = await client.auth.exchangeCodeForSession(code);
|
||||
if (error) throw error;
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash fragment flow.
|
||||
if (u.hash && u.hash.length > 1) {
|
||||
const hash = new URLSearchParams(u.hash.slice(1));
|
||||
const access_token = hash.get('access_token');
|
||||
const refresh_token = hash.get('refresh_token');
|
||||
if (access_token && refresh_token) {
|
||||
const { error } = await client.auth.setSession({ access_token, refresh_token });
|
||||
if (error) throw error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('callback URL contained no auth params');
|
||||
}
|
||||
|
||||
export async function signOut(client: AppSupabaseClient): Promise<void> {
|
||||
const { error } = await client.auth.signOut();
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
// Verify a 6-digit OTP that arrived via magic-link email. Establishes a
|
||||
// session in the CURRENT webview — no browser redirect involved. Used by the
|
||||
// desktop/mobile apps where cross-origin redirects don't carry the session
|
||||
// back to the native webview.
|
||||
export async function verifyMagicLinkOtp(
|
||||
client: AppSupabaseClient,
|
||||
email: string,
|
||||
token: string,
|
||||
): Promise<void> {
|
||||
const { error } = await client.auth.verifyOtp({
|
||||
email,
|
||||
token: token.trim(),
|
||||
type: 'email',
|
||||
});
|
||||
if (error) throw error;
|
||||
}
|
||||
Reference in New Issue
Block a user