I hate authentication

This commit is contained in:
2025-01-30 17:00:22 -05:00
parent 9ae4311b2e
commit 782384cad6
11 changed files with 72 additions and 27 deletions

View File

@@ -2,4 +2,4 @@ AUTH_SECRET="a73X70xifFO5+V9oQ+/NKDDTgA4dsuWWxvFX6T1v1ns=" # Added by `npx auth`
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_bmV3LXN3YW4tMjguY2xlcmsuYWNjb3VudHMuZGV2JA NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_bmV3LXN3YW4tMjguY2xlcmsuYWNjb3VudHMuZGV2JA
CLERK_SECRET_KEY=•••••••••••••••••••••••••••••••••••••••••••••••••• CLERK_SECRET_KEY=••••••••••••••••••••••••••••••••••••••••••••••••••
REACT_EDITOR=atom REACT_EDITOR=code

View File

@@ -52,6 +52,7 @@
"observable": "link:@trpc/server/observable", "observable": "link:@trpc/server/observable",
"oslo": "^1.2.1", "oslo": "^1.2.1",
"path": "^0.12.7", "path": "^0.12.7",
"perf_hooks": "^0.0.1",
"pg": "^8.13.1", "pg": "^8.13.1",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"prettier": "^3.4.2", "prettier": "^3.4.2",
@@ -63,6 +64,7 @@
"superjson": "^2.2.2", "superjson": "^2.2.2",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tls": "^0.0.1",
"uuid": "^11.0.3", "uuid": "^11.0.3",
"zod": "^3.24.1" "zod": "^3.24.1"
}, },
@@ -82,7 +84,7 @@
"drizzle-orm": "^0.38.2", "drizzle-orm": "^0.38.2",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "15.0.3", "eslint-config-next": "15.0.3",
"fs": "^0.0.1-security", "fs": "0.0.1-security",
"next-auth": "^5.0.0-beta.25", "next-auth": "^5.0.0-beta.25",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",

18
pnpm-lock.yaml generated
View File

@@ -137,6 +137,9 @@ importers:
path: path:
specifier: ^0.12.7 specifier: ^0.12.7
version: 0.12.7 version: 0.12.7
perf_hooks:
specifier: ^0.0.1
version: 0.0.1
pg: pg:
specifier: ^8.13.1 specifier: ^8.13.1
version: 8.13.1 version: 8.13.1
@@ -170,6 +173,9 @@ importers:
tailwindcss-animate: tailwindcss-animate:
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.17) version: 1.0.7(tailwindcss@3.4.17)
tls:
specifier: ^0.0.1
version: 0.0.1
uuid: uuid:
specifier: ^11.0.3 specifier: ^11.0.3
version: 11.0.5 version: 11.0.5
@@ -223,7 +229,7 @@ importers:
specifier: 15.0.3 specifier: 15.0.3
version: 15.0.3(eslint@8.57.1)(typescript@5.7.3) version: 15.0.3(eslint@8.57.1)(typescript@5.7.3)
fs: fs:
specifier: ^0.0.1-security specifier: 0.0.1-security
version: 0.0.1-security version: 0.0.1-security
next-auth: next-auth:
specifier: ^5.0.0-beta.25 specifier: ^5.0.0-beta.25
@@ -3624,6 +3630,9 @@ packages:
peberminta@0.9.0: peberminta@0.9.0:
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
perf_hooks@0.0.1:
resolution: {integrity: sha512-qG/D9iA4KDme+KF4vCObJy6Bouu3BlQnmJ8jPydVPm32NJBD9ZK1ZNgXSYaZKHkVC1sKSqUiLgFvAZPUiIEnBw==}
pg-cloudflare@1.1.1: pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
@@ -4195,6 +4204,9 @@ packages:
tiny-warning@1.0.3: tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
tls@0.0.1:
resolution: {integrity: sha512-GzHpG+hwupY8VMR6rYsnAhTHqT/97zT45PG8WD5eTT1lq+dFE0nN+1PYpsoBcHJgSmTz5ceK2Cv88IkPmIPOtQ==}
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@@ -7720,6 +7732,8 @@ snapshots:
peberminta@0.9.0: {} peberminta@0.9.0: {}
perf_hooks@0.0.1: {}
pg-cloudflare@1.1.1: pg-cloudflare@1.1.1:
optional: true optional: true
@@ -8356,6 +8370,8 @@ snapshots:
tiny-warning@1.0.3: {} tiny-warning@1.0.3: {}
tls@0.0.1: {}
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0

View File

@@ -2,9 +2,13 @@
import { eq, not , asc} from "drizzle-orm"; import { eq, not , asc} from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { db } from "@src/db"; import { db } from "@src/db";
import { users } from "@schemas/schema"; import { sessions, users } from "@schemas/schema";
import { stringWidth } from "bun"; import { stringWidth } from "bun";
import { generateId } from "lucia"; import { generateId } from "lucia";
import { validateRequest } from "@/lib/auth/validate-request";
import { lucia } from "@/lib/auth";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
export const getData = async () => { export const getData = async () => {
const data = await db.select().from(users).orderBy(asc(users.last_name)); const data = await db.select().from(users).orderBy(asc(users.last_name));
@@ -29,12 +33,7 @@ export const getUserByID = async (id:string) => {
return data[0]; return data[0];
}; };
/*export const addUser = async ( first_name: string, last_name: string, username: string, email: string, password_hash : string) => { export const addUser = async (id: string, first_name: string, last_name: string, username: string, email: string, emailVerified: boolean, hashedPassword:string) => {
await db.insert(users).values({
first_name : first_name, last_name: last_name, username: email, email: email, password_hash : password_hash
});
};*/
export const addUser = async (id: string, first_name: string, last_name: string, username: string, email: string, emailVerified: boolean, password_hash: string, hashedPassword:string) => {
const [addedUser] = await db.insert(users).values({ const [addedUser] = await db.insert(users).values({
id: id, id: id,
name: `${first_name} ${last_name}`, name: `${first_name} ${last_name}`,
@@ -43,8 +42,7 @@ export const addUser = async (id: string, first_name: string, last_name: string,
username: email, username: email,
email: email, email: email,
emailVerified:emailVerified, emailVerified:emailVerified,
password_hash: password_hash, hashedPassword: hashedPassword,
hash_password: hashedPassword,
full_name: `${first_name} ${last_name}`, full_name: `${first_name} ${last_name}`,
}).returning(); // Returns the inserted user (adjust "*" to specific columns if necessary) }).returning(); // Returns the inserted user (adjust "*" to specific columns if necessary)
@@ -56,7 +54,7 @@ export const deleteUser = async (id: string) => {
revalidatePath("/"); revalidatePath("/");
}; };
export const editUser = async (id: string, first_name: string, last_name: string, username: string, email : string, password_hash: string) => { export const editUser = async (id: string, first_name: string, last_name: string, username: string, email : string, hashedPassword: string) => {
await db await db
.update(users) .update(users)
.set({ .set({
@@ -64,7 +62,7 @@ export const editUser = async (id: string, first_name: string, last_name: string
last_name: last_name, last_name: last_name,
username: username, username: username,
email: email, email: email,
password_hash: password_hash hashedPassword: hashedPassword
}) })
.where(eq(users.id, id)); .where(eq(users.id, id));
revalidatePath("/"); revalidatePath("/");
@@ -80,3 +78,22 @@ export const makeAdmin = async ( email : string) => {
.where(eq(users.email, email)); .where(eq(users.email, email));
revalidatePath("/"); revalidatePath("/");
}; };
export const logoutSessionKeep = async (sessionId: string) => {
console.log(sessionId);
await db.delete(sessions).where(eq(sessions.id, sessionId));
revalidatePath("/");
}
export async function logoutSession(): Promise<{ error: string } | void> {
const { session } = await validateRequest();
if (!session) {
return {
error: "No session found",
};
}
await lucia.invalidateSession(session.id);
const sessionCookie = lucia.createBlankSessionCookie();
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return redirect("/");
}

View File

@@ -1,5 +1,5 @@
import { accounts } from "@schemas/schema"; import { users } from "@schemas/schema";
import { getViewAccounts } from "@actions/accountActions"; import { getAllUsersOrdrByLastname } from "@actions/userActions";
import AccountsTable from "@components/AccountsTable"; // Adjust the import path as necessary import AccountsTable from "@components/AccountsTable"; // Adjust the import path as necessary
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import { ColumnHeadings } from "@src/lib/bb_utils"; import { ColumnHeadings } from "@src/lib/bb_utils";
@@ -10,11 +10,12 @@ import PageHero from "@components/PageHero";
export default async function AccountsPage() { export default async function AccountsPage() {
const columnHeadings = new ColumnHeadings([ const columnHeadings = new ColumnHeadings([
"E-Mail",
"First Name", "First Name",
"Last Name", "Last Name",
"Actions", "Actions",
]); ]);
const data = await getViewAccounts(); const data = await getAllUsersOrdrByLastname();
return ( return (
<div> <div>
<PageHero title="Accounts" /> <PageHero title="Accounts" />

View File

@@ -23,6 +23,10 @@ import {
import { validateRequest } from "@/lib/auth/validate-request"; import { validateRequest } from "@/lib/auth/validate-request";
import { User } from "lucia"; import { User } from "lucia";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { logoutSession } from "@/actions/userActions";
import { useRouter } from "next/navigation";
import { lucia } from "@/lib/auth";
import { logout } from "@/lib/auth/actions";
const navigation = { const navigation = {
categories: [ categories: [
@@ -90,6 +94,7 @@ const navigation = {
export default function PopNavDialog(props:any) { export default function PopNavDialog(props:any) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [user, setUser] = useState<User | null>(null); const [user, setUser] = useState<User | null>(null);
const router = useRouter();
useEffect(() => { useEffect(() => {
const fetchUser = async () => { const fetchUser = async () => {
@@ -372,8 +377,7 @@ export default function PopNavDialog(props:any) {
<div className="ml-auto flex items-center"> <div className="ml-auto flex items-center">
<div className="hidden lg:flex lg:flex-1 lg:items-center lg:justify-end lg:space-x-6"> <div className="hidden lg:flex lg:flex-1 lg:items-center lg:justify-end lg:space-x-6">
<a <a
href= {linkPath} href={linkPath} onClick={async (e) => {e.preventDefault; console.log("in the onclick"); await logout()} } className="text-sm font-medium text-gray-700 hover:text-gray-800">
className="text-sm font-medium text-gray-700 hover:text-gray-800" >
{user == null?"Sign In": "Log Out"} {user == null?"Sign In": "Log Out"}
</a> </a>
<span aria-hidden="true" className="h-6 w-px bg-gray-200" /> <span aria-hidden="true" className="h-6 w-px bg-gray-200" />

View File

@@ -70,13 +70,15 @@ export default async function AccountsTable( props: any ) {
<tr key={item.uuid}> <tr key={item.uuid}>
<td className="whitespace-wrap flex items-center py-4 pl-4 pr-3 text-xs font-medium text-gray-900 "> <td className="whitespace-wrap flex items-center py-4 pl-4 pr-3 text-xs font-medium text-gray-900 ">
<Link href={`/UserProfile/${item.uuid}`}><span className="pl-2"> {item.first_name}</span></Link> <Link href={`/UserProfile/${item.id}`}><span className="pl-2"> {item.email}</span></Link>
</td>
<td className="whitespace-nowrap px-3 py-4 text-xs text-gray-900">
{item.first_name}
</td> </td>
<td className="whitespace-nowrap px-3 py-4 text-xs text-gray-900"> <td className="whitespace-nowrap px-3 py-4 text-xs text-gray-900">
{item.last_name} {item.last_name}
</td> </td>
<td className="whitespace-nowrap px-3 py-4 text-xs text-gray-900"> <td className="whitespace-nowrap px-3 py-4 text-xs text-gray-900">
<button <button
type="button" type="button"

View File

@@ -1,5 +1,6 @@
import 'dotenv/config'; import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres'; import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg'; import { Pool } from 'pg';
// db/index.ts // db/index.ts

View File

@@ -404,7 +404,7 @@ export const accounts = pgTable("accounts", {
} }
); );
/* export const vw_accounts = pgView("vw_accounts", { /* export const vw_accounts = pgView("vw_accounts", {
uuid: uuid().defaultRandom(), uuid: uuid().defaultRandom(),
userId: text("user_id").notNull(), userId: text("user_id").notNull(),
type: text().notNull(), type: text().notNull(),
@@ -420,7 +420,7 @@ export const accounts = pgTable("accounts", {
first_name: text("first_name"), first_name: text("first_name"),
last_name: text("last_name"), last_name: text("last_name"),
},).existing(); */ },) */
/* From here down is the authentication library Lusia tables */ /* From here down is the authentication library Lusia tables */
@@ -430,7 +430,6 @@ export const users = pgTable("users",
name: varchar("name"), name: varchar("name"),
username: varchar({ length: 50 }), username: varchar({ length: 50 }),
discordId: varchar("discord_id", { length: 255 }).unique(), discordId: varchar("discord_id", { length: 255 }).unique(),
password_hash: varchar("password_hash", { length: 255 }),
email: varchar("email", { length: 255 }).unique().notNull(), email: varchar("email", { length: 255 }).unique().notNull(),
emailVerified: boolean("email_verified").default(false).notNull(), emailVerified: boolean("email_verified").default(false).notNull(),
hashedPassword: varchar("hashed_password", { length: 255 }), hashedPassword: varchar("hashed_password", { length: 255 }),
@@ -469,6 +468,8 @@ export const sessions = pgTable(
id: varchar("id", { length: 255 }).primaryKey(), id: varchar("id", { length: 255 }).primaryKey(),
userId: varchar("user_id", { length: 21 }).notNull(), userId: varchar("user_id", { length: 21 }).notNull(),
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
createdAt: timestamp("created_at", { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp("updated_at", { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
}, },
(t) => ({ (t) => ({
userIdx: index("session_user_idx").on(t.userId), userIdx: index("session_user_idx").on(t.userId),

View File

@@ -3,6 +3,7 @@ import { Discord } from "arctic";
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { env } from "@/env.js"; import { env } from "@/env.js";
import { db } from "@/server/db"; import { db } from "@/server/db";
//import { db } from "@db/index";
import { sessions, users, type User as DbUser } from "@schemas/schema"; import { sessions, users, type User as DbUser } from "@schemas/schema";
import { absoluteUrl } from "@/lib/utils" import { absoluteUrl } from "@/lib/utils"
@@ -26,7 +27,7 @@ export const lucia = new Lucia(adapter, {
updatedAt: attributes.updatedAt, updatedAt: attributes.updatedAt,
}; };
}, },
sessionExpiresIn: new TimeSpan(30, "d"), sessionExpiresIn: new TimeSpan(1, "d"),
sessionCookie: { sessionCookie: {
name: "session", name: "session",

View File

@@ -1,7 +1,7 @@
import { drizzle } from "drizzle-orm/postgres-js"; import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres"; import postgres from "postgres";
import { env } from "@/env"; import { env } from "@/env";
import * as schema from "@schemas/schema"; import * as schema from "./schema";
export const connection = postgres(env.DATABASE_URL, { export const connection = postgres(env.DATABASE_URL, {
max_lifetime: 10, // Remove this line if you're deploying to Docker / VPS max_lifetime: 10, // Remove this line if you're deploying to Docker / VPS