wip: sessions
This commit is contained in:
parent
5e63ffb18c
commit
8a892c1e17
|
@ -2,7 +2,6 @@ import "~/styles/globals.css";
|
|||
|
||||
import { Inter } from "next/font/google";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import { TRPCReactProvider } from "~/trpc/react";
|
||||
|
||||
const inter = Inter({
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import Link from "next/link";
|
||||
|
||||
import { CreatePost } from "~/app/_components/create-post";
|
||||
import { api } from "~/trpc/server";
|
||||
|
||||
export default async function Home() {
|
||||
const hello = await api.post.hello.query({ text: "from tRPC" });
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
|
||||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 ">
|
||||
|
@ -47,19 +42,3 @@ export default async function Home() {
|
|||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
async function CrudShowcase() {
|
||||
const latestPost = await api.post.getLatest.query();
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-xs">
|
||||
{latestPost ? (
|
||||
<p className="truncate">Your most recent post: {latestPost.name}</p>
|
||||
) : (
|
||||
<p>You have no posts yet.</p>
|
||||
)}
|
||||
|
||||
<CreatePost />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
0
src/app/setup/layout.tsx
Normal file
0
src/app/setup/layout.tsx
Normal file
1
src/app/setup/page.tsx
Normal file
1
src/app/setup/page.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export default function SetupInstance() {}
|
49
src/server/api/routers/setup.ts
Normal file
49
src/server/api/routers/setup.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { z } from "zod";
|
||||
import { publicProcedure } from "../trpc";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { users } from "~/server/db/schema";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import assert from "assert";
|
||||
import { Session } from "~/server/auth/Session";
|
||||
|
||||
export const setupProcedure = publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// check if user already exists
|
||||
const [userCount] = await ctx.db
|
||||
.select({
|
||||
count: sql<number>`count(*)`,
|
||||
})
|
||||
.from(users)
|
||||
.limit(1);
|
||||
|
||||
// if user already exists, throw error
|
||||
if (userCount && userCount.count > 0)
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Instance already set up",
|
||||
});
|
||||
|
||||
// otherwise, create user
|
||||
const [user] = await ctx.db
|
||||
.insert(users)
|
||||
.values({
|
||||
username: input.username,
|
||||
password: input.password,
|
||||
})
|
||||
.returning({ id: users.id });
|
||||
|
||||
// log the user in
|
||||
assert(user, "User should be created");
|
||||
const session = await Session.createForUser(user.id, ctx.request);
|
||||
ctx.request.cookies.set("session", session.data.token);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
});
|
|
@ -23,6 +23,7 @@ import { Session } from "../auth/Session";
|
|||
*/
|
||||
interface CreateContextOptions {
|
||||
headers: Headers;
|
||||
request: NextRequest;
|
||||
session: Session | null;
|
||||
}
|
||||
|
||||
|
@ -41,6 +42,7 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
|||
headers: opts.headers,
|
||||
session: opts.session,
|
||||
db,
|
||||
request: opts.request,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -64,6 +66,7 @@ export const createTRPCContext = async (opts: { req: NextRequest }) => {
|
|||
return createInnerTRPCContext({
|
||||
headers: opts.req.headers,
|
||||
session,
|
||||
request: opts.req,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db";
|
||||
import { users, sessions } from "../db/schema";
|
||||
import { randomBytes } from "crypto";
|
||||
import assert from "assert";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export type SessionUpdateData = Partial<{
|
||||
ua: string;
|
||||
ip: string;
|
||||
}>;
|
||||
|
||||
export class Session {
|
||||
/**
|
||||
|
@ -29,14 +37,21 @@ export class Session {
|
|||
*/
|
||||
static async fetchFromTokenAndUpdate(
|
||||
token: string,
|
||||
context: {
|
||||
ip?: string;
|
||||
ua?: string;
|
||||
},
|
||||
context: SessionUpdateData | NextRequest,
|
||||
) {
|
||||
// parse context
|
||||
const parsedContext =
|
||||
context instanceof NextRequest
|
||||
? Session.getContextFromRequest(context)
|
||||
: context;
|
||||
|
||||
const [sessionData] = await db
|
||||
.update(sessions)
|
||||
.set({ lastAccessed: new Date(), lastIP: context.ip, lastUA: context.ua })
|
||||
.set({
|
||||
lastAccessed: new Date(),
|
||||
lastIP: parsedContext.ip,
|
||||
lastUA: parsedContext.ua,
|
||||
})
|
||||
.where(eq(sessions.token, token))
|
||||
.returning();
|
||||
|
||||
|
@ -47,6 +62,47 @@ export class Session {
|
|||
return new Session(sessionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session for a user.
|
||||
*/
|
||||
static async createForUser(
|
||||
userId: string,
|
||||
context: SessionUpdateData | NextRequest,
|
||||
) {
|
||||
// generate a session token
|
||||
const token = randomBytes(64).toString("hex");
|
||||
|
||||
// parse context
|
||||
const parsedContext =
|
||||
context instanceof NextRequest
|
||||
? Session.getContextFromRequest(context)
|
||||
: context;
|
||||
|
||||
// insert the session into the database
|
||||
const [sessionData] = await db
|
||||
.insert(sessions)
|
||||
.values({
|
||||
lastUA: parsedContext.ua,
|
||||
lastIP: parsedContext.ip,
|
||||
token,
|
||||
userId,
|
||||
})
|
||||
.returning();
|
||||
|
||||
assert(sessionData, "Session should be created");
|
||||
return new Session(sessionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to extract context from a request.
|
||||
*/
|
||||
static getContextFromRequest(request: NextRequest) {
|
||||
return {
|
||||
ua: request.headers.get("user-agent") ?? undefined,
|
||||
ip: request.ip,
|
||||
} satisfies SessionUpdateData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session instance from a user's session data.
|
||||
* @param sessionData The user's session data.
|
||||
|
|
Loading…
Reference in a new issue