prefer using providers conf from supabase instead of env
This commit is contained in:
@@ -13,7 +13,8 @@ import { Button } from '@kit/ui/button';
|
|||||||
import { If } from '@kit/ui/if';
|
import { If } from '@kit/ui/if';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { authConfig, featureFlagsConfig, pathsConfig } from '@kit/shared/config';
|
import { featureFlagsConfig, pathsConfig } from '@kit/shared/config';
|
||||||
|
import { useAuthConfig } from '@kit/shared/hooks';
|
||||||
|
|
||||||
const ModeToggle = dynamic(() =>
|
const ModeToggle = dynamic(() =>
|
||||||
import('@kit/ui/mode-toggle').then((mod) => ({
|
import('@kit/ui/mode-toggle').then((mod) => ({
|
||||||
@@ -57,6 +58,8 @@ export function SiteHeaderAccountSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function AuthButtons() {
|
function AuthButtons() {
|
||||||
|
const { config } = useAuthConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'animate-in fade-in flex gap-x-2.5 duration-500'}>
|
<div className={'animate-in fade-in flex gap-x-2.5 duration-500'}>
|
||||||
<div className={'hidden md:flex'}>
|
<div className={'hidden md:flex'}>
|
||||||
@@ -65,14 +68,17 @@ function AuthButtons() {
|
|||||||
</If>
|
</If>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{config && (
|
||||||
<div className={'flex gap-x-2.5'}>
|
<div className={'flex gap-x-2.5'}>
|
||||||
|
{(config.providers.password || config.providers.oAuth.length > 0) && (
|
||||||
<Button className={'block'} asChild variant={'ghost'}>
|
<Button className={'block'} asChild variant={'ghost'}>
|
||||||
<Link href={pathsConfig.auth.signIn}>
|
<Link href={pathsConfig.auth.signIn}>
|
||||||
<Trans i18nKey={'auth:signIn'} />
|
<Trans i18nKey={'auth:signIn'} />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{authConfig.providers.password && (
|
{config.providers.password && (
|
||||||
<Button asChild className="text-xs md:text-sm" variant={'default'}>
|
<Button asChild className="text-xs md:text-sm" variant={'default'}>
|
||||||
<Link href={pathsConfig.auth.signUp}>
|
<Link href={pathsConfig.auth.signUp}>
|
||||||
<Trans i18nKey={'auth:signUp'} />
|
<Trans i18nKey={'auth:signUp'} />
|
||||||
@@ -80,6 +86,7 @@ function AuthButtons() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { SignInMethodsContainer } from '@kit/auth/sign-in';
|
import { Providers, SignInMethodsContainer } from '@kit/auth/sign-in';
|
||||||
import { authConfig, pathsConfig } from '@kit/shared/config';
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
import { Heading } from '@kit/ui/heading';
|
import { Heading } from '@kit/ui/heading';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
@@ -9,9 +9,11 @@ import { Trans } from '@kit/ui/trans';
|
|||||||
export default function PasswordOption({
|
export default function PasswordOption({
|
||||||
inviteToken,
|
inviteToken,
|
||||||
returnPath,
|
returnPath,
|
||||||
|
providers,
|
||||||
}: {
|
}: {
|
||||||
inviteToken?: string;
|
inviteToken?: string;
|
||||||
returnPath?: string;
|
returnPath?: string;
|
||||||
|
providers: Providers;
|
||||||
}) {
|
}) {
|
||||||
const signUpPath =
|
const signUpPath =
|
||||||
pathsConfig.auth.signUp +
|
pathsConfig.auth.signUp +
|
||||||
@@ -39,7 +41,7 @@ export default function PasswordOption({
|
|||||||
<SignInMethodsContainer
|
<SignInMethodsContainer
|
||||||
inviteToken={inviteToken}
|
inviteToken={inviteToken}
|
||||||
paths={paths}
|
paths={paths}
|
||||||
providers={authConfig.providers}
|
providers={providers}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={'flex justify-center'}>
|
<div className={'flex justify-center'}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pathsConfig, authConfig } from '@kit/shared/config';
|
import { getServerAuthConfig, pathsConfig } from '@kit/shared/config';
|
||||||
|
|
||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
@@ -24,11 +24,23 @@ async function SignInPage({ searchParams }: SignInPageProps) {
|
|||||||
const { invite_token: inviteToken, next: returnPath = pathsConfig.app.home } =
|
const { invite_token: inviteToken, next: returnPath = pathsConfig.app.home } =
|
||||||
await searchParams;
|
await searchParams;
|
||||||
|
|
||||||
|
const authConfig = await getServerAuthConfig();
|
||||||
|
|
||||||
if (authConfig.providers.password) {
|
if (authConfig.providers.password) {
|
||||||
return <PasswordOption inviteToken={inviteToken} returnPath={returnPath} />;
|
return (
|
||||||
|
<PasswordOption
|
||||||
|
inviteToken={inviteToken}
|
||||||
|
returnPath={returnPath}
|
||||||
|
providers={authConfig.providers}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authConfig.providers.oAuth.includes('keycloak')) {
|
||||||
return <SignInPageClientRedirect />;
|
return <SignInPageClientRedirect />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n(SignInPage);
|
export default withI18n(SignInPage);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Link from 'next/link';
|
|||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { SignUpMethodsContainer } from '@kit/auth/sign-up';
|
import { SignUpMethodsContainer } from '@kit/auth/sign-up';
|
||||||
import { authConfig, pathsConfig } from '@kit/shared/config';
|
import { getServerAuthConfig, pathsConfig } from '@kit/shared/config';
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
import { Heading } from '@kit/ui/heading';
|
import { Heading } from '@kit/ui/heading';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
@@ -38,6 +38,8 @@ async function SignUpPage({ searchParams }: Props) {
|
|||||||
pathsConfig.auth.signIn +
|
pathsConfig.auth.signIn +
|
||||||
(inviteToken ? `?invite_token=${inviteToken}` : '');
|
(inviteToken ? `?invite_token=${inviteToken}` : '');
|
||||||
|
|
||||||
|
const authConfig = await getServerAuthConfig();
|
||||||
|
|
||||||
if (!authConfig.providers.password) {
|
if (!authConfig.providers.password) {
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
@@ -56,9 +58,9 @@ async function SignUpPage({ searchParams }: Props) {
|
|||||||
|
|
||||||
<SignUpMethodsContainer
|
<SignUpMethodsContainer
|
||||||
providers={authConfig.providers}
|
providers={authConfig.providers}
|
||||||
displayTermsCheckbox={authConfig.displayTermsCheckbox}
|
|
||||||
inviteToken={inviteToken}
|
inviteToken={inviteToken}
|
||||||
paths={paths}
|
paths={paths}
|
||||||
|
displayTermsCheckbox={authConfig.displayTermsCheckbox}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={'flex justify-center'}>
|
<div className={'flex justify-center'}>
|
||||||
|
|||||||
@@ -146,14 +146,21 @@ export default function Dashboard({
|
|||||||
}) {
|
}) {
|
||||||
const height = account.accountParams?.height || 0;
|
const height = account.accountParams?.height || 0;
|
||||||
const weight = account.accountParams?.weight || 0;
|
const weight = account.accountParams?.weight || 0;
|
||||||
const { age = 0, gender } = PersonalCode.parsePersonalCode(account.personal_code!);
|
|
||||||
|
let age: number = 0;
|
||||||
|
let gender: { label: string; value: string } | null = null;
|
||||||
|
try {
|
||||||
|
({ age = 0, gender } = PersonalCode.parsePersonalCode(account.personal_code!));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse personal code", e);
|
||||||
|
}
|
||||||
const bmiStatus = getBmiStatus(bmiThresholds, { age, height, weight });
|
const bmiStatus = getBmiStatus(bmiThresholds, { age, height, weight });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="xs:grid-cols-2 grid auto-rows-fr gap-3 sm:grid-cols-4 lg:grid-cols-5">
|
<div className="xs:grid-cols-2 grid auto-rows-fr gap-3 sm:grid-cols-4 lg:grid-cols-5">
|
||||||
{cards({
|
{cards({
|
||||||
gender: gender.label,
|
gender: gender?.label,
|
||||||
age,
|
age,
|
||||||
height,
|
height,
|
||||||
weight,
|
weight,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default class PersonalCode {
|
|||||||
if (age >= 60) {
|
if (age >= 60) {
|
||||||
return '60';
|
return '60';
|
||||||
}
|
}
|
||||||
throw new Error('Age range not supported');
|
throw new Error('Age range not supported, age=' + age);
|
||||||
})();
|
})();
|
||||||
const gender = (() => {
|
const gender = (() => {
|
||||||
const gender = parsed.getGender();
|
const gender = parsed.getGender();
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ import { MagicLinkAuthContainer } from './magic-link-auth-container';
|
|||||||
import { OauthProviders } from './oauth-providers';
|
import { OauthProviders } from './oauth-providers';
|
||||||
import { PasswordSignInContainer } from './password-sign-in-container';
|
import { PasswordSignInContainer } from './password-sign-in-container';
|
||||||
|
|
||||||
|
export type Providers = {
|
||||||
|
password: boolean;
|
||||||
|
magicLink: boolean;
|
||||||
|
oAuth: Provider[];
|
||||||
|
};
|
||||||
|
|
||||||
export function SignInMethodsContainer(props: {
|
export function SignInMethodsContainer(props: {
|
||||||
inviteToken?: string;
|
inviteToken?: string;
|
||||||
|
|
||||||
@@ -25,11 +31,7 @@ export function SignInMethodsContainer(props: {
|
|||||||
updateAccount: string;
|
updateAccount: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
providers: {
|
providers: Providers;
|
||||||
password: boolean;
|
|
||||||
magicLink: boolean;
|
|
||||||
oAuth: Provider[];
|
|
||||||
};
|
|
||||||
}) {
|
}) {
|
||||||
const client = useSupabase();
|
const client = useSupabase();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
|
|
||||||
import type { Provider } from '@supabase/supabase-js';
|
import type { Provider } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import { isBrowser } from '@kit/shared/utils';
|
import { isBrowser } from '@kit/shared/utils';
|
||||||
|
|||||||
137
packages/shared/src/config/auth-providers.service.ts
Normal file
137
packages/shared/src/config/auth-providers.service.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
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] = await Promise.all([
|
||||||
|
this.isPasswordEnabled({ settings }),
|
||||||
|
this.isMagicLinkEnabled(),
|
||||||
|
this.getEnabledOAuthProviders({ settings }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
providers: {
|
||||||
|
password: passwordEnabled,
|
||||||
|
magicLink: magicLinkEnabled,
|
||||||
|
oAuth: oAuthProviders,
|
||||||
|
},
|
||||||
|
displayTermsCheckbox: authConfig.displayTermsCheckbox,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
102
packages/shared/src/config/dynamic-auth.config.ts
Normal file
102
packages/shared/src/config/dynamic-auth.config.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import type { Provider } from '@supabase/supabase-js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { createAuthProvidersService } from './auth-providers.service';
|
||||||
|
|
||||||
|
const providers: z.ZodType<Provider> = getProviders();
|
||||||
|
|
||||||
|
const DynamicAuthConfigSchema = z.object({
|
||||||
|
providers: z.object({
|
||||||
|
password: z.boolean().describe('Enable password authentication.'),
|
||||||
|
magicLink: z.boolean().describe('Enable magic link authentication.'),
|
||||||
|
oAuth: providers.array(),
|
||||||
|
}),
|
||||||
|
displayTermsCheckbox: z.boolean().describe('Whether to display the terms checkbox during sign-up.'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getDynamicAuthConfig() {
|
||||||
|
const authService = createAuthProvidersService();
|
||||||
|
const dynamicProviders = await authService.getAuthConfig();
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
providers: dynamicProviders.providers,
|
||||||
|
displayTermsCheckbox: dynamicProviders.displayTermsCheckbox,
|
||||||
|
};
|
||||||
|
|
||||||
|
return DynamicAuthConfigSchema.parse(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCachedAuthConfig() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const cached = sessionStorage.getItem('auth-config');
|
||||||
|
if (cached) {
|
||||||
|
try {
|
||||||
|
const { data, timestamp } = JSON.parse(cached);
|
||||||
|
// Cache for 5 minutes
|
||||||
|
if (Date.now() - timestamp < 5 * 60 * 1000) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Invalid auth config cache:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await getDynamicAuthConfig();
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem('auth-config', JSON.stringify({
|
||||||
|
data: config,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to cache auth config:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerAuthConfig() {
|
||||||
|
return getDynamicAuthConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isProviderEnabled(provider: 'password' | 'magicLink' | Provider): Promise<boolean> {
|
||||||
|
const authService = createAuthProvidersService();
|
||||||
|
const settings = await authService.fetchAuthSettings();
|
||||||
|
|
||||||
|
switch (provider) {
|
||||||
|
case 'password':
|
||||||
|
return authService.isPasswordEnabled({ settings });
|
||||||
|
case 'magicLink':
|
||||||
|
return authService.isMagicLinkEnabled();
|
||||||
|
default:
|
||||||
|
return authService.isOAuthProviderEnabled({ provider, settings });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProviders() {
|
||||||
|
return z.enum([
|
||||||
|
'apple',
|
||||||
|
'azure',
|
||||||
|
'bitbucket',
|
||||||
|
'discord',
|
||||||
|
'facebook',
|
||||||
|
'figma',
|
||||||
|
'github',
|
||||||
|
'gitlab',
|
||||||
|
'google',
|
||||||
|
'kakao',
|
||||||
|
'keycloak',
|
||||||
|
'linkedin',
|
||||||
|
'linkedin_oidc',
|
||||||
|
'notion',
|
||||||
|
'slack',
|
||||||
|
'spotify',
|
||||||
|
'twitch',
|
||||||
|
'twitter',
|
||||||
|
'workos',
|
||||||
|
'zoom',
|
||||||
|
'fly',
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
createPath,
|
createPath,
|
||||||
getTeamAccountSidebarConfig,
|
getTeamAccountSidebarConfig,
|
||||||
} from './team-account-navigation.config';
|
} from './team-account-navigation.config';
|
||||||
|
import { getCachedAuthConfig, getServerAuthConfig } from './dynamic-auth.config';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
appConfig,
|
appConfig,
|
||||||
@@ -18,4 +19,6 @@ export {
|
|||||||
getTeamAccountSidebarConfig,
|
getTeamAccountSidebarConfig,
|
||||||
pathsConfig,
|
pathsConfig,
|
||||||
personalAccountNavigationConfig,
|
personalAccountNavigationConfig,
|
||||||
|
getCachedAuthConfig,
|
||||||
|
getServerAuthConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './use-csrf-token';
|
export * from './use-csrf-token';
|
||||||
export * from './use-current-locale-language-names';
|
export * from './use-current-locale-language-names';
|
||||||
|
export * from './use-auth-config';
|
||||||
|
|||||||
76
packages/shared/src/hooks/use-auth-config.ts
Normal file
76
packages/shared/src/hooks/use-auth-config.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import type { Provider } from '@supabase/supabase-js';
|
||||||
|
import { getCachedAuthConfig } from '../config/dynamic-auth.config';
|
||||||
|
import { authConfig } from '../config';
|
||||||
|
|
||||||
|
interface AuthConfig {
|
||||||
|
providers: {
|
||||||
|
password: boolean;
|
||||||
|
magicLink: boolean;
|
||||||
|
oAuth: Provider[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseAuthConfigResult {
|
||||||
|
config: AuthConfig | null;
|
||||||
|
loading: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
refetch: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthConfig(): UseAuthConfigResult {
|
||||||
|
const [config, setConfig] = useState<AuthConfig | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
const fetchConfig = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const authConfig = await getCachedAuthConfig();
|
||||||
|
setConfig(authConfig);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch auth config', err);
|
||||||
|
setError(err instanceof Error ? err : new Error('Failed to fetch auth config'));
|
||||||
|
setConfig(authConfig);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refetch: fetchConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useProviderEnabled(provider: 'password' | 'magicLink' | Provider) {
|
||||||
|
const { config, loading, error } = useAuthConfig();
|
||||||
|
|
||||||
|
const isEnabled = (() => {
|
||||||
|
if (!config) return false;
|
||||||
|
|
||||||
|
switch (provider) {
|
||||||
|
case 'password':
|
||||||
|
return config.providers.password;
|
||||||
|
case 'magicLink':
|
||||||
|
return config.providers.magicLink;
|
||||||
|
default:
|
||||||
|
return config.providers.oAuth.includes(provider);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: isEnabled,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user