43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
import { ConvexError } from "convex/values";
|
|
|
|
import type { Id } from "./_generated/dataModel";
|
|
import type { MutationCtx, QueryCtx } from "./_generated/server";
|
|
|
|
export interface AuthContext {
|
|
readonly auth: {
|
|
readonly getUserIdentity: () => Promise<{
|
|
readonly tokenIdentifier: string;
|
|
} | null>;
|
|
};
|
|
}
|
|
|
|
export const requireOwnerId = async (ctx: AuthContext): Promise<string> => {
|
|
const identity = await ctx.auth.getUserIdentity();
|
|
if (!identity) {
|
|
throw new ConvexError("Authentication required");
|
|
}
|
|
return identity.tokenIdentifier;
|
|
};
|
|
|
|
/**
|
|
* Resolve the authenticated identity and prove it is a member of the given
|
|
* organization. Throws on unauthenticated or non-member access. Returns the
|
|
* canonical user ID (the Better Auth `tokenIdentifier`).
|
|
*/
|
|
export const requireOrganizationMember = async (
|
|
ctx: QueryCtx | MutationCtx,
|
|
organizationId: Id<"organizations">
|
|
): Promise<string> => {
|
|
const userId = await requireOwnerId(ctx);
|
|
const membership = await ctx.db
|
|
.query("organizationMembers")
|
|
.withIndex("by_organizationId_and_userId", (q) =>
|
|
q.eq("organizationId", organizationId).eq("userId", userId)
|
|
)
|
|
.unique();
|
|
if (!membership) {
|
|
throw new ConvexError("Organization membership required");
|
|
}
|
|
return userId;
|
|
};
|