use authentik for auth
All checks were successful
Build / build (push) Successful in 3m31s

This commit is contained in:
Nareshkumar Rao
2025-04-04 23:29:57 +02:00
parent 4656df7fca
commit 434536d1f2
12 changed files with 151 additions and 192 deletions

View File

@ -0,0 +1,2 @@
import { handlers } from "@/auth";
export const { GET, POST } = handlers;

View File

@ -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 (

View File

@ -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>
);
}

View File

@ -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);
}
}

View File

@ -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 />;
}

View File

@ -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} />

View File

@ -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];

View File

@ -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
View 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],
});

View File

@ -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: "/",
});
}