refactor(login): added in a check for lastlogin and force reset password

this also includes passowrd for non alpla users will deal with them later
This commit is contained in:
2025-10-21 20:24:55 -05:00
parent eb3fa4dd52
commit 17e13d4604
6 changed files with 10924 additions and 10540 deletions

View File

@@ -18,7 +18,7 @@ import { settings } from "./src/pkg/db/schema/settings.js";
import { createLogger } from "./src/pkg/logger/logger.js";
import { v1Listener } from "./src/pkg/logger/v1Listener.js";
import { apiHitMiddleware } from "./src/pkg/middleware/apiHits.js";
import { initializeProdPool } from "./src/pkg/prodSql/prodSqlConnect.js";
import { initializeProdPool, pool } from "./src/pkg/prodSql/prodSqlConnect.js";
import { validateEnv } from "./src/pkg/utils/envValidator.js";
import { sendNotify } from "./src/pkg/utils/notify.js";
import { returnFunc } from "./src/pkg/utils/return.js";
@@ -89,7 +89,10 @@ const main = async () => {
const allowedOrigins = [
/^https?:\/\/localhost:(5173|5500|4200|3000|4000)$/, // all the allowed backend ports
/^http?:\/\/localhost:(5173|5500|4200|3000|4000)$/,
/^https?:\/\/.*\.alpla\.net$/,
"http://localhost:4173",
"http://localhost:4200",
env.BETTER_AUTH_URL, // prod
];
@@ -120,6 +123,8 @@ const main = async () => {
},
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
credentials: true,
exposedHeaders: ["set-cookie"],
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
}),
);
@@ -130,6 +135,14 @@ const main = async () => {
express.static(join(__dirname, "../frontend/dist")),
);
app.get(basePath + "/app/*splat", (req, res) => {
res.sendFile(join(__dirname, "../frontend/dist/index.html"));
});
app.get(basePath + "/d/*splat", (req, res) => {
res.sendFile(join(__dirname, "../lstDocs/build/index.html"));
});
// server setup
const server = createServer(app);
@@ -188,6 +201,30 @@ const main = async () => {
//process.exit(1);
});
process.on("SIGINT", async () => {
console.log("\nGracefully shutting down...");
try {
await pool.close();
console.log("Closed SQL connection.");
} catch (err) {
console.error("Error closing SQL connection:", err);
} finally {
process.exit(0);
}
});
// Also handle other termination signals (optional)
process.on("SIGTERM", async () => {
console.log("SIGTERM received. Closing SQL connection...");
try {
await pool.close();
} catch (err) {
console.error(err);
} finally {
process.exit(0);
}
});
// setInterval(() => {
// const used = process.memoryUsage();
// console.log(

View File

@@ -0,0 +1,105 @@
import { fromNodeHeaders } from "better-auth/node";
import { differenceInDays } from "date-fns";
import { eq, sql } from "drizzle-orm";
import { type Request, type Response, Router } from "express";
import z from "zod";
import { auth } from "../../../pkg/auth/auth.js";
import { db } from "../../../pkg/db/db.js";
import { account, user } from "../../../pkg/db/schema/auth-schema.js";
const router = Router();
const signin = z.object({
username: z.string(),
password: z.string().min(8, "Password must be at least 8 characters"),
});
// GET /health
router.post("/", async (req: Request, res: Response) => {
try {
const validated = signin.parse(req.body);
const userLogin = await db
.select()
.from(user)
.where(eq(user.username, validated.username));
if (
!userLogin[0].lastLogin ||
differenceInDays(userLogin[0].lastLogin, new Date(Date.now())) > 120
) {
// due to the new change we want to check if the user is alpla if alpla then we send a password reset if not an alpla email we need to change there password to the defined Alpla2025!
if (userLogin[0].email.includes("@alpla.com")) {
// send password reset email
await auth.api.requestPasswordReset({
body: {
email: userLogin[0].email,
redirectTo: `${process.env.BETTER_AUTH_URL}/user/resetpassword`,
},
});
await db
.update(user)
.set({ lastLogin: sql`NOW()` })
.where(eq(user.id, userLogin[0].id));
return res.status(401).json({
success: false,
message: `${validated.username} it looks like you haven't been here in a while, you will need to change your password, an email was just sent to ${userLogin[0].email} with a link to reset your password.`,
data: { user: userLogin[0].id },
});
} else {
//reset the password so its updated to the new one
await db
.update(account)
.set({
password:
"6ab221fdf322129ae48d808f6db3f592:f8e34a1e4e3c8133a54d8063e1d2b640d5e573cc53bd799cf78abfa2d2bfcc3c6cd84540e73e75d9da8faefad4ea31fe50a87a6f5773e421c082b5095a7b0491",
})
.where(eq(account.userId, userLogin[0].id));
// change last login to now
await db
.update(user)
.set({ lastLogin: sql`NOW()` })
.where(eq(user.id, userLogin[0].id));
return res.status(401).json({
success: false,
message: `${validated.username} dose not have a valid alpla email your password will be changed to Alpla2025! it is recommended to login and change your password.`,
data: [],
});
}
}
const logging = (await auth.api.signInUsername({
body: {
username: validated.username,
password: validated.password,
},
asResponse: true,
})) as any;
logging.headers.forEach((value: string, key: string) => {
if (key.toLowerCase() === "set-cookie") {
res.append("set-cookie", value); // Express method
} else {
res.setHeader(key, value);
}
});
const data = await logging.json();
await db
.update(user)
.set({ lastLogin: sql`NOW()` })
.where(eq(user.id, userLogin[0].id));
return res.status(logging.status).json(data);
} catch (error) {
console.log(error);
return res
.status(500)
.json({ message: "seem to have encountered an error please try again." });
}
});
export default router;

View File

@@ -1,60 +1,65 @@
import { Router } from "express";
import { APIError, betterAuth } from "better-auth";
import { count, eq, sql } from "drizzle-orm";
import type { Request, Response } from "express";
import { Router } from "express";
import z from "zod";
import { auth } from "../../../pkg/auth/auth.js";
import { db } from "../../../pkg/db/db.js";
import { count } from "drizzle-orm";
import { user } from "../../../pkg/db/schema/auth-schema.js";
import { APIError, betterAuth } from "better-auth";
import { systemAdminRole } from "../../admin/controller/systemAdminRole.js";
const router = Router();
const registerSchema = z.object({
email: z.email(),
name: z.string().min(2).max(100),
password: z.string().min(8, "Password must be at least 8 characters"),
username: z
.string()
.min(3)
.max(32)
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric + underscores"),
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
email: z.email(),
name: z.string().min(2).max(100),
password: z.string().min(8, "Password must be at least 8 characters"),
username: z
.string()
.min(3)
.max(32)
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric + underscores"),
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
});
router.post("/", async (req: Request, res: Response) => {
// check if we are the first user so we can add as system admin to all modules
const totalUsers = await db.select({ count: count() }).from(user);
try {
// Parse + validate incoming JSON against Zod schema
const validated = registerSchema.parse(req.body);
// check if we are the first user so we can add as system admin to all modules
const totalUsers = await db.select({ count: count() }).from(user);
try {
// Parse + validate incoming JSON against Zod schema
const validated = registerSchema.parse(req.body);
// Call Better Auth signUp
const user = await auth.api.signUpEmail({
body: validated,
});
// Call Better Auth signUp
const newUser = await auth.api.signUpEmail({
body: validated,
});
if (totalUsers[0].count === 0) {
systemAdminRole(user.user.id);
}
return res.status(201).json(user);
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
return res.status(400).json({
error: "Validation failed",
details: flattened,
});
}
if (totalUsers[0].count === 0) {
systemAdminRole(newUser.user.id);
}
if (err instanceof APIError) {
return res.status(400).json({
success: false,
message: err.message,
error: err.status,
});
}
}
await db
.update(user)
.set({ lastLogin: sql`NOW()` })
.where(eq(user.id, newUser.user.id));
return res.status(201).json(user);
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
return res.status(400).json({
error: "Validation failed",
details: flattened,
});
}
if (err instanceof APIError) {
return res.status(400).json({
success: false,
message: err.message,
error: err.status,
});
}
}
});
export default router;

View File

@@ -1,13 +1,15 @@
import type { Express, Request, Response } from "express";
import { requireAuth } from "../../../pkg/middleware/authMiddleware.js";
import login from "./login.js";
import me from "./me.js";
import register from "./register.js";
import userRoles from "./userroles.js";
import resetPassword from "./resetPassword.js";
import { requireAuth } from "../../../pkg/middleware/authMiddleware.js";
import userRoles from "./userroles.js";
export const setupAuthRoutes = (app: Express, basePath: string) => {
app.use(basePath + "/api/user/me", requireAuth(), me);
app.use(basePath + "/api/user/resetpassword", resetPassword);
app.use(basePath + "/api/user/register", register);
app.use(basePath + "/api/user/roles", requireAuth(), userRoles);
app.use(basePath + "/api/user/login", login);
app.use(basePath + "/api/user/me", requireAuth(), me);
app.use(basePath + "/api/user/resetpassword", resetPassword);
app.use(basePath + "/api/user/register", register);
app.use(basePath + "/api/user/roles", requireAuth(), userRoles);
};