feat: Projects backend slice — auth, primitives, backend, UI (#3)
This commit is contained in:
@@ -11,7 +11,7 @@ export interface AuthContext {
|
||||
};
|
||||
}
|
||||
|
||||
export const requireOwnerId = async (ctx: AuthContext): Promise<string> => {
|
||||
export const requireAuthUserId = async (ctx: AuthContext): Promise<string> => {
|
||||
const identity = await ctx.auth.getUserIdentity();
|
||||
if (!identity) {
|
||||
throw new ConvexError("Authentication required");
|
||||
@@ -28,7 +28,7 @@ export const requireOrganizationMember = async (
|
||||
ctx: QueryCtx | MutationCtx,
|
||||
organizationId: Id<"organizations">
|
||||
): Promise<string> => {
|
||||
const userId = await requireOwnerId(ctx);
|
||||
const userId = await requireAuthUserId(ctx);
|
||||
const membership = await ctx.db
|
||||
.query("organizationMembers")
|
||||
.withIndex("by_organizationId_and_userId", (q) =>
|
||||
@@ -40,3 +40,41 @@ export const requireOrganizationMember = async (
|
||||
}
|
||||
return userId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the authenticated user's personal/current organization. Browsers
|
||||
* never submit organization IDs for Project commands; this resolves the
|
||||
* tenant boundary server-side through the authenticated identity.
|
||||
*/
|
||||
export const requireCurrentOrganization = async (
|
||||
ctx: QueryCtx | MutationCtx
|
||||
): Promise<{ organizationId: Id<"organizations">; userId: string }> => {
|
||||
const userId = await requireAuthUserId(ctx);
|
||||
const organization = await ctx.db
|
||||
.query("organizations")
|
||||
.withIndex("by_createdBy_and_kind", (q) =>
|
||||
q.eq("createdBy", userId).eq("kind", "personal")
|
||||
)
|
||||
.unique();
|
||||
if (!organization) {
|
||||
throw new ConvexError("Organization not found");
|
||||
}
|
||||
return { organizationId: organization._id, userId };
|
||||
};
|
||||
|
||||
/**
|
||||
* Prove the authenticated user is a member of the project's organization.
|
||||
* Projects are organization-scoped; unauthorized single-object operations
|
||||
* throw "Project not found" to avoid leaking existence.
|
||||
*/
|
||||
export const requireProjectMember = async (
|
||||
ctx: QueryCtx | MutationCtx,
|
||||
projectId: Id<"projects">
|
||||
): Promise<{ organizationId: Id<"organizations">; userId: string }> => {
|
||||
const project = await ctx.db.get(projectId);
|
||||
if (!project) {
|
||||
throw new ConvexError("Project not found");
|
||||
}
|
||||
const userId = await requireOrganizationMember(ctx, project.organizationId);
|
||||
return { organizationId: project.organizationId, userId };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user