api/security: jwt session token

This commit is contained in:
wukko 2024-08-16 23:28:03 +06:00
parent 33c2fee847
commit 16acf62886
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
4 changed files with 110 additions and 19 deletions

View file

@ -34,6 +34,8 @@ const env = {
externalProxy: process.env.API_EXTERNAL_PROXY,
turnstileSecret: process.env.TURNSTILE_SECRET,
jwtSecret: process.env.JWT_SECRET,
jwtLifetime: process.env.JWT_EXPIRY || 120,
}
export {

View file

@ -13,10 +13,11 @@ import { languageCode } from "../misc/utils.js";
import { createResponse, normalizeRequest, getIP } from "../processing/request.js";
import { verifyStream, getInternalStream } from "../stream/manage.js";
import { randomizeCiphers } from "../misc/randomize-ciphers.js";
import { verifyTurnstileToken } from "../misc/turnstile.js";
import { verifyTurnstileToken } from "../security/turnstile.js";
import { extract } from "../processing/url.js";
import match from "../processing/match.js";
import stream from "../stream/stream.js";
import jwt from "../security/jwt.js";
const git = {
branch: await getBranch(),
@ -100,7 +101,7 @@ export function runAPI(express, app, __dirname) {
})
app.use('/', express.json({ limit: 1024 }));
app.use('/', (err, _, res, next) => {
app.use('/post', (err, _, res, next) => {
if (err) {
const { status, body } = createResponse("error", {
code: "error.body_invalid",
@ -114,6 +115,33 @@ export function runAPI(express, app, __dirname) {
next();
});
app.post("/session", async (req, res) => {
if (!env.turnstileSecret || !env.jwtSecret) {
return fail("error.api.auth.not_configured")
}
const turnstileResponse = req.header("cf-turnstile-response");
if (!turnstileResponse) {
return fail("error.api.auth.turnstile.missing");
}
const turnstileResult = await verifyTurnstileToken(
turnstileResponse,
req.ip
);
if (!turnstileResult) {
return fail("error.api.auth.turnstile.invalid");
}
try {
res.json(jwt.generate());
} catch {
return fail("error.api.generic");
}
});
app.post('/', async (req, res) => {
const request = req.body;
const lang = languageCode(req);
@ -123,6 +151,25 @@ export function runAPI(express, app, __dirname) {
res.status(status).json(body);
}
if (env.jwtSecret) {
const authorization = req.header("Authorization");
if (!authorization) {
return fail("error.api.auth.jwt.missing");
}
if (!authorization.startsWith("Bearer ")) {
return fail("error.api.auth.jwt.invalid");
}
const verifyJwt = jwt.verify(
req.header("Authorization").split("Bearer ", 2)[1]
);
if (!verifyJwt) {
return fail("error.api.auth.jwt.invalid");
}
}
if (!acceptRegex.test(req.header('Accept'))) {
return fail('ErrorInvalidAcceptHeader');
}
@ -135,23 +182,6 @@ export function runAPI(express, app, __dirname) {
return fail('ErrorNoLink');
}
if (env.turnstileSecret) {
const turnstileResponse = req.header("cf-turnstile-response");
if (!turnstileResponse) {
return fail("error.api.authentication");
}
const turnstileResult = await verifyTurnstileToken(
turnstileResponse,
req.ip
);
if (!turnstileResult) {
return fail("error.api.authentication");
}
}
if (request.youtubeDubBrowserLang) {
request.youtubeDubLang = lang;
}

59
api/src/security/jwt.js Normal file
View file

@ -0,0 +1,59 @@
import { nanoid } from "nanoid";
import { createHmac } from "crypto";
import { env } from "../config.js";
const toBase64URL = (b) => Buffer.from(b).toString("base64url");
const fromBase64URL = (b) => Buffer.from(b, "base64url").toString();
const makeHmac = (header, payload) =>
createHmac("sha256", env.jwtSecret)
.update(`${header}.${payload}`)
.digest("base64url");
export const generate = () => {
const exp = new Date().getTime() + env.jwtLifetime * 1000;
const header = toBase64URL(JSON.stringify({
alg: "HS256",
typ: "JWT"
}));
const payload = toBase64URL(JSON.stringify({
jti: nanoid(3),
exp,
}));
const signature = makeHmac(header, payload);
return {
token: `${header}.${payload}.${signature}`,
exp,
};
}
export const verify = (jwt) => {
const [header, payload, signature] = jwt.split(".", 3);
const timestamp = new Date().getTime();
if ([header, payload, signature].join('.') !== jwt) {
return false;
}
const verifySignature = makeHmac(header, payload);
if (verifySignature !== signature) {
return false;
}
if (timestamp >= JSON.parse(fromBase64URL(payload)).exp) {
return false;
}
return true;
}
export default {
generate,
verify,
}