diff --git a/src/server/lib/auth.ts b/src/server/lib/auth.ts index 4495f2f..899c215 100644 --- a/src/server/lib/auth.ts +++ b/src/server/lib/auth.ts @@ -1,15 +1,18 @@ import crypto from "crypto" +import express from "express" import { readFile, writeFile } from "fs/promises" export let AuthTokens: AuthToken[] = [] export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {} export const ValidTokenPermissions = [ - "user", // permissions to /auth/me, with email docked - "email", // adds email back to /auth/me - "upload", // allows an app to upload under an account - "manage", // allows an app to manage an account's files - "admin" // only available for accounts with admin - // gives an app access to all admin tools + "user", // permissions to /auth/me, with email docked + "email", // adds email back to /auth/me + "private", // allows app to read private files + "upload", // allows an app to upload under an account + "manage", // allows an app to manage an account's files + "customize", // allows an app to change customization settings + "admin" // only available for accounts with admin + // gives an app access to all admin tools ] as const export type TokenType = "User" | "App" @@ -48,6 +51,14 @@ export function create( return token.token } +export function tokenFor(req: express.Request) { + return req.cookies.auth || ( + req.header("authorization")?.startsWith("Bearer ") + ? req.header("authorization")?.split(" ")[1] + : undefined + ) +} + export function validate(token:string) { return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account } diff --git a/src/server/lib/middleware.ts b/src/server/lib/middleware.ts index 4f365ca..bcb2bc6 100644 --- a/src/server/lib/middleware.ts +++ b/src/server/lib/middleware.ts @@ -3,16 +3,8 @@ import express, { type RequestHandler } from "express" import ServeError from "../lib/errors"; import * as auth from "./auth"; -function tokenFor(req: express.Request) { - return req.cookies.auth || ( - req.header("authorization")?.startsWith("Bearer ") - ? req.header("authorization")?.split(" ")[1] - : undefined - ) -} - export const getAccount: RequestHandler = function(req, res, next) { - res.locals.acc = Accounts.getFromToken(tokenFor(req)) + res.locals.acc = Accounts.getFromToken(auth.tokenFor(req)) next() } @@ -40,7 +32,7 @@ export const requiresAdmin: RequestHandler = function(_req, res, next) { export const requiresPermissions = function(...tokenPermissions: auth.TokenPermission[]): RequestHandler { return function(req, res, next) { - let token = tokenFor(req) + let token = auth.tokenFor(req) let type = auth.getType(token) if (type == "App") { @@ -67,6 +59,6 @@ export const requiresPermissions = function(...tokenPermissions: auth.TokenPermi */ export const noAPIAccess: RequestHandler = function(req, res, next) { - if (auth.getType(tokenFor(req)) == "App") ServeError(res, 403, "apps are not allowed to access this endpoint") + if (auth.getType(auth.tokenFor(req)) == "App") ServeError(res, 403, "apps are not allowed to access this endpoint") else next() } \ No newline at end of file diff --git a/src/server/routes/authRoutes.ts b/src/server/routes/authRoutes.ts index 72b5863..0c5bb19 100644 --- a/src/server/routes/authRoutes.ts +++ b/src/server/routes/authRoutes.ts @@ -3,7 +3,7 @@ import { Router } from "express"; import * as Accounts from "../lib/accounts"; import * as auth from "../lib/auth"; import { sendMail } from "../lib/mail"; -import { getAccount, requiresAccount } from "../lib/middleware" +import { getAccount, noAPIAccess, requiresAccount, requiresPermissions } from "../lib/middleware" import { accountRatelimit } from "../lib/ratelimit" import ServeError from "../lib/errors"; @@ -123,7 +123,7 @@ authRoutes.post("/logout", (req,res) => { res.send("logged out") }) -authRoutes.post("/dfv", requiresAccount, parser, (req,res) => { +authRoutes.post("/dfv", requiresAccount, requiresPermissions("manage"), parser, (req,res) => { let acc = res.locals.acc as Accounts.Account if (['public','private','anonymous'].includes(req.body.defaultFileVisibility)) { @@ -136,7 +136,7 @@ authRoutes.post("/dfv", requiresAccount, parser, (req,res) => { } }) -authRoutes.post("/customcss", requiresAccount, parser, (req,res) => { +authRoutes.post("/customcss", requiresAccount, requiresPermissions("customize"), parser, (req,res) => { let acc = res.locals.acc as Accounts.Account if (typeof req.body.fileId != "string") req.body.fileId = undefined; @@ -158,7 +158,7 @@ authRoutes.post("/customcss", requiresAccount, parser, (req,res) => { } }) -authRoutes.post("/embedcolor", requiresAccount, parser, (req,res) => { +authRoutes.post("/embedcolor", requiresAccount, requiresPermissions("customize"), parser, (req,res) => { let acc = res.locals.acc as Accounts.Account if (typeof req.body.color != "string") req.body.color = undefined; @@ -181,7 +181,7 @@ authRoutes.post("/embedcolor", requiresAccount, parser, (req,res) => { } }) -authRoutes.post("/embedsize", requiresAccount, parser, (req,res) => { +authRoutes.post("/embedsize", requiresAccount, requiresPermissions("customize"), parser, (req,res) => { let acc = res.locals.acc as Accounts.Account if (typeof req.body.largeImage != "boolean") req.body.color = false; @@ -193,7 +193,7 @@ authRoutes.post("/embedsize", requiresAccount, parser, (req,res) => { res.send(`custom embed image size saved`) }) -authRoutes.post("/delete_account", requiresAccount, parser, async (req,res) => { +authRoutes.post("/delete_account", requiresAccount, noAPIAccess, parser, async (req,res) => { let acc = res.locals.acc as Accounts.Account let accId = acc.id @@ -217,7 +217,7 @@ authRoutes.post("/delete_account", requiresAccount, parser, async (req,res) => { } else cpl() }) -authRoutes.post("/change_username", requiresAccount, parser, (req,res) => { +authRoutes.post("/change_username", requiresAccount, noAPIAccess, parser, (req,res) => { let acc = res.locals.acc as Accounts.Account if (typeof req.body.username != "string" || req.body.username.length < 3 || req.body.username.length > 20) { @@ -253,7 +253,7 @@ authRoutes.post("/change_username", requiresAccount, parser, (req,res) => { let verificationCodes = new Map() -authRoutes.post("/request_email_change", requiresAccount, accountRatelimit({ requests: 4, per: 60*60*1000 }), parser, (req,res) => { +authRoutes.post("/request_email_change", requiresAccount, noAPIAccess, accountRatelimit({ requests: 4, per: 60*60*1000 }), parser, (req,res) => { let acc = res.locals.acc as Accounts.Account @@ -292,7 +292,7 @@ authRoutes.post("/request_email_change", requiresAccount, accountRatelimit({ req }) }) -authRoutes.get("/confirm_email/:code", requiresAccount, (req,res) => { +authRoutes.get("/confirm_email/:code", requiresAccount, noAPIAccess, (req,res) => { let acc = res.locals.acc as Accounts.Account @@ -314,7 +314,7 @@ authRoutes.get("/confirm_email/:code", requiresAccount, (req,res) => { } }) -authRoutes.post("/remove_email", requiresAccount, (req,res) => { +authRoutes.post("/remove_email", requiresAccount, noAPIAccess, (req,res) => { let acc = res.locals.acc as Accounts.Account if (acc.email) { @@ -404,7 +404,7 @@ authRoutes.get("/emergency_login/:code", (req,res) => { } }) -authRoutes.post("/change_password", requiresAccount, parser, (req,res) => { +authRoutes.post("/change_password", requiresAccount, noAPIAccess, parser, (req,res) => { let acc = res.locals.acc as Accounts.Account if (typeof req.body.password != "string" || req.body.password.length < 8) { @@ -429,7 +429,7 @@ authRoutes.post("/change_password", requiresAccount, parser, (req,res) => { res.send("password changed - logged out all sessions") }) -authRoutes.post("/logout_sessions", requiresAccount, (req,res) => { +authRoutes.post("/logout_sessions", requiresAccount, noAPIAccess, (req,res) => { let acc = res.locals.acc as Accounts.Account let accId = acc.id @@ -441,15 +441,20 @@ authRoutes.post("/logout_sessions", requiresAccount, (req,res) => { res.send("logged out all sessions") }) -authRoutes.get("/me", requiresAccount, (req,res) => { +authRoutes.get("/me", requiresAccount, requiresPermissions("user"), (req,res) => { let acc = res.locals.acc as Accounts.Account - + + let sessionToken = auth.tokenFor(req) let accId = acc.id res.send({ ...acc, - sessionCount: auth.AuthTokens.filter(e => e.account == accId && e.expire > Date.now()).length, - sessionExpires: auth.AuthTokens.find(e => e.token == req.cookies.auth)?.expire, - password: undefined + sessionCount: auth.AuthTokens.filter(e => e.type != "App" && e.account == accId && e.expire > Date.now()).length, + sessionExpires: auth.AuthTokens.find(e => e.token == sessionToken)?.expire, + password: undefined, + email: + auth.getType(sessionToken) == "User" || auth.getPermissions(sessionToken)?.includes("email") + ? acc.email + : undefined }) })