diff --git a/app/api/auth/[...nextauth]/options.ts b/app/api/auth/[...nextauth]/options.ts new file mode 100644 index 00000000..7bf5f674 --- /dev/null +++ b/app/api/auth/[...nextauth]/options.ts @@ -0,0 +1,80 @@ +import { createClient } from "falkordb"; +import CredentialsProvider from "next-auth/providers/credentials" +import { AuthOptions } from "next-auth" + +const authOptions : AuthOptions = { + providers: [ + CredentialsProvider({ + // The name to display on the sign in form (e.g. 'Sign in with...') + name: 'Credentials', + // The credentials is used to generate a suitable form on the sign in page. + // You can specify whatever fields you are expecting to be submitted. + // e.g. domain, username, password, 2FA token, etc. + // You can pass any HTML attribute to the tag through the object. + credentials: { + host: { label: "Host", type: "text", placeholder: "localhost" }, + port: { label: "Port", type: "number", placeholder: "6379" }, + username: { label: "Username", type: "text" }, + password: { label: "Password", type: "password" } + }, + async authorize(credentials, req) { + + if (!credentials) { + return null + } + + try { + let client = await createClient({ + socket: { + host: credentials.host ?? "localhost", + port: credentials.port ? parseInt(credentials.port) : 6379, + reconnectStrategy: false + }, + password: credentials.password ?? undefined, + username: credentials.username ?? undefined + }) + .on('error', err => console.log('FalkorDB Client Error', err)) + .connect(); + + let res : any = { + host: credentials.host, + port: credentials.port, + password: credentials.password, + username: credentials.username, + } + return res + } catch (err) { + console.log(err) + return null; + } + } + + }) + ], + callbacks: { + async jwt({ token, user }) { + if (user) { + token.host = user.host; + token.port = user.port; + token.username = user.username; + token.password = user.password; + } + + return token; + }, + async session({ session, token, user }) { + if (session.user) { + session.user.host = token.host as string; + session.user.port = parseInt(token.port as string); + session.user.username = token.username as string; + session.user.password = token.password as string; + + } + return session; + } + } +} + + + +export default authOptions \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..27ead0b0 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,7 @@ +import NextAuth from "next-auth" +import authOptions from "./options"; + + +const handler = NextAuth(authOptions) + +export { handler as GET, handler as POST } \ No newline at end of file diff --git a/app/api/graph/route.ts b/app/api/graph/route.ts index 15d43ac3..2f718e05 100644 --- a/app/api/graph/route.ts +++ b/app/api/graph/route.ts @@ -1,11 +1,17 @@ import { NextRequest, NextResponse } from "next/server"; import { Graph, RedisClientType, RedisDefaultModules, createClient } from 'falkordb'; +import { getServerSession } from "next-auth/next"; +import authOptions from "../auth/[...nextauth]/options"; +import { cookies } from 'next/headers' const client = createClient(); client.connect() export async function GET(request: NextRequest) { + const session = await getServerSession(authOptions) + console.log(JSON.stringify( session)) + const graphID = request.nextUrl.searchParams.get("graph"); try { if (graphID) { diff --git a/app/layout.tsx b/app/layout.tsx index 307db4fc..5673253f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from 'next/font/google' import './globals.css' import Navbar from '@/components/custom/navbar' import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable' +import NextAuth from 'next-auth' +import { NextAuthProvider } from './providers' const inter = Inter({ subsets: ['latin'] }) @@ -20,13 +22,7 @@ export default function RootLayout({
- - - - - - {children} - + {children}
diff --git a/app/login/page.tsx b/app/login/page.tsx index cc903711..530dfe2d 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,46 +1,54 @@ +'use client' + import { CardTitle, CardDescription, CardHeader, CardContent, CardFooter, Card } from "@/components/ui/card" import { Label } from "@/components/ui/label" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" +import { FormEvent } from "react" export default function Page() { + function handleSubmit(event: FormEvent): void { + event.preventDefault(); + + // Read the form data + const form: any = event.target; + const formData = new FormData(form); + console.log(formData); + } + return ( - - +
+
Login FalkorDB server Fill in the form below to login to your FalkorDB server. - - +
+
- - + +
- - + +
- - + +
- - + +
-
- - -
- - - - - +
+
+ +
+
) } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index c97bd1b3..06ae8dd7 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { signIn } from "next-auth/react"; import Link from "next/link"; @@ -7,7 +11,7 @@ export default function Home() {

Welcome to{' '} - + signIn("Credentials", { callbackUrl: '/graph' })} href="/"> FalkorDB Browser

diff --git a/app/providers.tsx b/app/providers.tsx new file mode 100644 index 00000000..0dbd3fd8 --- /dev/null +++ b/app/providers.tsx @@ -0,0 +1,24 @@ +"use client"; + +import Navbar from "@/components/custom/navbar"; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; +import { SessionProvider } from "next-auth/react"; +// import Navigation from "./components/navigation"; + +type Props = { + children?: React.ReactNode; +}; + +export const NextAuthProvider = ({ children }: Props) => { + return ( + + + + + + + {children} + + + ) +}; \ No newline at end of file diff --git a/components/custom/navbar.tsx b/components/custom/navbar.tsx index e4298486..68ba91e0 100644 --- a/components/custom/navbar.tsx +++ b/components/custom/navbar.tsx @@ -1,4 +1,6 @@ import { AirVentIcon } from "lucide-react"; +import { signOut } from "next-auth/react"; +import Link from "next/link"; export default function Navbar() { return ( @@ -33,7 +35,9 @@ export default function Navbar() {
  • - Setting + signOut({ callbackUrl: '/' })} href="/"> + Sign Out +
  • diff --git a/package-lock.json b/package-lock.json index 882f08b1..bd31e4a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "falkordb": "^5.0.1", "lucide-react": "^0.301.0", "next": "14.0.4", + "next-auth": "^4.24.5", "react": "^18", "react-cytoscapejs": "^2.0.0", "react-dom": "^18", @@ -479,6 +480,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1800,6 +1809,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3443,6 +3460,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3599,7 +3624,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3749,6 +3773,33 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz", + "integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.11.4", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -3799,6 +3850,11 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3924,6 +3980,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3933,6 +3997,28 @@ "wrappy": "1" } }, + "node_modules/openid-client": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.4.tgz", + "integrity": "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==", + "dependencies": { + "jose": "^4.15.4", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4225,6 +4311,26 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/preact": { + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4234,6 +4340,11 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5237,6 +5348,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index ebbf61ab..3d15322e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "falkordb": "^5.0.1", "lucide-react": "^0.301.0", "next": "14.0.4", + "next-auth": "^4.24.5", "react": "^18", "react-cytoscapejs": "^2.0.0", "react-dom": "^18", diff --git a/tsconfig.json b/tsconfig.json index c7146963..75a4570d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/api/auth/[...nextauth]/route.ts"], "exclude": ["node_modules"] } diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts new file mode 100644 index 00000000..e3f6ac21 --- /dev/null +++ b/types/next-auth.d.ts @@ -0,0 +1,15 @@ +import NextAuth, { DefaultUser } from "next-auth"; + +declare module "next-auth" { + + interface User extends DefaultUser { + host: string; + port: number; + username: string; + password: string; + } + + interface Session { + user: User; + } +} \ No newline at end of file