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:
39
app/main.ts
39
app/main.ts
@@ -18,7 +18,7 @@ import { settings } from "./src/pkg/db/schema/settings.js";
|
|||||||
import { createLogger } from "./src/pkg/logger/logger.js";
|
import { createLogger } from "./src/pkg/logger/logger.js";
|
||||||
import { v1Listener } from "./src/pkg/logger/v1Listener.js";
|
import { v1Listener } from "./src/pkg/logger/v1Listener.js";
|
||||||
import { apiHitMiddleware } from "./src/pkg/middleware/apiHits.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 { validateEnv } from "./src/pkg/utils/envValidator.js";
|
||||||
import { sendNotify } from "./src/pkg/utils/notify.js";
|
import { sendNotify } from "./src/pkg/utils/notify.js";
|
||||||
import { returnFunc } from "./src/pkg/utils/return.js";
|
import { returnFunc } from "./src/pkg/utils/return.js";
|
||||||
@@ -89,7 +89,10 @@ const main = async () => {
|
|||||||
|
|
||||||
const allowedOrigins = [
|
const allowedOrigins = [
|
||||||
/^https?:\/\/localhost:(5173|5500|4200|3000|4000)$/, // all the allowed backend ports
|
/^https?:\/\/localhost:(5173|5500|4200|3000|4000)$/, // all the allowed backend ports
|
||||||
|
/^http?:\/\/localhost:(5173|5500|4200|3000|4000)$/,
|
||||||
/^https?:\/\/.*\.alpla\.net$/,
|
/^https?:\/\/.*\.alpla\.net$/,
|
||||||
|
"http://localhost:4173",
|
||||||
|
"http://localhost:4200",
|
||||||
env.BETTER_AUTH_URL, // prod
|
env.BETTER_AUTH_URL, // prod
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -120,6 +123,8 @@ const main = async () => {
|
|||||||
},
|
},
|
||||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
||||||
credentials: true,
|
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")),
|
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
|
// server setup
|
||||||
const server = createServer(app);
|
const server = createServer(app);
|
||||||
|
|
||||||
@@ -188,6 +201,30 @@ const main = async () => {
|
|||||||
//process.exit(1);
|
//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(() => {
|
// setInterval(() => {
|
||||||
// const used = process.memoryUsage();
|
// const used = process.memoryUsage();
|
||||||
// console.log(
|
// console.log(
|
||||||
|
|||||||
105
app/src/internal/auth/routes/login.ts
Normal file
105
app/src/internal/auth/routes/login.ts
Normal 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;
|
||||||
@@ -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 type { Request, Response } from "express";
|
||||||
|
import { Router } from "express";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { auth } from "../../../pkg/auth/auth.js";
|
import { auth } from "../../../pkg/auth/auth.js";
|
||||||
import { db } from "../../../pkg/db/db.js";
|
import { db } from "../../../pkg/db/db.js";
|
||||||
import { count } from "drizzle-orm";
|
|
||||||
import { user } from "../../../pkg/db/schema/auth-schema.js";
|
import { user } from "../../../pkg/db/schema/auth-schema.js";
|
||||||
import { APIError, betterAuth } from "better-auth";
|
|
||||||
import { systemAdminRole } from "../../admin/controller/systemAdminRole.js";
|
import { systemAdminRole } from "../../admin/controller/systemAdminRole.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
const registerSchema = z.object({
|
const registerSchema = z.object({
|
||||||
email: z.email(),
|
email: z.email(),
|
||||||
name: z.string().min(2).max(100),
|
name: z.string().min(2).max(100),
|
||||||
password: z.string().min(8, "Password must be at least 8 characters"),
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.min(3)
|
.min(3)
|
||||||
.max(32)
|
.max(32)
|
||||||
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric + underscores"),
|
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric + underscores"),
|
||||||
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
|
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/", async (req: Request, res: Response) => {
|
router.post("/", async (req: Request, res: Response) => {
|
||||||
// check if we are the first user so we can add as system admin to all modules
|
// 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);
|
const totalUsers = await db.select({ count: count() }).from(user);
|
||||||
try {
|
try {
|
||||||
// Parse + validate incoming JSON against Zod schema
|
// Parse + validate incoming JSON against Zod schema
|
||||||
const validated = registerSchema.parse(req.body);
|
const validated = registerSchema.parse(req.body);
|
||||||
|
|
||||||
// Call Better Auth signUp
|
// Call Better Auth signUp
|
||||||
const user = await auth.api.signUpEmail({
|
const newUser = await auth.api.signUpEmail({
|
||||||
body: validated,
|
body: validated,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (totalUsers[0].count === 0) {
|
if (totalUsers[0].count === 0) {
|
||||||
systemAdminRole(user.user.id);
|
systemAdminRole(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) {
|
await db
|
||||||
return res.status(400).json({
|
.update(user)
|
||||||
success: false,
|
.set({ lastLogin: sql`NOW()` })
|
||||||
message: err.message,
|
.where(eq(user.id, newUser.user.id));
|
||||||
error: err.status,
|
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;
|
export default router;
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import type { Express, Request, Response } from "express";
|
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 me from "./me.js";
|
||||||
import register from "./register.js";
|
import register from "./register.js";
|
||||||
import userRoles from "./userroles.js";
|
|
||||||
import resetPassword from "./resetPassword.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) => {
|
export const setupAuthRoutes = (app: Express, basePath: string) => {
|
||||||
app.use(basePath + "/api/user/me", requireAuth(), me);
|
app.use(basePath + "/api/user/login", login);
|
||||||
app.use(basePath + "/api/user/resetpassword", resetPassword);
|
app.use(basePath + "/api/user/me", requireAuth(), me);
|
||||||
app.use(basePath + "/api/user/register", register);
|
app.use(basePath + "/api/user/resetpassword", resetPassword);
|
||||||
app.use(basePath + "/api/user/roles", requireAuth(), userRoles);
|
app.use(basePath + "/api/user/register", register);
|
||||||
|
app.use(basePath + "/api/user/roles", requireAuth(), userRoles);
|
||||||
};
|
};
|
||||||
|
|||||||
21205
package-lock.json
generated
21205
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -12,7 +12,7 @@
|
|||||||
"dev:db:generate": "tsc && npx drizzle-kit generate --config=drizzle-dev.config.ts",
|
"dev:db:generate": "tsc && npx drizzle-kit generate --config=drizzle-dev.config.ts",
|
||||||
"dev": "concurrently -n \"server,frontend,docs\" -c \"#007755,#2f6da3,#DB4FE0\" \"npm run dev:app\" \"npm run dev:front\" \"npm run dev:docs\"",
|
"dev": "concurrently -n \"server,frontend,docs\" -c \"#007755,#2f6da3,#DB4FE0\" \"npm run dev:app\" \"npm run dev:front\" \"npm run dev:docs\"",
|
||||||
"copy:docs": "node scripts/lstDocCopy.mjs",
|
"copy:docs": "node scripts/lstDocCopy.mjs",
|
||||||
"build:app": "rimraf dist && npx tsc && xcopy app\\src\\internal\\system\\controller\\settings\\settings.json dist\\src\\internal\\system\\controller\\settings /E /I /Y",
|
"build:app": "rimraf dist && npx tsc && node scripts/lstAppMoves.mjs",
|
||||||
"build:front": "cd frontend && rimraf dist && npm run build",
|
"build:front": "cd frontend && rimraf dist && npm run build",
|
||||||
"build:docs": "cd lstDocs && rimraf build && npm run build",
|
"build:docs": "cd lstDocs && rimraf build && npm run build",
|
||||||
"build:wrapper": "cd lstWrapper && rimraf publish && dotnet publish -c Release -o ./publish",
|
"build:wrapper": "cd lstWrapper && rimraf publish && dotnet publish -c Release -o ./publish",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"install:app": "npm i",
|
"install:app": "npm i",
|
||||||
"start:app": "node dist/main.js",
|
"start:app": "node dist/main.js",
|
||||||
"start": "dotenvx run -f .env -- npm run start:app",
|
"start": "dotenvx run -f .env -- npm run start:app",
|
||||||
"start:win": "set NODE_ENV=production && node dist/main.js",
|
"start:win": "set NODE_ENV=production && dotenvx run -f .env -- node dist/main.js",
|
||||||
"docker": "docker compose up --build --force-recreate -d",
|
"docker": "docker compose up --build --force-recreate -d",
|
||||||
"commit": "cz",
|
"commit": "cz",
|
||||||
"deploy": "standard-version --conventional-commits && npm run translateDocs && npm run build && cd lstV2 && npm run build",
|
"deploy": "standard-version --conventional-commits && npm run translateDocs && npm run build && cd lstV2 && npm run build",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"db:generate": "npx drizzle-kit generate",
|
"db:generate": "npx drizzle-kit generate",
|
||||||
"translateDocs": "cd scripts && node translateScript.js",
|
"translateDocs": "cd scripts && node translateScript.js",
|
||||||
"auth:generate": "npx @better-auth/cli generate --config ./app/src/pkg/auth/auth.ts",
|
"auth:generate": "npx @better-auth/cli generate --config ./app/src/pkg/auth/auth.ts",
|
||||||
"updates": "ncu -g"
|
"updates": "ncu"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"better-auth": "^1.3.27",
|
"better-auth": "^1.3.28",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
@@ -59,9 +59,9 @@
|
|||||||
"mssql": "^12.0.0",
|
"mssql": "^12.0.0",
|
||||||
"nodemailer": "^7.0.9",
|
"nodemailer": "^7.0.9",
|
||||||
"nodemailer-express-handlebars": "^7.0.0",
|
"nodemailer-express-handlebars": "^7.0.0",
|
||||||
"npm-check-updates": "^19.0.0",
|
"npm-check-updates": "^19.1.1",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"pino": "^10.0.0",
|
"pino": "^10.1.0",
|
||||||
"pino-pretty": "^13.1.2",
|
"pino-pretty": "^13.1.2",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
"@types/morgan": "^1.9.10",
|
"@types/morgan": "^1.9.10",
|
||||||
"@types/mssql": "^9.1.8",
|
"@types/mssql": "^9.1.8",
|
||||||
"@types/node": "^24.7.1",
|
"@types/node": "^24.9.1",
|
||||||
"@types/nodemailer": "^7.0.2",
|
"@types/nodemailer": "^7.0.2",
|
||||||
"@types/nodemailer-express-handlebars": "^4.0.5",
|
"@types/nodemailer-express-handlebars": "^4.0.5",
|
||||||
"@types/pg": "^8.15.5",
|
"@types/pg": "^8.15.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user