Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ee/apps/den-api/src/routes/org/api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
},
},
403: {
description: "Only workspace owners and admins can list API keys.",
description: "Only workspace owners can list API keys.",
content: {
"application/json": {
schema: resolver(forbiddenApiKeyManagerSchema),
Expand Down Expand Up @@ -196,7 +196,7 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
},
},
403: {
description: "Only workspace owners and admins can create API keys.",
description: "Only workspace owners can create API keys.",
content: {
"application/json": {
schema: resolver(forbiddenApiKeyManagerSchema),
Expand Down Expand Up @@ -300,7 +300,7 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
},
},
403: {
description: "Only workspace owners and admins can delete API keys.",
description: "Only workspace owners can delete API keys.",
content: {
"application/json": {
schema: resolver(forbiddenApiKeyManagerSchema),
Expand Down
8 changes: 6 additions & 2 deletions ee/apps/den-api/src/routes/org/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,23 @@ export function ensureApiKeyManager(c: { get: (key: "organizationContext") => Or
}
}

if (payload.currentMember.isOwner || memberHasRole(payload.currentMember.role, "admin")) {
if (canManageApiKeys(payload)) {
return { ok: true as const }
}

return {
ok: false as const,
response: {
error: "forbidden",
message: "Only workspace owners and admins can manage API keys.",
message: "Only workspace owners can manage API keys.",
},
}
}

export function canManageApiKeys(payload: { currentMember: { isOwner: boolean; role?: string } } | null | undefined) {
return payload?.currentMember.isOwner === true
}

export function canManageIdentityConfiguration(payload: { currentMember: { isOwner: boolean; role?: string } } | null | undefined) {
return payload?.currentMember.isOwner === true
}
Expand Down
16 changes: 16 additions & 0 deletions ee/apps/den-api/test/org-identity-access.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,19 @@ test("identity configuration management is owner-only", () => {

expect(sharedModule.canManageIdentityConfiguration(null)).toBe(false)
})

test("api key management is owner-only", () => {
expect(sharedModule.canManageApiKeys({
currentMember: { isOwner: true, role: "owner" },
})).toBe(true)

expect(sharedModule.canManageApiKeys({
currentMember: { isOwner: false, role: "admin" },
})).toBe(false)

expect(sharedModule.canManageApiKeys({
currentMember: { isOwner: false, role: "member" },
})).toBe(false)

expect(sharedModule.canManageApiKeys(null)).toBe(false)
})
2 changes: 1 addition & 1 deletion packages/docs/cloud/security-and-operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ For production organizations, use SSO with identity-provider MFA as the primary

- Enable enforced SSO for organizations that have owners, admins, SCIM, SSO, API keys, or shared production providers.
- Require MFA, phishing-resistant factors where available, and conditional-access policy in the identity provider before users reach OpenWork Cloud.
- Keep SSO and SCIM configuration owner-only. Treat owner-only changes, custom-role changes, member role changes, API-key creation, and emergency access as privileged actions that require a fresh IdP-authenticated session.
- Keep SSO, SCIM, and API-key management owner-only. Treat owner-only changes, custom-role changes, member role changes, API-key creation, and emergency access as privileged actions that require a fresh IdP-authenticated session.
- Do not rely on standalone email/password accounts for production owner access unless the deployment has an equivalent MFA and recovery control outside OpenWork.

Native factor enrollment and in-product reauthentication should be treated as product work before replacing SSO-enforced MFA as the production control.
Expand Down
Loading