110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
// This file is automatically generated. Do not edit it directly.
|
|||
|
|
import { createMiddleware } from '@tanstack/react-start'
|
||
|
|
import { getRequest } from '@tanstack/react-start/server'
|
||
|
|
import { createClient } from '@supabase/supabase-js'
|
||
|
|
import type { Database } from './types'
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
function isNewSupabaseApiKey(value: string): boolean {
|
||
|
|
return value.startsWith('sb_publishable_') || value.startsWith('sb_secret_');
|
||
|
|
}
|
||
|
|
|
||
|
|
function createSupabaseFetch(supabaseKey: string): typeof fetch {
|
||
|
|
return (input, init) => {
|
||
|
|
const headers = new Headers(
|
||
|
|
typeof Request !== 'undefined' && input instanceof Request ? input.headers : undefined,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (init?.headers) {
|
||
|
|
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
||
|
|
}
|
||
|
|
|
||
|
|
// New Supabase API keys are opaque strings, not bearer JWTs.
|
||
|
|
if (isNewSupabaseApiKey(supabaseKey) && headers.get('Authorization') === `Bearer ${supabaseKey}`) {
|
||
|
|
headers.delete('Authorization');
|
||
|
|
}
|
||
|
|
|
||
|
|
headers.set('apikey', supabaseKey);
|
||
|
|
return fetch(input, { ...init, headers });
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export const requireSupabaseAuth = createMiddleware({ type: 'function' }).server(
|
||
|
|
async ({ next }) => {
|
||
|
|
|
||
|
|
const SUPABASE_URL = process.env['SUPABASE_URL'];
|
||
|
|
const SUPABASE_PUBLISHABLE_KEY = process.env['SUPABASE_PUBLISHABLE_KEY'];
|
||
|
|
|
||
|
|
if (!SUPABASE_URL || !SUPABASE_PUBLISHABLE_KEY) {
|
||
|
|
const missing = [
|
||
|
|
...(!SUPABASE_URL ? ['SUPABASE_URL'] : []),
|
||
|
|
...(!SUPABASE_PUBLISHABLE_KEY ? ['SUPABASE_PUBLISHABLE_KEY'] : []),
|
||
|
|
];
|
||
|
|
const message = `Missing Supabase environment variable(s): ${missing.join(', ')}. Connect Supabase in Lovable Cloud.`;
|
||
|
|
console.error(`[Supabase] ${message}`);
|
||
|
|
throw new Error(message);
|
||
|
|
}
|
||
|
|
|
||
|
|
const request = getRequest();
|
||
|
|
|
||
|
|
if (!request?.headers) {
|
||
|
|
throw new Error('Unauthorized: No request headers available');
|
||
|
|
}
|
||
|
|
|
||
|
|
const authHeader = request.headers.get('authorization');
|
||
|
|
|
||
|
|
if (!authHeader) {
|
||
|
|
throw new Error('Unauthorized: No authorization header provided');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!authHeader.startsWith('Bearer ')) {
|
||
|
|
throw new Error('Unauthorized: Only Bearer tokens are supported');
|
||
|
|
}
|
||
|
|
|
||
|
|
const token = authHeader.replace('Bearer ', '');
|
||
|
|
if (!token) {
|
||
|
|
throw new Error('Unauthorized: No token provided');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (token.split('.').length !== 3) {
|
||
|
|
throw new Error('Unauthorized: Invalid token');
|
||
|
|
}
|
||
|
|
|
||
|
|
const supabase = createClient<Database>(
|
||
|
|
SUPABASE_URL!,
|
||
|
|
SUPABASE_PUBLISHABLE_KEY!,
|
||
|
|
{
|
||
|
|
global: {
|
||
|
|
fetch: createSupabaseFetch(SUPABASE_PUBLISHABLE_KEY!),
|
||
|
|
headers: {
|
||
|
|
Authorization: `Bearer ${token}`,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
auth: {
|
||
|
|
storage: undefined,
|
||
|
|
persistSession: false,
|
||
|
|
autoRefreshToken: false,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
const { data, error } = await supabase.auth.getClaims(token);
|
||
|
|
if (error || !data?.claims) {
|
||
|
|
throw new Error('Unauthorized: Invalid token');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!data.claims.sub) {
|
||
|
|
throw new Error('Unauthorized: No user ID found in token');
|
||
|
|
}
|
||
|
|
|
||
|
|
return next({
|
||
|
|
context: {
|
||
|
|
supabase,
|
||
|
|
userId: data.claims.sub,
|
||
|
|
claims: data.claims,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
},
|
||
|
|
);
|