refactor database to split ticket/user/oauth
This commit is contained in:
parent
ebea014083
commit
084a2f7618
8 changed files with 185 additions and 43 deletions
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `username` on the `AuthTicket` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `authTicketId` on the `DiscordAuth` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `refreshAt` on the `DiscordAuth` table. All the data in the column will be lost.
|
||||
- Added the required column `expiresAt` to the `AuthTicket` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `userId` to the `AuthTicket` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `expiresAt` to the `DiscordAuth` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `userId` to the `DiscordAuth` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"username" TEXT
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_AuthTicket" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"ticket" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
CONSTRAINT "AuthTicket_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_AuthTicket" ("id", "ticket") SELECT "id", "ticket" FROM "AuthTicket";
|
||||
DROP TABLE "AuthTicket";
|
||||
ALTER TABLE "new_AuthTicket" RENAME TO "AuthTicket";
|
||||
CREATE UNIQUE INDEX "AuthTicket_userId_key" ON "AuthTicket"("userId");
|
||||
CREATE TABLE "new_DiscordAuth" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"accessToken" TEXT NOT NULL,
|
||||
"refreshToken" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "DiscordAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_DiscordAuth" ("accessToken", "id", "refreshToken") SELECT "accessToken", "id", "refreshToken" FROM "DiscordAuth";
|
||||
DROP TABLE "DiscordAuth";
|
||||
ALTER TABLE "new_DiscordAuth" RENAME TO "DiscordAuth";
|
||||
CREATE UNIQUE INDEX "DiscordAuth_userId_key" ON "DiscordAuth"("userId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
|
@ -8,19 +8,29 @@ datasource db {
|
|||
}
|
||||
|
||||
model AuthTicket {
|
||||
id Int @id @default(autoincrement())
|
||||
username String?
|
||||
ticket String
|
||||
id Int @id @default(autoincrement())
|
||||
ticket String
|
||||
expiresAt DateTime
|
||||
|
||||
user User @relation(references: [id], fields: [userId])
|
||||
userId Int @unique
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String?
|
||||
authTicket AuthTicket?
|
||||
|
||||
discordAuth DiscordAuth?
|
||||
}
|
||||
|
||||
model DiscordAuth {
|
||||
id String @id
|
||||
|
||||
authTicket AuthTicket @relation(fields: [authTicketId], references: [id])
|
||||
authTicketId Int @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int @unique
|
||||
|
||||
accessToken String
|
||||
refreshToken String
|
||||
refreshAt DateTime
|
||||
expiresAt DateTime
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NextApiRequest } from "next";
|
||||
import * as ldap from "@/ldap";
|
||||
import prisma from "@/prisma";
|
||||
import { getUserFromRequest } from "@/auth";
|
||||
|
||||
type RequestBody = {
|
||||
username: string;
|
||||
|
@ -11,20 +11,14 @@ type RequestBody = {
|
|||
};
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authorization = request.headers
|
||||
.get("authorization")
|
||||
?.replace("Bearer ", "");
|
||||
|
||||
if (authorization == null) return new Response(null, { status: 401 });
|
||||
|
||||
const user = await prisma.authTicket.findFirst({
|
||||
where: {
|
||||
ticket: authorization
|
||||
}
|
||||
});
|
||||
|
||||
const user = await getUserFromRequest(request);
|
||||
if (user == null) return new Response(null, { status: 401 });
|
||||
|
||||
if (user.username !== null) {
|
||||
// user already has an account, don't re-register
|
||||
return new Response(null, { status: 403 });
|
||||
}
|
||||
|
||||
const { username, displayName, email, password, avatarBase64 } =
|
||||
(await request.json()) as RequestBody;
|
||||
|
||||
|
@ -89,12 +83,12 @@ export async function POST(request: Request) {
|
|||
await ldap.createUser(username, displayName, email, avatarBuf);
|
||||
await ldap.setPassword(username, password);
|
||||
|
||||
await prisma.authTicket.update({
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id
|
||||
},
|
||||
data: {
|
||||
username: username
|
||||
username
|
||||
}
|
||||
});
|
||||
|
||||
|
|
11
src/app/me/page.tsx
Normal file
11
src/app/me/page.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { getUserFromPage } from "@/auth";
|
||||
|
||||
export default async function Page() {
|
||||
const user = await getUserFromPage();
|
||||
|
||||
if (!user) {
|
||||
return <p>Not logged in</p>;
|
||||
}
|
||||
|
||||
return <p>{user.username}</p>;
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { discordRedirectUri } from "../oauth";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
let url = `https://discord.com/oauth2/authorize`;
|
||||
|
||||
let randomAssString = Math.random().toString(36).substring(2, 15);
|
||||
let state = v4();
|
||||
|
||||
let params = new URLSearchParams();
|
||||
params.set("response_type", "code");
|
||||
params.set("client_id", process.env.DISCORD_CLIENT_ID);
|
||||
params.set("scope", "guilds identify email");
|
||||
params.set("state", randomAssString);
|
||||
params.set("state", state);
|
||||
params.set("redirect_uri", discordRedirectUri());
|
||||
params.set("prompt", "consent");
|
||||
|
||||
|
@ -19,7 +19,7 @@ export async function GET(request: Request) {
|
|||
status: 302,
|
||||
headers: {
|
||||
Location: url,
|
||||
"Set-Cookie": `state=${randomAssString}; Path=/;`
|
||||
"Set-Cookie": `state=${state}; Path=/;`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,5 +39,3 @@ export async function getDiscordGuilds(token: string) {
|
|||
const res: DiscordGuildResponse[] = await req.json();
|
||||
return res.map((guild) => guild.id);
|
||||
}
|
||||
|
||||
export const makeTicket = (): string => v4();
|
||||
|
|
|
@ -2,12 +2,12 @@ import { URLSearchParams } from "url";
|
|||
import {
|
||||
discordRedirectUri,
|
||||
DiscordAccessTokenResponse,
|
||||
makeTicket,
|
||||
getDiscordID,
|
||||
getDiscordGuilds
|
||||
} from "../oauth";
|
||||
import { cookies } from "next/dist/client/components/headers";
|
||||
import prisma from "@/prisma";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
let url = new URL(request.url);
|
||||
|
@ -51,21 +51,62 @@ export async function GET(request: Request) {
|
|||
if (!allowed)
|
||||
return new Response("not permitted to register account", { status: 403 });
|
||||
|
||||
const user = await prisma.authTicket.create({
|
||||
// - create the discord auth data in prisma, which will make the user if it doesn't exist
|
||||
// - get the user from the discord auth data
|
||||
// - either create a new auth ticket or invalidate the old one
|
||||
// - update the user to point to the new auth ticket
|
||||
|
||||
const discordAuth = await prisma.discordAuth.upsert({
|
||||
where: {
|
||||
id
|
||||
},
|
||||
create: {
|
||||
id,
|
||||
accessToken: tokenBody.access_token,
|
||||
refreshToken: tokenBody.refresh_token,
|
||||
expiresAt: new Date(Date.now() + tokenBody.expires_in * 1000),
|
||||
user: {
|
||||
create: {
|
||||
username: null
|
||||
}
|
||||
}
|
||||
},
|
||||
update: {
|
||||
accessToken: tokenBody.access_token,
|
||||
refreshToken: tokenBody.refresh_token,
|
||||
expiresAt: new Date(Date.now() + tokenBody.expires_in * 1000)
|
||||
}
|
||||
});
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: discordAuth.userId
|
||||
}
|
||||
});
|
||||
|
||||
const authTicket = await prisma.authTicket.upsert({
|
||||
where: {
|
||||
userId: user!.id
|
||||
},
|
||||
create: {
|
||||
userId: user!.id,
|
||||
ticket: v4(),
|
||||
expiresAt: new Date(Date.now() + 86400000)
|
||||
},
|
||||
update: {
|
||||
ticket: v4(),
|
||||
expiresAt: new Date(Date.now() + 86400000)
|
||||
}
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user!.id
|
||||
},
|
||||
data: {
|
||||
username: null,
|
||||
ticket: makeTicket(),
|
||||
discordAuth: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
id
|
||||
},
|
||||
create: {
|
||||
id,
|
||||
accessToken: tokenBody.access_token,
|
||||
refreshToken: tokenBody.refresh_token,
|
||||
refreshAt: new Date(Date.now() + tokenBody.expires_in * 1000)
|
||||
}
|
||||
authTicket: {
|
||||
connect: {
|
||||
id: authTicket.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +115,7 @@ export async function GET(request: Request) {
|
|||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
"Set-Cookie": `ticket=${user.ticket}; Path=/;`,
|
||||
"Set-Cookie": `ticket=${authTicket.ticket}; Path=/;`,
|
||||
Location: "/register"
|
||||
}
|
||||
});
|
||||
|
|
43
src/auth.ts
Normal file
43
src/auth.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import prisma from "@/prisma";
|
||||
import { cookies } from "next/dist/client/components/headers";
|
||||
|
||||
export async function getUserFromRequest(request: Request) {
|
||||
const authorization = request.headers
|
||||
.get("authorization")
|
||||
?.replace("Bearer ", "");
|
||||
if (authorization === null) return null;
|
||||
|
||||
const ticket = await prisma.authTicket.findFirst({
|
||||
where: {
|
||||
ticket: authorization
|
||||
}
|
||||
});
|
||||
if (ticket === null) return null;
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: ticket.userId
|
||||
}
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function getUserFromPage() {
|
||||
const cookieStore = cookies();
|
||||
const cookieTicket = cookieStore.get("ticket");
|
||||
if (cookieTicket === null) return null;
|
||||
|
||||
const ticket = await prisma.authTicket.findFirst({
|
||||
where: {
|
||||
ticket: cookieTicket?.value
|
||||
}
|
||||
});
|
||||
if (ticket === null) return null;
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: ticket.userId
|
||||
}
|
||||
});
|
||||
return user;
|
||||
}
|
Loading…
Reference in a new issue