Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
sync API upstream (6da12a2)
  • Loading branch information
Sticks committed Mar 31, 2025
1 parent 99b8faa commit c444150
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 27 deletions.
4 changes: 2 additions & 2 deletions api/package.json
@@ -1,7 +1,7 @@
{
"name": "@imput/cobalt-api",
"description": "save what you love",
"version": "10.8.2",
"version": "10.8.4",
"author": "imput",
"exports": "./src/cobalt.js",
"type": "module",
Expand Down Expand Up @@ -38,7 +38,7 @@
"set-cookie-parser": "2.6.0",
"undici": "^5.19.1",
"url-pattern": "1.0.3",
"youtubei.js": "^13.2.0",
"youtubei.js": "^13.3.0",
"zod": "^3.23.8"
},
"optionalDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions api/src/config.js
Expand Up @@ -28,6 +28,9 @@ const env = {
rateLimitWindow: (process.env.RATELIMIT_WINDOW && parseInt(process.env.RATELIMIT_WINDOW)) || 60,
rateLimitMax: (process.env.RATELIMIT_MAX && parseInt(process.env.RATELIMIT_MAX)) || 20,

sessionRateLimitWindow: (process.env.SESSION_RATELIMIT_WINDOW && parseInt(process.env.SESSION_RATELIMIT_WINDOW)) || 60,
sessionRateLimit: (process.env.SESSION_RATELIMIT && parseInt(process.env.SESSION_RATELIMIT)) || 10,

durationLimit: (process.env.DURATION_LIMIT && parseInt(process.env.DURATION_LIMIT)) || 10800,
streamLifespan: (process.env.TUNNEL_LIFESPAN && parseInt(process.env.TUNNEL_LIFESPAN)) || 90,

Expand Down
12 changes: 6 additions & 6 deletions api/src/core/api.js
Expand Up @@ -74,8 +74,8 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
const keyGenerator = (req) => hashHmac(getIP(req), 'rate').toString('base64url');

const sessionLimiter = rateLimit({
windowMs: 60000,
limit: 10,
windowMs: env.sessionRateLimitWindow * 1000,
limit: env.sessionRateLimit,
standardHeaders: 'draft-6',
legacyHeaders: false,
keyGenerator,
Expand All @@ -91,7 +91,7 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
keyGenerator: req => req.rateLimitKey || keyGenerator(req),
store: await createStore('api'),
handler: handleRateExceeded
})
});

const apiTunnelLimiter = rateLimit({
windowMs: env.rateLimitWindow * 1000,
Expand All @@ -103,7 +103,7 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
handler: (_, res) => {
return res.sendStatus(429)
}
})
});

app.set('trust proxy', ['loopback', 'uniquelocal']);

Expand Down Expand Up @@ -175,7 +175,7 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
return fail(res, "error.api.auth.jwt.invalid");
}

if (!jwt.verify(token)) {
if (!jwt.verify(token, getIP(req, 32))) {
return fail(res, "error.api.auth.jwt.invalid");
}

Expand Down Expand Up @@ -221,7 +221,7 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
}

try {
res.json(jwt.generate());
res.json(jwt.generate(getIP(req, 32)));
} catch {
return fail(res, "error.api.generic");
}
Expand Down
9 changes: 8 additions & 1 deletion api/src/processing/helpers/youtube-session.js
@@ -1,8 +1,11 @@
import * as cluster from "../../misc/cluster.js";

import { Agent } from "undici";
import { env } from "../../config.js";
import { Green, Yellow } from "../../misc/console-text.js";

const defaultAgent = new Agent();

let session;

const validateSession = (sessionResponse) => {
Expand Down Expand Up @@ -32,7 +35,11 @@ const loadSession = async () => {
const sessionServerUrl = new URL(env.ytSessionServer);
sessionServerUrl.pathname = "/token";

const newSession = await fetch(sessionServerUrl).then(a => a.json());
const newSession = await fetch(
sessionServerUrl,
{ dispatcher: defaultAgent }
).then(a => a.json());

validateSession(newSession);

if (!session || session.updated < newSession?.updated) {
Expand Down
3 changes: 1 addition & 2 deletions api/src/processing/request.js
Expand Up @@ -82,14 +82,13 @@ export function normalizeRequest(request) {
));
}

export function getIP(req) {
export function getIP(req, prefix = 56) {
const strippedIP = req.ip.replace(/^::ffff:/, '');
const ip = ipaddr.parse(strippedIP);
if (ip.kind() === 'ipv4') {
return strippedIP;
}

const prefix = 56;
const v6Bytes = ip.toByteArray();
v6Bytes.fill(0, prefix / 8);

Expand Down
3 changes: 2 additions & 1 deletion api/src/processing/service-config.js
Expand Up @@ -136,12 +136,13 @@ export const services = {
tiktok: {
patterns: [
":user/video/:postId",
"i18n/share/video/:postId",
":shortLink",
"t/:shortLink",
":user/photo/:postId",
"v/:postId.html"
],
subdomains: ["vt", "vm", "m"],
subdomains: ["vt", "vm", "m", "t"],
},
tumblr: {
patterns: [
Expand Down
6 changes: 3 additions & 3 deletions api/src/processing/services/tiktok.js
@@ -1,6 +1,6 @@
import Cookie from "../cookie/cookie.js";

import { extract } from "../url.js";
import { extract, normalizeURL } from "../url.js";
import { genericUserAgent } from "../../config.js";
import { updateCookie } from "../cookie/manager.js";
import { createStream } from "../../stream/manage.js";
Expand All @@ -23,8 +23,8 @@ export default async function(obj) {

if (html.startsWith('<a href="https://')) {
const extractedURL = html.split('<a href="')[1].split('?')[0];
const { patternMatch } = extract(extractedURL);
postId = patternMatch.postId;
const { patternMatch } = extract(normalizeURL(extractedURL));
postId = patternMatch?.postId;
}
}
if (!postId) return { error: "fetch.short_link" };
Expand Down
31 changes: 19 additions & 12 deletions api/src/security/jwt.js
Expand Up @@ -6,12 +6,19 @@ 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");
const makeHmac = (data) => {
return createHmac("sha256", env.jwtSecret)
.update(data)
.digest("base64url");
}

const sign = (header, payload) =>
makeHmac(`${header}.${payload}`);

const getIPHash = (ip) =>
makeHmac(ip).slice(0, 8);

const generate = () => {
const generate = (ip) => {
const exp = Math.floor(new Date().getTime() / 1000) + env.jwtLifetime;

const header = toBase64URL(JSON.stringify({
Expand All @@ -21,36 +28,36 @@ const generate = () => {

const payload = toBase64URL(JSON.stringify({
jti: nanoid(8),
sub: getIPHash(ip),
exp,
}));

const signature = makeHmac(header, payload);
const signature = sign(header, payload);

return {
token: `${header}.${payload}.${signature}`,
exp: env.jwtLifetime - 2,
};
}

const verify = (jwt) => {
const verify = (jwt, ip) => {
const [header, payload, signature] = jwt.split(".", 3);
const timestamp = Math.floor(new Date().getTime() / 1000);

if ([header, payload, signature].join('.') !== jwt) {
return false;
}

const verifySignature = makeHmac(header, payload);
const verifySignature = sign(header, payload);

if (verifySignature !== signature) {
return false;
}

if (timestamp >= JSON.parse(fromBase64URL(payload)).exp) {
return false;
}
const data = JSON.parse(fromBase64URL(payload));

return true;
return getIPHash(ip) === data.sub
&& timestamp <= data.exp;
}

export default {
Expand Down

0 comments on commit c444150

Please sign in to comment.