Skip to content

Commit 69d52db

Browse files
authored
fix(webapp): org scoping issues in plan selection, alerts, pats and usage (#2549)
* fix: org scoping in the select plan flow Adds proper org scoping in the loader and action in the plans page. * Fix billing alerts scope * Fix org usage page scope * Fix token revoking flow scope check * Throw error for failed PAT revokes instead of silent failure
1 parent a3cea13 commit 69d52db

File tree

6 files changed

+32
-27
lines changed

6 files changed

+32
-27
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-alerts/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ export const meta: MetaFunction = () => {
4747
};
4848

4949
export async function loader({ params, request }: LoaderFunctionArgs) {
50-
await requireUserId(request);
50+
const userId = await requireUserId(request);
5151
const { organizationSlug } = OrganizationParamsSchema.parse(params);
5252

5353
const { isManagedCloud } = featuresForRequest(request);
5454
if (!isManagedCloud) {
5555
return redirect(organizationPath({ slug: organizationSlug }));
5656
}
5757

58-
const organization = await prisma.organization.findUnique({
59-
where: { slug: organizationSlug },
58+
const organization = await prisma.organization.findFirst({
59+
where: { slug: organizationSlug, members: { some: { userId } } },
6060
});
6161

6262
if (!organization) {

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const meta: MetaFunction = () => {
2828
};
2929

3030
export async function loader({ params, request }: LoaderFunctionArgs) {
31-
await requireUserId(request);
31+
const userId = await requireUserId(request);
3232
const { organizationSlug } = OrganizationParamsSchema.parse(params);
3333

3434
const { isManagedCloud } = featuresForRequest(request);
@@ -41,8 +41,8 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
4141
throw new Response(null, { status: 404, statusText: "Plans not found" });
4242
}
4343

44-
const organization = await prisma.organization.findUnique({
45-
where: { slug: organizationSlug },
44+
const organization = await prisma.organization.findFirst({
45+
where: { slug: organizationSlug, members: { some: { userId } } },
4646
});
4747

4848
if (!organization) {

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ export const meta: MetaFunction = () => {
4646
};
4747

4848
export async function loader({ params, request }: LoaderFunctionArgs) {
49-
await requireUserId(request);
49+
const userId = await requireUserId(request);
5050
const { organizationSlug } = OrganizationParamsSchema.parse(params);
5151

5252
const { isManagedCloud } = featuresForRequest(request);
5353
if (!isManagedCloud) {
5454
return redirect(organizationPath({ slug: organizationSlug }));
5555
}
5656

57-
const organization = await prisma.organization.findUnique({
58-
where: { slug: organizationSlug },
57+
const organization = await prisma.organization.findFirst({
58+
where: { slug: organizationSlug, members: { some: { userId } } },
5959
});
6060

6161
if (!organization) {

apps/webapp/app/routes/account.tokens/route.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { parse } from "@conform-to/zod";
33
import { BookOpenIcon, ShieldCheckIcon, TrashIcon } from "@heroicons/react/20/solid";
44
import { ShieldExclamationIcon } from "@heroicons/react/24/solid";
55
import { DialogClose } from "@radix-ui/react-dialog";
6-
import { Form, MetaFunction, useActionData, useFetcher } from "@remix-run/react";
7-
import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
6+
import { Form, type MetaFunction, useActionData, useFetcher } from "@remix-run/react";
7+
import { type ActionFunction, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
88
import { typedjson, useTypedLoaderData } from "remix-typedjson";
99
import { z } from "zod";
1010
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
@@ -16,7 +16,6 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components
1616
import { Fieldset } from "~/components/primitives/Fieldset";
1717
import { FormButtons } from "~/components/primitives/FormButtons";
1818
import { FormError } from "~/components/primitives/FormError";
19-
import { Header2 } from "~/components/primitives/Headers";
2019
import { Hint } from "~/components/primitives/Hint";
2120
import { Input } from "~/components/primitives/Input";
2221
import { InputGroup } from "~/components/primitives/InputGroup";
@@ -36,8 +35,8 @@ import {
3635
import { SimpleTooltip } from "~/components/primitives/Tooltip";
3736
import { redirectWithSuccessMessage } from "~/models/message.server";
3837
import {
39-
CreatedPersonalAccessToken,
40-
ObfuscatedPersonalAccessToken,
38+
type CreatedPersonalAccessToken,
39+
type ObfuscatedPersonalAccessToken,
4140
createPersonalAccessToken,
4241
getValidPersonalAccessTokens,
4342
revokePersonalAccessToken,
@@ -53,7 +52,7 @@ export const meta: MetaFunction = () => {
5352
];
5453
};
5554

56-
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
55+
export const loader = async ({ request }: LoaderFunctionArgs) => {
5756
const userId = await requireUserId(request);
5857

5958
try {
@@ -113,7 +112,7 @@ export const action: ActionFunction = async ({ request }) => {
113112
}
114113
case "revoke": {
115114
try {
116-
await revokePersonalAccessToken(submission.value.tokenId);
115+
await revokePersonalAccessToken(submission.value.tokenId, userId);
117116

118117
return redirectWithSuccessMessage(
119118
personalAccessTokensPath(),
@@ -125,6 +124,7 @@ export const action: ActionFunction = async ({ request }) => {
125124
}
126125
}
127126
default: {
127+
submission.value satisfies never;
128128
return json({ errors: { body: "Invalid action" } }, { status: 400 });
129129
}
130130
}

apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ import {
77
} from "@heroicons/react/20/solid";
88
import { ArrowDownCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/24/outline";
99
import { Form, useLocation, useNavigation } from "@remix-run/react";
10-
import { ActionFunctionArgs } from "@remix-run/server-runtime";
10+
import { type ActionFunctionArgs } from "@remix-run/server-runtime";
1111
import { uiComponent } from "@team-plain/typescript-sdk";
1212
import { GitHubLightIcon } from "@trigger.dev/companyicons";
1313
import {
14-
FreePlanDefinition,
15-
Limits,
16-
PaidPlanDefinition,
17-
Plans,
18-
SetPlanBody,
19-
SubscriptionResult,
14+
type FreePlanDefinition,
15+
type Limits,
16+
type PaidPlanDefinition,
17+
type Plans,
18+
type SetPlanBody,
19+
type SubscriptionResult,
2020
} from "@trigger.dev/platform";
2121
import React, { useEffect, useState } from "react";
2222
import { z } from "zod";
@@ -75,8 +75,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
7575
message: message || undefined,
7676
});
7777

78-
const organization = await prisma.organization.findUnique({
79-
where: { slug: organizationSlug },
78+
const organization = await prisma.organization.findFirst({
79+
where: { slug: organizationSlug, members: { some: { userId: user.id } } },
8080
});
8181

8282
if (!organization) {

apps/webapp/app/services/personalAccessToken.server.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,20 @@ export async function getPersonalAccessTokenFromAuthorizationCode(authorizationC
7979
};
8080
}
8181

82-
export async function revokePersonalAccessToken(tokenId: string) {
83-
await prisma.personalAccessToken.update({
82+
export async function revokePersonalAccessToken(tokenId: string, userId: string) {
83+
const result = await prisma.personalAccessToken.updateMany({
8484
where: {
8585
id: tokenId,
86+
userId,
8687
},
8788
data: {
8889
revokedAt: new Date(),
8990
},
9091
});
92+
93+
if (result.count === 0) {
94+
throw new Error("PAT not found or already revoked");
95+
}
9196
}
9297

9398
export type PersonalAccessTokenAuthenticationResult = {

0 commit comments

Comments
 (0)