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> = {
|
export type fieldDefinition<Optional extends boolean = boolean> = {
|
||||||
key: string,
|
key: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
type: "text" | "number",
|
type: "text" | "password" | "number",
|
||||||
optional?: Optional,
|
optional?: Optional,
|
||||||
value?: optionalMap<Optional>,
|
value?: optionalMap<Optional>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fields: Array<fieldDefinition>,
|
fields: Array<fieldDefinition>,
|
||||||
modelValue?: any,
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// eslint-disable-next-line func-call-spacing
|
// eslint-disable-next-line func-call-spacing
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Alerts, { type AlertData } from '~/components/alerts.vue';
|
||||||
|
|
||||||
const editorFields: Array<fieldDefinition> = [
|
const editorFields: Array<fieldDefinition> = [
|
||||||
{ key: "username", type: "text", label: "Username", optional: false },
|
{ 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 },
|
{ key: "email", type: "text", label: "email", optional: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,14 @@ model User {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
|
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
|
||||||
userId BigInt @map("user") @db.UnsignedBigInt
|
userId BigInt @map("user") @db.UnsignedBigInt
|
||||||
expiry_date DateTime? @default(dbgenerated("(now() + interval 30 day)")) @db.Timestamp(0)
|
sessionToken Bytes @db.Binary(64)
|
||||||
user User @relation(fields: [userId], references: [id])
|
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([userId], map: "user_idx")
|
||||||
|
@@index([sessionToken])
|
||||||
@@map("sessions")
|
@@map("sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default defineEventHandler(async (e) => {
|
||||||
if (typeof email !== "string") throw createError({ message: "email is not string", statusCode: 400 });
|
if (typeof email !== "string") throw createError({ message: "email is not string", statusCode: 400 });
|
||||||
|
|
||||||
execSync("npx prisma db push --force-reset");
|
execSync("npx prisma db push --force-reset");
|
||||||
database.user.create({
|
await database.user.create({
|
||||||
data: {
|
data: {
|
||||||
id: new Snowflake().state,
|
id: new Snowflake().state,
|
||||||
username,
|
username,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { defineEventHandler, getCookie, setCookie, readBody } from "h3";
|
||||||
import { database } from "../utils/database";
|
import { database } from "../utils/database";
|
||||||
import { isString } from "../utils/isString";
|
import { isString } from "../utils/isString";
|
||||||
import { cookieSettings } from "../utils/rootUtils";
|
import { cookieSettings } from "../utils/rootUtils";
|
||||||
import Snowflake from "~/utils/snowflake";
|
import SessionToken from "../utils/SessionToken";
|
||||||
|
|
||||||
import { createError } from "#imports";
|
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." });
|
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({
|
await database.session.create({
|
||||||
data: {
|
data: session.toPrisma(),
|
||||||
id: sessionId.state,
|
|
||||||
userId: account.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setCookie(e, "token", sessionId.toString(), cookieSettings);
|
setCookie(e, "token", session.toString(), cookieSettings);
|
||||||
return { message: "Login successful", token: sessionId.toString() };
|
return { message: "Login successful", token: session.toString() };
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { defineEventHandler, getCookie } from "h3";
|
import { defineEventHandler, getCookie } from "h3";
|
||||||
import { createError } from "#imports";
|
import SessionToken from "../utils/SessionToken";
|
||||||
|
|
||||||
import { database } from "~/server/utils/database";
|
import { database } from "~/server/utils/database";
|
||||||
import getRequestingUser from "~/server/utils/getRequestingUser";
|
import getRequestingUser from "~/server/utils/getRequestingUser";
|
||||||
|
|
||||||
|
import { createError } from "#imports";
|
||||||
|
|
||||||
const endpointsWithoutAuth: string[] = [
|
const endpointsWithoutAuth: string[] = [
|
||||||
"/dbtest",
|
"/dbtest",
|
||||||
"/echo",
|
"/echo",
|
||||||
|
@ -37,7 +39,10 @@ export async function isAuthorised(token: string | undefined): Promise<boolean>
|
||||||
try {
|
try {
|
||||||
await database.session.findUniqueOrThrow({
|
await database.session.findUniqueOrThrow({
|
||||||
where: {
|
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 { getCookie, H3Event } from "h3";
|
||||||
|
|
||||||
import { database } from "./database";
|
import { database } from "./database";
|
||||||
|
import SessionToken from "./SessionToken";
|
||||||
|
|
||||||
import { createError } from "#imports";
|
import { createError } from "#imports";
|
||||||
|
|
||||||
|
@ -9,7 +10,10 @@ export default async function getRequestingUser(e: H3Event) {
|
||||||
if (!cookie) throw createError("User not found");
|
if (!cookie) throw createError("User not found");
|
||||||
const { user } = await database.session.findUnique({
|
const { user } = await database.session.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: BigInt(cookie),
|
...SessionToken.fromString(cookie).toPrisma(),
|
||||||
|
expiry_date: {
|
||||||
|
gte: new Date(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
user: {
|
user: {
|
||||||
|
|
Loading…
Add table
Reference in a new issue