Files
medreport_mrb2b/packages/shared/src/config/auth-providers.service.ts
2025-09-10 06:34:27 +03:00

145 lines
4.2 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);
}