A Deno-powered backend service for Plants vs. Zombies: MODDED. [Read-only GitHub mirror]
docs.pvzm.net
express
typescript
expressjs
plant
deno
jspvz
pvzm
game
online
backend
plants-vs-zombies
zombie
javascript
plants
modded
vs
plantsvszombies
openapi
pvz
noads
1import session from "express-session";
2import memorystore from "memorystore";
3import passport from "passport";
4import { Strategy as GitHubStrategy } from "passport-github2";
5
6import type { ServerConfig } from "./config.ts";
7
8export function setupSession(app: any, config: ServerConfig) {
9 const MemoryStore = (memorystore as any)(session);
10 const maxAgeMs = 24 * 60 * 60 * 1000;
11
12 app.use(
13 session({
14 secret: config.sessionSecret,
15 resave: false,
16 saveUninitialized: false,
17 store: new MemoryStore({
18 checkPeriod: maxAgeMs,
19 }),
20 cookie: {
21 secure: "auto" as any,
22 sameSite: "lax",
23 maxAge: maxAgeMs,
24 },
25 })
26 );
27}
28
29export function setupGithubAuth(app: any, config: ServerConfig) {
30 if (!config.useGithubAuth) return;
31
32 app.use(passport.initialize());
33 app.use(passport.session());
34
35 passport.use(
36 new GitHubStrategy(
37 {
38 clientID: config.githubClientID,
39 clientSecret: config.githubClientSecret,
40 callbackURL: `/api/auth/github/callback`,
41 scope: ["user:email"],
42 },
43 function (_accessToken: string, _refreshToken: string, profile: any, done: (error: any, user?: any, info?: any) => void) {
44 if (config.allowedUsers.includes(profile.username)) {
45 return done(null, profile);
46 }
47 return done(null, false, { message: "Unauthorized user" });
48 }
49 )
50 );
51
52 passport.serializeUser(function (user: any, done: (error: any, id?: any) => void) {
53 done(null, user);
54 });
55
56 passport.deserializeUser(function (obj: any, done: (error: any, user?: any) => void) {
57 done(null, obj);
58 });
59
60 app.get("/api/auth/github", passport.authenticate("github", { session: true }));
61
62 app.get(
63 "/api/auth/github/callback",
64 passport.authenticate("github", {
65 successRedirect: "/admin.html",
66 failureRedirect: "/auth-error.html",
67 })
68 );
69
70 app.get("/api/auth/status", (req: any, res: any) => {
71 if (req.isAuthenticated()) {
72 res.json({
73 authenticated: true,
74 user: {
75 username: req.user.username,
76 displayName: req.user.displayName || req.user.username,
77 profileUrl: req.user.profileUrl,
78 avatarUrl: req.user.photos?.[0]?.value,
79 },
80 });
81 } else {
82 res.json({ authenticated: false });
83 }
84 });
85
86 app.get("/api/auth/logout", (req: any, res: any) => {
87 req.logout(function (err: Error) {
88 if (err) {
89 console.error("Error during logout:", err);
90 return res.status(500).json({ error: "Failed to logout" });
91 }
92 res.redirect("/");
93 });
94 });
95}
96
97export function ensureAuthenticated(config: ServerConfig) {
98 return function (req: any, res: any, next: () => void) {
99 if (!config.useGithubAuth) {
100 return next();
101 }
102
103 if (req.isAuthenticated()) {
104 return next();
105 }
106
107 res.status(401).json({ error: "Unauthorized" });
108 };
109}
110
111export function ensureAuthenticatedOrConsumeTokenForLevelParam(config: ServerConfig, consumeOneTimeTokenForLevel: (token: string, levelId: number) => boolean) {
112 return function (req: any, res: any, next: () => void) {
113 if (!config.useGithubAuth) {
114 return next();
115 }
116
117 if (req.isAuthenticated && req.isAuthenticated()) {
118 return next();
119 }
120
121 const token = typeof req.query?.token === "string" ? req.query.token : "";
122 const levelId = parseInt(String(req.params?.id));
123
124 if (!token) {
125 return res.status(401).json({ error: "Unauthorized" });
126 }
127 if (!Number.isFinite(levelId) || levelId <= 0) {
128 return res.status(400).json({ error: "Invalid level ID" });
129 }
130
131 const consumed = consumeOneTimeTokenForLevel(token, levelId);
132 if (!consumed) {
133 return res.status(401).json({ error: "Invalid token" });
134 }
135
136 return next();
137 };
138}