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 { AccessTokens } from './mongodb';
|
||||||
import type AccessToken from '../../types/AccessToken';
|
import type AccessToken from '../../types/AccessToken';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
export const createAccessToken = async (accessToken: AccessToken) => {
|
export const createAccessToken = async (accessToken: AccessToken) => {
|
||||||
const newAccessToken = await (await AccessTokens()).insertOne(accessToken);
|
const newAccessToken = await (await AccessTokens()).insertOne(accessToken);
|
||||||
|
@ -12,7 +13,7 @@ export const getAccessToken = async (accessToken: string) => {
|
||||||
|
|
||||||
const type = accessToken.split(".")[0];
|
const type = accessToken.split(".")[0];
|
||||||
const createdAt = new Date(Number(Buffer.from(accessToken.split(".")[1], 'base64url').toString()));
|
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 random = accessToken.split(".")[3];
|
||||||
const entropy = createHash("sha256").update(random).digest("hex");
|
const entropy = createHash("sha256").update(random).digest("hex");
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ export const getAccessToken = async (accessToken: string) => {
|
||||||
return accessTokens.findOne({ $and: [
|
return accessTokens.findOne({ $and: [
|
||||||
{ type },
|
{ type },
|
||||||
{ createdAt },
|
{ createdAt },
|
||||||
{ username },
|
{ user },
|
||||||
{ entropy }
|
{ entropy }
|
||||||
] }) as Promise<AccessToken | null>;
|
] }) as Promise<AccessToken | null>;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +29,7 @@ export const getAccessToken = async (accessToken: string) => {
|
||||||
export const getAllAccessTokens = async () => {
|
export const getAllAccessTokens = async () => {
|
||||||
const master: AccessToken = {
|
const master: AccessToken = {
|
||||||
type: "X",
|
type: "X",
|
||||||
username: "0",
|
user: null,
|
||||||
entropy: "b70972be3921e04a8d0c442f64c2c19a4ac01e886e8e5649916f9b88870fa6dd",
|
entropy: "b70972be3921e04a8d0c442f64c2c19a4ac01e886e8e5649916f9b88870fa6dd",
|
||||||
createdAt: new Date(1698851542427),
|
createdAt: new Date(1698851542427),
|
||||||
expiresAt: null,
|
expiresAt: null,
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { Users } from '../db/mongodb';
|
||||||
import type User from '../../types/User';
|
import type User from '../../types/User';
|
||||||
import type Resources from '../../types/Resources';
|
import type Resources from '../../types/Resources';
|
||||||
import type Building from '../../types/Building';
|
import type Building from '../../types/Building';
|
||||||
|
import type AccessToken from '../../types/AccessToken';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
export const getAllUsers = async () => {
|
export const getAllUsers = async () => {
|
||||||
const users = await Users();
|
const users = await Users();
|
||||||
|
@ -13,6 +15,13 @@ export const createUser = async (user: User) => {
|
||||||
return newUser;
|
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) => {
|
export const getUserByNickOrEmail = async (searchString: string) => {
|
||||||
const users = await Users();
|
const users = await Users();
|
||||||
return users.findOne({
|
return users.findOne({
|
||||||
|
@ -23,6 +32,13 @@ export const getUserByNickOrEmail = async (searchString: string) => {
|
||||||
}) as Promise<User | null>;
|
}) 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> => {
|
export const getUserResources = async (username: string): Promise<Resources> => {
|
||||||
const users = await Users();
|
const users = await Users();
|
||||||
const user = await users.findOne({ username });
|
const user = await users.findOne({ username });
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import type { ObjectId } from "mongodb";
|
||||||
import type AccessToken from "../../types/AccessToken";
|
import type AccessToken from "../../types/AccessToken";
|
||||||
import { getAccessToken } from "../db/accessTokens";
|
import { getAccessToken } from "../db/accessTokens";
|
||||||
|
import { getUserById } from "../db/users";
|
||||||
|
|
||||||
export default async function validateAccessToken(request: Request): Promise<Response | AccessToken> {
|
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];
|
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 }
|
}), { 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(
|
if(response.createdAt.getTime() > Date.now()) return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
code: 403,
|
code: 403,
|
||||||
message: "Forbidden",
|
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 }
|
}), { status: 403 }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -43,7 +54,7 @@ export default async function validateAccessToken(request: Request): Promise<Res
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
code: 403,
|
code: 403,
|
||||||
message: "Forbidden",
|
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 }
|
}), { status: 403 }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "../../layouts/Layout.astro";
|
||||||
import { getAllAccessTokens } from "../../lib/db/accessTokens";
|
import { getAllAccessTokens } from "../../lib/db/accessTokens";
|
||||||
|
import { getUserByAccessToken } from "../../lib/db/users";
|
||||||
|
|
||||||
const tokens = await getAllAccessTokens();
|
const tokens = await getAllAccessTokens();
|
||||||
|
|
||||||
|
@ -26,13 +27,13 @@ const type = {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{tokens.map(token => <tr>
|
{tokens.map(async token => <tr>
|
||||||
<td>{token._id}</td>
|
<td>{token._id}</td>
|
||||||
<td>{type[token.type] ?? `Other`}</td>
|
<td>{type[token.type] ?? `Other`}</td>
|
||||||
<td>{token.username}</td>
|
<td>{(await getUserByAccessToken(token))?.username}</td>
|
||||||
<td>{token.entropy}</td>
|
<td>{token.entropy}</td>
|
||||||
<td>{token.createdAt.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ")}</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.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>
|
<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>)}
|
</tr>)}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { randomBytes, createHash } from "crypto";
|
||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import type AccessToken from "../../../types/AccessToken";
|
import type AccessToken from "../../../types/AccessToken";
|
||||||
import { createAccessToken } from "../../../lib/db/accessTokens";
|
import { createAccessToken } from "../../../lib/db/accessTokens";
|
||||||
|
import { getUserByNickOrEmail } from "../../../lib/db/users";
|
||||||
|
import type { ObjectId } from "mongodb";
|
||||||
|
|
||||||
export const POST: APIRoute = async({ request }) => {
|
export const POST: APIRoute = async({ request }) => {
|
||||||
const data = await request.json().catch(() => {return new Response(
|
const data = await request.json().catch(() => {return new Response(
|
||||||
|
@ -9,7 +11,7 @@ export const POST: APIRoute = async({ request }) => {
|
||||||
code: 400,
|
code: 400,
|
||||||
message: "Bad Request",
|
message: "Bad Request",
|
||||||
error: "Invalid JSON"
|
error: "Invalid JSON"
|
||||||
})
|
}), { status: 400 }
|
||||||
)});
|
)});
|
||||||
|
|
||||||
if(!data.username) return new Response(
|
if(!data.username) return new Response(
|
||||||
|
@ -17,7 +19,7 @@ export const POST: APIRoute = async({ request }) => {
|
||||||
code: 400,
|
code: 400,
|
||||||
message: "Bad Request",
|
message: "Bad Request",
|
||||||
error: "Username is required"
|
error: "Username is required"
|
||||||
})
|
}), { status: 400 }
|
||||||
)
|
)
|
||||||
|
|
||||||
const header = request.headers.get("Authorization");
|
const header = request.headers.get("Authorization");
|
||||||
|
@ -28,7 +30,7 @@ export const POST: APIRoute = async({ request }) => {
|
||||||
code: 401,
|
code: 401,
|
||||||
message: "Unauthorized",
|
message: "Unauthorized",
|
||||||
error: "Access Token is required"
|
error: "Access Token is required"
|
||||||
})
|
}), { status: 401 }
|
||||||
)
|
)
|
||||||
|
|
||||||
if(token !== import.meta.env.MASTER_ACCESSTOKEN) return new Response(
|
if(token !== import.meta.env.MASTER_ACCESSTOKEN) return new Response(
|
||||||
|
@ -36,21 +38,30 @@ export const POST: APIRoute = async({ request }) => {
|
||||||
code: 401,
|
code: 401,
|
||||||
message: "Unauthorized",
|
message: "Unauthorized",
|
||||||
error: "Invalid Access Token"
|
error: "Invalid Access Token"
|
||||||
})
|
}), { status: 401 }
|
||||||
)
|
)
|
||||||
else {
|
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 now = new Date();
|
||||||
const timestamp = Buffer.from(String(Date.now())).toString('base64url');
|
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 random = randomBytes(16).toString("base64url");
|
||||||
const randomHashed = createHash("sha256").update(random).digest("hex");
|
const randomHashed = createHash("sha256").update(random).digest("hex");
|
||||||
const expiresIn = (data.duration ?? 86400) * 1000;
|
const expiresIn = (data.duration ?? 86400) * 1000;
|
||||||
|
|
||||||
const tokenString = `A.${timestamp}.${username}.${random}`;
|
const tokenString = `A.${timestamp}.${user}.${random}`;
|
||||||
|
|
||||||
const accessToken: AccessToken = {
|
const accessToken: AccessToken = {
|
||||||
type: "A",
|
type: "A",
|
||||||
username: data.username,
|
user: userFromDb._id as ObjectId,
|
||||||
entropy: randomHashed.toString(),
|
entropy: randomHashed.toString(),
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
expiresAt: new Date(now.getTime() + expiresIn),
|
expiresAt: new Date(now.getTime() + expiresIn),
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import validateAccessToken from "../../../lib/utils/validateAccessToken";
|
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 }) => {
|
export const GET: APIRoute = async({ request }) => {
|
||||||
const response = await validateAccessToken(request);
|
const response = await validateAccessToken(request);
|
||||||
if(response instanceof Response) return response;
|
if(response instanceof Response) return response;
|
||||||
|
|
||||||
|
const user = (await getUserByAccessToken(response)) as User;
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
code: 200,
|
code: 200,
|
||||||
message: "OK",
|
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 {
|
export default interface AccessToken {
|
||||||
_id?: ObjectId;
|
_id?: ObjectId;
|
||||||
type: "A" | "X";
|
type: "A" | "X";
|
||||||
username: string;
|
user: ObjectId | null;
|
||||||
entropy: string;
|
entropy: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
expiresAt: Date | null;
|
expiresAt: Date | null;
|
||||||
|
|
Loading…
Reference in New Issue