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
at main 138 lines 3.6 kB view raw
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}