Update login pipeline. Limit login to only password

This commit is contained in:
2026-09-12 14:20:07 +01:00
parent 4d75af0418
commit 9cfbde9c9a
30 changed files with 145 additions and 1068 deletions
-11
View File
@@ -40,13 +40,6 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
}
try {
if (
user_types.find((ty) => ty.user_type == "read_only") &&
!req.method.match(/get/)
) {
throw new Error(`This user can only read records.`);
}
const [table, id] = params.query.paths.split("/") as [
TableType,
string | number,
@@ -63,10 +56,6 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
};
switch (table) {
case "users":
return await users(crud_params);
case "user_types":
return await userTypes(crud_params);
case "media":
return await media(crud_params);
+1 -1
View File
@@ -34,7 +34,7 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
const { id, ids, media_paradigm, user_id } = body;
const can_delete_all_media = checkUserAccess({
includes: ["admin", "board_member"],
includes: ["admin"],
user_types,
});
-133
View File
@@ -1,133 +0,0 @@
import type {
BUN_SQLITE_WGUI_SSO_LOGIN_CODES,
BUN_SQLITE_WGUI_USERS,
BunSQLiteTables,
} from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import { SiteData } from "@/src/data/site-data";
import sendEmail from "@/src/functions/backend/email/send-email";
import type { ApiReqParams, SSOAuth } from "@/src/types";
import { setCookies } from "@/src/utils/cookies";
import { encrypt } from "@/src/utils/crypt";
import EJSON from "@/src/utils/ejson";
import BunSQLite from "@moduletrace/bun-sqlite";
import BunSQLiteB from "@moduletrace/bun-sqlite";
import type {
APIResponseObject,
BunextAPIRouteHandler,
} from "@moduletrace/bunext/types";
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
req,
body,
}) => {
try {
const { login } = body as ApiReqParams;
if (!login?.email_or_username) {
throw new Error(`Please pass an email or phone number`);
}
const target_user = (
await BunSQLiteB.select<
BUN_SQLITE_WGUI_USERS,
(typeof BunSQLiteTables)[number]
>({
table: "users",
query: {
query: {
email: {
value: login.email_or_username,
},
username: {
value: login.email_or_username,
},
},
searchOperator: "OR",
},
})
)?.singleRes;
if (!target_user?.id || !target_user.email) {
throw new Error(`User not Found!`);
}
const sso_code = Math.random().toString().slice(2, 8);
const email_component = (
<div>
<div>Use this Code to Complete your login</div>
<h1>{sso_code}</h1>
<div>
Code expires in {AppData["SSOCodeExpiryMinutes"]} minutes.
</div>
</div>
);
const send_mail = await sendEmail({
content: email_component,
to:
process.env.NODE_ENV == "production"
? target_user.email
: "[email protected]",
subject: `Use one-time-code to complete your login.`,
text: `Complete your login to the SBF portal`,
title: `${SiteData["SiteName"]} SSO`,
options: {
priority: "high",
},
});
if (!send_mail.success) {
return send_mail;
}
const record_sso = await BunSQLite.insert<
BUN_SQLITE_WGUI_SSO_LOGIN_CODES,
(typeof BunSQLiteTables)[number]
>({
data: [
{
code: sso_code,
user_id: target_user.id,
},
],
table: "sso_login_codes",
update_on_duplicate: true,
});
const sso_auth: SSOAuth = {
user_id: target_user.id,
sso_code,
email: target_user.email,
};
const sso_auth_string = EJSON.stringify(sso_auth);
if (!sso_auth_string) {
throw new Error(`Couldn't Stringify SSO Auth`);
}
const encrypted_sso_auth_string = await encrypt(sso_auth_string);
return {
success: true,
bunext_api_route_res_transform_fn(res) {
const new_res = res.clone();
setCookies(new_res, [
{
name: AppData["SSOAuthCookieName"],
value: encrypted_sso_auth_string,
},
]);
return new_res;
},
};
} catch (error: any) {
console.log("error", error.message);
return {
success: false,
msg: error.message,
};
}
};
-74
View File
@@ -1,74 +0,0 @@
import loginUser from "@/src/functions/backend/auth/login-user";
import verifyGoogleIdToken from "@/src/functions/backend/auth/verify-google-id-token";
import grabUsers from "@/src/functions/backend/db/users/grab-users";
import uploadAndRecordMedia from "@/src/functions/backend/db/users/media/upload-and-record-media";
import type { ApiReqParams } from "@/src/types";
import type {
APIResponseObject,
BunextAPIRouteHandler,
} from "@moduletrace/bunext/types";
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
body,
}) => {
try {
const { google_token } = body as ApiReqParams;
if (!google_token) {
throw new Error(`No Google token was provided`);
}
const googleClientId = process.env.GOOGLE_CLIENT_ID;
if (!googleClientId) {
throw new Error(`Google login is not configured on this server`);
}
const googleUser = await verifyGoogleIdToken({
clientId: googleClientId,
idToken: google_token,
});
const target_user_res = await grabUsers({
query: {
search_term_email: googleUser.email,
},
exact_match: true,
});
const target_user = target_user_res.singleRes;
if (!target_user?.id) {
throw new Error(`No account exists for ${googleUser.email}`);
}
if (googleUser.picture && !target_user.profile_media_id) {
const fullResUrl = googleUser.picture.replace(/=s\d+-c$/, "=s0");
const res = await fetch(fullResUrl);
if (res.ok) {
const bytes = new Uint8Array(await res.arrayBuffer());
await uploadAndRecordMedia({
data: bytes,
user: target_user,
media_paradigms: ["user-profile-image"],
media_type: "image",
is_primary: true,
media_name: `user-${target_user.id}-profile`,
media_mime_type: "jpeg",
});
}
}
return await loginUser({
user_id: target_user.id,
});
} catch (error: any) {
return {
success: false,
msg: error.message,
};
}
};
+32 -52
View File
@@ -1,14 +1,9 @@
import type {
BUN_SQLITE_WGUI_SSO_LOGIN_CODES,
BUN_SQLITE_WGUI_USERS,
BunSQLiteTables,
} from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import loginUser from "@/src/functions/backend/auth/login-user";
import type { ApiReqParams, SSOAuth } from "@/src/types";
import { getCookie } from "@/src/utils/cookies";
import { decrypt } from "@/src/utils/crypt";
import EJSON from "@/src/utils/ejson";
import type { ApiReqParams } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
import type {
APIResponseObject,
@@ -16,51 +11,17 @@ import type {
} from "@moduletrace/bunext/types";
export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
req,
body,
}) => {
try {
const { sso_code } = body as ApiReqParams;
const { login } = body as ApiReqParams;
const sso_cookie = getCookie(req, AppData["SSOAuthCookieName"]);
if (!sso_cookie) {
throw new Error(`No SSO session found!`);
if (!login?.email_or_username) {
throw new Error(`Please enter your email or username`);
}
if (!sso_code) {
throw new Error(`No SSO code sent!`);
}
const decrypted_sso_json = await decrypt(sso_cookie);
const decrypted_sso_object = EJSON.parse(decrypted_sso_json) as SSOAuth;
const target_sso = (
await BunSQLite.select<
BUN_SQLITE_WGUI_SSO_LOGIN_CODES,
(typeof BunSQLiteTables)[number]
>({
table: "sso_login_codes",
query: {
query: {
code: {
value: sso_code,
},
},
},
})
).singleRes;
if (!target_sso?.code) {
throw new Error(`Invalid Code!`);
}
const now = Date.now();
const time_elapsed = now - Number(target_sso.updated_at);
const expirty_time = AppData["SSOCodeExpiryMinutes"] * 60 * 1000;
if (time_elapsed > expirty_time) {
throw new Error(`Code Expired. Please Login again.`);
if (!login?.password) {
throw new Error(`Please enter your password`);
}
const target_user = (
@@ -71,16 +32,35 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
table: "users",
query: {
query: {
email: decrypted_sso_object.email
? { value: decrypted_sso_object.email }
: undefined,
email: {
value: login.email_or_username,
},
username: {
value: login.email_or_username,
},
},
searchOperator: "OR",
},
})
).singleRes;
)?.singleRes;
if (!target_user?.id) {
throw new Error(`This device wasn't used to get this SSO code`);
if (!target_user?.id || !target_user.password) {
throw new Error(`Invalid email or password`);
}
let password_matches = false;
try {
password_matches = await Bun.password.verify(
login.password,
target_user.password,
);
} catch (error) {
password_matches = false;
}
if (!password_matches) {
throw new Error(`Invalid email or password`);
}
return await loginUser({
@@ -92,4 +72,4 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async ({
msg: error.message,
};
}
};
};
+56 -11
View File
@@ -1,4 +1,8 @@
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
import type {
BUN_SQLITE_WGUI_USER_TYPES,
BUN_SQLITE_WGUI_USERS,
} from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import grabUsers from "@/src/functions/backend/db/users/grab-users";
import type { ApiReqParams, TableType } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
@@ -6,8 +10,6 @@ import type {
APIResponseObject,
BunextAPIRouteHandler,
} from "@moduletrace/bunext/types";
import { hash } from "bun";
import _ from "lodash";
export const handler: BunextAPIRouteHandler<
APIResponseObject<BUN_SQLITE_WGUI_USERS>
@@ -23,26 +25,69 @@ export const handler: BunextAPIRouteHandler<
},
});
console.log("users_res", users_res);
if (users_res.singleRes?.id) {
return {
success: false,
};
}
let final_insert_data = [
...(body?.insert_data?.map((data) => {
return { ...data, password: hash(data.password || "") };
}) || []),
];
const final_insert_data = await Promise.all(
body?.insert_data?.map(async (data) => {
if (!data.password) {
throw new Error(
`Password is required to create the first user.`,
);
}
return {
...data,
password: await Bun.password.hash(data.password, {
algorithm: "bcrypt",
cost: AppData["HashCost"],
}),
id: 1,
};
}) || [],
);
const POST = await BunSQLite.insert<BUN_SQLITE_WGUI_USERS, TableType>({
table: "users",
data: final_insert_data,
});
return POST;
const new_users_res = await grabUsers({
query: {
sql_query: {
limit: 1,
},
},
});
if (!new_users_res.singleRes?.id) {
throw new Error(
`Couldn't find newly created user record! Try again.`,
);
}
const add_super_admin_user_types = await BunSQLite.insert<
BUN_SQLITE_WGUI_USER_TYPES,
TableType
>({
table: "user_types",
data: [
{
user_type: "super_admin",
user_id: new_users_res.singleRes.id,
},
],
});
console.log("add_super_admin_user_types", add_super_admin_user_types);
return {
...POST,
singleRes: new_users_res.singleRes,
};
} catch (error: any) {
return {
success: false,