From eafea0c1f5741e5c4259e645f9b5d81aaf963cdc Mon Sep 17 00:00:00 2001 From: Aelita4 Date: Fri, 26 Jan 2024 08:52:40 +0100 Subject: [PATCH] Restructure access token model to store user ID Signed-off-by: Aelita4 --- src/lib/db/accessTokens.ts | 7 ++++--- src/lib/db/users.ts | 16 +++++++++++++++ src/lib/utils/validateAccessToken.ts | 15 ++++++++++++-- src/pages/admin/accessTokens.astro | 7 ++++--- src/pages/api/auth/generateAccessToken.ts | 25 ++++++++++++++++------- src/pages/api/auth/testAccessToken.ts | 7 +++++-- src/types/AccessToken.ts | 2 +- 7 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/lib/db/accessTokens.ts b/src/lib/db/accessTokens.ts index da09682..78b6bbb 100644 --- a/src/lib/db/accessTokens.ts +++ b/src/lib/db/accessTokens.ts @@ -1,6 +1,7 @@ import { AccessTokens } from './mongodb'; import type AccessToken from '../../types/AccessToken'; import { createHash } from 'crypto'; +import { ObjectId } from 'mongodb'; export const createAccessToken = async (accessToken: AccessToken) => { const newAccessToken = await (await AccessTokens()).insertOne(accessToken); @@ -12,7 +13,7 @@ export const getAccessToken = async (accessToken: string) => { const type = accessToken.split(".")[0]; const createdAt = new Date(Number(Buffer.from(accessToken.split(".")[1], 'base64url').toString())); - const username = Buffer.from(accessToken.split(".")[2], 'base64url').toString(); + const user = new ObjectId(Buffer.from(accessToken.split(".")[2], 'base64url').toString()); const random = accessToken.split(".")[3]; const entropy = createHash("sha256").update(random).digest("hex"); @@ -20,7 +21,7 @@ export const getAccessToken = async (accessToken: string) => { return accessTokens.findOne({ $and: [ { type }, { createdAt }, - { username }, + { user }, { entropy } ] }) as Promise; } @@ -28,7 +29,7 @@ export const getAccessToken = async (accessToken: string) => { export const getAllAccessTokens = async () => { const master: AccessToken = { type: "X", - username: "0", + user: null, entropy: "b70972be3921e04a8d0c442f64c2c19a4ac01e886e8e5649916f9b88870fa6dd", createdAt: new Date(1698851542427), expiresAt: null, diff --git a/src/lib/db/users.ts b/src/lib/db/users.ts index fa3543c..bd0d938 100644 --- a/src/lib/db/users.ts +++ b/src/lib/db/users.ts @@ -2,6 +2,8 @@ import { Users } from '../db/mongodb'; import type User from '../../types/User'; import type Resources from '../../types/Resources'; import type Building from '../../types/Building'; +import type AccessToken from '../../types/AccessToken'; +import { ObjectId } from 'mongodb'; export const getAllUsers = async () => { const users = await Users(); @@ -13,6 +15,13 @@ export const createUser = async (user: User) => { return newUser; } +export const getUserById = async (id: ObjectId) => { + const users = await Users(); + return users.findOne({ + _id: id + }) as Promise; +} + export const getUserByNickOrEmail = async (searchString: string) => { const users = await Users(); return users.findOne({ @@ -23,6 +32,13 @@ export const getUserByNickOrEmail = async (searchString: string) => { }) as Promise; } +export const getUserByAccessToken = async(accessToken: string | AccessToken): Promise => { + if(typeof accessToken === "string") { + const userId = new ObjectId(Buffer.from(accessToken.split(".")[2], 'base64url').toString()); + return getUserById(userId); + } else return getUserById(accessToken.user as ObjectId) +} + export const getUserResources = async (username: string): Promise => { const users = await Users(); const user = await users.findOne({ username }); diff --git a/src/lib/utils/validateAccessToken.ts b/src/lib/utils/validateAccessToken.ts index 2f7587a..d943e37 100644 --- a/src/lib/utils/validateAccessToken.ts +++ b/src/lib/utils/validateAccessToken.ts @@ -1,5 +1,7 @@ +import type { ObjectId } from "mongodb"; import type AccessToken from "../../types/AccessToken"; import { getAccessToken } from "../db/accessTokens"; +import { getUserById } from "../db/users"; export default async function validateAccessToken(request: Request): Promise { let accessToken = request.url.split("?")[1]?.split("&").filter((x) => x.split("=")[0] === "token")[0].split("=")[1]; @@ -30,12 +32,21 @@ export default async function validateAccessToken(request: Request): Promise Date.now()) return new Response( JSON.stringify({ code: 403, message: "Forbidden", - data: "Access token is invalid for user " + response.username + ", are you travelling in time?" + data: "Access token is invalid for user " + user.username + ", are you travelling in time?" }), { status: 403 } ); @@ -43,7 +54,7 @@ export default async function validateAccessToken(request: Request): Promise - {tokens.map(token => + {tokens.map(async token => {token._id} {type[token.type] ?? `Other`} - {token.username} + {(await getUserByAccessToken(token))?.username} {token.entropy} {token.createdAt.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ")} - {token.expiresAt?.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ") ?? "null"} + {token.expiresAt?.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ") ?? "Never"} {token.createdFrom} {token.expiresAt !== null && Date.now() > token.expiresAt.getTime() ? Expired {Math.floor((Date.now() - token.expiresAt.getTime()) / 1000 / 60)} minutes ago : Date.now() < token.createdAt.getTime() ? TimeTravelException (check in {Math.floor((token.createdAt.getTime() - Date.now()) / 1000 / 60)} minutes) : Valid for{token.expiresAt === null ? "ever" : ` ${Math.floor((token.expiresAt.getTime() - Date.now()) / 1000 / 60)} minutes`}} )} diff --git a/src/pages/api/auth/generateAccessToken.ts b/src/pages/api/auth/generateAccessToken.ts index 641a103..ca8da59 100644 --- a/src/pages/api/auth/generateAccessToken.ts +++ b/src/pages/api/auth/generateAccessToken.ts @@ -2,6 +2,8 @@ import { randomBytes, createHash } from "crypto"; import type { APIRoute } from "astro"; import type AccessToken from "../../../types/AccessToken"; import { createAccessToken } from "../../../lib/db/accessTokens"; +import { getUserByNickOrEmail } from "../../../lib/db/users"; +import type { ObjectId } from "mongodb"; export const POST: APIRoute = async({ request }) => { const data = await request.json().catch(() => {return new Response( @@ -9,7 +11,7 @@ export const POST: APIRoute = async({ request }) => { code: 400, message: "Bad Request", error: "Invalid JSON" - }) + }), { status: 400 } )}); if(!data.username) return new Response( @@ -17,7 +19,7 @@ export const POST: APIRoute = async({ request }) => { code: 400, message: "Bad Request", error: "Username is required" - }) + }), { status: 400 } ) const header = request.headers.get("Authorization"); @@ -28,7 +30,7 @@ export const POST: APIRoute = async({ request }) => { code: 401, message: "Unauthorized", error: "Access Token is required" - }) + }), { status: 401 } ) if(token !== import.meta.env.MASTER_ACCESSTOKEN) return new Response( @@ -36,21 +38,30 @@ export const POST: APIRoute = async({ request }) => { code: 401, message: "Unauthorized", error: "Invalid Access Token" - }) + }), { status: 401 } ) else { + const userFromDb = await getUserByNickOrEmail(data.username); + if(!userFromDb) return new Response( + JSON.stringify({ + code: 404, + message: "Not found", + error: `User ${data.username} not found` + }), { status: 404 } + ) + const now = new Date(); const timestamp = Buffer.from(String(Date.now())).toString('base64url'); - const username = Buffer.from(data.username).toString('base64url'); + const user = Buffer.from(userFromDb._id?.toString() ?? "").toString('base64url'); const random = randomBytes(16).toString("base64url"); const randomHashed = createHash("sha256").update(random).digest("hex"); const expiresIn = (data.duration ?? 86400) * 1000; - const tokenString = `A.${timestamp}.${username}.${random}`; + const tokenString = `A.${timestamp}.${user}.${random}`; const accessToken: AccessToken = { type: "A", - username: data.username, + user: userFromDb._id as ObjectId, entropy: randomHashed.toString(), createdAt: now, expiresAt: new Date(now.getTime() + expiresIn), diff --git a/src/pages/api/auth/testAccessToken.ts b/src/pages/api/auth/testAccessToken.ts index e46e69e..a6d80fc 100644 --- a/src/pages/api/auth/testAccessToken.ts +++ b/src/pages/api/auth/testAccessToken.ts @@ -1,16 +1,19 @@ import type { APIRoute } from "astro"; import validateAccessToken from "../../../lib/utils/validateAccessToken"; -import { getAccessToken } from "../../../lib/db/accessTokens"; +import { getUserByAccessToken } from "../../../lib/db/users"; +import type User from "../../../types/User"; export const GET: APIRoute = async({ request }) => { const response = await validateAccessToken(request); if(response instanceof Response) return response; + const user = (await getUserByAccessToken(response)) as User; + return new Response( JSON.stringify({ code: 200, message: "OK", - data: "Access token valid for user " + response.username + data: "Access token valid for user " + user.username }) ); } \ No newline at end of file diff --git a/src/types/AccessToken.ts b/src/types/AccessToken.ts index ee09b73..aa022d2 100644 --- a/src/types/AccessToken.ts +++ b/src/types/AccessToken.ts @@ -3,7 +3,7 @@ import type { ObjectId } from "mongodb"; export default interface AccessToken { _id?: ObjectId; type: "A" | "X"; - username: string; + user: ObjectId | null; entropy: string; createdAt: Date; expiresAt: Date | null;