Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@init/utils": "workspace:*",
"@tanstack/react-query": "^5.90.20",
"convex": "1.31.6",
"convex-helpers": "0.1.111",
"fluent-convex": "0.12.3",
"qte": "0.1.0",
"std-env": "3.10.0"
},
Expand Down
16 changes: 11 additions & 5 deletions packages/backend/src/functions/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
* @module
*/

import type * as auth from "../auth.js";
import type * as crons from "../crons.js";
import type * as http from "../http.js";
import type * as models_documents from "../models/documents.js";
import type * as private_users from "../private/users.js";
import type * as public_auth from "../public/auth.js";
import type * as public_documents from "../public/documents.js";
import type * as public_messages from "../public/messages.js";
import type * as shared_auth_index from "../shared/auth/index.js";
import type * as shared_auth_options from "../shared/auth/options.js";
import type * as shared_auth from "../shared/auth.js";
import type * as shared_convex from "../shared/convex.js";
import type * as shared_env from "../shared/env.js";
import type * as system_health from "../system/health.js";

import type {
ApiFromModules,
Expand All @@ -25,15 +28,18 @@ import type {
} from "convex/server";

declare const fullApi: ApiFromModules<{
auth: typeof auth;
crons: typeof crons;
http: typeof http;
"models/documents": typeof models_documents;
"private/users": typeof private_users;
"public/auth": typeof public_auth;
"public/documents": typeof public_documents;
"public/messages": typeof public_messages;
"shared/auth/index": typeof shared_auth_index;
"shared/auth/options": typeof shared_auth_options;
"shared/auth": typeof shared_auth;
"shared/convex": typeof shared_convex;
"shared/env": typeof shared_env;
"system/health": typeof system_health;
}>;

/**
Expand Down Expand Up @@ -63,7 +69,7 @@ export declare const internal: FilterApi<
>;

export declare const components: {
betterAuth: {
auth: {
adapter: {
create: FunctionReference<
"mutation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { GenericCtx } from "@convex-dev/better-auth"
import { createAuth } from "@init/auth/server"
import type { DataModel } from "#functions/_generated/dataModel.js"
import { authOptions } from "#functions/shared/auth/options.ts"
import { authComponent, createAuthOptions } from "#functions/shared/auth.ts"
import env from "#functions/shared/env.ts"

export { authComponent } from "#functions/shared/auth.ts"

export const { onCreate, onDelete, onUpdate } = authComponent.triggersApi()

export const convexAuth = (ctx: GenericCtx<DataModel>) =>
createAuth({
...authOptions(ctx),
...createAuthOptions(ctx),
baseURL: env.CONVEX_SITE_URL,
secret: env.AUTH_SECRET,
trustedOrigins: env.AUTH_TRUSTED_ORIGINS,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createApi } from "@convex-dev/better-auth"
import schema from "#functions/components/better-auth/schema.ts"
import { authOptions } from "#functions/shared/auth/options.ts"
import { createAuthOptions } from "#functions/shared/auth.ts"
import schema from "./schema"

export const { create, findOne, findMany, updateOne, updateMany, deleteOne, deleteMany } =
createApi(schema, authOptions)
createApi(schema, createAuthOptions)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createAuthOptions is a function that requires a ctx parameter, but it's being passed directly to createApi without being called. This will cause a type error since createApi expects AuthOptions, not a function.

Suggested change
createApi(schema, createAuthOptions)
createApi(schema, createAuthOptions({} as GenericCtx<DataModel>))
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/backend/src/functions/components/better-auth/adapter.ts
Line: 6

Comment:
`createAuthOptions` is a function that requires a `ctx` parameter, but it's being passed directly to `createApi` without being called. This will cause a type error since `createApi` expects `AuthOptions`, not a function.

```suggestion
  createApi(schema, createAuthOptions({} as GenericCtx<DataModel>))
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createAuthOptions requires a ctx parameter but is passed as a reference. This will fail at runtime.

Suggested change
createApi(schema, createAuthOptions)
createApi(schema, createAuthOptions)

The issue is that createApi expects either an AuthOptions object or a function (ctx) => AuthOptions. Since createAuthOptions is defined as (ctx: GenericCtx<DataModel>) => AuthOptions, passing it as a reference should work correctly in the fluent-convex pattern.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/backend/src/functions/components/better-auth/adapter.ts
Line: 6

Comment:
`createAuthOptions` requires a `ctx` parameter but is passed as a reference. This will fail at runtime.

```suggestion
  createApi(schema, createAuthOptions)
```

The issue is that `createApi` expects either an `AuthOptions` object or a function `(ctx) => AuthOptions`. Since `createAuthOptions` is defined as `(ctx: GenericCtx<DataModel>) => AuthOptions`, passing it as a reference should work correctly in the fluent-convex pattern.

How can I resolve this? If you propose a fix, please make it concise.

8 changes: 3 additions & 5 deletions packages/backend/src/functions/components/better-auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type { GenericCtx } from "@convex-dev/better-auth"
import { createAuth } from "@init/auth/server"
import type { DataModel } from "#functions/_generated/dataModel.js"
import { authOptions } from "#functions/shared/auth/options.ts"
import { createAuthOptions } from "#functions/shared/auth.ts"

// Export a static instance for Better Auth schema generation
// Static instance for Better Auth schema generation only
export const auth = createAuth({
// Casting as GenericCtx<DataModel> since the Convex component does not need
// the running context
...authOptions({} as GenericCtx<DataModel>),
...createAuthOptions({} as GenericCtx<DataModel>),
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineComponent } from "convex/server"

const component = defineComponent("betterAuth")
const component = defineComponent("auth")

export default component
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineSchema } from "convex/server"
import { tables } from "#functions/components/better-auth/schema.generated.ts"

export default defineSchema({ ...tables })
export default defineSchema({ ...tables, user: tables.user.index("by_email", ["email"]) })
10 changes: 7 additions & 3 deletions packages/backend/src/functions/http.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { httpRouter } from "convex/server"
import { convexAuth } from "#functions/shared/auth/index.ts"
import { authComponent } from "#functions/shared/auth/options.ts"
import { authComponent, convexAuth } from "#functions/auth.ts"
import env from "#functions/shared/env.ts"

const http = httpRouter()

authComponent.registerRoutes(http, convexAuth)
authComponent.registerRoutes(http, convexAuth, {
cors: {
allowedOrigins: env.AUTH_TRUSTED_ORIGINS,
},
})

export default http
29 changes: 29 additions & 0 deletions packages/backend/src/functions/models/documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { UnauthenticatedError } from "@init/error"
import { v } from "convex/values"
import { protectedQuery, publicQuery } from "#functions/shared/convex.ts"

export const getDocument = publicQuery
.input({ name: v.string() })
.handler(async (ctx, { name }) => {
const document = await ctx.db
.query("documents")
.withIndex("by_name", (q) => q.eq("name", name))
.unique()

return document
})

export const withDocument = protectedQuery.createMiddleware(async (ctx, next) => {
const identity = await ctx.auth.getUserIdentity()

if (!identity) {
throw new UnauthenticatedError()
}

const document = await ctx.db
.query("documents")
.withIndex("by_name", (q) => q.eq("name", identity.subject))
.unique()

return next({ ...ctx, document })
})
12 changes: 5 additions & 7 deletions packages/backend/src/functions/private/users.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import type { UserWithRole } from "@init/auth/server/plugins"
import { convexAuth } from "#functions/shared/auth/index.ts"
import { authComponent } from "#functions/shared/auth/options.ts"
import { authComponent, convexAuth } from "#functions/auth.ts"
import { privateQuery } from "#functions/shared/convex.ts"

export const list = privateQuery({
handler: async (ctx) => {
export const list = privateQuery
.handler(async (ctx) => {
const { auth, headers } = await authComponent.getAuth(convexAuth, ctx)

const result = await auth.api.listUsers({ headers, query: { limit: 100 } })

return result.users as UserWithRole[]
},
})
})
.public()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

privateQuery already handles authentication/authorization via middleware, but ending with .public() exposes this endpoint publicly, bypassing the admin check. This contradicts the intent of using privateQuery.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/backend/src/functions/private/users.ts
Line: 12

Comment:
`privateQuery` already handles authentication/authorization via middleware, but ending with `.public()` exposes this endpoint publicly, bypassing the admin check. This contradicts the intent of using `privateQuery`.

How can I resolve this? If you propose a fix, please make it concise.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.public() on privateQuery exposes this admin-only endpoint publicly, bypassing the withAdmin middleware that checks for admin role. Remove .public().

Suggested change
.public()
})
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/backend/src/functions/private/users.ts
Line: 12

Comment:
`.public()` on `privateQuery` exposes this admin-only endpoint publicly, bypassing the `withAdmin` middleware that checks for admin role. Remove `.public()`.

```suggestion
  })
```

How can I resolve this? If you propose a fix, please make it concise.

10 changes: 10 additions & 0 deletions packages/backend/src/functions/public/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { authComponent } from "#functions/shared/auth.ts"
import { publicQuery } from "#functions/shared/convex.ts"

export const getCurrentUser = publicQuery
.handler(async (ctx) => {
const user = await authComponent.getAuthUser(ctx)

return user ?? null
})
.public()
6 changes: 3 additions & 3 deletions packages/backend/src/functions/public/documents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { publicQuery } from "#functions/shared/convex.ts"

export const list = publicQuery({
handler: async (ctx) => await ctx.db.query("documents").collect(),
})
export const list = publicQuery
.handler(async (ctx) => await ctx.db.query("documents").collect())
.public()
21 changes: 12 additions & 9 deletions packages/backend/src/functions/public/messages.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { publicQuery, vv } from "#functions/shared/convex.ts"
import { v } from "convex/values"
import { publicQuery } from "#functions/shared/convex.ts"

export const list = publicQuery({
args: { documentId: vv.id("documents") },
handler: async (ctx, args) =>
await ctx.db
.query("messages")
.withIndex("by_document_id", (q) => q.eq("documentId", args.documentId))
.collect(),
})
export const list = publicQuery
.input({ documentId: v.id("documents") })
.handler(
async (ctx, args) =>
await ctx.db
.query("messages")
.withIndex("by_document_id", (q) => q.eq("documentId", args.documentId))
.collect()
)
.public()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify whether .public() is needed when using publicQuery - this may be redundant or follow a different pattern in fluent-convex.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/backend/src/functions/public/messages.ts
Line: 13

Comment:
Verify whether `.public()` is needed when using `publicQuery` - this may be redundant or follow a different pattern in fluent-convex.

How can I resolve this? If you propose a fix, please make it concise.

Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// Separated this from the main auth.ts file to avoid environment variable
// errors when trying to use the auth options inside the better auth component..

import type { GenericCtx } from "@convex-dev/better-auth"
import type { AuthFunctions, GenericCtx } from "@convex-dev/better-auth"
import type { AuthOptions } from "@init/auth/server"
import { createClient } from "@convex-dev/better-auth"
import { convex } from "@convex-dev/better-auth/plugins"
import { admin, anonymous, organization } from "@init/auth/server/plugins"
import { APP_ID, APP_NAME } from "@init/utils/constants"
import { seconds } from "qte"
import type { DataModel } from "#functions/_generated/dataModel.js"
import { components } from "#functions/_generated/api.js"
import { components, internal } from "#functions/_generated/api.js"
import authConfig from "#functions/auth.config.ts"
import authSchema from "#functions/components/better-auth/schema.ts"

export const authComponent = createClient<DataModel, typeof authSchema>(components.betterAuth, {
const authFunctions: AuthFunctions = internal.auth

export const authComponent = createClient<DataModel, typeof authSchema>(components.auth, {
authFunctions,
local: { schema: authSchema },
})

export const authOptions = (ctx: GenericCtx<DataModel>) =>
export const createAuthOptions = (ctx: GenericCtx<DataModel>) =>
({
advanced: {
cookiePrefix: APP_ID,
Expand Down
Loading