mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-13 05:40:38 +01:00
117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
import db from "@server/db";
|
|
import {
|
|
Resource,
|
|
ResourceAccessToken,
|
|
resourceAccessToken,
|
|
resources
|
|
} from "@server/db/schemas";
|
|
import { and, eq } from "drizzle-orm";
|
|
import { isWithinExpirationDate } from "oslo";
|
|
import { verifyPassword } from "./password";
|
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
|
import { sha256 } from "@oslojs/crypto/sha2";
|
|
|
|
export async function verifyResourceAccessToken({
|
|
accessToken,
|
|
accessTokenId,
|
|
resourceId
|
|
}: {
|
|
accessToken: string;
|
|
accessTokenId?: string;
|
|
resourceId?: number; // IF THIS IS NOT SET, THE TOKEN IS VALID FOR ALL RESOURCES
|
|
}): Promise<{
|
|
valid: boolean;
|
|
error?: string;
|
|
tokenItem?: ResourceAccessToken;
|
|
resource?: Resource;
|
|
}> {
|
|
const accessTokenHash = encodeHexLowerCase(
|
|
sha256(new TextEncoder().encode(accessToken))
|
|
);
|
|
|
|
let tokenItem: ResourceAccessToken | undefined;
|
|
let resource: Resource | undefined;
|
|
|
|
if (!accessTokenId) {
|
|
const [res] = await db
|
|
.select()
|
|
.from(resourceAccessToken)
|
|
.where(and(eq(resourceAccessToken.tokenHash, accessTokenHash)))
|
|
.innerJoin(
|
|
resources,
|
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
|
);
|
|
|
|
tokenItem = res?.resourceAccessToken;
|
|
resource = res?.resources;
|
|
} else {
|
|
const [res] = await db
|
|
.select()
|
|
.from(resourceAccessToken)
|
|
.where(and(eq(resourceAccessToken.accessTokenId, accessTokenId)))
|
|
.innerJoin(
|
|
resources,
|
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
|
);
|
|
|
|
if (res && res.resourceAccessToken) {
|
|
if (res.resourceAccessToken.tokenHash?.startsWith("$argon")) {
|
|
const validCode = await verifyPassword(
|
|
accessToken,
|
|
res.resourceAccessToken.tokenHash
|
|
);
|
|
|
|
if (!validCode) {
|
|
return {
|
|
valid: false,
|
|
error: "Invalid access token"
|
|
};
|
|
}
|
|
} else {
|
|
const tokenHash = encodeHexLowerCase(
|
|
sha256(new TextEncoder().encode(accessToken))
|
|
);
|
|
|
|
if (res.resourceAccessToken.tokenHash !== tokenHash) {
|
|
return {
|
|
valid: false,
|
|
error: "Invalid access token"
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
tokenItem = res?.resourceAccessToken;
|
|
resource = res?.resources;
|
|
}
|
|
|
|
if (!tokenItem || !resource) {
|
|
return {
|
|
valid: false,
|
|
error: "Access token does not exist for resource"
|
|
};
|
|
}
|
|
|
|
if (
|
|
tokenItem.expiresAt &&
|
|
!isWithinExpirationDate(new Date(tokenItem.expiresAt))
|
|
) {
|
|
return {
|
|
valid: false,
|
|
error: "Access token has expired"
|
|
};
|
|
}
|
|
|
|
if (resourceId && resource.resourceId !== resourceId) {
|
|
return {
|
|
valid: false,
|
|
error: "Resource ID does not match"
|
|
};
|
|
}
|
|
|
|
return {
|
|
valid: true,
|
|
tokenItem,
|
|
resource
|
|
};
|
|
}
|