89e1bd5dbe
Security:
- Fix CORS origin validation to use exact match (includes) instead of
startsWith, preventing domain-prefix bypass attacks with credentials: true
RBAC — App Visibility:
- /api/apps now filters apps per user's permission set via app_permissions
and role_permissions tables: admin users see all active apps; regular users
see apps that either have no permission restriction or have a matching
permission assigned through their roles
Admin User Create:
- Add POST /api/users (requireAdmin) endpoint to create users via the admin
dashboard; uses bcryptjs hashing, checks for duplicate username, returns
safe profile payload
i18n — Remove Hardcoded Strings:
- Add "common.appName" key to en.json ("TeaBoy OS") and ar.json ("نظام TeaBoy")
- Replace hardcoded "TeaBoy OS" literal in login.tsx and register.tsx
with t("common.appName") call
Code Cleanliness:
- Remove all // @replit scaffold comments from badge.tsx and button.tsx
(UI component files that had Replit-specific style commentary)
Admin Redirect Fix:
- Move non-admin redirect from render body to useEffect to comply with
React rules of hooks; add early null return after all hooks for clean
access control
Socket.IO Security:
- Server-side auth: session middleware applied via io.engine.use
- io.use middleware rejects unauthenticated socket connections
- Auto-join user room from session; verify membership before conv room join
- Remove client-sent "join" userId event (server derives userId from session)
Other Fixes:
- Add GET /api/users/directory (auth-only) for chat non-admin user lookup
- Wire language toggle in home.tsx to persist via PUT /api/auth/language
- Fix admin.tsx nullable field coercions with ?? defaults
- All TypeScript checks pass; full e2e regression passes
84 lines
1.8 KiB
TypeScript
84 lines
1.8 KiB
TypeScript
import express, { type Express } from "express";
|
|
import cors from "cors";
|
|
import pinoHttp from "pino-http";
|
|
import session from "express-session";
|
|
import connectPgSimple from "connect-pg-simple";
|
|
import router from "./routes";
|
|
import { logger } from "./lib/logger";
|
|
import { pool } from "@workspace/db";
|
|
|
|
const PgSession = connectPgSimple(session);
|
|
|
|
const app: Express = express();
|
|
|
|
app.set("trust proxy", 1);
|
|
|
|
app.use(
|
|
pinoHttp({
|
|
logger,
|
|
serializers: {
|
|
req(req) {
|
|
return {
|
|
id: req.id,
|
|
method: req.method,
|
|
url: req.url?.split("?")[0],
|
|
};
|
|
},
|
|
res(res) {
|
|
return {
|
|
statusCode: res.statusCode,
|
|
};
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
const allowedOrigins = process.env.ALLOWED_ORIGINS
|
|
? process.env.ALLOWED_ORIGINS.split(",").map((o) => o.trim())
|
|
: null;
|
|
|
|
app.use(
|
|
cors({
|
|
origin: allowedOrigins
|
|
? (origin, callback) => {
|
|
if (!origin || allowedOrigins.includes(origin)) {
|
|
callback(null, true);
|
|
} else {
|
|
callback(new Error("Not allowed by CORS"));
|
|
}
|
|
}
|
|
: true,
|
|
credentials: true,
|
|
}),
|
|
);
|
|
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
export const sessionMiddleware = session({
|
|
store: new PgSession({
|
|
pool,
|
|
tableName: "user_sessions",
|
|
}),
|
|
secret: process.env.SESSION_SECRET ?? (() => {
|
|
if (process.env.NODE_ENV === "production") {
|
|
throw new Error("SESSION_SECRET must be set in production");
|
|
}
|
|
return "teaboy-dev-secret-change-before-deploy";
|
|
})(),
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: {
|
|
secure: false,
|
|
httpOnly: true,
|
|
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
sameSite: "lax",
|
|
},
|
|
});
|
|
|
|
app.use(sessionMiddleware);
|
|
|
|
app.use("/api", router);
|
|
|
|
export default app;
|