Compare commits
4 commits
500a9ad595
...
1d8220d92c
Author | SHA1 | Date | |
---|---|---|---|
1d8220d92c | |||
ebf5690519 | |||
434ae5843e | |||
e509bb22c1 |
8 changed files with 75 additions and 19 deletions
|
@ -7,14 +7,13 @@ type optionalMap<Optional> = Optional extends true ? undefined : string | number
|
|||
export type fieldDefinition<Optional extends boolean = boolean> = {
|
||||
key: string,
|
||||
label?: string,
|
||||
type: "text" | "number",
|
||||
type: "text" | "password" | "number",
|
||||
optional?: Optional,
|
||||
value?: optionalMap<Optional>,
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
fields: Array<fieldDefinition>,
|
||||
modelValue?: any,
|
||||
}>();
|
||||
|
||||
// eslint-disable-next-line func-call-spacing
|
||||
|
|
|
@ -9,7 +9,7 @@ import Alerts, { type AlertData } from '~/components/alerts.vue';
|
|||
|
||||
const editorFields: Array<fieldDefinition> = [
|
||||
{ key: "username", type: "text", label: "Username", optional: false },
|
||||
{ key: "password", type: "text", label: "Password", optional: false },
|
||||
{ key: "password", type: "password", label: "Password", optional: false },
|
||||
{ key: "email", type: "text", label: "email", optional: false },
|
||||
];
|
||||
|
||||
|
|
|
@ -22,10 +22,12 @@ model User {
|
|||
model Session {
|
||||
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
|
||||
userId BigInt @map("user") @db.UnsignedBigInt
|
||||
sessionToken Bytes @db.Binary(64)
|
||||
expiry_date DateTime? @default(dbgenerated("(now() + interval 30 day)")) @db.Timestamp(0)
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([userId], map: "user_idx")
|
||||
@@index([sessionToken])
|
||||
@@map("sessions")
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export default defineEventHandler(async (e) => {
|
|||
if (typeof email !== "string") throw createError({ message: "email is not string", statusCode: 400 });
|
||||
|
||||
execSync("npx prisma db push --force-reset");
|
||||
database.user.create({
|
||||
await database.user.create({
|
||||
data: {
|
||||
id: new Snowflake().state,
|
||||
username,
|
||||
|
|
|
@ -4,7 +4,7 @@ import { defineEventHandler, getCookie, setCookie, readBody } from "h3";
|
|||
import { database } from "../utils/database";
|
||||
import { isString } from "../utils/isString";
|
||||
import { cookieSettings } from "../utils/rootUtils";
|
||||
import Snowflake from "~/utils/snowflake";
|
||||
import SessionToken from "../utils/SessionToken";
|
||||
|
||||
import { createError } from "#imports";
|
||||
|
||||
|
@ -40,14 +40,11 @@ export default defineEventHandler(async (e) => {
|
|||
|
||||
if (account === null) throw createError({ statusCode: 400, message: "Invalid username or password." });
|
||||
|
||||
const sessionId = new Snowflake();
|
||||
const session = new SessionToken(account.id);
|
||||
|
||||
await database.session.create({
|
||||
data: {
|
||||
id: sessionId.state,
|
||||
userId: account.id,
|
||||
},
|
||||
data: session.toPrisma(),
|
||||
});
|
||||
setCookie(e, "token", sessionId.toString(), cookieSettings);
|
||||
return { message: "Login successful", token: sessionId.toString() };
|
||||
setCookie(e, "token", session.toString(), cookieSettings);
|
||||
return { message: "Login successful", token: session.toString() };
|
||||
});
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { defineEventHandler, getCookie } from "h3";
|
||||
import { createError } from "#imports";
|
||||
import SessionToken from "../utils/SessionToken";
|
||||
|
||||
import { database } from "~/server/utils/database";
|
||||
import getRequestingUser from "~/server/utils/getRequestingUser";
|
||||
|
||||
import { createError } from "#imports";
|
||||
|
||||
const endpointsWithoutAuth: string[] = [
|
||||
"/dbtest",
|
||||
"/echo",
|
||||
|
@ -37,7 +39,10 @@ export async function isAuthorised(token: string | undefined): Promise<boolean>
|
|||
try {
|
||||
await database.session.findUniqueOrThrow({
|
||||
where: {
|
||||
id: BigInt(token),
|
||||
...SessionToken.fromString(token).toPrisma(),
|
||||
expiry_date: {
|
||||
gte: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
49
server/utils/SessionToken.ts
Normal file
49
server/utils/SessionToken.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import crypto from "node:crypto";
|
||||
import { type Session } from "@prisma/client";
|
||||
|
||||
import Snowflake from "~/utils/snowflake";
|
||||
|
||||
/** Represents a Session token, without expiry data. */
|
||||
export default class SessionToken {
|
||||
userId: bigint;
|
||||
sessionId: bigint;
|
||||
sessionToken: Buffer;
|
||||
|
||||
constructor(userId: bigint, sessionId?: bigint, sessionToken?: Buffer) {
|
||||
this.userId = userId;
|
||||
this.sessionId = sessionId ?? new Snowflake().state;
|
||||
this.sessionToken = sessionToken ?? crypto.randomBytes(64);
|
||||
}
|
||||
|
||||
/** Creates SessionToken from a string.
|
||||
* @param string The strinct to create from.
|
||||
* @returns The SessionToken object.
|
||||
*/
|
||||
static fromString(string: string): SessionToken {
|
||||
const parameters = string.split(".");
|
||||
return new SessionToken(
|
||||
Buffer.from(parameters[0], "base64").readBigUInt64LE(),
|
||||
Buffer.from(parameters[1], "base64").readBigUInt64LE(),
|
||||
Buffer.from(parameters[2], "base64"),
|
||||
);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const stringUserId = Buffer.copyBytesFrom(new BigUint64Array([this.userId])).toString("base64");
|
||||
const stringSessionId = Buffer.copyBytesFrom(new BigUint64Array([this.sessionId])).toString("base64");
|
||||
const stringSessionToken = this.sessionToken.toString("base64");
|
||||
return `${stringUserId}.${stringSessionId}.${stringSessionToken}`;
|
||||
}
|
||||
|
||||
/** Returns this SessionToken as Prisma object.
|
||||
* For use in where parameter.
|
||||
* @returns this as prisma object.
|
||||
*/
|
||||
toPrisma(): Omit<Session, "expiry_date"> {
|
||||
return {
|
||||
id: this.sessionId,
|
||||
userId: this.userId,
|
||||
sessionToken: this.sessionToken,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { getCookie, H3Event } from "h3";
|
||||
|
||||
import { database } from "./database";
|
||||
import SessionToken from "./SessionToken";
|
||||
|
||||
import { createError } from "#imports";
|
||||
|
||||
|
@ -9,7 +10,10 @@ export default async function getRequestingUser(e: H3Event) {
|
|||
if (!cookie) throw createError("User not found");
|
||||
const { user } = await database.session.findUnique({
|
||||
where: {
|
||||
id: BigInt(cookie),
|
||||
...SessionToken.fromString(cookie).toPrisma(),
|
||||
expiry_date: {
|
||||
gte: new Date(),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
user: {
|
||||
|
|
Loading…
Add table
Reference in a new issue