polizy-troubleshooting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Polizy Troubleshooting

Polizy 问题排查

Debug authorization issues when things don't work as expected.
调试不符合预期的授权问题。

When to Apply

适用场景

  • User says "permission check not working"
  • User says "user can't access X but should"
  • Error messages from polizy
  • User confused about why authorization behaves a certain way
  • check()
    returns
    false
    unexpectedly
  • 用户反馈"权限检查不生效"
  • 用户反馈"用户无法访问X,但应该有权限"
  • 来自Polizy的错误提示
  • 用户对授权的行为逻辑感到困惑
  • check()
    意外返回
    false

Quick Diagnosis Flowchart

快速诊断流程图

check() returns false unexpectedly
Is the relation in actionToRelations?
    │           │
   NO           YES
    │            │
    ▼            ▼
ADD IT      Is there a group relation?
             │           │
            NO           YES
             │            │
             ▼            ▼
    (Direct check)    Is user in group?
             │            │
             ▼           NO → Check addMember()
    Is tuple           YES
    present?            │
       │                ▼
      NO              Does group have permission?
       │                │
       ▼               NO → Check group's allow()
    Add with          YES
    allow()            │
                  Check depth limit
check() returns false unexpectedly
Is the relation in actionToRelations?
    │           │
   NO           YES
    │            │
    ▼            ▼
ADD IT      Is there a group relation?
             │           │
            NO           YES
             │            │
             ▼            ▼
    (Direct check)    Is user in group?
             │            │
             ▼           NO → Check addMember()
    Is tuple           YES
    present?            │
       │                ▼
      NO              Does group have permission?
       │                │
       ▼               NO → Check group's allow()
    Add with          YES
    allow()            │
                  Check depth limit

Common Issues

常见问题

1. Relation Not Mapped to Action

1. 关系未映射到操作

Symptom:
check()
returns
false
even with permission granted.
typescript
// Schema
actionToRelations: {
  view: ["viewer"],  // "editor" missing!
  edit: ["editor"],
}

// Grant
await authz.allow({ who: alice, toBe: "editor", onWhat: doc });

// Check
await authz.check({ who: alice, canThey: "view", onWhat: doc }); // false!
Fix: Add relation to action's array:
typescript
actionToRelations: {
  view: ["viewer", "editor"],  // Now editors can view
  edit: ["editor"],
}
症状:
check()
即使已授予权限仍返回
false
typescript
// Schema
actionToRelations: {
  view: ["viewer"],  // "editor" missing!
  edit: ["editor"],
}

// Grant
await authz.allow({ who: alice, toBe: "editor", onWhat: doc });

// Check
await authz.check({ who: alice, canThey: "view", onWhat: doc }); // false!
修复方案: 将关系添加到操作的数组中:
typescript
actionToRelations: {
  view: ["viewer", "editor"],  // 现在编辑者可以查看
  edit: ["editor"],
}

2. Missing Group Relation

2. 缺少组关系

Symptom:
addMember()
throws error.
typescript
// Schema missing group type
relations: {
  viewer: { type: "direct" },
}

await authz.addMember({ member: alice, group: team });
// Error: No group relation defined in schema
Fix: Add group relation:
typescript
relations: {
  viewer: { type: "direct" },
  member: { type: "group" },  // Add this
}
症状:
addMember()
抛出错误。
typescript
// Schema missing group type
relations: {
  viewer: { type: "direct" },
}

await authz.addMember({ member: alice, group: team });
// Error: No group relation defined in schema
修复方案: 添加组关系:
typescript
relations: {
  viewer: { type: "direct" },
  member: { type: "group" },  // 添加这行
}

3. Missing Hierarchy Propagation

3. 缺少层级传播

Symptom: Parent permission doesn't flow to children.
typescript
// Schema
relations: {
  parent: { type: "hierarchy" },
  viewer: { type: "direct" },
},
// Missing hierarchyPropagation!

await authz.setParent({ child: doc, parent: folder });
await authz.allow({ who: alice, toBe: "viewer", onWhat: folder });
await authz.check({ who: alice, canThey: "view", onWhat: doc }); // false!
Fix: Add
hierarchyPropagation
:
typescript
hierarchyPropagation: {
  view: ["view"],  // Now view propagates
}
症状: 父级权限未传递到子级。
typescript
// Schema
relations: {
  parent: { type: "hierarchy" },
  viewer: { type: "direct" },
},
// Missing hierarchyPropagation!

await authz.setParent({ child: doc, parent: folder });
await authz.allow({ who: alice, toBe: "viewer", onWhat: folder });
await authz.check({ who: alice, canThey: "view", onWhat: doc }); // false!
修复方案: 添加
hierarchyPropagation
typescript
hierarchyPropagation: {
  view: ["view"],  // 现在查看权限会传播
}

4. User Not in Group

4. 用户不在组内

Symptom: Group has permission but user can't access.
Debug:
typescript
// Check group membership
const memberships = await authz.listTuples({
  subject: { type: "user", id: "alice" },
  relation: "member",
});
console.log("Alice's groups:", memberships);
Fix: Add user to group:
typescript
await authz.addMember({ member: alice, group: team });
症状: 组拥有权限,但用户无法访问。
调试:
typescript
// Check group membership
const memberships = await authz.listTuples({
  subject: { type: "user", id: "alice" },
  relation: "member",
});
console.log("Alice's groups:", memberships);
修复方案: 将用户添加到组中:
typescript
await authz.addMember({ member: alice, group: team });

5. Max Depth Exceeded

5. 超过最大深度

Symptom: Deep group chain returns
false
silently.
Detect:
typescript
const authz = new AuthSystem({
  storage,
  schema,
  throwOnMaxDepth: true,  // Throws instead of silent false
});

try {
  await authz.check({ who: alice, canThey: "view", onWhat: doc });
} catch (error) {
  if (error instanceof MaxDepthExceededError) {
    console.log("Depth exceeded at:", error.depth);
  }
}
Fix: Increase depth or reduce nesting:
typescript
const authz = new AuthSystem({
  storage,
  schema,
  defaultCheckDepth: 20,  // Increase from default 10
});
症状: 深层组链静默返回
false
检测:
typescript
const authz = new AuthSystem({
  storage,
  schema,
  throwOnMaxDepth: true,  // 抛出错误而非静默返回false
});

try {
  await authz.check({ who: alice, canThey: "view", onWhat: doc });
} catch (error) {
  if (error instanceof MaxDepthExceededError) {
    console.log("Depth exceeded at:", error.depth);
  }
}
修复方案: 增加深度限制或减少嵌套:
typescript
const authz = new AuthSystem({
  storage,
  schema,
  defaultCheckDepth: 20,  // 从默认10增加到20
});

6. Time-Based Condition Not Valid

6. 基于时间的条件无效

Symptom: Permission granted with
when
but check fails.
Debug:
typescript
const tuples = await authz.listTuples({
  subject: alice,
  object: doc,
});

for (const tuple of tuples) {
  console.log("Condition:", tuple.condition);
  if (tuple.condition?.validSince) {
    console.log("Starts:", tuple.condition.validSince);
  }
  if (tuple.condition?.validUntil) {
    console.log("Expires:", tuple.condition.validUntil);
  }
}
Common causes:
  • validSince
    is in the future
  • validUntil
    is in the past
症状: 通过
when
授予权限,但检查失败。
调试:
typescript
const tuples = await authz.listTuples({
  subject: alice,
  object: doc,
});

for (const tuple of tuples) {
  console.log("Condition:", tuple.condition);
  if (tuple.condition?.validSince) {
    console.log("Starts:", tuple.condition.validSince);
  }
  if (tuple.condition?.validUntil) {
    console.log("Expires:", tuple.condition.validUntil);
  }
}
常见原因:
  • validSince
    设置为未来时间
  • validUntil
    设置为过去时间

Debugging Techniques

调试技巧

1. List All Tuples for Subject

1. 列出主体的所有元组

typescript
const tuples = await authz.listTuples({
  subject: { type: "user", id: "alice" },
});

console.log("Alice's permissions:");
for (const tuple of tuples) {
  console.log(`  ${tuple.relation} on ${tuple.object.type}:${tuple.object.id}`);
}
typescript
const tuples = await authz.listTuples({
  subject: { type: "user", id: "alice" },
});

console.log("Alice's permissions:");
for (const tuple of tuples) {
  console.log(`  ${tuple.relation} on ${tuple.object.type}:${tuple.object.id}`);
}

2. List All Tuples for Object

2. 列出对象的所有元组

typescript
const tuples = await authz.listTuples({
  object: { type: "document", id: "doc1" },
});

console.log("Permissions on doc1:");
for (const tuple of tuples) {
  console.log(`  ${tuple.subject.type}:${tuple.subject.id} is ${tuple.relation}`);
}
typescript
const tuples = await authz.listTuples({
  object: { type: "document", id: "doc1" },
});

console.log("Permissions on doc1:");
for (const tuple of tuples) {
  console.log(`  ${tuple.subject.type}:${tuple.subject.id} is ${tuple.relation}`);
}

3. Trace Group Membership

3. 追踪组成员关系

typescript
async function traceGroupPath(userId: string) {
  const user = { type: "user", id: userId };
  const groups: string[] = [];

  const directMemberships = await authz.listTuples({
    subject: user,
    relation: "member",
  });

  for (const tuple of directMemberships) {
    groups.push(`${tuple.object.type}:${tuple.object.id}`);

    // Check nested groups
    const nestedMemberships = await authz.listTuples({
      subject: tuple.object,
      relation: "member",
    });

    for (const nested of nestedMemberships) {
      groups.push(`${nested.object.type}:${nested.object.id}`);
    }
  }

  return groups;
}

console.log("Group path:", await traceGroupPath("alice"));
typescript
async function traceGroupPath(userId: string) {
  const user = { type: "user", id: userId };
  const groups: string[] = [];

  const directMemberships = await authz.listTuples({
    subject: user,
    relation: "member",
  });

  for (const tuple of directMemberships) {
    groups.push(`${tuple.object.type}:${tuple.object.id}`);

    // Check nested groups
    const nestedMemberships = await authz.listTuples({
      subject: tuple.object,
      relation: "member",
    });

    for (const nested of nestedMemberships) {
      groups.push(`${nested.object.type}:${nested.object.id}`);
    }
  }

  return groups;
}

console.log("Group path:", await traceGroupPath("alice"));

4. Trace Hierarchy Path

4. 追踪层级路径

typescript
async function traceHierarchyPath(objectType: string, objectId: string) {
  const path: string[] = [`${objectType}:${objectId}`];
  let current = { type: objectType, id: objectId };

  while (true) {
    const parentTuples = await authz.listTuples({
      subject: current,
      relation: "parent",
    });

    if (parentTuples.length === 0) break;

    const parent = parentTuples[0].object;
    path.push(`${parent.type}:${parent.id}`);
    current = parent;
  }

  return path;
}

console.log("Hierarchy:", await traceHierarchyPath("document", "doc1"));
// ["document:doc1", "folder:subfolder", "folder:root"]
typescript
async function traceHierarchyPath(objectType: string, objectId: string) {
  const path: string[] = [`${objectType}:${objectId}`];
  let current = { type: objectType, id: objectId };

  while (true) {
    const parentTuples = await authz.listTuples({
      subject: current,
      relation: "parent",
    });

    if (parentTuples.length === 0) break;

    const parent = parentTuples[0].object;
    path.push(`${parent.type}:${parent.id}`);
    current = parent;
  }

  return path;
}

console.log("Hierarchy:", await traceHierarchyPath("document", "doc1"));
// ["document:doc1", "folder:subfolder", "folder:root"]

5. Enable Logging

5. 启用日志

typescript
const debugLog: string[] = [];

const authz = new AuthSystem({
  storage,
  schema,
  logger: {
    warn: (msg) => {
      debugLog.push(msg);
      console.warn("[Polizy]", msg);
    },
  },
});

// After operations, check debugLog for warnings
typescript
const debugLog: string[] = [];

const authz = new AuthSystem({
  storage,
  schema,
  logger: {
    warn: (msg) => {
      debugLog.push(msg);
      console.warn("[Polizy]", msg);
    },
  },
});

// After operations, check debugLog for warnings

Error Reference

错误参考

ErrorCauseFix
SchemaError: Relation "X" is not defined
Using undefined relationAdd relation to schema
SchemaError: No group relation defined
Missing group typeAdd
member: { type: "group" }
SchemaError: No hierarchy relation defined
Missing hierarchy typeAdd
parent: { type: "hierarchy" }
MaxDepthExceededError
Group/hierarchy too deepIncrease depth or flatten
ConfigurationError: storage is required
Missing storage adapterProvide storage in constructor
ConfigurationError: schema is required
Missing schemaProvide schema in constructor
错误原因修复方案
SchemaError: Relation "X" is not defined
使用了未定义的关系在Schema中添加该关系
SchemaError: No group relation defined
缺少组类型添加
member: { type: "group" }
SchemaError: No hierarchy relation defined
缺少层级类型添加
parent: { type: "hierarchy" }
MaxDepthExceededError
组/层级嵌套过深增加深度限制或扁平化结构
ConfigurationError: storage is required
缺少存储适配器在构造函数中提供存储配置
ConfigurationError: schema is required
缺少Schema在构造函数中提供Schema

Anti-Patterns to Avoid

需避免的反模式

See ANTI-PATTERNS.md for detailed explanations:
  1. Duplicating permissions across users - Use groups
  2. Deep group nesting - Keep 2-3 levels
  3. Generic relation names - Use semantic names
  4. Checking after action - Check before
  5. Not handling authorization errors - Show feedback
详细说明请参见 ANTI-PATTERNS.md
  1. 在用户间重复配置权限 - 使用组来管理
  2. 深层组嵌套 - 保持2-3层即可
  3. 通用关系名称 - 使用语义化名称
  4. 操作后才检查权限 - 应在操作前检查
  5. 不处理授权错误 - 需展示反馈信息

References

参考资料

  • CHECK-ALGORITHM.md - How check() works internally
  • COMMON-ISSUES.md - Detailed issue solutions
  • ANTI-PATTERNS.md - What NOT to do
  • CHECK-ALGORITHM.md -
    check()
    内部工作原理
  • COMMON-ISSUES.md - 详细的问题解决方案
  • ANTI-PATTERNS.md - 需避免的做法

Related Skills

相关技能

  • polizy-schema - Schema design
  • polizy-patterns - Implementation patterns
  • polizy-schema - Schema设计
  • polizy-patterns - 实现模式