diff --git a/index.js b/index.js index dd4d492..15b9e52 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,8 @@ import * as fs from 'fs' import { execSync } from 'child_process' import postgres from 'postgres' import express, { json } from 'express' +import cookieParser from 'cookie-parser' + const app = express() if (!process.env.DATABASE_URI) { @@ -24,6 +26,7 @@ export { app, fs, db, globalConfig, globalVersion } app.use(json()) // Allows receiving JSON bodies // see important note: https://expressjs.com/en/api.html#express.json +app.use(cookieParser()) // Allows receiving cookies process.on('SIGTERM', function() { console.log("Received SIGTERM, exiting...") diff --git a/package-lock.json b/package-lock.json index 6374e23..9e3a581 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "UNLICENSED", "dependencies": { + "cookie-parser": "^1.4.7", "express": "^4.18.2", "marked": "^14.1.3", "nodemailer": "^6.9.15", @@ -158,6 +159,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/package.json b/package.json index ee5577d..2daec5a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "cookie-parser": "^1.4.7", "express": "^4.18.2", "marked": "^14.1.3", "nodemailer": "^6.9.15", diff --git a/routes/auth.js b/routes/auth.js index cec9dc7..55b81a2 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -4,11 +4,37 @@ import { logRequest } from "../liberals/logging.js" import { randomStringBase62, getHumanReadableUserAgent } from "../liberals/misc.js" app.get("/api/auth/whoami", (rreq,rres) => { - rres.send("Non functional endpoint") + if (!rreq.cookies["APIToken"] && !rreq.get("Authorization")) { + rres.send({ "loggedIn": false, "username": "", "scopes": "" }) + } else { + db`select s.scopes, u.username from sessions s join users u on s.owner = u.id where s.token = ${rreq.cookies["APIToken"] ?? rreq.get("Authorization")}`.then(dbRes => { + if (dbRes.length > 0 && dbRes.length < 2) { + rres.send({ "loggedIn": true, "username": dbRes[0]?.username, "scopes": dbRes[0]?.scopes.split(",") }) + } else { + rres.send({ "loggedIn": false, "username": "", "scopes": "" }) + } + }).catch(dbErr => { + logRequest(rres,rreq,500,dbErr) + rres.status(500).send({ "loggedIn": false, "username": "", "scopes": "" }) + }) + } }) app.get("/api/auth/login", (rreq,rres) => { - rres.redirect(`${globalConfig.oidc.authorizeUrl}?client_id=${globalConfig.oidc.clientId}&response_type=code&scope=openid enstrayedapi&redirect_uri=${rreq.protocol}://${rreq.get("Host")}/api/auth/callback`) + + if (rreq.query.state === "redirect") { + if (!rreq.query.destination) { + rres.redirect(`${globalConfig.oidc.authorizeUrl}?client_id=${globalConfig.oidc.clientId}&response_type=code&scope=openid enstrayedapi&redirect_uri=${rreq.protocol}://${rreq.get("Host")}/api/auth/callback&state=none`) + } else { + let newState = `redirect_${btoa(rreq.query.destination).replace("/","-")}` + rres.redirect(`${globalConfig.oidc.authorizeUrl}?client_id=${globalConfig.oidc.clientId}&response_type=code&scope=openid enstrayedapi&redirect_uri=${rreq.protocol}://${rreq.get("Host")}/api/auth/callback&state=${newState}`) + } + } else if (rreq.query.state === "display" || rreq.query.state === "close") { + rres.redirect(`${globalConfig.oidc.authorizeUrl}?client_id=${globalConfig.oidc.clientId}&response_type=code&scope=openid enstrayedapi&redirect_uri=${rreq.protocol}://${rreq.get("Host")}/api/auth/callback&state=${rreq.query.state}`) + } else { + rres.redirect(`${globalConfig.oidc.authorizeUrl}?client_id=${globalConfig.oidc.clientId}&response_type=code&scope=openid enstrayedapi&redirect_uri=${rreq.protocol}://${rreq.get("Host")}/api/auth/callback&state=none`) + } + }) app.get("/api/auth/callback", (rreq,rres) => { @@ -31,11 +57,23 @@ app.get("/api/auth/callback", (rreq,rres) => { let newToken = randomStringBase62(64) let newExpiration = Date.now() + 86400 let newComment = `Login token for ${getHumanReadableUserAgent(rreq.get("User-Agent"))} on ${rreq.get("cf-connecting-ip") ?? rreq.ip}` - db`select * from users where oidc_username = ${fetchRes2.username};`.then(dbRes1 => { - db`insert into sessions (token,owner,scopes,expires,comment) values (${newToken},${dbRes1[0]?.id},${fetchRes2.enstrayedapi_scopes},${newExpiration},${newComment})`.then(dbRes2 => { - rres.send(dbRes2) - }) + + db`insert into sessions (token,owner,scopes,expires,comment) values (${newToken},(select id from users where oidc_username = ${fetchRes2.username}),${fetchRes2.enstrayedapi_scopes},${newExpiration},${newComment});`.then(dbRes1 => { + if (rreq.query.state.split("_")[0] === "redirect") { + let newDestination = atob(rreq.query.state.split("_")[1].replace("-","/")) + rres.setHeader("Set-Cookie", `APIToken=${newToken}; Domain=${rreq.hostname}; Expires=${new Date(newExpiration).toUTCString()}; Path=/`).redirect(newDestination) + } else if (rreq.query.state === "display") { + rres.setHeader("Set-Cookie", `APIToken=${newToken}; Domain=${rreq.hostname}; Expires=${new Date(newExpiration).toUTCString()}; Path=/`).send(`Success! Your token is ${newToken}`) + } else if (rreq.query.state === "close") { + rres.setHeader("Set-Cookie", `APIToken=${newToken}; Domain=${rreq.hostname}; Expires=${new Date(newExpiration).toUTCString()}; Path=/`).send(` Success! You may now close this window.`) + } else { + rres.setHeader("Set-Cookie", `APIToken=${newToken}; Domain=${rreq.hostname}; Expires=${new Date(newExpiration).toUTCString()}; Path=/`).send(`Success! No state was provided, so you can close this window.`) + } + }).catch(dbErr => { + localError500(`Callback-Write-${dbErr}`) }) + + }) } }).catch(fetchErr2 => { // Fetch to userinfo endpoint failed for some other reason