177 lines
4.4 KiB
TypeScript
177 lines
4.4 KiB
TypeScript
import type { Provider } from '@supabase/supabase-js';
|
|
|
|
import authConfig from './auth.config';
|
|
|
|
type SupabaseExternalProvider = Provider | 'email';
|
|
interface SupabaseAuthSettings {
|
|
external: Record<SupabaseExternalProvider, boolean>;
|
|
disable_signup: boolean;
|
|
mailer_autoconfirm: boolean;
|
|
}
|
|
|
|
export class AuthProvidersService {
|
|
private supabaseUrl: string;
|
|
private cache: Map<
|
|
string,
|
|
{ data: SupabaseAuthSettings; timestamp: number }
|
|
> = new Map();
|
|
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
|
|
constructor(supabaseUrl: string) {
|
|
this.supabaseUrl = supabaseUrl;
|
|
}
|
|
|
|
async fetchAuthSettings(): Promise<SupabaseAuthSettings | null> {
|
|
try {
|
|
const cacheKey = 'auth-settings';
|
|
const cached = this.cache.get(cacheKey);
|
|
|
|
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
|
|
return cached.data;
|
|
}
|
|
|
|
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
if (!anonKey) {
|
|
throw new Error('NEXT_PUBLIC_SUPABASE_ANON_KEY is required');
|
|
}
|
|
|
|
const response = await fetch(
|
|
`${this.supabaseUrl}/auth/v1/settings?apikey=${anonKey}`,
|
|
{
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
},
|
|
);
|
|
|
|
if (!response.ok) {
|
|
console.warn(
|
|
'Failed to fetch auth settings from Supabase:',
|
|
response.status,
|
|
);
|
|
return null;
|
|
}
|
|
|
|
const settings: SupabaseAuthSettings = await response.json();
|
|
|
|
this.cache.set(cacheKey, { data: settings, timestamp: Date.now() });
|
|
|
|
return settings;
|
|
} catch (error) {
|
|
console.warn('Error fetching auth settings from Supabase:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
isPasswordEnabled({
|
|
settings,
|
|
}: {
|
|
settings: SupabaseAuthSettings | null;
|
|
}): boolean {
|
|
if (settings) {
|
|
return settings.external.email === true && !settings.disable_signup;
|
|
}
|
|
|
|
return process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true';
|
|
}
|
|
|
|
isMailerAutoconfirmEnabled({
|
|
settings,
|
|
}: {
|
|
settings: SupabaseAuthSettings | null;
|
|
}): boolean {
|
|
return settings?.mailer_autoconfirm === true;
|
|
}
|
|
|
|
isMagicLinkEnabled(): boolean {
|
|
return process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true';
|
|
}
|
|
|
|
isOAuthProviderEnabled({
|
|
provider,
|
|
settings,
|
|
}: {
|
|
provider: SupabaseExternalProvider;
|
|
settings: SupabaseAuthSettings | null;
|
|
}): boolean {
|
|
if (settings && settings.external) {
|
|
return settings.external[provider] === true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getEnabledOAuthProviders({
|
|
settings,
|
|
}: {
|
|
settings: SupabaseAuthSettings | null;
|
|
}): SupabaseExternalProvider[] {
|
|
const enabledProviders: SupabaseExternalProvider[] = [];
|
|
|
|
if (settings && settings.external) {
|
|
for (const [providerName, isEnabled] of Object.entries(
|
|
settings.external,
|
|
)) {
|
|
if (isEnabled && providerName !== 'email') {
|
|
enabledProviders.push(providerName as SupabaseExternalProvider);
|
|
}
|
|
}
|
|
return enabledProviders;
|
|
}
|
|
|
|
const potentialProviders: SupabaseExternalProvider[] = ['keycloak'];
|
|
const enabledFallback: SupabaseExternalProvider[] = [];
|
|
|
|
for (const provider of potentialProviders) {
|
|
if (
|
|
provider !== 'email' &&
|
|
this.isOAuthProviderEnabled({ provider, settings })
|
|
) {
|
|
enabledFallback.push(provider);
|
|
}
|
|
}
|
|
|
|
return enabledFallback;
|
|
}
|
|
|
|
async getAuthConfig() {
|
|
const settings = await this.fetchAuthSettings();
|
|
const [
|
|
passwordEnabled,
|
|
magicLinkEnabled,
|
|
oAuthProviders,
|
|
isMailerAutoconfirmEnabled,
|
|
] = await Promise.all([
|
|
this.isPasswordEnabled({ settings }),
|
|
this.isMagicLinkEnabled(),
|
|
this.getEnabledOAuthProviders({ settings }),
|
|
this.isMailerAutoconfirmEnabled({ settings }),
|
|
]);
|
|
|
|
return {
|
|
providers: {
|
|
password: passwordEnabled,
|
|
magicLink: magicLinkEnabled,
|
|
oAuth: oAuthProviders,
|
|
},
|
|
displayTermsCheckbox: authConfig.displayTermsCheckbox,
|
|
isMailerAutoconfirmEnabled,
|
|
};
|
|
}
|
|
|
|
clearCache(): void {
|
|
this.cache.clear();
|
|
}
|
|
}
|
|
|
|
export function createAuthProvidersService(): AuthProvidersService {
|
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
|
|
if (!supabaseUrl) {
|
|
throw new Error('NEXT_PUBLIC_SUPABASE_URL is required');
|
|
}
|
|
|
|
return new AuthProvidersService(supabaseUrl);
|
|
}
|