Skip to content

Commit

Permalink
access-control: move rate limit to catalyst (#1982)
Browse files Browse the repository at this point in the history
* access-control: move rate limit to catalyst

* update

* fix tests
  • Loading branch information
gioelecerati authored Jan 29, 2024
1 parent 2405d59 commit 937f47d
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 55 deletions.
12 changes: 6 additions & 6 deletions packages/api/src/controllers/access-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe("controllers/signing-key", () => {
type: "jwt",
});

expect(res2.status).toBe(204);
expect(res2.status).toBe(200);
});

it("should not allow playback on not existing streams or public keys", async () => {
Expand Down Expand Up @@ -147,7 +147,7 @@ describe("controllers/signing-key", () => {
stream: `video+${publicPlaybackId}`,
type: "jwt",
});
expect(res.status).toBe(204);
expect(res.status).toBe(200);
client.jwtAuth = nonAdminToken;
const stream = await client.post("/stream", {
name: "test",
Expand All @@ -158,7 +158,7 @@ describe("controllers/signing-key", () => {
stream: `video+${streamPlaybackId}`,
type: "jwt",
});
expect(res2.status).toBe(204);
expect(res2.status).toBe(200);
});

it("should allow playback if playbackPolicy is not specified", async () => {
Expand All @@ -173,7 +173,7 @@ describe("controllers/signing-key", () => {
type: "jwt",
pub: signingKey.publicKey,
});
expect(res.status).toBe(204);
expect(res.status).toBe(200);
});

it("should allow playback on gated asset", async () => {
Expand All @@ -183,7 +183,7 @@ describe("controllers/signing-key", () => {
type: "jwt",
pub: signingKey.publicKey,
});
expect(res.status).toBe(204);
expect(res.status).toBe(200);
});

it("should not allow playback if user is suspended", async () => {
Expand All @@ -193,7 +193,7 @@ describe("controllers/signing-key", () => {
type: "jwt",
pub: signingKey.publicKey,
});
expect(res.status).toBe(204);
expect(res.status).toBe(200);
await db.user.update(gatedAsset.userId, { suspended: true });

const res2 = await client.post("/access-control/gate", {
Expand Down
60 changes: 14 additions & 46 deletions packages/api/src/controllers/access-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import { signatureHeaders, storeTriggerStatus } from "../webhooks/cannon";
import { Response } from "node-fetch";
import { fetchWithTimeoutAndRedirects } from "../util";
import fetch from "node-fetch";
import { WithID } from "../store/types";
import { DBStream } from "../store/stream-table";
import { HACKER_DISABLE_CUTOFF_DATE } from "./utils/notification";
import { isFreeTierUser } from "./helpers";
import { cache } from "../store/cache";

const WEBHOOK_TIMEOUT = 30 * 1000;
const MAX_ALLOWED_VIEWERS_FOR_FREE_TIER = 5;
const MAX_ALLOWED_VIEWERS_FOR_FREE_TIER = 30;
const app = Router();

interface HitRecord {
Expand Down Expand Up @@ -135,10 +133,13 @@ app.post(

const playbackPolicyType = content.playbackPolicy?.type ?? "public";

if (user.createdAt < HACKER_DISABLE_CUTOFF_DATE) {
let limitReached = freeTierLimitReached(content, user, req);
if (limitReached) {
throw new ForbiddenError("Free tier user reached viewership limit");
let response = {};

if (user.createdAt > HACKER_DISABLE_CUTOFF_DATE) {
if (isFreeTierUser(user)) {
response = {
rateLimit: MAX_ALLOWED_VIEWERS_FOR_FREE_TIER,
};
}
}

Expand All @@ -154,8 +155,8 @@ app.post(

switch (playbackPolicyType) {
case "public":
res.status(204);
return res.end();
res.status(200);
return res.json(response);
case "jwt":
if (!req.body.pub) {
console.log(`
Expand Down Expand Up @@ -213,8 +214,8 @@ app.post(
}

tracking.recordSigningKeyValidation(signingKey.id);
res.status(204);
return res.end();
res.status(200);
return res.json(response);
case "webhook":
if (!req.body.accessKey || req.body.type !== "accessKey") {
throw new ForbiddenError(
Expand All @@ -238,8 +239,8 @@ app.post(
req.body.accessKey
);
if (statusCode >= 200 && statusCode < 300) {
res.status(204);
return res.end();
res.status(200);
return res.json(response);
} else if (statusCode === 0) {
console.log(`
access-control: gate: content with playbackId=${playbackId} is gated but webhook=${webhook.id} failed, disallowing playback
Expand Down Expand Up @@ -293,37 +294,4 @@ app.get("/public-key", async (req, res) => {
});
});

function freeTierLimitReached(
content: DBStream | WithID<Asset>,
user: User,
req: Request
): boolean {
if (!isFreeTierUser(user)) {
return false;
}

// Register a hit for the given playbackId
const now = Date.now();
const playbackId = content.playbackId;

if (!playbackHits[playbackId]) {
playbackHits[playbackId] = [];
}

// Remove hits that are older than three minutes
playbackHits[playbackId] = playbackHits[playbackId].filter(
(hit) => now - hit.timestamp < 60 * 3 * 1000
);

// Add a new hit record
playbackHits[playbackId].push({ timestamp: now });

// Check if the number of hits in the last minute exceeds the threshold
if (playbackHits[playbackId].length > MAX_ALLOWED_VIEWERS_FOR_FREE_TIER) {
return true;
} else {
return false;
}
}

export default app;
6 changes: 3 additions & 3 deletions packages/api/src/controllers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,10 @@ export function pathJoin(...items: string[]) {
}

export function isFreeTierUser(user: User) {
return (
const isFreeTier =
user.stripeProductId === "hacker_1" ||
user.stripeProductId === "prod_O9XuIjn7EqYRVW"
);
user.stripeProductId === "prod_O9XuIjn7EqYRVW";
return isFreeTier;
}

export function trimPathPrefix(prefix: string, path: string) {
Expand Down

0 comments on commit 937f47d

Please sign in to comment.