UNPKG

@gftdcojp/gftd-orm

Version:

Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture

459 lines (443 loc) 14.5 kB
/** * Next.js Auth0 React Hooks & Protection Middleware * * nextjs-auth0互換のHooksとミドルウェア: * - useUser() - ユーザー情報取得フック * - UserProvider - Reactコンテキストプロバイダー * - withApiAuthRequired() - API保護ミドルウェア * - withPageAuthRequired() - ページ保護ミドルウェア */ 'use client'; "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.nextjsAuth0HooksExamples = void 0; exports.UserProvider = UserProvider; exports.useUser = useUser; exports.withApiAuthRequired = withApiAuthRequired; exports.withPageAuthRequired = withPageAuthRequired; exports.AuthenticatedLayout = AuthenticatedLayout; exports.useAccessToken = useAccessToken; exports.useLogout = useLogout; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); /** * User Context */ const UserContext = (0, react_1.createContext)(undefined); /** * 🎣 UserProvider - Reactコンテキストプロバイダー */ function UserProvider({ children, user: initialUser, profileUrl = '/auth/profile', loginUrl = '/auth/login' }) { const [user, setUser] = (0, react_1.useState)(initialUser || null); const [error, setError] = (0, react_1.useState)(); const [isLoading, setIsLoading] = (0, react_1.useState)(!initialUser); /** * セッション確認 */ const checkSession = async () => { try { setIsLoading(true); setError(undefined); const response = await fetch(profileUrl, { credentials: 'same-origin', }); if (response.ok) { const data = await response.json(); setUser(data.user); } else if (response.status === 401) { setUser(null); } else { throw new Error(`Failed to fetch user: ${response.status}`); } } catch (err) { setError(err instanceof Error ? err : new Error('Unknown error')); setUser(null); } finally { setIsLoading(false); } }; /** * 初期化時にセッション確認 */ (0, react_1.useEffect)(() => { if (!initialUser) { checkSession(); } }, [initialUser]); const contextValue = { user, error, isLoading, checkSession, }; return ((0, jsx_runtime_1.jsx)(UserContext.Provider, { value: contextValue, children: children })); } /** * 🎣 useUser Hook - ユーザー情報取得 */ function useUser() { const context = (0, react_1.useContext)(UserContext); if (context === undefined) { throw new Error('useUser must be used within a UserProvider'); } return context; } /** * 🛡️ Server-side Protection Middleware */ /** * API Route保護用のwrapper */ function withApiAuthRequired(handler) { return async (...args) => { try { // Server-side sessionチェック const session = await getServerSession(); if (!session) { return new Response(JSON.stringify({ error: 'unauthorized', message: 'Authentication required' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } // 元のハンドラーを実行 return await handler(...args); } catch (error) { console.error('withApiAuthRequired error:', error); return new Response(JSON.stringify({ error: 'internal_server_error', message: 'Internal server error' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } }; } /** * Page Component保護用のwrapper */ function withPageAuthRequired(Component, options = {}) { const { loginUrl = '/auth/login' } = options; const ProtectedComponent = (props) => { const { user, isLoading, error } = useUser(); // ローディング中 if (isLoading) { return ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }, children: (0, jsx_runtime_1.jsx)("div", { children: "Loading..." }) })); } // エラー発生 if (error) { return ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }, children: (0, jsx_runtime_1.jsxs)("div", { children: ["Error: ", error.message] }) })); } // 未認証の場合はリダイレクト if (!user) { if (typeof window !== 'undefined') { const returnTo = options.returnTo || window.location.pathname; const redirectUrl = `${loginUrl}?returnTo=${encodeURIComponent(returnTo)}`; window.location.href = redirectUrl; } return ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }, children: (0, jsx_runtime_1.jsx)("div", { children: "Redirecting to login..." }) })); } // 認証済みの場合は元のコンポーネントを表示 return (0, jsx_runtime_1.jsx)(Component, { ...props }); }; // Display nameを設定 ProtectedComponent.displayName = `withPageAuthRequired(${Component.displayName || Component.name || 'Component'})`; return ProtectedComponent; } /** * 🔧 Server-side Utilities */ /** * サーバーサイドでセッションを取得(Server Componentで使用) */ async function getServerSession() { try { // Dynamic importを使用してServer-side限定の関数を呼び出し const { getSession } = await Promise.resolve().then(() => __importStar(require('./nextjs-auth0'))); return await getSession(); } catch (error) { console.error('Failed to get server session:', error); return null; } } /** * 🎯 Higher-Order Components (HOCs) */ /** * 認証が必要なレイアウトコンポーネント */ function AuthenticatedLayout({ children, fallback }) { const { user, isLoading } = useUser(); if (isLoading) { return fallback || ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }, children: (0, jsx_runtime_1.jsx)("div", { children: "Loading..." }) })); } if (!user) { return ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }, children: [(0, jsx_runtime_1.jsx)("h2", { children: "Authentication Required" }), (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '1rem' }, children: [(0, jsx_runtime_1.jsx)("a", { href: "/auth/login", style: { padding: '0.5rem 1rem', backgroundColor: '#0070f3', color: 'white', textDecoration: 'none', borderRadius: '4px', marginRight: '0.5rem' }, children: "Log In" }), (0, jsx_runtime_1.jsx)("a", { href: "/auth/login?screen_hint=signup", style: { padding: '0.5rem 1rem', backgroundColor: '#28a745', color: 'white', textDecoration: 'none', borderRadius: '4px' }, children: "Sign Up" })] })] })); } return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }); } /** * 🎛️ Utility Hooks */ /** * Access Token取得フック */ function useAccessToken() { const [accessToken, setAccessToken] = (0, react_1.useState)(null); const [isLoading, setIsLoading] = (0, react_1.useState)(false); const [error, setError] = (0, react_1.useState)(null); const fetchAccessToken = async () => { try { setIsLoading(true); setError(null); const response = await fetch('/auth/access-token', { credentials: 'same-origin', }); if (response.ok) { const data = await response.json(); setAccessToken(data.accessToken); } else { throw new Error(`Failed to fetch access token: ${response.status}`); } } catch (err) { setError(err instanceof Error ? err : new Error('Unknown error')); } finally { setIsLoading(false); } }; (0, react_1.useEffect)(() => { fetchAccessToken(); }, []); return { accessToken, isLoading, error, refetch: fetchAccessToken, }; } /** * ログアウト機能フック */ function useLogout() { const logout = (returnTo) => { const logoutUrl = returnTo ? `/auth/logout?returnTo=${encodeURIComponent(returnTo)}` : '/auth/logout'; if (typeof window !== 'undefined') { window.location.href = logoutUrl; } }; return logout; } /** * 📖 使用例 */ exports.nextjsAuth0HooksExamples = { /** * App Component での UserProvider 設定例 */ appComponent: ` // app/layout.tsx import { UserProvider } from './src/nextjs-auth0-hooks'; import { getSession } from './src/nextjs-auth0'; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { const session = await getSession(); return ( <html> <body> <UserProvider user={session?.user}> {children} </UserProvider> </body> </html> ); } `, /** * Client Component での useUser 使用例 */ clientComponent: ` 'use client'; import { useUser, useLogout } from './src/nextjs-auth0-hooks'; export default function UserProfile() { const { user, isLoading, error } = useUser(); const logout = useLogout(); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!user) return <div>Not authenticated</div>; return ( <div> <h1>Welcome, {user.name}!</h1> <p>Email: {user.email}</p> <button onClick={() => logout()}>Logout</button> </div> ); } `, /** * Protected Page の実装例 */ protectedPage: ` 'use client'; import { withPageAuthRequired } from './src/nextjs-auth0-hooks'; function Dashboard() { return ( <div> <h1>Dashboard</h1> <p>This page is protected and requires authentication.</p> </div> ); } export default withPageAuthRequired(Dashboard); `, /** * Protected API Route の実装例 */ protectedApi: ` // app/api/protected/route.ts import { withApiAuthRequired } from './src/nextjs-auth0-hooks'; async function handler() { return Response.json({ message: 'This is a protected API route', timestamp: new Date().toISOString() }); } export const GET = withApiAuthRequired(handler); export const POST = withApiAuthRequired(handler); `, /** * Layout with Authentication の実装例 */ authenticatedLayout: ` 'use client'; import { AuthenticatedLayout } from './src/nextjs-auth0-hooks'; export default function AppLayout({ children }: { children: React.ReactNode }) { return ( <AuthenticatedLayout fallback={<div>Loading your dashboard...</div>} > <nav> <h1>My App</h1> </nav> <main>{children}</main> </AuthenticatedLayout> ); } `, /** * Access Token使用例 */ accessTokenUsage: ` 'use client'; import { useAccessToken } from './src/nextjs-auth0-hooks'; export default function ApiCaller() { const { accessToken, isLoading, error } = useAccessToken(); const callProtectedApi = async () => { if (!accessToken) return; const response = await fetch('/api/protected', { headers: { 'Authorization': \`Bearer \${accessToken}\` } }); const data = await response.json(); console.log(data); }; if (isLoading) return <div>Loading access token...</div>; if (error) return <div>Error: {error.message}</div>; return ( <button onClick={callProtectedApi}> Call Protected API </button> ); } `, }; //# sourceMappingURL=nextjs-auth0-hooks.js.map