codalware-auth
Version:
Complete authentication system with enterprise security, attack protection, team workspaces, waitlist, billing, UI components, 2FA, and account recovery - production-ready in 5 minutes. Enhanced CLI with verification, rollback, and App Router scaffolding.
123 lines (101 loc) • 3.35 kB
text/typescript
'use client';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useI18n } from '../i18n';
import type { AuthPolicy } from '../types/auth';
type UseAuthPolicyOptions = {
tenantDomain?: string;
enabled?: boolean;
debounce?: number;
};
type UseAuthPolicyResponse = {
policy: AuthPolicy | null;
loading: boolean;
error: string | null;
tenant: string;
refresh: () => void;
};
const normaliseTenant = (value?: string) => value?.trim().toLowerCase() ?? '';
export const useAuthPolicy = (options: UseAuthPolicyOptions = {}): UseAuthPolicyResponse => {
const { tenantDomain, enabled = true, debounce = 350 } = options;
const [resolvedTenant, setResolvedTenant] = useState('');
const [requestedTenant, setRequestedTenant] = useState(() => normaliseTenant(tenantDomain));
const [policy, setPolicy] = useState<AuthPolicy | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [refreshKey, setRefreshKey] = useState(0);
const abortRef = useRef<AbortController | null>(null);
const { t } = useI18n();
const normalisedTenant = useMemo(() => normaliseTenant(tenantDomain), [tenantDomain]);
useEffect(() => {
if (!enabled) {
setRequestedTenant('');
return;
}
if (debounce <= 0) {
setRequestedTenant(normalisedTenant);
return;
}
const timer = setTimeout(() => {
setRequestedTenant(normalisedTenant);
}, debounce);
return () => {
clearTimeout(timer);
};
}, [normalisedTenant, enabled, debounce]);
useEffect(() => {
if (!enabled) {
abortRef.current?.abort();
setPolicy(null);
setLoading(false);
setError(null);
return;
}
const controller = new AbortController();
abortRef.current?.abort();
abortRef.current = controller;
const query = requestedTenant ? `?tenantDomain=${encodeURIComponent(requestedTenant)}` : '';
const fetchPolicy = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/auth/policy${query}`, { signal: controller.signal });
const json = await response.json();
if (!response.ok || !json?.success) {
throw new Error(json?.error?.message || 'Policy request failed');
}
if (!controller.signal.aborted) {
setPolicy(json.data?.policy ?? null);
setResolvedTenant(requestedTenant);
}
} catch (policyError) {
if (controller.signal.aborted) return;
console.warn('Unable to load auth policy', policyError);
setPolicy(null);
setError(t('errors.policyLoadFailed'));
} finally {
if (!controller.signal.aborted) {
setLoading(false);
}
}
};
fetchPolicy();
return () => {
controller.abort();
};
}, [requestedTenant, enabled, refreshKey, t]);
const refresh = () => {
if (!enabled) {
return;
}
setRefreshKey(current => current + 1);
setRequestedTenant(normaliseTenant(tenantDomain));
};
return {
policy,
loading,
error,
tenant: resolvedTenant,
refresh,
};
};
export default useAuthPolicy;