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
2 changes: 1 addition & 1 deletion docs/guides/multiple-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ model Post extends Base
viewCount Int @default(0)
}

model ToDo extends Base
model Todo extends Base
{
title String
completed Boolean @default(false)
Expand Down
2 changes: 1 addition & 1 deletion docs/quick-start/authentication/better-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ type Auth {
Here is how you could access it in the access policies:

```tsx
model ToDo {
model Todo {
...
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String? @default(auth().organizationId) @allow('update', false)
Expand Down
2 changes: 1 addition & 1 deletion docs/quick-start/authentication/supabase.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ To get access policies to work, ZenStack needs to be connected to the authentica
This section is only relevant if you're also using Supabase's Database service as the underlying Postgres database of Prisma/ZenStack.
:::

This section is not directly to integrating authentication, but since ZenStack is based on Prisma, understanding how Prisma and Supabase should be set up appropriately when Supabase Auth is involved is important.
This section is not directly related to integrating authentication, but since ZenStack is based on Prisma, understanding how Prisma and Supabase should be set up appropriately when Supabase Auth is involved is important.

Supabase Auth stores user data in a separate Postgres schema called "auth". Since that schema is managed by Supabase, it's good idea not to directly import it into ZModel and use it in your application, since Supabase team may decide to change table schemas at any time. Instead, a better approach is to define your own `User` model in ZModel, and use database triggers to synchronize user data from Supabase Auth to your `User` table.

Expand Down
2 changes: 1 addition & 1 deletion versioned_docs/version-1.x/guides/multiple-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ model Post extends Base
viewCount Int @default(0)
}

model ToDo extends Base
model Todo extends Base
{
title String
completed Boolean @default(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
position: 1
label: Integrating With Authentication
collapsible: true
collapsed: true
150 changes: 150 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/better-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
sidebar_position: 1
---

import PackageInstall from '../../_components/PackageInstall';
import PackageExec from '../../_components/PackageExec';

# Better-Auth Integration

[Better-Auth](https://better-auth.com) is a comprehensive authentication library for TypeScript applications. It supports a wide range of authentication providers and offers a flexible plugin system.

This guide will show you how to integrate better-auth with ZenStack.

## Using ZenStack as better-auth's database provider

ZenStack provides a better-auth database adapter in the `@zenstackhq/better-auth` package. You can use it to configure better-auth to use ZenStack ORM as its database backend.

### Installation

<PackageInstall dependencies={["@zenstackhq/better-auth@next"]} />

### Better-Auth configuration

Add the adapter to your better-auth configuration:

```ts
import { zenstackAdapter } from '@zenstackhq/better-auth';
import { db } from './db'; // your ZenStack ORM client

const auth = new BetterAuth({
database: zenstackAdapter(db, {
provider: 'postgresql',
}),
// other better-auth options...
});
```

### Schema generation

With the adapter set up, you can use better-auth CLI to populate its database models into your ZModel schema. Make sure you've installed the CLI:

<PackageInstall devDependencies={["@better-auth/cli"]} />

Then, run the "generate" command to generate the schema:

<PackageExec command="@better-auth/cli generate" />

Alternatively, you can refer to [better-auth schema documentation](https://www.better-auth.com/docs/concepts/database#core-schema) to manually add the necessary models.

After the schema is configured, you can then use the regular ZenStack database schema migration workflow to push the schema to your database.

## Integrating better-auth with ZenStack's access control

### Creating user-bound ORM client

ZenStack provides a powerful [access control system](../../orm/access-control/) that allows you to define fine-grained access policies for your data models. Enforcing access control often requires fetching the validated current user's identity, which is authentication system's responsibility.

With better-auth, you can use the `auth.api.getSession()` API to get the current session on the server side. The following code shows an example with Next.js:

```tsx
import { betterAuth } from "better-auth";
import { headers } from "next/headers";

export const auth = betterAuth({
//...
})

// calling get session on the server
const {session} = await auth.api.getSession({
headers: await headers() // some endpoint might require headers
});

// get the userId from session data
const userId = session.userId;
```

Then you can pass it to `ZenStackClient`'s `$setAuth()` method to get a user-bound ORM client.

```tsx
const userDb = db.$setAuth({ userId });
```

### Organization plugin support

Better-Auth has a powerful plugin system that allows you to add new features that contribute extensions across the entire stack - data model, backend API, and frontend hooks. A good example is the [Organization plugin](https://www.better-auth.com/docs/plugins/organization), which sets the foundation for implementing multi-tenant apps with access control.

After enabling the Organization plugin and running the CLI to generate the additional models and fields in the schema, you can use the code below on the server side to get the organization info together with the user identity:

```tsx

let organizationRole: string | undefined = undefined;
const organizationId = session.activeOrganizationId;
const org = await auth.api.getFullOrganization({ headers: reqHeaders });
if (org?.members) {
const myMember = org.members.find(
(m) => m.userId === session.userId
);
organizationRole = myMember?.role;
}

// user identity with organization info
const userContext = {
userId: session.userId,
organizationId,
organizationRole,
};
```

Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
```

The user context will be accessible in ZModel policy rules via the special `auth()` function. To get it to work, let's add a type in ZModel to define the shape of `auth()`:

```tsx
type Auth {
userId String @id
organizationId String?
organizationRole String?

@@auth
}
```

Now you can access the organization information in policy rules:

```tsx
model Todo {
...
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String? @default(auth().organizationId)

// deny access that don't belong to the user's active organization
@@deny('all', auth().organizationId != organizationId)

// full access to: list owner, org owner, and org admins
@@allow('all',
auth().userId == ownerId ||
auth().organizationRole == 'owner' ||
auth().organizationRole == 'admin')
}
```

## Sample project

Here is a fully working multi-tenant sample project using better-auth, ZenStack v3, and Next.js:

[https://github.com/ymc9/better-auth-zenstack-multitenancy/tree/zenstack-v3](https://github.com/ymc9/better-auth-zenstack-multitenancy/tree/zenstack-v3)
2 changes: 1 addition & 1 deletion versioned_docs/version-3.x/recipe/postgres-multi-schema.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 1
sidebar_position: 2
---

# Working With PostgreSQL Schemas
Expand Down
4 changes: 3 additions & 1 deletion versioned_docs/version-3.x/reference/plugins/prisma.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ Please note that ZenStack's ORM runtime doesn't depend on Prisma, so you don't n

## Options

- `output`: Specifies the path of the generated Prisma schema file. If a relative path is provided, it will be resolved relative to the ZModel schema.
- `output`

Optional string. Specifies the path of the generated Prisma schema file. If a relative path is provided, it will be resolved relative to the ZModel schema. Defaults to the same directory as the ZModel schema.

## Example

Expand Down
16 changes: 15 additions & 1 deletion versioned_docs/version-3.x/reference/plugins/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,21 @@ The `@core/typescript` plugin generates TypeScript code from ZModel. The generat

## Options

- `output`: Specifies the output directory for the generated TypeScript code. If a relative path is provided, it will be resolved relative to the ZModel schema.
- `output`

Optional string. Specifies the output directory for the generated TypeScript code. If a relative path is provided, it will be resolved relative to the ZModel schema. Defaults to the same directory as the ZModel schema.

- `lite`

Optional boolean. If set to `true`, the plugin will generate a lite version of schema file "schema-lite.ts" with attributes removed, along side with the full schema. The lite schema is suited to be used in frontend code like with the `@zenstackhq/tanstack-query` library. Defaults to `false`.

- `liteOnly`

Optional boolean. If set to `true`, the plugin will only generate the lite version of schema file "schema-lite.ts" with attributes removed, and skip generating the full schema. The lite schema is suited to be used in frontend code like with the `@zenstackhq/tanstack-query` library. Defaults to `false`.

- `importWithFileExtension`

Optional string. Used to control the `import` statements in the generated code. If set to a string value like ".js", the generated code will import from local modules with the specified file extension. This option is useful in ESM projects with certain TypeScript `moduleResolution` mode.

## Output

Expand Down
14 changes: 10 additions & 4 deletions versioned_docs/version-3.x/reference/zmodel/datasource.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,30 @@ datasource NAME {

- **`provider`**:

Name of database connector. Valid values:
Required. Name of database connector. Valid values:

- sqlite
- postgresql

- **`url`**:

Database connection string. Either a plain string or an invocation of `env` function to fetch from an environment variable. For SQLite provider, the URL should be a file protocol, like `file:./dev.db`. For PostgreSQL provider, it should be a postgres connection string, like `postgresql://user:password@localhost:5432/dbname`.
Optional. Database connection string. Either a plain string or an invocation of `env` function to fetch from an environment variable. For SQLite provider, the URL should be a file protocol, like `file:./dev.db`. For PostgreSQL provider, it should be a postgres connection string, like `postgresql://user:password@localhost:5432/dbname`.

The `url` option is only used by the migration engine to connect to the database. The ORM runtime doesn't rely on it. Instead, you provide the connection information when constructing an ORM client.

- **`directUrl`**:

Optional. Connection URL for direct connection to the database.

If you use a connection pooler URL in the url argument, Prisma CLI commands that require a direct connection to the database use the URL in the directUrl argument. This option is passed through to Prisma Migrate for database migrations.

- **`defaultSchema`**:

(PostgreSQL only) The default schema to use for models that don't have an explicit `@@schema` attribute. Defaults to "public". See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details.
Optional. (PostgreSQL only) The default schema to use for models that don't have an explicit `@@schema` attribute. Defaults to "public". See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details.

- **`schemas`**:

(PostgreSQL only) List of schemas to use. If specified, you can use the `@@schema` attribute to specify the schema name for a model. See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details.
Optional. (PostgreSQL only) List of schemas to use. If specified, you can use the `@@schema` attribute to specify the schema name for a model. See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details.

## Example

Expand Down