mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
146 lines
3.8 KiB
TypeScript
146 lines
3.8 KiB
TypeScript
import type { GenericEndpointContext } from "@better-auth/core";
|
|
import { APIError } from "@better-auth/core/error";
|
|
import type { OrganizationOptions } from "better-auth/plugins/organization";
|
|
import { API_KEY_ERROR_CODES as ERROR_CODES } from ".";
|
|
|
|
/**
|
|
* API Key permission actions that can be configured in organization roles.
|
|
* Users must add these permissions to their organization role definitions.
|
|
*/
|
|
export const API_KEY_PERMISSIONS = {
|
|
create: "create",
|
|
read: "read",
|
|
update: "update",
|
|
delete: "delete",
|
|
} as const;
|
|
|
|
export type ApiKeyPermissionAction =
|
|
(typeof API_KEY_PERMISSIONS)[keyof typeof API_KEY_PERMISSIONS];
|
|
|
|
interface Member {
|
|
id: string;
|
|
userId: string;
|
|
organizationId: string;
|
|
role: string;
|
|
createdAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Gets the organization plugin options from the context.
|
|
* Returns null if the organization plugin is not installed.
|
|
*/
|
|
function getOrgOptions(
|
|
ctx: GenericEndpointContext,
|
|
): OrganizationOptions | null {
|
|
const context = ctx.context;
|
|
if ("orgOptions" in context && context.orgOptions) {
|
|
return context.orgOptions as OrganizationOptions;
|
|
}
|
|
|
|
const orgPlugin = context.getPlugin?.("organization");
|
|
if (orgPlugin && "options" in orgPlugin) {
|
|
return orgPlugin.options as OrganizationOptions;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if a user is a member of an organization and has the required permission.
|
|
* This is used for organization-owned API keys to validate access.
|
|
*
|
|
* @param ctx - The endpoint context
|
|
* @param userId - The ID of the user to check
|
|
* @param organizationId - The ID of the organization (from API key's referenceId)
|
|
* @param requiredAction - The action the user is trying to perform (create, read, update, delete)
|
|
* @returns The member object if authorized
|
|
* @throws APIError if not authorized
|
|
*/
|
|
export async function checkOrgApiKeyPermission(
|
|
ctx: GenericEndpointContext,
|
|
userId: string,
|
|
organizationId: string,
|
|
requiredAction: ApiKeyPermissionAction,
|
|
): Promise<Member> {
|
|
// Check if organization plugin is installed
|
|
const orgOptions = getOrgOptions(ctx);
|
|
if (!orgOptions) {
|
|
const msg = ERROR_CODES.ORGANIZATION_PLUGIN_REQUIRED;
|
|
throw APIError.from("INTERNAL_SERVER_ERROR", msg);
|
|
}
|
|
|
|
// Query the member table to check if user is a member of the organization
|
|
const member = await ctx.context.adapter.findOne<Member>({
|
|
model: "member",
|
|
where: [
|
|
{
|
|
field: "userId",
|
|
value: userId,
|
|
},
|
|
{
|
|
field: "organizationId",
|
|
value: organizationId,
|
|
},
|
|
],
|
|
});
|
|
|
|
if (!member) {
|
|
const msg = ERROR_CODES.USER_NOT_MEMBER_OF_ORGANIZATION;
|
|
throw APIError.from("FORBIDDEN", msg);
|
|
}
|
|
|
|
// Check permission using the organization's permission system
|
|
const hasPermissionResult = await checkPermission(
|
|
ctx,
|
|
member.role,
|
|
organizationId,
|
|
requiredAction,
|
|
orgOptions,
|
|
);
|
|
|
|
if (!hasPermissionResult) {
|
|
const msg = ERROR_CODES.INSUFFICIENT_API_KEY_PERMISSIONS;
|
|
throw APIError.from("FORBIDDEN", msg);
|
|
}
|
|
|
|
return member;
|
|
}
|
|
|
|
/**
|
|
* Checks if a role has the required permission for API key operations.
|
|
* Uses the organization's access control system.
|
|
*
|
|
* Organization owners (determined by orgOptions.creatorRole, default "owner")
|
|
* are granted full access to API key operations.
|
|
*/
|
|
async function checkPermission(
|
|
ctx: GenericEndpointContext,
|
|
role: string,
|
|
organizationId: string,
|
|
action: ApiKeyPermissionAction,
|
|
orgOptions: OrganizationOptions,
|
|
): Promise<boolean> {
|
|
// Import hasPermission dynamically to avoid circular dependencies
|
|
const { hasPermission } = await import("better-auth/plugins/organization");
|
|
|
|
try {
|
|
const result = await hasPermission(
|
|
{
|
|
role,
|
|
options: orgOptions,
|
|
permissions: {
|
|
apiKey: [action],
|
|
},
|
|
organizationId,
|
|
// Allow organization owners full access to API keys
|
|
allowCreatorAllPermissions: true,
|
|
},
|
|
ctx,
|
|
);
|
|
|
|
return result;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|