@gftdcojp/gftd-orm
Version:
Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture
459 lines (443 loc) • 14.5 kB
JavaScript
/**
* Next.js Auth0 React Hooks & Protection Middleware
*
* nextjs-auth0互換のHooksとミドルウェア:
* - useUser() - ユーザー情報取得フック
* - UserProvider - Reactコンテキストプロバイダー
* - withApiAuthRequired() - API保護ミドルウェア
* - withPageAuthRequired() - ページ保護ミドルウェア
*/
'use client';
;
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