{"version":3,"sources":["../src/index.ts","../src/permissions.ts","../src/queries.ts","../src/rbac.ts"],"sourcesContent":["export * from \"./permissions\";\nexport * from \"./queries\";\nexport * from \"./rbac\";\nexport type { Flatten } from \"./types\";\n","/**\n * The database takes care of isolating roles between workspaces.\n * That's why we can assume the highest scope of a role is an `api` or later `gateway`\n *\n * role identifiers can look like this:\n * - `api_id.xxx`\n * - `gateway_id.xxx`\n *\n */\n\nimport { z } from \"zod\";\nimport type { Flatten } from \"./types\";\n\nexport function buildIdSchema(prefix: string) {\n  return z.string().refine((s) => {\n    if (s === \"*\") {\n      return true;\n    }\n    const regex = new RegExp(\n      `^${prefix}_[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{8,32}$`,\n    );\n    return regex.test(s);\n  });\n}\nconst apiId = buildIdSchema(\"api\");\nconst ratelimitNamespaceId = buildIdSchema(\"rl\");\n\nexport const apiActions = z.enum([\n  \"read_api\",\n  \"create_api\",\n  \"delete_api\",\n  \"update_api\",\n  \"create_key\",\n  \"update_key\",\n  \"delete_key\",\n  \"encrypt_key\",\n  \"decrypt_key\",\n  \"read_key\",\n]);\n\nexport const ratelimitActions = z.enum([\n  \"limit\",\n  \"create_namespace\",\n  \"read_namespace\",\n  \"update_namespace\",\n  \"delete_namespace\",\n]);\n\nexport type Resources = {\n  [resourceId in `api.${z.infer<typeof apiId>}`]: z.infer<typeof apiActions>;\n} & {\n  [resourceId in `ratelimit.${z.infer<typeof ratelimitNamespaceId>}`]: z.infer<\n    typeof ratelimitActions\n  >;\n};\n\nexport type UnkeyPermission = Flatten<Resources> | \"*\";\n\n/**\n * Validation for roles used for our root keys\n */\nexport const unkeyPermissionValidation = z.custom<UnkeyPermission>().refine((s) => {\n  z.string().parse(s);\n  if (s === \"*\") {\n    /**\n     * This is a legacy role granting access to everything\n     */\n    return true;\n  }\n  const split = s.split(\".\");\n  if (split.length !== 3) {\n    return false;\n  }\n  const [resource, id, action] = split;\n  switch (resource) {\n    case \"api\": {\n      return apiId.safeParse(id).success && apiActions.safeParse(action).success;\n    }\n    case \"ratelimit\": {\n      return (\n        ratelimitNamespaceId.safeParse(id).success && ratelimitActions.safeParse(action).success\n      );\n    }\n\n    default: {\n      return false;\n    }\n  }\n});\n","import { z } from \"zod\";\nimport type { unkeyPermissionValidation } from \"./permissions\";\n\ntype Rule = \"and\" | \"or\";\n\nexport type PermissionQuery<R extends string = string> =\n  | R\n  | {\n      and: Array<PermissionQuery<R> | undefined>;\n      or?: never;\n    }\n  | {\n      and?: never;\n      or: Array<PermissionQuery<R> | undefined>;\n    };\n\nexport const permissionQuerySchema: z.ZodType<PermissionQuery> = z.union([\n  z.string(),\n  z.object({\n    and: z.array(z.lazy(() => permissionQuerySchema)).min(1, \"provide at least one permission\"),\n  }),\n  z.object({\n    or: z.array(z.lazy(() => permissionQuerySchema)).min(1, \"provide at least one permission\"),\n  }),\n]);\n\nfunction merge<R extends string>(\n  rule: Rule,\n  ...args: Array<PermissionQuery<R> | undefined>\n): PermissionQuery<R> {\n  return args.filter(Boolean).reduce(\n    (acc: PermissionQuery<R>, arg) => {\n      if (typeof acc === \"string\") {\n        throw new Error(\"Cannot merge into a string\");\n      }\n      if (!acc[rule]) {\n        acc[rule] = [];\n      }\n      acc[rule]!.push(arg);\n      return acc;\n    },\n    {} as PermissionQuery<R>,\n  );\n}\n\nexport function or<R extends string = string>(\n  ...args: Array<PermissionQuery<R> | undefined>\n): PermissionQuery<R> {\n  return merge(\"or\", ...args);\n}\n\nexport function and<R extends string = string>(\n  ...args: Array<PermissionQuery<R> | undefined>\n): PermissionQuery<R> {\n  return merge(\"and\", ...args);\n}\nexport function buildQuery<R extends string = string>(\n  fn: (ops: { or: typeof or<R>; and: typeof and<R> }) => PermissionQuery<R>,\n): PermissionQuery {\n  return fn({ or, and });\n}\n\n/**\n * buildUnkeyQuery is preloaded with out available roles and ensures typesafety for root key validation\n */\nexport const buildUnkeyQuery = buildQuery<z.infer<typeof unkeyPermissionValidation>>;\n","import { Err, Ok, type Result, SchemaError } from \"@unkey/error\";\nimport { type PermissionQuery, permissionQuerySchema } from \"./queries\";\n\nexport class RBAC {\n  public evaluatePermissions(\n    q: PermissionQuery,\n    roles: string[],\n  ): Result<{ valid: true; message?: never } | { valid: false; message: string }, SchemaError> {\n    return this.evaluateQueryV1(q, roles);\n  }\n  public validateQuery(q: PermissionQuery): Result<{ query: PermissionQuery }> {\n    const validQuery = permissionQuerySchema.safeParse(q);\n    if (!validQuery.success) {\n      return Err(SchemaError.fromZod(validQuery.error, q));\n    }\n\n    return Ok({ query: validQuery.data });\n  }\n\n  private evaluateQueryV1(\n    query: PermissionQuery,\n    roles: string[],\n  ): Result<{ valid: true; message?: never } | { valid: false; message: string }, SchemaError> {\n    if (typeof query === \"string\") {\n      // Check if the role is in the list of roles\n      if (roles.includes(query)) {\n        return Ok({ valid: true });\n      }\n      return Ok({ valid: false, message: `Role ${query} not allowed` });\n    }\n\n    if (query.and) {\n      const results = query.and\n        .filter(Boolean)\n        .map((q) => this.evaluateQueryV1(q as Required<PermissionQuery>, roles));\n      for (const r of results) {\n        if (r.err) {\n          return r;\n        }\n        if (!r.val.valid) {\n          return r;\n        }\n      }\n      return Ok({ valid: true });\n    }\n\n    if (query.or) {\n      for (const q of query.or) {\n        const r = this.evaluateQueryV1(q as Required<PermissionQuery>, roles);\n        if (r.err) {\n          return r;\n        }\n        if (r.val.valid) {\n          return r;\n        }\n      }\n      return Ok({ valid: false, message: \"No role matched\" });\n    }\n\n    return Err(new SchemaError({ message: \"reached end of evaluate and no match\" }));\n  }\n}\n"],"mappings":"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","names":["import_zod"]}