Skip to content

Commit 4ed47b8

Browse files
authored
always infer orgID and userID from session (#2561)
1 parent 26f028a commit 4ed47b8

20 files changed

+178
-202
lines changed

ui/src/api/helpers.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
1+
import { withAuth } from '@/authkit/ssr/session';
2+
13
function redirectWithFallback(redirectUri: string, headers?: Headers) {
24
const newHeaders = headers ? new Headers(headers) : new Headers();
35
newHeaders.set('Location', redirectUri);
4-
6+
57
return new Response(null, { status: 307, headers: newHeaders });
68
}
7-
9+
810
function errorResponseWithFallback(errorBody: { error: { message: string; description: string } }) {
911
return new Response(JSON.stringify(errorBody), {
1012
status: 500,
1113
headers: { 'Content-Type': 'application/json' },
1214
});
1315
}
1416

17+
/**
18+
* Requires authentication and returns session-derived user identity.
19+
* Use this instead of accepting userId/organizationId from client input
20+
* to prevent IDOR vulnerabilities.
21+
*/
22+
export async function requireAuth() {
23+
const auth = await withAuth();
24+
if (!auth.user) {
25+
throw new Error('Unauthorized');
26+
}
27+
return {
28+
userId: auth.user.id,
29+
email: auth.user.email!,
30+
organizationId: auth.organizationId!,
31+
};
32+
}
33+
1534
export { redirectWithFallback, errorResponseWithFallback };

ui/src/api/orchestrator_serverFunctions.ts

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,28 @@ import { fetchRepos } from "./orchestrator_repos";
44
import { getOrgSettings, updateOrgSettings } from "./orchestrator_orgs";
55
import { testSlackWebhook } from "./drift_slack";
66
import { fetchProjects, updateProject, fetchProject } from "./orchestrator_projects";
7+
import { requireAuth } from "./helpers";
78

89
export const getOrgSettingsFn = createServerFn({method: 'GET'})
9-
.inputValidator((data : {userId: string, organisationId: string}) => data)
10-
.handler(async ({ data }) => {
11-
const settings : any = await getOrgSettings(data.organisationId, data.userId)
10+
.handler(async () => {
11+
const auth = await requireAuth();
12+
const settings : any = await getOrgSettings(auth.organizationId, auth.userId)
1213
return settings
1314
})
1415

1516
export const updateOrgSettingsFn = createServerFn({method: 'POST'})
16-
.inputValidator((data : {userId: string, organisationId: string, settings: OrgSettings}) => data)
17+
.inputValidator((data : {settings: OrgSettings}) => data)
1718
.handler(async ({ data }) => {
18-
const settings : any = await updateOrgSettings(data.organisationId, data.userId, data.settings)
19+
const auth = await requireAuth();
20+
const settings : any = await updateOrgSettings(auth.organizationId, auth.userId, data.settings)
1921
return settings.result
2022
})
2123

2224
export const getProjectsFn = createServerFn({method: 'GET'})
23-
.inputValidator((data : {userId: string, organisationId: string}) => {
24-
if (!data.userId || !data.organisationId) {
25-
throw new Error('Missing required fields: userId and organisationId are required')
26-
}
27-
return data
28-
})
29-
.handler(async ({ data }) => {
25+
.handler(async () => {
26+
const auth = await requireAuth();
3027
try {
31-
const projects : any = await fetchProjects(data.organisationId, data.userId)
28+
const projects : any = await fetchProjects(auth.organizationId, auth.userId)
3229
return projects.result || []
3330
} catch (error) {
3431
console.error('Error in getProjectsFn:', error)
@@ -38,66 +35,69 @@ export const getProjectsFn = createServerFn({method: 'GET'})
3835

3936

4037
export const updateProjectFn = createServerFn({method: 'POST'})
41-
.inputValidator((data : {projectId: string, driftEnabled: boolean, organisationId: string, userId: string}) => data)
38+
.inputValidator((data : {projectId: string, driftEnabled: boolean}) => data)
4239
.handler(async ({ data }) => {
43-
const project : any = await updateProject(data.projectId, data.driftEnabled, data.organisationId, data.userId)
44-
return project.result
40+
const auth = await requireAuth();
41+
const project : any = await updateProject(data.projectId, data.driftEnabled, auth.organizationId, auth.userId)
42+
return project.result
4543
})
4644

4745
export const getReposFn = createServerFn({method: 'GET'})
48-
.inputValidator((data : {organisationId: string, userId: string}) => data)
49-
.handler(async ({ data }) => {
50-
let repos = []
51-
try {
52-
const reposData :any = await fetchRepos(data.organisationId, data.userId)
53-
repos = reposData.result
54-
} catch (error) {
55-
console.error('Error fetching repos:', error)
56-
throw error
57-
}
58-
return repos
46+
.handler(async () => {
47+
const auth = await requireAuth();
48+
let repos = []
49+
try {
50+
const reposData :any = await fetchRepos(auth.organizationId, auth.userId)
51+
repos = reposData.result
52+
} catch (error) {
53+
console.error('Error fetching repos:', error)
54+
throw error
55+
}
56+
return repos
5957
})
6058

6159
export const getProjectFn = createServerFn({method: 'GET'})
62-
.inputValidator((data : {projectId: string, organisationId: string, userId: string}) => data)
60+
.inputValidator((data : {projectId: string}) => data)
6361
.handler(async ({ data }) => {
64-
const project : any = await fetchProject(data.projectId, data.organisationId, data.userId)
65-
return project
62+
const auth = await requireAuth();
63+
const project : any = await fetchProject(data.projectId, auth.organizationId, auth.userId)
64+
return project
6665
})
6766

6867

6968
export const getRepoDetailsFn = createServerFn({method: 'GET'})
70-
.inputValidator((data : {repoId: string, organisationId: string, userId: string}) => data)
69+
.inputValidator((data : {repoId: string}) => data)
7170
.handler(async ({ data }) => {
72-
const { repoId, organisationId, userId } = data;
71+
const auth = await requireAuth();
72+
const { repoId } = data;
7373
let allJobs: Job[] = [];
74-
let repo: Repo
74+
let repo: Repo
7575
try {
7676
const response = await fetch(`${process.env.ORCHESTRATOR_BACKEND_URL}/api/repos/${repoId}/jobs`, {
7777
method: 'GET',
7878
headers: {
7979
'Authorization': `Bearer ${process.env.ORCHESTRATOR_BACKEND_SECRET}`,
80-
'DIGGER_ORG_ID': organisationId,
81-
'DIGGER_USER_ID': userId,
80+
'DIGGER_ORG_ID': auth.organizationId,
81+
'DIGGER_USER_ID': auth.userId,
8282
'DIGGER_ORG_SOURCE': 'workos',
8383
},
8484
});
85-
85+
8686
if (!response.ok) {
8787
throw new Error('Failed to fetch jobs');
8888
}
89-
89+
9090
const result = await response.json();
91-
92-
repo = result.repo
91+
92+
repo = result.repo
9393
allJobs = result.jobs || []
94-
94+
9595
} catch (error) {
9696
console.error('Error fetching jobs:', error);
9797
allJobs = [];
9898
throw error
9999
}
100-
100+
101101
return { repo, allJobs }
102102
})
103103

@@ -106,4 +106,3 @@ export const switchToOrganizationFn = createServerFn({method: 'POST'})
106106
.handler(async ({ data }) => {
107107
return null
108108
})
109-
Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,81 @@
11
import { createServerFn } from "@tanstack/react-start"
22
import { createUnit, getUnit, listUnits, getUnitVersions, unlockUnit, lockUnit, getUnitStatus, deleteUnit, downloadLatestState, forcePushState, restoreUnitStateVersion } from "./statesman_units"
3+
import { requireAuth } from "./helpers"
34

45
export const listUnitsFn = createServerFn({method: 'GET'})
5-
.inputValidator((data : {userId: string, organisationId: string, email: string}) => data)
6-
.handler(async ({ data }) => {
7-
const units : any = await listUnits(data.organisationId, data.userId, data.email);
6+
.handler(async () => {
7+
const auth = await requireAuth();
8+
const units : any = await listUnits(auth.organizationId, auth.userId, auth.email);
89
return units;
910
})
1011

1112
export const getUnitFn = createServerFn({method: 'GET'})
12-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
13+
.inputValidator((data : {unitId: string}) => data)
1314
.handler(async ({ data }) => {
14-
const unit : any = await getUnit(data.organisationId, data.userId, data.email, data.unitId)
15+
const auth = await requireAuth();
16+
const unit : any = await getUnit(auth.organizationId, auth.userId, auth.email, data.unitId)
1517
return unit
1618
})
1719

1820
export const getUnitVersionsFn = createServerFn({method: 'GET'})
19-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
21+
.inputValidator((data : {unitId: string}) => data)
2022
.handler(async ({ data }) => {
21-
const unitVersions : any = await getUnitVersions(data.organisationId, data.userId, data.email, data.unitId)
23+
const auth = await requireAuth();
24+
const unitVersions : any = await getUnitVersions(auth.organizationId, auth.userId, auth.email, data.unitId)
2225
return unitVersions
2326
})
2427

2528
export const lockUnitFn = createServerFn({method: 'POST'})
26-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
29+
.inputValidator((data : {unitId: string}) => data)
2730
.handler(async ({ data }) => {
28-
const unit : any = await lockUnit(data.organisationId, data.userId, data.email, data.unitId)
31+
const auth = await requireAuth();
32+
const unit : any = await lockUnit(auth.organizationId, auth.userId, auth.email, data.unitId)
2933
return unit
3034
})
3135

3236
export const unlockUnitFn = createServerFn({method: 'POST'})
33-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
37+
.inputValidator((data : {unitId: string}) => data)
3438
.handler(async ({ data }) => {
35-
const unit : any = await unlockUnit(data.organisationId, data.userId, data.email, data.unitId)
39+
const auth = await requireAuth();
40+
const unit : any = await unlockUnit(auth.organizationId, auth.userId, auth.email, data.unitId)
3641
return unit
3742
})
3843

3944
export const downloadLatestStateFn = createServerFn({method: 'GET'})
40-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
45+
.inputValidator((data : {unitId: string}) => data)
4146
.handler(async ({ data }) => {
42-
const state : any = await downloadLatestState(data.organisationId, data.userId, data.email, data.unitId)
47+
const auth = await requireAuth();
48+
const state : any = await downloadLatestState(auth.organizationId, auth.userId, auth.email, data.unitId)
4349
return state
4450
})
4551

4652
export const forcePushStateFn = createServerFn({method: 'POST'})
47-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string, state: string}) => data)
53+
.inputValidator((data : {unitId: string, state: string}) => data)
4854
.handler(async ({ data }) => {
49-
const state : any = await forcePushState(data.organisationId, data.userId, data.email, data.unitId, data.state)
55+
const auth = await requireAuth();
56+
const state : any = await forcePushState(auth.organizationId, auth.userId, auth.email, data.unitId, data.state)
5057
return state
5158
})
5259

5360
export const restoreUnitStateVersionFn = createServerFn({method: 'POST'})
54-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string, timestamp: string, lockId: string}) => data)
61+
.inputValidator((data : {unitId: string, timestamp: string, lockId: string}) => data)
5562
.handler(async ({ data }) => {
56-
const state : any = await restoreUnitStateVersion(data.organisationId, data.userId, data.email, data.unitId, data.timestamp, data.lockId)
63+
const auth = await requireAuth();
64+
const state : any = await restoreUnitStateVersion(auth.organizationId, auth.userId, auth.email, data.unitId, data.timestamp, data.lockId)
5765
return state
5866
})
5967

6068
export const getUnitStatusFn = createServerFn({method: 'GET'})
61-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
69+
.inputValidator((data : {unitId: string}) => data)
6270
.handler(async ({ data }) => {
63-
const unitStatus : any = await getUnitStatus(data.organisationId, data.userId, data.email, data.unitId)
71+
const auth = await requireAuth();
72+
const unitStatus : any = await getUnitStatus(auth.organizationId, auth.userId, auth.email, data.unitId)
6473
return unitStatus
6574
})
6675

6776
export const createUnitFn = createServerFn({method: 'POST'})
6877
.inputValidator((data : {
69-
userId: string,
70-
organisationId: string,
71-
email: string,
72-
name: string,
78+
name: string,
7379
requestId?: string,
7480
tfeAutoApply?: boolean,
7581
tfeExecutionMode?: string,
@@ -78,10 +84,11 @@ export const createUnitFn = createServerFn({method: 'POST'})
7884
tfeWorkingDirectory?: string
7985
}) => data)
8086
.handler(async ({ data }) => {
87+
const auth = await requireAuth();
8188
const unit : any = await createUnit(
82-
data.organisationId,
83-
data.userId,
84-
data.email,
89+
auth.organizationId,
90+
auth.userId,
91+
auth.email,
8592
data.name,
8693
data.tfeAutoApply,
8794
data.tfeExecutionMode,
@@ -94,9 +101,6 @@ export const createUnitFn = createServerFn({method: 'POST'})
94101

95102
export const updateUnitFn = createServerFn({method: 'POST'})
96103
.inputValidator((data : {
97-
userId: string,
98-
organisationId: string,
99-
email: string,
100104
unitId: string,
101105
tfeAutoApply?: boolean,
102106
tfeExecutionMode?: string,
@@ -105,11 +109,12 @@ export const updateUnitFn = createServerFn({method: 'POST'})
105109
tfeWorkingDirectory?: string
106110
}) => data)
107111
.handler(async ({ data }) => {
112+
const auth = await requireAuth();
108113
const { updateUnit } = await import("./statesman_units")
109114
const unit : any = await updateUnit(
110-
data.organisationId,
111-
data.userId,
112-
data.email,
115+
auth.organizationId,
116+
auth.userId,
117+
auth.email,
113118
data.unitId,
114119
data.tfeAutoApply,
115120
data.tfeExecutionMode,
@@ -121,7 +126,8 @@ export const updateUnitFn = createServerFn({method: 'POST'})
121126
})
122127

123128
export const deleteUnitFn = createServerFn({method: 'POST'})
124-
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
129+
.inputValidator((data : {unitId: string}) => data)
125130
.handler(async ({ data }) => {
126-
await deleteUnit(data.organisationId, data.userId, data.email, data.unitId)
127-
})
131+
const auth = await requireAuth();
132+
await deleteUnit(auth.organizationId, auth.userId, auth.email, data.unitId)
133+
})

ui/src/api/tokens_serverFunctions.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ import { createServerFn } from "@tanstack/react-start";
22
import { createToken, getTokens } from "./tokens";
33
import { verifyToken } from "./tokens";
44
import { deleteToken } from "./tokens";
5+
import { requireAuth } from "./helpers";
56

67
export const getTokensFn = createServerFn({method: 'GET'})
7-
.inputValidator((data: {organizationId: string, userId: string}) => data)
8-
.handler(async ({data: {organizationId, userId}}) => {
9-
return getTokens(organizationId, userId);
8+
.handler(async () => {
9+
const auth = await requireAuth();
10+
return getTokens(auth.organizationId, auth.userId);
1011
})
1112

1213
export const createTokenFn = createServerFn({method: 'POST'})
13-
.inputValidator((data: {organizationId: string, userId: string, name: string, expiresAt: string | null}) => data)
14-
.handler(async ({data: {organizationId, userId, name, expiresAt}}) => {
15-
return createToken(organizationId, userId, name, expiresAt);
14+
.inputValidator((data: {name: string, expiresAt: string | null}) => data)
15+
.handler(async ({data: {name, expiresAt}}) => {
16+
const auth = await requireAuth();
17+
return createToken(auth.organizationId, auth.userId, name, expiresAt);
1618
})
1719

1820
export const verifyTokenFn = createServerFn({method: 'POST'})
@@ -22,7 +24,8 @@ export const verifyTokenFn = createServerFn({method: 'POST'})
2224
})
2325

2426
export const deleteTokenFn = createServerFn({method: 'POST'})
25-
.inputValidator((data: {organizationId: string, userId: string, tokenId: string}) => data)
26-
.handler(async ({data: {organizationId, userId, tokenId}}) => {
27-
return deleteToken(organizationId, userId, tokenId);
28-
})
27+
.inputValidator((data: {tokenId: string}) => data)
28+
.handler(async ({data: {tokenId}}) => {
29+
const auth = await requireAuth();
30+
return deleteToken(auth.organizationId, auth.userId, tokenId);
31+
})

0 commit comments

Comments
 (0)