Restructure access token model to store user ID
Signed-off-by: Aelita4 <kontakt@mikorosa.pl>
This commit is contained in:
parent
d3ac213590
commit
eafea0c1f5
|
@ -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<AccessToken | null>;
|
||||
}
|
||||
|
@ -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,
|
||||
|
|
|
@ -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<User | null>;
|
||||
}
|
||||
|
||||
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<User | null>;
|
||||
}
|
||||
|
||||
export const getUserByAccessToken = async(accessToken: string | AccessToken): Promise<User | null> => {
|
||||
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<Resources> => {
|
||||
const users = await Users();
|
||||
const user = await users.findOne({ username });
|
||||
|
|
|
@ -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<Response | AccessToken> {
|
||||
let accessToken = request.url.split("?")[1]?.split("&").filter((x) => x.split("=")[0] === "token")[0].split("=")[1];
|
||||
|
@ -31,11 +33,20 @@ export default async function validateAccessToken(request: Request): Promise<Res
|
|||
}), { status: 401 }
|
||||
);
|
||||
|
||||
const user = await getUserById(response.user as ObjectId);
|
||||
if(!user) return new Response (
|
||||
JSON.stringify({
|
||||
code: 404,
|
||||
message: "Not found",
|
||||
data: "Access token does not match any user"
|
||||
}), { status: 404 }
|
||||
);
|
||||
|
||||
if(response.createdAt.getTime() > 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<Res
|
|||
JSON.stringify({
|
||||
code: 403,
|
||||
message: "Forbidden",
|
||||
data: "Access token is invalid for user " + response.username + ", token expired"
|
||||
data: "Access token is invalid for user " + user.username + ", token expired"
|
||||
}), { status: 403 }
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import { getAllAccessTokens } from "../../lib/db/accessTokens";
|
||||
import { getUserByAccessToken } from "../../lib/db/users";
|
||||
|
||||
const tokens = await getAllAccessTokens();
|
||||
|
||||
|
@ -26,13 +27,13 @@ const type = {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tokens.map(token => <tr>
|
||||
{tokens.map(async token => <tr>
|
||||
<td>{token._id}</td>
|
||||
<td>{type[token.type] ?? `Other`}</td>
|
||||
<td>{token.username}</td>
|
||||
<td>{(await getUserByAccessToken(token))?.username}</td>
|
||||
<td>{token.entropy}</td>
|
||||
<td>{token.createdAt.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ")}</td>
|
||||
<td>{token.expiresAt?.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ") ?? "null"}</td>
|
||||
<td>{token.expiresAt?.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ") ?? "Never"}</td>
|
||||
<td>{token.createdFrom}</td>
|
||||
<td>{token.expiresAt !== null && Date.now() > token.expiresAt.getTime() ? <span style="color: red;">Expired {Math.floor((Date.now() - token.expiresAt.getTime()) / 1000 / 60)} minutes ago</span> : Date.now() < token.createdAt.getTime() ? <span style="color: cyan;">TimeTravelException (check in {Math.floor((token.createdAt.getTime() - Date.now()) / 1000 / 60)} minutes)</span> : <span style="color: lime;">Valid for{token.expiresAt === null ? "ever" : ` ${Math.floor((token.expiresAt.getTime() - Date.now()) / 1000 / 60)} minutes`}</span>}</td>
|
||||
</tr>)}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
})
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue