From c0ac3b767e8c5c1dd6921b6efc1e3b6825535ecf Mon Sep 17 00:00:00 2001 From: TheRealToxicDev Date: Wed, 24 Jun 2026 00:18:42 -0600 Subject: [PATCH] feat(add): updated team management --- .../get-started/create-aws-credentials.mdx | 38 ------------------- apps/web/src/app/login/page.tsx | 15 +++++++- apps/web/src/server/api/routers/admin.ts | 4 ++ apps/web/src/server/public-api/auth.ts | 8 ++++ apps/web/src/server/service/limit-service.ts | 22 +++++------ 5 files changed, 36 insertions(+), 51 deletions(-) delete mode 100644 apps/docs/get-started/create-aws-credentials.mdx diff --git a/apps/docs/get-started/create-aws-credentials.mdx b/apps/docs/get-started/create-aws-credentials.mdx deleted file mode 100644 index 40d3864..0000000 --- a/apps/docs/get-started/create-aws-credentials.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Create AWS credentials -description: Step by step guide to create AWS credentials to self-host ByteSend. ---- - - - - Login to your AWS console and go to IAM > Users > Create user. Type in user name, in this case `bytesend` - - ![create user](/images/aws/key-1.png) - - - - Search for `AmazonSNSFullAccess` and `AmazonSESFullAccess` and check the checkboxes. Then proceed to create the user. - - ![set permission](/images/aws/key-2.png) - - - - Click on the created user and click on the `Create access key` button. - - ![create access key](/images/aws/key-3.png) - ![option and create](/images/aws/key-4.png) - ![description](/images/aws/key-5.png) - - - - Copy the access key ID and secret access key to your `.env` file. - - ```env - AWS_ACCESS_KEY= - AWS_SECRET_KEY= - ``` - - ![create access key](/images/aws/key-6.png) - - - diff --git a/apps/web/src/app/login/page.tsx b/apps/web/src/app/login/page.tsx index 65da307..142bf5f 100644 --- a/apps/web/src/app/login/page.tsx +++ b/apps/web/src/app/login/page.tsx @@ -3,13 +3,24 @@ import { getServerAuthSession } from "~/server/auth"; import LoginPage from "./login-page"; import { getProviders } from "next-auth/react"; -export default async function Login() { - const session = await getServerAuthSession(); +export default async function Login({ + searchParams, +}: { + searchParams: Promise<{ error?: string }>; +}) { + const [session, params] = await Promise.all([ + getServerAuthSession(), + searchParams, + ]); if (session) { redirect("/dashboard"); } + if (params.error === "banned") { + redirect("/banned"); + } + const providers = await getProviders(); return ; diff --git a/apps/web/src/server/api/routers/admin.ts b/apps/web/src/server/api/routers/admin.ts index 5e85fe0..3a5e004 100644 --- a/apps/web/src/server/api/routers/admin.ts +++ b/apps/web/src/server/api/routers/admin.ts @@ -14,6 +14,7 @@ import { isCloud } from "~/utils/common"; import { toPlainHtml } from "~/server/utils/email-content"; import { Target } from "lucide-react"; import { createCheckoutSessionForTeam, type CheckoutPlan } from "~/server/billing/payments"; +import { TeamService } from "~/server/service/team-service"; const userAdminSelection = { id: true, @@ -459,6 +460,8 @@ export const adminRouter = createTRPCRouter({ select: teamAdminSelection, }); + await TeamService.invalidateTeamCache(teamId); + return updatedTeam; }), @@ -499,6 +502,7 @@ export const adminRouter = createTRPCRouter({ }, select: teamAdminSelection, }); + await TeamService.invalidateTeamCache(teamId); logger.info( { teamId, plan, method: "complimentary" }, "[AdminRouter]: Plan assigned complimentarily", diff --git a/apps/web/src/server/public-api/auth.ts b/apps/web/src/server/public-api/auth.ts index ed5cf70..26f34d6 100644 --- a/apps/web/src/server/public-api/auth.ts +++ b/apps/web/src/server/public-api/auth.ts @@ -45,6 +45,14 @@ export const getTeamFromToken = async (c: Context) => { }); } + // Block API access if the team is blocked + if (team.isBlocked) { + throw new ByteSendApiError({ + code: "FORBIDDEN", + message: "This team has been blocked. Please contact support via Discord: https://discord.com/invite/BU8n8pJv8S", + }); + } + // Block API access if any admin on the team is banned const bannedAdmin = await db.teamUser.findFirst({ where: { diff --git a/apps/web/src/server/service/limit-service.ts b/apps/web/src/server/service/limit-service.ts index fa179e8..5b73dbd 100644 --- a/apps/web/src/server/service/limit-service.ts +++ b/apps/web/src/server/service/limit-service.ts @@ -292,19 +292,9 @@ export class LimitService { reason?: LimitReason; available?: number; }> { - // Limits only apply in cloud mode - if (!env.NEXT_PUBLIC_IS_CLOUD) { - return { isLimitReached: false, limit: -1 }; - } - - // Admin/founder teams have no limits - if (await LimitService.isAdminOrFounderTeam(teamId)) { - return { isLimitReached: false, limit: -1 }; - } - + // Block flag enforced in all deployment modes const team = await TeamService.getTeamCached(teamId); - // In cloud, enforce verification and block flags first if (team.isBlocked) { return { isLimitReached: true, @@ -313,6 +303,16 @@ export class LimitService { }; } + // Remaining limits only apply in cloud mode + if (!env.NEXT_PUBLIC_IS_CLOUD) { + return { isLimitReached: false, limit: -1 }; + } + + // Admin/founder teams have no limits + if (await LimitService.isAdminOrFounderTeam(teamId)) { + return { isLimitReached: false, limit: -1 }; + } + // Bounce / complaint rate enforcement (7-day rolling window) const recentStats = await LimitService.getRecentBounceStats(teamId); if (recentStats.delivered >= BOUNCE_RATE_MIN_VOLUME) {