@unkey/rbac
Version:
1 lines • 9.24 kB
Source Map (JSON)
{"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"]}