Skip to main content ホーム クリエイター kunanonj ai-skills-hub cursor-plugin-convex-rule-custom-functions-for-auth
cursor-plugin-convex-rule-custom-functions-for-auth Use custom functions for data protection - this is Convex's alternative to Row Level Security (RLS)
インストールへ移動 Skills Marketplace コミュニティが作成したAIスキルを発見・探索
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/KunanonJ/ai-skills-hub --skill cursor-plugin-convex-rule-custom-functions-for-authコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
Zipをダウンロード ダウンロード中... cursor-plugin-cf-agents-sdk Build AI agents on Cloudflare Workers using the Agents SDK. Load when creating stateful agents, durable workflows, real-time WebSocket apps, scheduled tasks, MCP servers, chat applications, voice agents, or browser automation. Covers Agent class, state management, callable RPC, Workflows, durable execution, queues, retries, observability, and React hooks. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.
cursor-plugin-cf-cloudflare Comprehensive Cloudflare platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), feature flags (Flagship), networking (Tunnel, Spectrum), security (WAF, DDoS), and infrastructure-as-code (Terraform, Pulumi). Use for any Cloudflare development task. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.
name cursor-plugin-convex-rule-custom-functions-for-auth description Use custom functions for data protection - this is Convex's alternative to Row Level Security (RLS) metadata {"version":"0.1.0"}
Custom Functions for Data Protection
Convex's approach to data protection: Instead of Row Level Security (RLS) like PostgreSQL, use custom functions to wrap all queries and mutations with automatic auth and access control.
Why Custom Functions, Not RLS?
Traditional databases (PostgreSQL):
Use Row Level Security policies
SQL-based access rules
Runs at database layer
Complex policy syntax
Convex approach:
Use custom function wrappers
TypeScript-based access logic
Runs at application layer
Full type safety and flexibility
The Pattern
Instead of writing auth checks in every function:
❌ Bad: Repeating Auth Everywhere
export const getTasks = query ({
handler : async (ctx) => {
const identity = await ctx.auth .getUserIdentity ();
if (!identity) ( );
user = (ctx, identity);
ctx. . ( )
. ( , q. ( , user. ))
. ();
},
});
getProjects = ({
: (ctx) => {
identity = ctx. . ();
(!identity) ( );
user = (ctx, identity);
ctx. . ( )
. ( , q. ( , user. ))
. ();
},
});
throw
new
Error
"Not authenticated"
const
await
getUser
return
await
db
query
"tasks"
withIndex
"by_user"
q =>
eq
"userId"
_id
collect
export
const
query
handler
async
const
await
auth
getUserIdentity
if
throw
new
Error
"Not authenticated"
const
await
getUser
return
await
db
query
"projects"
withIndex
"by_user"
q =>
eq
"userId"
_id
collect
✅ Good: Custom Function Wrapper
import { customQuery, customMutation } from "convex-helpers/server/customFunctions" ;
import { query, mutation } from "../_generated/server" ;
import { getCurrentUser } from "./auth" ;
export const authedQuery = customQuery (query, {
args : {},
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
return { ctx : { ...ctx, user }, args };
},
});
export const authedMutation = customMutation (mutation, {
args : {},
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
return { ctx : { ...ctx, user }, args };
},
});
export const getTasks = authedQuery ({
handler : async (ctx) => {
return await ctx.db .query ("tasks" )
.withIndex ("by_user" , q => q.eq ("userId" , ctx.user ._id ))
.collect ();
},
});
export const getProjects = authedQuery ({
handler : async (ctx) => {
return await ctx.db .query ("projects" )
.withIndex ("by_user" , q => q.eq ("userId" , ctx.user ._id ))
.collect ();
},
});
Common Data Protection Patterns
1. Basic Authentication export const authedQuery = customQuery (query, {
args : {},
input : async (ctx, args) => {
const identity = await ctx.auth .getUserIdentity ();
if (!identity) throw new Error ("Not authenticated" );
const user = await ctx.db
.query ("users" )
.withIndex ("by_token" , q =>
q.eq ("tokenIdentifier" , identity.tokenIdentifier )
)
.unique ();
if (!user) throw new Error ("User not found" );
return { ctx : { ...ctx, user }, args };
},
});
2. Role-Based Access Control (RBAC)
export const adminQuery = customQuery (query, {
args : {},
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
if (user.role !== "admin" ) {
throw new Error ("Admin access required" );
}
return { ctx : { ...ctx, user }, args };
},
});
export const getAllUsers = adminQuery ({
handler : async (ctx) => {
return await ctx.db .query ("users" ).collect ();
},
});
3. Multi-Tenant Access Control export const tenantQuery = customQuery (query, {
args : { organizationId : v.id ("organizations" ) },
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
const membership = await ctx.db
.query ("organizationMembers" )
.withIndex ("by_org_and_user" , q =>
q.eq ("organizationId" , args.organizationId )
.eq ("userId" , user._id )
)
.unique ();
if (!membership) {
throw new Error ("Not a member of this organization" );
}
return {
ctx : {
...ctx,
user,
organizationId : args.organizationId ,
role : membership.role
},
args
};
},
});
export const getOrgProjects = tenantQuery ({
args : { organizationId : v.id ("organizations" ) },
handler : async (ctx, args) => {
return await ctx.db
.query ("projects" )
.withIndex ("by_organization" , q =>
q.eq ("organizationId" , ctx.organizationId )
)
.collect ();
},
});
4. Resource Ownership export const ownerQuery = customQuery (query, {
args : { resourceId : v.id ("resources" ) },
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
const resource = await ctx.db .get (args.resourceId );
if (!resource) throw new Error ("Resource not found" );
if (resource.ownerId !== user._id ) {
throw new Error ("You don't own this resource" );
}
return {
ctx : { ...ctx, user, resource },
args
};
},
});
export const getResourceDetails = ownerQuery ({
args : { resourceId : v.id ("resources" ) },
handler : async (ctx, args) => {
return ctx.resource ;
},
});
5. Team/Group Based Access export const teamQuery = customQuery (query, {
args : { teamId : v.id ("teams" ) },
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
const membership = await ctx.db
.query ("teamMembers" )
.withIndex ("by_team_and_user" , q =>
q.eq ("teamId" , args.teamId )
.eq ("userId" , user._id )
)
.unique ();
if (!membership) {
throw new Error ("Not a member of this team" );
}
return {
ctx : {
...ctx,
user,
teamId : args.teamId ,
permissions : membership.permissions
},
args
};
},
});
export const getTeamData = teamQuery ({
args : { teamId : v.id ("teams" ) },
handler : async (ctx) => {
return await ctx.db
.query ("teamData" )
.withIndex ("by_team" , q => q.eq ("teamId" , ctx.teamId ))
.collect ();
},
});
6. Read/Write Separation
export const viewerQuery = customQuery (query, {
args : { teamId : v.id ("teams" ) },
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
const member = await ctx.db
.query ("teamMembers" )
.withIndex ("by_team_and_user" , q =>
q.eq ("teamId" , args.teamId ).eq ("userId" , user._id )
)
.unique ();
if (!member) throw new Error ("Not a team member" );
return { ctx : { ...ctx, user, teamId : args.teamId }, args };
},
});
export const editorMutation = customMutation (mutation, {
args : { teamId : v.id ("teams" ) },
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
const member = await ctx.db
.query ("teamMembers" )
.withIndex ("by_team_and_user" , q =>
q.eq ("teamId" , args.teamId ).eq ("userId" , user._id )
)
.unique ();
if (!member || (member.role !== "editor" && member.role !== "admin" )) {
throw new Error ("Editor access required" );
}
return { ctx : { ...ctx, user, teamId : args.teamId }, args };
},
});
7. Public vs Private Data
export const publicQuery = query;
export const privateQuery = authedQuery;
export const listPublicPosts = publicQuery ({
handler : async (ctx) => {
return await ctx.db
.query ("posts" )
.withIndex ("by_published" , q => q.eq ("published" , true ))
.collect ();
},
});
export const listMyDrafts = privateQuery ({
handler : async (ctx) => {
return await ctx.db
.query ("posts" )
.withIndex ("by_author" , q => q.eq ("authorId" , ctx.user ._id ))
.filter (q => q.eq (q.field ("published" ), false ))
.collect ();
},
});
File Organization convex/
├── lib/
│ ├── auth.ts # getCurrentUser helper
│ └── customFunctions.ts # All custom wrappers
├── users.ts # Public functions
├── tasks.ts # Use authedQuery/authedMutation
├── admin.ts # Use adminQuery/adminMutation
└── organizations.ts # Use tenantQuery/tenantMutation
Benefits vs RLS Aspect Custom Functions (Convex) Row Level Security (PostgreSQL) Language TypeScript SQL Type Safety Full Limited Complexity Medium High Flexibility Very High Medium Testing Easy (unit tests) Hard (DB-level) Debugging Standard debugging DB logs Reusability High (compose wrappers) Medium
Installation npm install convex-helpers
Complete Example: Multi-Tenant SaaS
import { customQuery, customMutation } from "convex-helpers/server/customFunctions" ;
import { query, mutation } from "../_generated/server" ;
import { getCurrentUser } from "./auth" ;
export const authedQuery = customQuery (query, {
args : {},
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
return { ctx : { ...ctx, user }, args };
},
});
export const authedMutation = customMutation (mutation, {
args : {},
input : async (ctx, args) => {
const user = await getCurrentUser (ctx);
return { ctx : { ...ctx, user }, args };
},
});
export const orgQuery = customQuery (authedQuery, {
args : { orgId : v.id ("organizations" ) },
input : async (ctx, args) => {
const member = await ctx.db
.query ("members" )
.withIndex ("by_org_and_user" , q =>
q.eq ("orgId" , args.orgId ).eq ("userId" , ctx.user ._id )
)
.unique ();
if (!member) throw new Error ("Not a member" );
return {
ctx : { ...ctx, orgId : args.orgId , role : member.role },
args
};
},
});
export const orgMutation = customMutation (authedMutation, {
args : { orgId : v.id ("organizations" ) },
input : async (ctx, args) => {
const member = await ctx.db
.query ("members" )
.withIndex ("by_org_and_user" , q =>
q.eq ("orgId" , args.orgId ).eq ("userId" , ctx.user ._id )
)
.unique ();
if (!member) throw new Error ("Not a member" );
return {
ctx : { ...ctx, orgId : args.orgId , role : member.role },
args
};
},
});
export const orgAdminMutation = customMutation (orgMutation, {
args : { orgId : v.id ("organizations" ) },
input : async (ctx, args) => {
if (ctx.role !== "admin" ) {
throw new Error ("Admin access required" );
}
return { ctx, args };
},
});
Key Takeaways
Custom functions ARE Convex's RLS — This is the recommended pattern
Define once, use everywhere — Create wrappers for your access patterns
Compose wrappers — Build complex access control by layering
Type-safe — Full TypeScript support, unlike SQL policies
Testable — Easy to unit test your auth logic
Flexible — Can implement any access control pattern
Checklist
Learn More