Files
blog/apps/backend/src/index.ts
T
alex f4faef0fa0
Build and Publish Docker Image / build (push) Successful in 1m34s
add trust proxy to fix cookies
2026-09-07 01:19:24 -04:00

126 lines
3.1 KiB
TypeScript

import express, {
type Request,
type Response,
type NextFunction,
} from "express";
import cors from "cors";
import session from "express-session";
import passport from "passport";
import bcrypt from "bcrypt";
import env from "./utils/env.js";
import { RedisStore } from "connect-redis";
import { createClient, RedisClient } from "redis";
import { Strategy as LocalStrategy } from "passport-local";
import { prisma } from "database";
import { ZodError } from "zod";
// routes
import usersRoute from "./routers/users.js";
import postsRoute from "./routers/posts.js";
// redis client for session mgmt
const redisClient = createClient({ url: env.REDIS_URL });
redisClient.connect().catch(console.error);
const app = express();
// TLS terminates at Traefik and the SSR frontend proxies onward over plain
// HTTP, so req.protocol is "http" here and the session cookie's `secure` flag
// would silently suppress Set-Cookie. Trusting X-Forwarded-Proto restores it.
// The only reachable client is that proxy, on an internal network.
app.set("trust proxy", true);
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// passport strategy
passport.use(
new LocalStrategy(
{ usernameField: "email" },
async (email, password, done) => {
try {
const user = await prisma.user.findFirst({
where: {
email,
},
});
if (!user) return done(null, false, { message: "Bad credentials" });
const ok = await bcrypt.compare(password, user.password);
return ok
? done(null, user)
: done(null, false, { message: "Bad credentials" });
} catch (err) {
return done(err);
}
},
),
);
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id: number, done) => {
const user = await prisma.user.findFirst({
where: {
id,
},
});
done(null, user ?? false);
});
app.use(
session({
store: new RedisStore({ client: redisClient, prefix: "sess:" }),
secret: env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
sameSite: "lax",
secure: env.NODE_ENV === "production",
maxAge: 1000 * 60 * 60 * 24 * 7,
},
}),
);
app.use(passport.session());
if (env.NODE_ENV === "production") {
app.use(
cors({
origin: "blog.alexav.gg",
credentials: true,
}),
);
}
app.get("/", (req: Request, res: Response) => {
res.status(200).json({
status: 200,
});
});
app.use("/users", usersRoute);
app.use("/posts", postsRoute);
app.use(
(err: Error | ZodError, req: Request, res: Response, next: NextFunction) => {
if (err instanceof ZodError) {
res.status(400).json({
status: 400,
message: "Malformed request body",
issues: err.issues,
});
} else {
console.log(err);
res.status(500).json({
status: 500,
message:
"Sorry, an unexpected error occurred while performing that action",
});
}
},
);
app.listen(3000, () => {
console.log("Server started");
});