fix(access-keys): load object policy responses (#172)

Co-authored-by: 季宏伟 <jihongwei@jihongweis-MacBook-Pro.local>
This commit is contained in:
GatewayJ
2026-07-20 11:43:38 +08:00
committed by GitHub
parent e1b93972c0
commit 40f9deb646
3 changed files with 67 additions and 27 deletions
+3 -27
View File
@@ -2,6 +2,7 @@
import { useCallback } from "react"
import { useApi } from "@/contexts/api-context"
import { normalizePolicyStatements } from "@/lib/user-policy"
export function usePolicies() {
const api = useApi()
@@ -81,33 +82,8 @@ export function usePolicies() {
if (allPolicyNames.size > 0) {
await Promise.all(
Array.from(allPolicyNames).map(async (policyName) => {
const policyInfo = (await getPolicy(policyName)) as { policy?: string }
const policyDocument = JSON.parse(policyInfo.policy ?? "{}") as {
Statement?: Record<string, unknown>[]
}
if (!Array.isArray(policyDocument.Statement)) return
const groupOrigins = Object.entries(groupPoliciesMap)
.filter(([, policies]) => policies.includes(policyName))
.map(([groupName]) => groupName)
policyDocument.Statement.forEach((statement) => {
policyStatement.push({
...statement,
Sid: statement.Sid ?? policyName,
Origin: {
type:
directPolicyNames.includes(policyName) && groupOrigins.length > 0
? "both"
: directPolicyNames.includes(policyName)
? "direct"
: "group",
groups: groupOrigins,
policyName,
},
})
})
const policyInfo = (await getPolicy(policyName)) as { policy?: unknown }
policyStatement.push(...normalizePolicyStatements(policyName, policyInfo.policy ?? {}))
}),
)
}
+27
View File
@@ -0,0 +1,27 @@
type PolicyStatement = Record<string, unknown>
export function normalizePolicyStatements(policyName: string, rawPolicy: unknown): PolicyStatement[] {
const policy = typeof rawPolicy === "string" ? (JSON.parse(rawPolicy) as unknown) : rawPolicy
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
throw new TypeError("Policy must be a valid JSON object")
}
const statements = (policy as { Statement?: unknown }).Statement
if (statements === undefined) return []
if (!Array.isArray(statements)) {
throw new TypeError("Policy Statement must be an array")
}
return statements.map((statement) => {
if (typeof statement !== "object" || statement === null || Array.isArray(statement)) {
throw new TypeError("Policy Statement entries must be JSON objects")
}
const normalized = statement as PolicyStatement
return {
...normalized,
Sid: normalized.Sid ?? policyName,
}
})
}
+37
View File
@@ -0,0 +1,37 @@
import test from "node:test"
import assert from "node:assert/strict"
import { normalizePolicyStatements } from "../../lib/user-policy"
const policyStatement = {
Effect: "Allow",
Action: ["s3:GetObject"],
Resource: ["arn:aws:s3:::example/*"],
}
test("normalizePolicyStatements accepts policy objects returned by the admin API", () => {
assert.deepEqual(
normalizePolicyStatements("readonly", {
Version: "2012-10-17",
Statement: [policyStatement],
}),
[{ ...policyStatement, Sid: "readonly" }],
)
})
test("normalizePolicyStatements supports legacy string policy responses", () => {
assert.deepEqual(
normalizePolicyStatements(
"readonly",
JSON.stringify({
Version: "2012-10-17",
Statement: [{ ...policyStatement, Sid: "existing" }],
}),
),
[{ ...policyStatement, Sid: "existing" }],
)
})
test("normalizePolicyStatements rejects invalid policy response shapes", () => {
assert.throws(() => normalizePolicyStatements("readonly", []), /valid JSON object/)
assert.throws(() => normalizePolicyStatements("readonly", { Statement: {} }), /Statement must be an array/)
})