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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Better-Auth
---

import PackageInstall from '../../_components/PackageInstall';
Expand All@@ -25,7 +26,9 @@ Add the adapter to your better-auth configuration:

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

// ZenStack ORM client
import { db } from './db';

const auth = new BetterAuth({
database: zenstackAdapter(db, {
Expand All@@ -45,6 +48,8 @@ Then, run the "generate" command to generate the schema:

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

You should see models like `User`, `Session`, and `Account` added to your `schema.zmodel` file if they don't already exist.

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.
Expand DownExpand Up@@ -77,7 +82,10 @@ 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 });
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

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

### Organization plugin support
Expand DownExpand Up@@ -109,7 +117,7 @@ After enabling the Organization plugin and running the CLI to generate the addit
Then you can use the full `userContext` object to get a user-bound client.

```tsx
const userDb = db.$setAuth(userContext);
const userDb = authDb.$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()`:
Expand Down
68 changes: 68 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/clerk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
description: Integrating with Clerk.
sidebar_position: 2
sidebar_label: Clerk
---

# Clerk Integration

[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components. This guide will show you how to integrate Clerk with ZenStack's [access control system](../../orm/access-control/).

## Set up Clerk

First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project if you haven't already.

## Adjust your ZModel

Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:

You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object when calling `ZenStackClient`'s `$setAuth()` method.

The following code shows an example blog post schema:

```zmodel
type User {
id String @id

@@auth
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
published Boolean @default(false)
authorId String // stores Clerk's user ID

// author has full access
@@allow('all', auth() != null && auth().id == authorId)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.

## Create a user-bound ORM client

When using ZenStack's built-in access control, you often use the `auth()` function in policy rules to reference the current user's identity. The evaluation of `auth()` at runtime requires you to call the `$setAuth()` method and pass in the validated user identity from Clerk.

Please refer to clerk's documentation on how to fetch the current user on the server side for your specific framework. The following code shows an example for Next.js (app router):

```ts
import { auth } from "@clerk/nextjs/server";

// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

async function getUserDb() {
// get the validated user identity from Clerk
const authObject = await auth();

// create a user-bound ORM client
return authDb.$setAuth(
authObject.userId ? { id: authObject.userId } : undefined);
}
```
59 changes: 59 additions & 0 deletions versioned_docs/version-3.x/recipe/auth-integration/custom.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
---
description: Integrating with a custom authentication system.
sidebar_position: 100
sidebar_label: Custom Authentication
---

# Custom Authentication Integration

You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.

## Determine what's needed for access control

The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.

The `auth()` call needs to be resolved to a "model" or "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.

```zmodel
model User {
id String @id
role String
permissions String[]
...
}
```

ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.

```zmodel
type Auth {
id String @id
role String
permissions String[]

@@auth
}
```

Just remember that any thing that you access from `auth().` must be resolved.

## Fetch the current user along with the additional information

At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:

1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
2. If you use a dedicated authentication service, you can call it to get the current user's information.

You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.

## Create a user-bound ORM client

Finally, you can call the `$setAuth()` method on `ZenStackClient` to create an ORM instance that's bound to the current user.

```ts
// ZenStack ORM client with access policy plugin installed
import { authDb } from './db';

const user = await getCurrentUser(); // your implementation
const db = authDb.$setAuth(user);
```