This commit is contained in:
2
src/app/api/auth/[...nextauth]/route.ts
Normal file
2
src/app/api/auth/[...nextauth]/route.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { handlers } from "@/auth";
|
||||
export const { GET, POST } = handlers;
|
@ -7,8 +7,8 @@ import React from "react";
|
||||
import PostSummary from "./PostSummary";
|
||||
import Pagination from "./Pagination";
|
||||
import TagOverview from "./TagOverview";
|
||||
import { isLoggedIn } from "@/components/auth";
|
||||
import ActionButtons from "./ActionButtons";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
export default async function Blog({
|
||||
params,
|
||||
@ -31,7 +31,7 @@ export default async function Blog({
|
||||
if (pageNumber > numberOfPages) {
|
||||
notFound();
|
||||
}
|
||||
const loggedIn = await isLoggedIn();
|
||||
const loggedIn = (await auth()) != null;
|
||||
const tags = await getTags(loggedIn);
|
||||
|
||||
return (
|
||||
|
@ -1,74 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Form from "next/form";
|
||||
import { handleLogin } from "./action";
|
||||
|
||||
export default function FormComponent() {
|
||||
return (
|
||||
<Form action={handleLogin}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "1rem",
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
Username:
|
||||
<input
|
||||
type="text"
|
||||
style={{
|
||||
height: "2rem",
|
||||
backgroundColor: "#333",
|
||||
borderStyle: "none",
|
||||
color: "#eee",
|
||||
fontSize: 16,
|
||||
padding: "0.5rem 1rem",
|
||||
}}
|
||||
name="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
Password:
|
||||
<input
|
||||
type="password"
|
||||
style={{
|
||||
height: "2rem",
|
||||
backgroundColor: "#333",
|
||||
borderStyle: "none",
|
||||
color: "#eee",
|
||||
fontSize: 16,
|
||||
padding: "0.5rem 1rem",
|
||||
}}
|
||||
name="password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: "#333",
|
||||
borderStyle: "none",
|
||||
color: "#eee",
|
||||
fontSize: "1rem",
|
||||
padding: "0.5rem 1rem",
|
||||
transition: "background-color 0.3s linear",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onMouseOver={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "#444";
|
||||
}}
|
||||
onMouseOut={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "#333";
|
||||
}}
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import argon2 from "argon2";
|
||||
import { setSession } from "@/components/auth";
|
||||
import { redirect, RedirectType } from "next/navigation";
|
||||
|
||||
export async function handleLogin(data: FormData) {
|
||||
const prisma = new PrismaClient();
|
||||
const username = data.get("username")?.toString();
|
||||
const password = data.get("password")?.toString();
|
||||
if (!username || !password) {
|
||||
throw new Error("Missing username or password");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { username } });
|
||||
if (!user) {
|
||||
redirect("/blog/login?error=Invalid%20credentials", RedirectType.replace);
|
||||
}
|
||||
|
||||
if (await argon2.verify(user.password, password)) {
|
||||
setSession();
|
||||
redirect("/blog/write", RedirectType.replace);
|
||||
} else {
|
||||
redirect("/blog/login?error=Invalid%20credentials", RedirectType.replace);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import { isLoggedIn } from "@/components/auth";
|
||||
import FormComponent from "./Form";
|
||||
|
||||
export default async function Login() {
|
||||
if (await isLoggedIn()) {
|
||||
redirect("/blog/write");
|
||||
}
|
||||
return <FormComponent />;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPost } from "../../action";
|
||||
import PostDisplay from "./PostDisplay";
|
||||
import { isLoggedIn } from "@/components/auth";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
export default async function Post({
|
||||
params,
|
||||
@ -13,7 +13,7 @@ export default async function Post({
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
const loggedIn = await isLoggedIn();
|
||||
const loggedIn = (await auth()) != null;
|
||||
return (
|
||||
<>
|
||||
<PostDisplay post={post} loggedIn={loggedIn} />
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Post } from "../../types";
|
||||
import Write from "../Write";
|
||||
import { isLoggedIn } from "@/components/auth";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { auth, signIn } from "@/auth";
|
||||
|
||||
export default async function WritePage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] | undefined }>;
|
||||
}) {
|
||||
if (!(await isLoggedIn())) {
|
||||
redirect("/blog/login");
|
||||
if (!(await auth())) {
|
||||
signIn();
|
||||
}
|
||||
|
||||
const slug = (await params).slug?.[0];
|
||||
|
@ -5,6 +5,7 @@ import MarkdownIt from "markdown-it";
|
||||
import hljs from "highlight.js";
|
||||
import * as cheerio from "cheerio";
|
||||
import slugify from "slugify";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
export async function savePostServer(
|
||||
title: string,
|
||||
@ -13,6 +14,10 @@ export async function savePostServer(
|
||||
is_draft: boolean,
|
||||
existingSlug?: string
|
||||
) {
|
||||
if (!(await auth())) {
|
||||
throw new Error("Not authenticated");
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const md = new MarkdownIt({
|
||||
|
6
src/auth.ts
Normal file
6
src/auth.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import NextAuth from "next-auth";
|
||||
import Authentik from "next-auth/providers/authentik";
|
||||
|
||||
export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
providers: [Authentik],
|
||||
});
|
@ -1,49 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { jwtVerify, SignJWT } from "jose";
|
||||
import { cookies } from "next/headers";
|
||||
const SECRET_KEY = process.env.SESSION_SECRET;
|
||||
const encodedKey = new TextEncoder().encode(SECRET_KEY);
|
||||
|
||||
export type SessionPayload = { admin: true };
|
||||
|
||||
export async function encrypt(payload: SessionPayload) {
|
||||
return new SignJWT(payload)
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("7d")
|
||||
.sign(encodedKey);
|
||||
}
|
||||
|
||||
export async function decrypt(
|
||||
token: string | undefined = ""
|
||||
): Promise<SessionPayload | null> {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, encodedKey, {
|
||||
algorithms: ["HS256"],
|
||||
});
|
||||
return payload as SessionPayload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isLoggedIn(): Promise<boolean> {
|
||||
const cookieStore = (await cookies()).get("session")?.value;
|
||||
const session = await decrypt(cookieStore);
|
||||
if (session != null && session.admin) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function setSession(): Promise<void> {
|
||||
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||
(await cookies()).set("session", await encrypt({ admin: true }), {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
expires: expiresAt,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user