fosrl.pangolin/server/auth/verifyResourceAccessToken.ts

118 lines
3.2 KiB
TypeScript
Raw Normal View History

2025-01-11 19:47:07 -05:00
import db from "@server/db";
import {
Resource,
ResourceAccessToken,
resourceAccessToken,
2025-04-04 22:58:01 -04:00
resources
2025-03-23 17:11:48 -04:00
} from "@server/db/schemas";
2025-01-11 19:47:07 -05:00
import { and, eq } from "drizzle-orm";
import { isWithinExpirationDate } from "oslo";
import { verifyPassword } from "./password";
2025-04-04 22:58:01 -04:00
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
2025-04-05 22:28:47 -04:00
export async function verifyResourceAccessToken({
accessToken,
accessTokenId,
resourceId
2025-04-04 22:58:01 -04:00
}: {
accessToken: string;
2025-04-05 22:28:47 -04:00
accessTokenId?: string;
resourceId?: number; // IF THIS IS NOT SET, THE TOKEN IS VALID FOR ALL RESOURCES
2025-04-04 22:58:01 -04:00
}): Promise<{
valid: boolean;
error?: string;
tokenItem?: ResourceAccessToken;
resource?: Resource;
}> {
const accessTokenHash = encodeHexLowerCase(
sha256(new TextEncoder().encode(accessToken))
);
2025-04-05 22:28:47 -04:00
let tokenItem: ResourceAccessToken | undefined;
let resource: Resource | undefined;
2025-04-04 22:58:01 -04:00
2025-04-05 22:28:47 -04:00
if (!accessTokenId) {
const [res] = await db
.select()
.from(resourceAccessToken)
.where(and(eq(resourceAccessToken.tokenHash, accessTokenHash)))
.innerJoin(
resources,
eq(resourceAccessToken.resourceId, resources.resourceId)
);
2025-04-04 22:58:01 -04:00
2025-04-05 22:28:47 -04:00
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)
);
2025-04-04 22:58:01 -04:00
2025-04-05 22:28:47 -04:00
if (res && res.resourceAccessToken) {
if (res.resourceAccessToken.tokenHash?.startsWith("$argon")) {
const validCode = await verifyPassword(
accessToken,
res.resourceAccessToken.tokenHash
);
2025-01-11 19:47:07 -05:00
2025-04-05 22:28:47 -04:00
if (!validCode) {
return {
valid: false,
error: "Invalid access token"
};
}
} else {
const tokenHash = encodeHexLowerCase(
sha256(new TextEncoder().encode(accessToken))
);
2025-01-11 19:47:07 -05:00
2025-04-05 22:28:47 -04:00
if (res.resourceAccessToken.tokenHash !== tokenHash) {
return {
valid: false,
error: "Invalid access token"
};
}
}
}
2025-01-11 19:47:07 -05:00
2025-04-05 22:28:47 -04:00
tokenItem = res?.resourceAccessToken;
resource = res?.resources;
2025-01-11 19:47:07 -05:00
}
2025-04-05 22:28:47 -04:00
if (!tokenItem || !resource) {
2025-01-11 19:47:07 -05:00
return {
valid: false,
2025-04-05 22:28:47 -04:00
error: "Access token does not exist for resource"
2025-01-11 19:47:07 -05:00
};
}
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"
};
}
2025-01-11 19:47:07 -05:00
return {
valid: true,
2025-04-05 22:28:47 -04:00
tokenItem,
resource
2025-01-11 19:47:07 -05:00
};
}