First Commit

This commit is contained in:
2026-09-12 13:56:36 +01:00
commit 4d75af0418
426 changed files with 36452 additions and 0 deletions
@@ -0,0 +1,66 @@
import type { BUN_SQLITE_WGUI_USER_TYPES } from "@/db/types/db";
import type { UserType } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
type Params = {
includes?: UserType[];
excludes?: UserType[];
user_types?: BUN_SQLITE_WGUI_USER_TYPES[] | null;
};
export default function checkUserAccess({
user_types,
excludes,
includes,
}: Params): APIResponseObject {
if (!user_types?.[0]) {
return {
success: false,
msg: `No User Types Provided`,
};
}
if (user_types.find((t) => t.user_type === "super_admin")) {
return {
success: true,
};
}
const userTypeSet = new Set(user_types.map((t) => t.user_type));
if (excludes) {
for (let i = 0; i < excludes.length; i++) {
const exclude = excludes[i] as any;
if (userTypeSet.has(exclude)) {
return {
success: false,
msg: `User Excluded`,
};
}
}
}
if (includes) {
const is_user_included = includes.find((i) =>
userTypeSet.has(i as any),
);
if (!is_user_included) {
return {
success: false,
msg: `User not included`,
};
}
}
if (!includes?.[0] && !excludes?.[0]) {
return {
success: false,
msg: `No includes or excludes provided`,
};
}
return {
success: true,
};
}
+7
View File
@@ -0,0 +1,7 @@
export default function generateCSRF() {
return (
Math.random().toString(36).substring(2) +
"-" +
Math.random().toString(36).substring(2)
);
}
@@ -0,0 +1,50 @@
import type {
BUN_SQLITE_WGUI_USER_TYPES,
BunSQLiteTables,
} from "@/db/types/db";
import type { UserAuthReturn } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
user_id: number | string;
};
export default async function grabUserAuthTypes({
user_id,
}: Params): Promise<UserAuthReturn> {
try {
const user_types =
(
await BunSQLite.select<
BUN_SQLITE_WGUI_USER_TYPES,
(typeof BunSQLiteTables)[number]
>({
table: "user_types",
query: {
query: {
user_id: {
value: user_id,
},
},
},
})
).payload || [];
if (user_types.find((ty) => ty.user_type == "revoked")) {
return {
success: false,
msg: "User Access Revoked",
};
}
return {
success: true,
user_types,
};
} catch (error: any) {
return {
success: false,
msg: error.msg,
};
}
}
+48
View File
@@ -0,0 +1,48 @@
import type {
APIResponseObject,
BunextAPIRouteHandler,
} from "@moduletrace/bunext/types";
import _ from "lodash";
import userAuth from "./user-auth";
import checkUserAccess from "./check-user-access";
import grabUsers from "../db/users/grab-users";
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
req,
query,
}) => {
const { user, user_types } = await userAuth({ req });
if (!user?.logged_in_status) {
return {
success: false,
msg: `Unauthorized`,
};
}
try {
const is_admin = checkUserAccess({
user_types,
includes: ["admin"],
});
if (
!is_admin.success &&
(!query?.user_id || query.user_id !== user.id)
) {
return {
success: false,
msg: `Unauthorized`,
};
}
return await grabUsers({
query,
});
} catch (error: any) {
return {
success: false,
msg: error.message,
};
}
};
+137
View File
@@ -0,0 +1,137 @@
import { AppData } from "@/src/data/app-data";
import { setCookies } from "@/src/utils/cookies";
import { encrypt } from "@/src/utils/crypt";
import grabUsers from "../db/users/grab-users";
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
BUN_SQLITE_WGUI_MEDIA,
BUN_SQLITE_WGUI_SSO_LOGIN_CODES,
BUN_SQLITE_WGUI_USER_TYPES,
BunSQLiteTables,
} from "@/db/types/db";
import generateCSRF from "./gen-csrf";
import type { User } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import grabMediaURL from "../media/grab-media-url";
type Params = {
user_id: string | number;
};
export default async function loginUser({
user_id,
}: Params): Promise<Response> {
const now = Date.now();
const target_user_res = await grabUsers({
query: { user_id },
});
const target_user = target_user_res.singleRes;
if (!target_user?.id) {
throw new Error(`Couldn't Find user with ID ${user_id}`);
}
const target_user_types = (
await BunSQLite.select<
BUN_SQLITE_WGUI_USER_TYPES,
(typeof BunSQLiteTables)[number]
>({
table: "user_types",
query: { query: { user_id: { value: target_user.id } } },
})
).payload;
if (!target_user_types?.[0]) {
throw new Error(`No User types found for this user.`);
}
if (target_user_types.find((ty) => ty.user_type == "revoked")) {
throw new Error(`User access Revoked`);
}
let csrf_key = generateCSRF();
const profile_media = target_user.profile_media_id
? (
await BunSQLite.select<
BUN_SQLITE_WGUI_MEDIA,
(typeof BunSQLiteTables)[number]
>({
table: "media",
targetId: target_user.profile_media_id,
})
).singleRes
: undefined;
const { media_url, media_thumbnail_url } = profile_media?.id
? grabMediaURL({ media: profile_media })
: {};
const new_logged_in_user: User = {
id: target_user.id,
csrf: csrf_key,
date: now,
first_name: target_user.first_name || "",
last_name: target_user.last_name || "",
email: target_user.email || "",
image: media_url,
image_thumbnail: media_thumbnail_url,
verification_status: 1,
logged_in_status: true,
roles: target_user.user_types || "none",
};
const user_payload_json = JSON.stringify(new_logged_in_user);
const encrypted_key = await encrypt(user_payload_json);
const res_obj: APIResponseObject = {
success: true,
singleRes: new_logged_in_user,
};
const res = new Response(JSON.stringify(res_obj), {
headers: {
"Content-Type": "application/json",
},
});
const maxAge = AppData["AuthExpiryDays"] * 24 * 60 * 60;
setCookies(res, [
{
name: AppData["AuthKeyCookieName"],
value: encrypted_key,
maxAge,
},
{
name: AppData["AuthCSRFCookieName"],
value: csrf_key,
maxAge,
},
]);
const delete_all_user_sso_codes = await BunSQLite.delete<
BUN_SQLITE_WGUI_SSO_LOGIN_CODES,
(typeof BunSQLiteTables)[number]
>({
table: "sso_login_codes",
query: {
query: {
user_id: {
value: new_logged_in_user.id,
},
},
},
});
console.log(
`User #${new_logged_in_user.id} [${new_logged_in_user.first_name} ${new_logged_in_user.last_name}] logged in successfully.`,
);
console.log(
`Date: ${Date()}\n==============================================================`,
);
return res;
}
+55
View File
@@ -0,0 +1,55 @@
import { AppData } from "@/src/data/app-data";
import { getCookie } from "@/src/utils/cookies";
import { decrypt } from "@/src/utils/crypt";
import grabUserAuthTypes from "./grab-user-auth-types";
import type { User, UserAuthReturn } from "@/src/types";
type Params = {
req: Request;
};
export default async function userAuth({
req,
}: Params): Promise<UserAuthReturn> {
try {
const auth_key_cookie = getCookie(req, AppData["AuthKeyCookieName"]);
const auth_csrf_cookie = getCookie(req, AppData["AuthCSRFCookieName"]);
const now = Date.now();
if (!auth_key_cookie) throw new Error(`Auth Key not found!`);
if (!auth_csrf_cookie) throw new Error(`Auth CSRF not found!`);
const decrypted_key = await decrypt(auth_key_cookie);
const decrypted_user_payload = JSON.parse(decrypted_key) as User;
if (!decrypted_user_payload?.id) throw new Error(`Invalid Key`);
if (!decrypted_user_payload.date) throw new Error(`Key has no date!`);
const time_elapsed = now - decrypted_user_payload.date;
const expiry_time = AppData["AuthExpiryDays"] * 24 * 60 * 60 * 1000;
if (time_elapsed > expiry_time) {
throw new Error(`Key Expired`);
}
const user_auth_types = await grabUserAuthTypes({
user_id: decrypted_user_payload.id,
});
if (!user_auth_types.success) {
return user_auth_types;
}
return {
success: true,
user: decrypted_user_payload,
user_types: user_auth_types.user_types,
};
} catch (error: any) {
return {
success: false,
msg: error.msg,
};
}
}
@@ -0,0 +1,190 @@
type GoogleJWTHeader = {
alg?: string;
kid?: string;
typ?: string;
};
type GoogleJWKS = {
keys?: GoogleJWK[];
};
type GoogleJWK = JsonWebKey & {
kid?: string;
};
type GoogleIDTokenPayload = {
aud?: string | string[];
email?: string;
email_verified?: boolean | string;
exp?: number | string;
iss?: string;
name?: string;
picture?: string;
sub?: string;
};
type Params = {
clientId: string;
idToken: string;
};
type GoogleIDTokenUser = Required<Pick<GoogleIDTokenPayload, "email" | "sub">> &
Pick<GoogleIDTokenPayload, "name" | "picture">;
const GoogleJWKSUrl = "https://www.googleapis.com/oauth2/v3/certs";
let cachedGoogleKeys: {
expiresAt: number;
keys: GoogleJWK[];
} | null = null;
function decodeBase64Url(value: string) {
const normalizedValue = value.replace(/-/g, "+").replace(/_/g, "/");
const padding = normalizedValue.length % 4;
const paddedValue =
padding === 0
? normalizedValue
: normalizedValue.padEnd(
normalizedValue.length + (4 - padding),
"=",
);
return Buffer.from(paddedValue, "base64");
}
function parseGoogleTokenPart<T>(value: string): T {
return JSON.parse(decodeBase64Url(value).toString("utf8")) as T;
}
async function getGoogleSigningKeys() {
if (cachedGoogleKeys && cachedGoogleKeys.expiresAt > Date.now()) {
return cachedGoogleKeys.keys;
}
const response = await fetch(GoogleJWKSUrl);
if (!response.ok) {
throw new Error(`Couldn't fetch Google's signing keys`);
}
const data = (await response.json()) as GoogleJWKS;
if (!data.keys?.length) {
throw new Error(`Google didn't return any signing keys`);
}
const maxAge =
Number(
response.headers.get("cache-control")?.match(/max-age=(\d+)/)?.[1],
) || 300;
cachedGoogleKeys = {
expiresAt: Date.now() + maxAge * 1000,
keys: data.keys,
};
return data.keys;
}
function hasMatchingAudience(
aud: GoogleIDTokenPayload["aud"],
clientId: string,
) {
if (Array.isArray(aud)) {
return aud.includes(clientId);
}
return aud === clientId;
}
export default async function verifyGoogleIdToken({
clientId,
idToken,
}: Params): Promise<GoogleIDTokenUser> {
const tokenParts = idToken.split(".");
if (tokenParts.length !== 3) {
throw new Error(`Invalid Google token`);
}
const [encodedHeader, encodedPayload, encodedSignature] = tokenParts;
if (!encodedHeader) {
throw new Error(`No encodedHeader`);
}
if (!encodedPayload) {
throw new Error(`No encodedPayload`);
}
if (!encodedSignature) {
throw new Error(`No encodedSignature`);
}
const header = parseGoogleTokenPart<GoogleJWTHeader>(encodedHeader);
const payload = parseGoogleTokenPart<GoogleIDTokenPayload>(encodedPayload);
if (header.alg !== "RS256" || !header.kid) {
throw new Error(`Unsupported Google token signature`);
}
const signingKeys = await getGoogleSigningKeys();
const signingKey = signingKeys.find((key) => key.kid === header.kid);
if (!signingKey) {
throw new Error(`Unknown Google signing key`);
}
const importedKey = await crypto.subtle.importKey(
"jwk",
signingKey,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
},
false,
["verify"],
);
const isValidSignature = await crypto.subtle.verify(
"RSASSA-PKCS1-v1_5",
importedKey,
decodeBase64Url(encodedSignature),
new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`),
);
if (!isValidSignature) {
throw new Error(`Invalid Google token signature`);
}
if (!hasMatchingAudience(payload.aud, clientId)) {
throw new Error(`Google token was issued for a different app`);
}
if (
payload.iss !== "https://accounts.google.com" &&
payload.iss !== "accounts.google.com"
) {
throw new Error(`Invalid Google token issuer`);
}
const expiresAt = Number(payload.exp || 0) * 1000;
if (!expiresAt || expiresAt <= Date.now()) {
throw new Error(`Google token has expired`);
}
const emailVerified =
payload.email_verified === true || payload.email_verified === "true";
if (!payload.email || !emailVerified || !payload.sub) {
throw new Error(`Google account email is not verified`);
}
return {
email: payload.email,
sub: payload.sub,
name: payload.name,
picture: payload.picture,
};
}