Skip to content

Commit 7815a2f

Browse files
committed
Accept OATs in the whoami v2 endpoint
1 parent 2243830 commit 7815a2f

File tree

1 file changed

+180
-77
lines changed

1 file changed

+180
-77
lines changed
Lines changed: 180 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,200 @@
1-
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2-
import { json } from "@remix-run/server-runtime";
3-
import { WhoAmIResponse } from "@trigger.dev/core/v3";
1+
import { json, type LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { type WhoAmIResponse } from "@trigger.dev/core/v3";
43
import { prisma } from "~/db.server";
54
import { env } from "~/env.server";
6-
import { logger } from "~/services/logger.server";
7-
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
85
import { v3ProjectPath } from "~/utils/pathBuilder";
6+
import { authenticateRequest } from "~/services/apiAuth.server";
97

108
export async function loader({ request }: LoaderFunctionArgs) {
11-
logger.info("whoami v2", { url: request.url });
12-
try {
13-
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
14-
if (!authenticationResult) {
9+
const authenticationResult = await authenticateRequest(request, {
10+
personalAccessToken: true,
11+
organizationAccessToken: true,
12+
apiKey: false,
13+
});
14+
15+
if (!authenticationResult) {
16+
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
17+
}
18+
19+
const url = new URL(request.url);
20+
const projectRef = url.searchParams.get("projectRef") ?? undefined;
21+
22+
switch (authenticationResult.type) {
23+
case "personalAccessToken": {
24+
const result = await getIdentityFromPAT(authenticationResult.result.userId, projectRef);
25+
if (!result.success) {
26+
if (result.error === "user_not_found") {
27+
return json({ error: "User not found" }, { status: 404 });
28+
}
29+
30+
return json({ error: result.error }, { status: 401 });
31+
}
32+
return json(result.result);
33+
}
34+
case "organizationAccessToken": {
35+
const result = await getIdentityFromOAT(
36+
authenticationResult.result.organizationId,
37+
projectRef
38+
);
39+
return json(result.result);
40+
}
41+
default: {
42+
authenticationResult satisfies never;
1543
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
1644
}
45+
}
46+
}
47+
48+
async function getIdentityFromPAT(
49+
userId: string,
50+
projectRef: string | undefined
51+
): Promise<
52+
{ success: true; result: WhoAmIResponse } | { success: false; error: "user_not_found" }
53+
> {
54+
const user = await prisma.user.findFirst({
55+
select: {
56+
email: true,
57+
},
58+
where: {
59+
id: userId,
60+
},
61+
});
62+
63+
if (!user) {
64+
return { success: false, error: "user_not_found" };
65+
}
66+
67+
const userDetails = {
68+
userId,
69+
email: user.email,
70+
dashboardUrl: env.APP_ORIGIN,
71+
} satisfies WhoAmIResponse;
72+
73+
if (!projectRef) {
74+
return {
75+
success: true,
76+
result: userDetails,
77+
};
78+
}
79+
80+
const orgs = await prisma.organization.findMany({
81+
select: {
82+
id: true,
83+
},
84+
where: {
85+
members: {
86+
some: {
87+
userId,
88+
},
89+
},
90+
},
91+
});
1792

18-
const user = await prisma.user.findUnique({
19-
select: {
20-
email: true,
93+
if (orgs.length === 0) {
94+
return {
95+
success: true,
96+
result: userDetails,
97+
};
98+
}
99+
100+
const project = await prisma.project.findFirst({
101+
select: {
102+
externalRef: true,
103+
name: true,
104+
slug: true,
105+
organization: {
106+
select: {
107+
slug: true,
108+
title: true,
109+
},
21110
},
22-
where: {
23-
id: authenticationResult.userId,
111+
},
112+
where: {
113+
externalRef: projectRef,
114+
organizationId: {
115+
in: orgs.map((org) => org.id),
24116
},
25-
});
117+
},
118+
});
26119

27-
if (!user) {
28-
return json({ error: "User not found" }, { status: 404 });
29-
}
120+
if (!project) {
121+
return {
122+
success: true,
123+
result: userDetails,
124+
};
125+
}
126+
127+
const projectPath = v3ProjectPath({ slug: project.organization.slug }, { slug: project.slug });
30128

31-
const url = new URL(request.url);
32-
const projectRef = url.searchParams.get("projectRef");
129+
return {
130+
success: true,
131+
result: {
132+
...userDetails,
133+
project: {
134+
url: new URL(projectPath, env.APP_ORIGIN).href,
135+
name: project.name,
136+
orgTitle: project.organization.title,
137+
},
138+
},
139+
};
140+
}
33141

34-
let projectDetails: WhoAmIResponse["project"];
142+
async function getIdentityFromOAT(
143+
organizationId: string,
144+
projectRef: string | undefined
145+
): Promise<{ success: true; result: WhoAmIResponse }> {
146+
// Organization auth tokens are currently only used internally for the build server.
147+
// We will eventually expose them in the application as well, as they are useful beyond the build server.
148+
// At that point we will need a v3 whoami endpoint that properly handles org auth tokens.
149+
// For now, we just return a dummy user id and email and keep using the existing v2 whoami endpoint.
150+
const orgDetails = {
151+
userId: `org_${organizationId}`,
152+
153+
dashboardUrl: env.APP_ORIGIN,
154+
} satisfies WhoAmIResponse;
35155

36-
if (projectRef) {
37-
const orgs = await prisma.organization.findMany({
156+
if (!projectRef) {
157+
return {
158+
success: true,
159+
result: orgDetails,
160+
};
161+
}
162+
163+
const project = await prisma.project.findFirst({
164+
select: {
165+
externalRef: true,
166+
name: true,
167+
slug: true,
168+
organization: {
38169
select: {
39-
id: true,
170+
slug: true,
171+
title: true,
40172
},
41-
where: {
42-
members: {
43-
some: {
44-
userId: authenticationResult.userId,
45-
},
46-
},
47-
},
48-
});
49-
50-
if (orgs.length > 0) {
51-
const project = await prisma.project.findFirst({
52-
select: {
53-
externalRef: true,
54-
name: true,
55-
slug: true,
56-
organization: {
57-
select: {
58-
slug: true,
59-
title: true,
60-
},
61-
},
62-
},
63-
where: {
64-
externalRef: projectRef,
65-
organizationId: {
66-
in: orgs.map((org) => org.id),
67-
},
68-
},
69-
});
70-
71-
if (project) {
72-
const projectPath = v3ProjectPath(
73-
{ slug: project.organization.slug },
74-
{ slug: project.slug }
75-
);
76-
projectDetails = {
77-
url: new URL(projectPath, env.APP_ORIGIN).href,
78-
name: project.name,
79-
orgTitle: project.organization.title,
80-
};
81-
}
82-
}
83-
}
173+
},
174+
},
175+
where: {
176+
externalRef: projectRef,
177+
organizationId,
178+
},
179+
});
84180

85-
const result: WhoAmIResponse = {
86-
userId: authenticationResult.userId,
87-
email: user.email,
88-
dashboardUrl: env.APP_ORIGIN,
89-
project: projectDetails,
181+
if (!project) {
182+
return {
183+
success: true,
184+
result: orgDetails,
90185
};
91-
return json(result);
92-
} catch (error) {
93-
const errorMessage = error instanceof Error ? error.message : "Something went wrong";
94-
logger.error("Error in whoami v2", { error: errorMessage });
95-
return json({ error: errorMessage }, { status: 400 });
96186
}
187+
188+
const projectPath = v3ProjectPath({ slug: project.organization.slug }, { slug: project.slug });
189+
return {
190+
success: true,
191+
result: {
192+
...orgDetails,
193+
project: {
194+
url: new URL(projectPath, env.APP_ORIGIN).href,
195+
name: project.name,
196+
orgTitle: project.organization.title,
197+
},
198+
},
199+
};
97200
}

0 commit comments

Comments
 (0)