add next auth & login with github

This commit is contained in:
Michi 2025-04-27 18:31:17 +02:00
parent 78afa6be22
commit 9a9110e58c
10 changed files with 226 additions and 22 deletions

117
package-lock.json generated
View file

@ -16,9 +16,11 @@
"clsx": "^2.1.1",
"lucide-react": "^0.503.0",
"next": "15.3.1",
"next-auth": "^5.0.0-beta.27",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"simple-icons": "^14.12.3",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
@ -47,6 +49,35 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@auth/core": {
"version": "0.39.0",
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.39.0.tgz",
"integrity": "sha512-jusviw/sUSfAh6S/wjY5tRmJOq0Itd3ImF+c/b4HB9DfmfChtcfVJTNJeqCeExeCG8oh4PBKRsMQJsn2W6NhFQ==",
"license": "ISC",
"dependencies": {
"@panva/hkdf": "^1.2.1",
"jose": "^6.0.6",
"oauth4webapi": "^3.3.0",
"preact": "10.24.3",
"preact-render-to-string": "6.5.11"
},
"peerDependencies": {
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.2",
"nodemailer": "^6.8.0"
},
"peerDependenciesMeta": {
"@simplewebauthn/browser": {
"optional": true
},
"@simplewebauthn/server": {
"optional": true
},
"nodemailer": {
"optional": true
}
}
},
"node_modules/@emnapi/core": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
@ -904,6 +935,15 @@
"node": ">=12.4.0"
}
},
"node_modules/@panva/hkdf": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/@radix-ui/primitive": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
@ -4498,6 +4538,15 @@
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/jose": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",
"integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==",
"license": "MIT",
"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",
@ -5055,6 +5104,33 @@
}
}
},
"node_modules/next-auth": {
"version": "5.0.0-beta.27",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.27.tgz",
"integrity": "sha512-/QtP9C0C99YpEuBEJqMaDXH3ISWMgObQalwVZEoC7sskaIPhv5fQl6fXS/rXJQKqLY6MNJ42rqjqmRdoXZH2EQ==",
"license": "ISC",
"dependencies": {
"@auth/core": "0.39.0"
},
"peerDependencies": {
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.2",
"next": "^14.0.0-0 || ^15.0.0-0",
"nodemailer": "^6.6.5",
"react": "^18.2.0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@simplewebauthn/browser": {
"optional": true
},
"@simplewebauthn/server": {
"optional": true
},
"nodemailer": {
"optional": true
}
}
},
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
@ -5093,6 +5169,15 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/oauth4webapi": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.5.0.tgz",
"integrity": "sha512-DF3mLWNuxPkxJkHmWxbSFz4aE5CjWOsm465VBfBdWzmzX4Mg3vF8icxK+iKqfdWrIumBJ2TaoNQWx+SQc2bsPQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -5382,6 +5467,25 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/preact": {
"version": "10.24.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/preact-render-to-string": {
"version": "6.5.11",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
"integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
"license": "MIT",
"peerDependencies": {
"preact": ">=10"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -5915,6 +6019,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/simple-icons": {
"version": "14.12.3",
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-14.12.3.tgz",
"integrity": "sha512-P85Pqak4picfTzdujmGL7+pFfsd32K/tDjHPQ8vvJcr2Xk380A9kBVIW5QH86qWqa+YJgFa5huLcUuwmW+YBPQ==",
"license": "CC0-1.0",
"engines": {
"node": ">=0.12.18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/simple-icons"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",

View file

@ -17,9 +17,11 @@
"clsx": "^2.1.1",
"lucide-react": "^0.503.0",
"next": "15.3.1",
"next-auth": "^5.0.0-beta.27",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"simple-icons": "^14.12.3",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {

View file

@ -0,0 +1,2 @@
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers

12
src/app/login/page.tsx Normal file
View file

@ -0,0 +1,12 @@
import LoginForm from "@/components/login/login-form";
import { Suspense } from "react";
export default function LoginPage(){
return (
<main className="h-screen flex justify-center items-center">
<Suspense>
<LoginForm />
</Suspense>
</main>
);
}

View file

@ -1,5 +1,6 @@
import { Button } from "@/components/ui/button";
import { Rss } from "lucide-react";
import Link from "next/link";
export default function Page() {
return (
@ -12,7 +13,11 @@ export default function Page() {
</div>
<div>
<Button>Log in</Button>
<Link
href="/login"
key="Login">
<Button>Log in</Button>
</Link>
</div>
</div>
);

9
src/auth.ts Normal file
View file

@ -0,0 +1,9 @@
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
pages: {
signIn: "/login",
}
})

View file

@ -1,5 +1,6 @@
import { Home, Rss, LogOut } from "lucide-react";
import { Home, Rss } from "lucide-react";
import Link from 'next/link';
import { SignOut } from "./login/signout-button";
import {
Sidebar,
@ -28,13 +29,6 @@ const items = {
icon: Rss,
},
],
footer: [
{
title: "Log out",
url: "#",
icon: LogOut,
},
]
};
export function AppSidebar() {
@ -74,19 +68,11 @@ export function AppSidebar() {
<SidebarFooter>
<SidebarMenu>
{items.footer.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<Link
key={item.title}
href={item.url}
>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton asChild>
<SignOut />
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>

View file

@ -0,0 +1,41 @@
import { signIn } from "@/auth";
import { Button } from '@/components/ui/button';
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter
} from '@/components/ui/card';
export default function LoginForm(){
return (
<div>
<Card>
<CardHeader className="text-center">
<CardTitle>Nice to meet you!</CardTitle>
<CardDescription>Sign in below with your GitHub account.</CardDescription>
</CardHeader>
<CardContent>
<form action={async () => {
"use server";
await signIn("github", { redirectTo: "/home" });
}}>
<Button type="submit" variant="outline" className="w-full">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>GitHub</title>
<path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
</svg>
Login with GitHub
</Button>
</form>
</CardContent>
</Card>
<p className="my-4 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our Terms of Service and Privacy Policy.
</p>
</div>
);
}

View file

@ -0,0 +1,18 @@
import { signOut } from "@/auth";
import { LogOut } from "lucide-react";
export function SignOut() {
return (
<form
action={async () => {
"use server";
await signOut();
}}
>
<button className="flex flex-row items-center gap-2 cursor-pointer w-full" type="submit">
<LogOut size={16} />
<span>Log out</span>
</button>
</form>
)
}

12
src/middleware.ts Normal file
View file

@ -0,0 +1,12 @@
import { auth } from "@/auth"
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico|$).*)"],
}