@unkey/rbac
Version:
154 lines (152 loc) • 3.64 kB
JavaScript
// src/permissions.ts
import { z } from "zod";
function buildIdSchema(prefix) {
return z.string().refine((s) => {
if (s === "*") {
return true;
}
const regex = new RegExp(
`^${prefix}_[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{8,32}$`
);
return regex.test(s);
});
}
var apiId = buildIdSchema("api");
var ratelimitNamespaceId = buildIdSchema("rl");
var apiActions = z.enum([
"read_api",
"create_api",
"delete_api",
"update_api",
"create_key",
"update_key",
"delete_key",
"encrypt_key",
"decrypt_key",
"read_key"
]);
var ratelimitActions = z.enum([
"limit",
"create_namespace",
"read_namespace",
"update_namespace",
"delete_namespace"
]);
var unkeyPermissionValidation = z.custom().refine((s) => {
z.string().parse(s);
if (s === "*") {
return true;
}
const split = s.split(".");
if (split.length !== 3) {
return false;
}
const [resource, id, action] = split;
switch (resource) {
case "api": {
return apiId.safeParse(id).success && apiActions.safeParse(action).success;
}
case "ratelimit": {
return ratelimitNamespaceId.safeParse(id).success && ratelimitActions.safeParse(action).success;
}
default: {
return false;
}
}
});
// src/queries.ts
import { z as z2 } from "zod";
var permissionQuerySchema = z2.union([
z2.string(),
z2.object({
and: z2.array(z2.lazy(() => permissionQuerySchema)).min(1, "provide at least one permission")
}),
z2.object({
or: z2.array(z2.lazy(() => permissionQuerySchema)).min(1, "provide at least one permission")
})
]);
function merge(rule, ...args) {
return args.filter(Boolean).reduce(
(acc, arg) => {
if (typeof acc === "string") {
throw new Error("Cannot merge into a string");
}
if (!acc[rule]) {
acc[rule] = [];
}
acc[rule].push(arg);
return acc;
},
{}
);
}
function or(...args) {
return merge("or", ...args);
}
function and(...args) {
return merge("and", ...args);
}
function buildQuery(fn) {
return fn({ or, and });
}
var buildUnkeyQuery = buildQuery;
// src/rbac.ts
import { Err, Ok, SchemaError } from "@unkey/error";
var RBAC = class {
evaluatePermissions(q, roles) {
return this.evaluateQueryV1(q, roles);
}
validateQuery(q) {
const validQuery = permissionQuerySchema.safeParse(q);
if (!validQuery.success) {
return Err(SchemaError.fromZod(validQuery.error, q));
}
return Ok({ query: validQuery.data });
}
evaluateQueryV1(query, roles) {
if (typeof query === "string") {
if (roles.includes(query)) {
return Ok({ valid: true });
}
return Ok({ valid: false, message: `Role ${query} not allowed` });
}
if (query.and) {
const results = query.and.filter(Boolean).map((q) => this.evaluateQueryV1(q, roles));
for (const r of results) {
if (r.err) {
return r;
}
if (!r.val.valid) {
return r;
}
}
return Ok({ valid: true });
}
if (query.or) {
for (const q of query.or) {
const r = this.evaluateQueryV1(q, roles);
if (r.err) {
return r;
}
if (r.val.valid) {
return r;
}
}
return Ok({ valid: false, message: "No role matched" });
}
return Err(new SchemaError({ message: "reached end of evaluate and no match" }));
}
};
export {
RBAC,
and,
apiActions,
buildIdSchema,
buildQuery,
buildUnkeyQuery,
or,
permissionQuerySchema,
ratelimitActions,
unkeyPermissionValidation
};
//# sourceMappingURL=index.js.map