77 lines
1.7 KiB
TypeScript
77 lines
1.7 KiB
TypeScript
/**
|
|
* The database takes care of isolating roles between workspaces.
|
|
* That's why we can assume the highest scope of a role is an `api` or later `gateway`
|
|
*
|
|
* role identifiers can look like this:
|
|
* - `api_id.xxx`
|
|
* - `gateway_id.xxx`
|
|
*
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
import { Flatten } from "./types";
|
|
|
|
export function buildIdSchema(prefix: string) {
|
|
return z.string().refine((s) => {
|
|
if (s === "*") {
|
|
return true;
|
|
}
|
|
const regex = new RegExp(
|
|
`^${prefix}_[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{8,32}$`,
|
|
);
|
|
return regex.test(s);
|
|
});
|
|
}
|
|
const apiId = buildIdSchema("api");
|
|
const ratelimitNamespaceId = buildIdSchema("rl");
|
|
|
|
export const apiActions = z.enum([
|
|
"read_api",
|
|
"create_api",
|
|
"delete_api",
|
|
"update_api",
|
|
"create_key",
|
|
"update_key",
|
|
"delete_key",
|
|
"read_key",
|
|
]);
|
|
|
|
export const ratelimitActions = z.enum(["limit"]);
|
|
|
|
export type Resources = {
|
|
[resourceId in `api.${z.infer<typeof apiId>}`]: z.infer<typeof apiActions>;
|
|
} & {
|
|
[resourceId in `ratelimit.${z.infer<typeof ratelimitNamespaceId>}`]: z.infer<
|
|
typeof ratelimitActions
|
|
>;
|
|
};
|
|
|
|
export type UnkeyPermission = Flatten<Resources> | "*";
|
|
|
|
/**
|
|
* Validation for roles used for our root keys
|
|
*/
|
|
export const unkeyPermissionValidation = z.custom<UnkeyPermission>().refine((s) => {
|
|
z.string().parse(s);
|
|
if (s === "*") {
|
|
/**
|
|
* This is a legacy role granting access to everything
|
|
*/
|
|
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;
|
|
}
|
|
|
|
default: {
|
|
return false;
|
|
}
|
|
}
|
|
});
|