mirror of
https://github.com/imputnet/cobalt.git
synced 2025-01-17 04:15:16 +00:00
api/security: jwt session token
This commit is contained in:
parent
33c2fee847
commit
16acf62886
|
@ -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 {
|
||||
|
|
|
@ -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
59
api/src/security/jwt.js
Normal 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,
|
||||
}
|
Loading…
Reference in a new issue