# CloudBase NoSQL Database Security Rules

This document covers how to configure security rules for CloudBase NoSQL database collections to control read/write permissions.

## Overview

**Important:** To control database permissions, you **MUST** use `managePermissions(action="updateResourcePermission")` to configure security rules. Security rule changes take effect after a few minutes due to caching.

**General Rule:** In most cases, use **simple permissions** (READONLY, PRIVATE, ADMINWRITE, ADMINONLY). Only use CUSTOM rules when you need fine-grained control.

**Scope note:** The detailed semantics in this document apply only to CloudBase **NoSQL database collections** with `resourceType: "noSqlDatabase"`. Examples such as `doc._openid`, `auth.openid`, query-condition subset validation, and `create` / `update` / `delete` JSON rule templates are **not** generic rules for `function`, `storage`, or `sqlDatabase` resources.

**Official references:**
- General security rules overview: `https://cloud.tencent.com/document/product/876/41802`
- NoSQL database security rules: `https://docs.cloudbase.net/database/security-rules`
- Cloud function security rules: `https://docs.cloudbase.net/cloud-function/security-rules`
- Storage security rules: `https://docs.cloudbase.net/storage/security-rules`

### Critical Understanding: Query Condition Requirements

**Security rules are validation-based, NOT filter-based.**

For query or update operations, the **input query conditions must be a subset of the security rules**. The system does **not** actually fetch data from the database. Instead, it validates whether the input query conditions form a subset of the security rules. If the query conditions are not a subset of the rules, it indicates an attempt to access data without permission, and the operation will be **directly rejected**.

**Example:**
- If you define a read/write rule: `auth.openid == doc._openid`
- This means the query condition's `_openid` must equal the current user's `openid` (provided by the system-assigned, non-tamperable `auth.openid`)
- If the query condition doesn't include this constraint, it indicates an attempt to access records where `_openid` is not equal to the user's own, which will be rejected by the backend

**Key Points:**
- Security rules **validate** queries, they don't **filter** results
- The system performs **rule matching** before any database access
- Query conditions must match or be more restrictive than the security rule
- Missing required conditions in queries will result in permission denied errors
- For **create** operations, the system validates the **written data** against the security rules, not query conditions

## Data Permission Management System

CloudBase provides a multi-layered data permission management mechanism that ensures data security while meeting different business scenario permission control requirements.

### Permission Management Hierarchy

CloudBase data permission management includes two levels:

| Permission Type | Control Granularity | Applicable Scenarios | Configuration Complexity |
|----------------|---------------------|----------------------|--------------------------|
| **Basic Permission Control** | Collection level | Simple permission needs | Low |
| **Security Rules** | Document level | Complex business logic | High |

### Basic Permission Control

**Configuration Method:**
Configure permissions for each collection in the [CloudBase Platform](https://tcb.cloud.tencent.com/dev) collection management page.

**Permission Options:**

| Permission Type | Applicable Scenarios | Usage Recommendation |
|----------------|----------------------|----------------------|
| **Read all data, modify own data** | Public content, such as articles, products | Suitable for content display applications |
| **Read and modify own data** | Private data, such as user profiles | Suitable for personal information management |
| **Read all data, cannot modify** | Configuration data, such as system settings | Suitable for read-only configuration and reference data |
| **No permission** | Sensitive data, such as financial information | Suitable for sensitive data requiring server-side processing |

### Security Rules (CUSTOM)

**Function Overview:**

Security rules provide more flexible, extensible, and fine-grained permission control capabilities, supporting dynamic permission judgment based on document content.

**Core Features:**
- **Document-level control**: Can decide access permissions based on specific document content
- **Expression-driven**: Uses programming-like expressions to define permission logic
- **Dynamic permissions**: Supports dynamic permission judgment based on user identity, time, and data content
- **Client-only restriction**: Only restricts client user access, does not affect server-side (cloud function) operations

**Configuration Entry:**
Configure security rules in the [CloudBase Platform/Database](https://tcb.cloud.tencent.com/dev#/db/doc/model) collection management page.

## Permission Categories

CloudBase provides two types of permissions:

### 1. Simple Permissions (Recommended for Most Cases)

These are pre-configured permission templates that cover most common scenarios:

- **READONLY**: All users can read, only creator and admin can write
- **PRIVATE**: Only creator and admin can read/write
- **ADMINWRITE**: All users can read, only admin can write
- **ADMINONLY**: Only admin can read/write

### 2. Custom Security Rules (CUSTOM)

Use CUSTOM when you need fine-grained control based on document data, user identity, or complex conditions.

## Configuring Security Rules

### Using MCP Tool `managePermissions`

**Important:** When developing applications that need permission control, you **MUST** call `managePermissions(action="updateResourcePermission")` to configure database security rules. Do not assume permissions are already configured.

Compatibility note:
- Canonical plugin name: `permissions`
- Legacy plugin aliases `security-rule`, `security-rules`, `secret-rule`, `secret-rules`, and `access-control` still resolve to the `permissions` plugin
- Legacy tools `readSecurityRule` and `writeSecurityRule` are removed; use `queryPermissions` and `managePermissions`

**Scope reminder:** The examples below are for `resourceType: "noSqlDatabase"` only. Do not reuse NoSQL-only expressions such as `doc._openid`, `auth.openid`, query-subset validation, or `create` / `update` / `delete` rule templates as generic guidance for `function`, `storage`, or `sqlDatabase` permissions.

**Basic Usage:**

```javascript
// Example: Set simple permission (PRIVATE)
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "collectionName",  // Collection name
  permission: "PRIVATE",
  // securityRule parameter not needed for simple permissions
});
```

**Cache Notice:** After configuring security rules, changes take effect after a few minutes (typically 2-5 minutes) due to caching. Wait a few minutes before testing the new rules.

### Simple Permission Examples

```javascript
// Example 1: Public read, creator-only write
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "posts",
  permission: "READONLY"
});

// Example 2: Private collection (only creator and admin)
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "userSettings",
  permission: "PRIVATE"
});

// Example 3: Public read, admin-only write
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "announcements",
  permission: "ADMINWRITE"
});

// Example 4: Admin-only access
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "adminLogs",
  permission: "ADMINONLY"
});
```

## Custom Security Rules (CUSTOM)

### When to Use CUSTOM

Use CUSTOM rules when you need:
- User-specific data access (e.g., users can only read/write their own documents)
- Complex conditions based on document fields
- Time-based access control
- Role-based permissions

### Custom Rule Format

Custom security rules use JSON structure with operation types as keys and conditions as values:

```json
{
  "read": "<condition>",
  "write": "<condition>",
  "create": "<condition>",
  "update": "<condition>",
  "delete": "<condition>"
}
```

**Operation Types:**

| Operation Type | Description | Default Value | Example Scenarios |
|----------------|-------------|---------------|-------------------|
| **read** | Read documents | `false` | Query, get documents |
| **write** | Write documents (general) | `false` | Default rule when specific write operations are not specified |
| **create** | Create documents | Inherits `write` | Add new data |
| **update** | Update documents | Inherits `write` | Modify existing data |
| **delete** | Delete documents | Inherits `write` | Delete data |

> 💡 Note: If specific write operation rules (create/update/delete) are not specified, the `write` rule will be automatically used.

**Condition Values:**
- `true` or `false`: Simple boolean permission
- Expression string: JavaScript-like expression that evaluates to true/false

### Predefined Variables (Global Variables)

Custom rules can use these predefined variables:

| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `auth` | Object | User authentication info (null if not logged in) | `auth.openid`, `auth.uid` |
| `doc` | Object | Document data or query conditions | `doc.userId`, `doc.status` |
| `request` | Object | Request information | `request.data` |
| `now` | Number | Current timestamp in milliseconds | `now > doc.expireTime` |

**User Identity Information (auth):**

| Field | Type | Description | Applicable Scenarios |
|-------|------|-------------|---------------------|
| **openid** | String | WeChat user OpenID | WeChat Mini Program login |
| **uid** | String | User unique ID | Web login |
| **loginType** | String | Login method | Distinguish different login channels |

**LoginType Values:**
- `WECHAT_PUBLIC`: WeChat Official Account
- `WECHAT_OPEN`: WeChat Open Platform
- `ANONYMOUS`: Anonymous login (disabled by default for new environments)
- `EMAIL`: Email login
- `CUSTOM`: Custom login

**Request Object:**
- `request.data`: Data object passed in the request (only available for create/update operations)

**Doc Object:**
- Contains all fields of the current document being accessed
- For queries, `doc` represents the query conditions

**Important: `_openid` Field Management**

The `_openid` field is **automatically managed by the CloudBase SDK** and should **never** be included in write operations:

- **Automatic Assignment**: When performing create, update, or set operations through the SDK, the system automatically writes the `_openid` field based on the current authenticated user's identity
- **Do Not Include**: The `_openid` field should **not** appear in any `data` parameter for write operations (`.add()`, `.update()`, `.set()`)
- **Error on Manual Setting**: If you manually include or modify `_openid` in write operations, the operation will **fail with an error**
- **Security Rules Usage**: In security rules, you can reference `doc._openid` to check the document's owner, but you cannot modify it through write operations

**Example:**
```javascript
// Correct: Do not include _openid in write operations
await db.collection('todos').add({
    title: 'My Todo',
    completed: false
    // _openid is automatically added by SDK
});

// Wrong: Including _openid will cause an error
await db.collection('todos').add({
    title: 'My Todo',
    _openid: 'some-id'  // ERROR: Cannot manually set _openid
});

// Correct: Use _openid in security rules for permission checks
{
    "read": "doc._openid == auth.openid",
    "write": "doc._openid == auth.openid"
}
```

### Custom Rule Examples

**Example 1: User can only read/write their own documents**

```javascript
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "userTodos",
  permission: "CUSTOM",
  securityRule: JSON.stringify({
    "read": "auth.uid == doc.user_id",
    "write": "auth.uid == doc.user_id"
  })
});
```

**Example 2: Public read, authenticated users can create, only owner can update/delete**

```javascript
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "publicPosts",
  permission: "CUSTOM",
  securityRule: JSON.stringify({
    "read": true,
    "create": "auth != null",
    "update": "auth.uid == doc.author_id",
    "delete": "auth.uid == doc.author_id"
  })
});
```

> Warning: This owner-only pattern is not the best fit for CMS article collections that need app-level admin override. For article collections with admin override, prefer a `CUSTOM` rule that combines `get('database.user_roles.' + auth.uid).role == 'admin'` with `doc.authorId == auth.uid`, and keep frontend writes on `.doc(id).update()` / `.doc(id).remove()`.

**Example 2A: Keep owner-only CUSTOM rule and switch the client write path to `where(...)`**

```javascript
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "publicPosts",
  permission: "CUSTOM",
  securityRule: JSON.stringify({
    "read": true,
    "create": "auth != null",
    "update": "doc.author_id == auth.uid",
    "delete": "doc.author_id == auth.uid"
  })
});
```

And update through an explicit owner subset:

```javascript
await db.collection('publicPosts')
  .where({
    _id: postId,
    author_id: '{openid}'
  })
  .update({ title: 'Updated Title' });
```

**Example 3: Prevent price modification on update**

```javascript
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "orders",
  permission: "CUSTOM",
  securityRule: JSON.stringify({
    "read": "auth.uid == doc.user_id",
    "create": "auth != null",
    "update": "auth.uid == doc.user_id && (doc.price == request.data.price || request.data.price == undefined)",
    "delete": false
  })
});
```

**Example 4: Admin-only delete, users can read/write their own**

```javascript
await managePermissions({
  action: "updateResourcePermission",
  resourceType: "noSqlDatabase",
  resourceId: "userData",
  permission: "CUSTOM",
  securityRule: JSON.stringify({
    "read": "auth.uid == doc.user_id",
    "write": "auth.uid == doc.user_id",
    "delete": false  // Only admin can delete (admin bypasses rules)
  })
});
```

### Expression Syntax

**Expression Length Limit:** Expressions are pseudo-code statements. When configuring, expressions cannot be too long. A single expression is limited to **1024 characters**.

Custom rules support JavaScript-like expressions:

**Supported Operators:**

| Operator | Description | Example | Example Explanation (Collection Query) |
|----------|-------------|---------|----------------------------------------|
| **==** | Equal to | `auth.uid == 'zzz'` | User's uid is zzz |
| **!=** | Not equal to | `auth.uid != 'zzz'` | User's uid is not zzz |
| **>** | Greater than | `doc.age > 10` | Query condition's age property is greater than 10 |
| **>=** | Greater than or equal | `doc.age >= 10` | Query condition's age property is greater than or equal to 10 |
| **<** | Less than | `doc.age < 10` | Query condition's age property is less than 10 |
| **<=** | Less than or equal | `doc.age <= 10` | Query condition's age property is less than or equal to 10 |
| **in** | Exists in collection | `auth.uid in ['zzz','aaa']` | User's uid is one of ['zzz','aaa'] |
| **!(xx in [])** | Does not exist in collection | `!(auth.uid in ['zzz','aaa'])` | User's uid is not any of ['zzz','aaa'] |
| **&&** | Logical AND | `auth.uid == 'zzz' && doc.age > 10` | User's uid is zzz AND query condition's age property is greater than 10 |
| **\|\|** | Logical OR | `auth.uid == 'zzz' \|\| doc.age > 10` | User's uid is zzz OR query condition's age property is greater than 10 |
| **.** | Object element access | `auth.uid` | User's uid |
| **[]** | Array access operator | `get('database.collection_a.user')[auth.uid] == 'zzz'` | In collection_a, document with id 'user', key is user uid, property value is zzz |

### Supported Database Commands

Security rules support the following database commands:

**Logic Commands:**

| Command | Description |
|---------|-------------|
| `or` | `\|\|` Logical OR |
| `and` | `&&` Logical AND |

**Query Commands:**

| Command | Description |
|---------|-------------|
| `eq` | `==` |
| `ne` / `neq` | `!=` |
| `gt` | `>` |
| `gte` | `>=` |
| `lt` | `<` |
| `lte` | `<=` |
| `in` | `in` |
| `nin` | `!(in [])` |

**Update Commands:**

| Command | Description |
|---------|-------------|
| `set` | Overwrite write, `{key: set(object)}` |
| `remove` | Delete field, `{key: remove()}` |

**Example Expressions:**
```javascript
// User ID matches document owner
"auth.uid == doc.user_id"

// User is authenticated
"auth != null"

// User ID in allowed list
"auth.uid in ['admin1', 'admin2']"

// Complex condition
"auth.uid == doc.user_id && doc.status == 'active'"

// Price not modified or undefined
"doc.price == request.data.price || request.data.price == undefined"
```

### Built-in Functions

#### get() Function: Cross-Document Permission Verification

**Function Description:**

The `get()` function allows accessing other document data during permission verification, enabling complex cross-document permission control.

**Syntax:** `get('database.collectionName.documentId')`

**Usage Examples:**

**Role-based Permission Control:**
```json
{
  "read": "get('database.user_roles.' + auth.uid).role in ['admin', 'editor']",
  "write": "get('database.user_roles.' + auth.uid).role == 'admin'"
}
```

**Important syntax note:** put the field access **after** `get(...)`.

```json
{
  "write": "get('database.user_roles.' + auth.uid).role == 'admin'"
}
```

Do **not** write:

```json
{
  "write": "get('database.user_roles.' + auth.uid + '.role') == 'admin'"
}
```

Do **not** use JS template-literal placeholders inside the rule string either:

```json
{
  "write": "get('database.user_roles.${auth.uid}').role == 'admin'"
}
```

Security rules are expression strings, so use concatenation:

```json
{
  "write": "get('database.user_roles.' + auth.uid).role == 'admin'"
}
```

**Admin-or-owner control:**
```json
{
  "update": "get('database.users.' + auth.uid).role == 'admin' || doc.authorId == auth.uid",
  "delete": "get('database.users.' + auth.uid).role == 'admin' || doc.authorId == auth.uid"
}
```

If this collection only needs simple owner-only writes, `READONLY` may be enough. But if the product requirement is “admin users in the app can edit/delete all articles while editors only own their own articles”, use a `CUSTOM` rule such as:

```json
{
  "read": "auth.uid != null",
  "create": "auth.uid != null",
  "update": "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)",
  "delete": "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)"
}
```

For that CMS pattern, `.doc(id).update()` / `.doc(id).remove()` is a validated path, as long as article documents really store `authorId` and `user_roles` documents are keyed by `uid`.

**Associated Data Permissions:**
```json
{
  "read": "auth.uid == get('database.projects.' + doc.projectId).owner"
}
```

**Usage Limitations:**

> **Important:** When using the `get()` function, note the following limitations:
- **Variable restrictions in get parameters**: Variables `doc` that exist in get parameters must appear in query conditions in `==` or `in` format. If using `in` format, only `in` with a single value is allowed, i.e., `doc.shopId in array, array.length == 1`
- Maximum 3 `get` functions per expression
- Maximum access to 10 different documents
- Maximum nesting depth of 2 levels (i.e., `get(get(path))`)
- Generates additional database read operations (billed)

**Billing Notes:**

> **Important:** Security rules themselves are not charged, but additional data access by security rules will be counted in billing:
- **get() function**: Each `get()` produces additional data access
- **Document ID queries for all write operations**: All write operations for document ID queries produce one data access
- **Variable usage**: When not using variables, each `get()` produces one read operation. When using variables, each `get()` produces one read operation for each variable value. For example: rule `get(\`database.collection.${doc._id}\`).test`, when querying `_.or([{_id:1},{_id:2},{_id:3},{_id:4},{_id:5}])` will produce 5 reads. The system will cache reads for the same doc and field.

**Important:** Using `get()` or accessing `doc` counts toward database quota as it reads from the service.

## Best Practices

### 1. Rule Design Principles

- **Principle of Least Privilege:** Only grant necessary permissions
- **Clarity:** Rule expressions should be clear and understandable
- **Performance Considerations:** Avoid excessive `get()` function calls

### 2. General Best Practices

1. **Prefer Simple Permissions:** Use READONLY, PRIVATE, ADMINWRITE, or ADMINONLY for most cases
2. **Use CUSTOM Sparingly:** Only when you need fine-grained control
3. **Test After Configuration:** Wait a few minutes for cache to clear before testing
4. **Avoid Complex Expressions:** Keep custom rules simple and readable
5. **Document Your Rules:** Comment complex rules for future maintenance
6. **Handle Errors:** Always handle permission denied errors in your application code

### 3. Debugging Tips

- Start with simple rules and gradually increase complexity
- Fully test various scenarios in the development environment
- Pay attention to permission error messages in the console
- Reasonably use logs to record permission verification processes

**CRITICAL ERROR: Using ADMINWRITE with Frontend SDK**

| Error Scenario | Symptoms | Root Cause | Correct Approach |
|---------------|----------|------------|------------------|
| Using `ADMINWRITE` for cart/order collections | `.add()` or `.update()` fails<br>Keeps loading or permission error | "ADMIN" in `ADMINWRITE` refers to cloud function environment<br>Frontend SDK has no admin privileges | Use `CUSTOM` rules<br>`{"read": "auth.uid != null", "write": "auth.uid != null"}` |
| Using `PRIVATE` for product collections | Product list disappears after login | `PRIVATE` only allows creator and admin to read<br>Regular users have no permission | Use `READONLY`<br>All users can read, creator and admin can write |

**Key Understanding**:
- `ADMINWRITE` = Cloud functions have write access, Frontend SDK **can only read**
- `CUSTOM` = Configurable read/write permissions for Frontend SDK
- `READONLY` = All users (including anonymous) can read, creator and admin can write. Note: although `READONLY` permits anonymous reads at the ACL level, anonymous login is disabled by default for new environments — callers still need an active login method to obtain a session.

### Role-Based Access Limitations

Security rules work **per request** and cannot selectively grant access to “some” users while denying others unless those users belong to the same ownership context. Typical examples that fail:

- Allowing customer service reps to view **all** orders while normal users only see their own
- Granting merchandisers permission to edit every product while other employees cannot

For these scenarios:

1. Keep frontend collections locked down with `CUSTOM` rules that restrict users to their own data
2. Build **management console APIs** with **cloud functions** (CloudBase Run or functions)
3. Cloud functions bypass security rules, so they can read/write all data safely based on backend authentication/authorization

> TL;DR: **Frontend SDK permissions ≠ backend role management.** If a role needs global data access (e.g., admin dashboard), implement it via cloud functions and never expose that data directly through frontend security rules.

## Query Restrictions and Optimization

### Valid Queries

In actual use, queries are mainly divided into two types: **document ID queries** and **collection queries**.

- **Document ID queries**: Specify a single document ID through `doc` conditions
- **Collection queries**: Can be queries through `where` conditions or aggregate search `match` restriction conditions. For aggregate search, only the first `match` restriction condition is matched.

### Query Condition Requirements

**Critical: Rule Matching Mechanism**

For query or update operations, the input query conditions **must be a subset** of the security rules. The system does **not** actually fetch data from the database. Instead, it validates whether the input query conditions form a subset of the security rules. If the query conditions are not a subset of the rules, it indicates an attempt to access data without permission, and the operation will be **directly rejected**.

**Key Points:**
- Security rules **validate** queries, they don't **filter** results
- Query conditions must match or be more restrictive than the security rule
- Missing required conditions in queries will result in permission denied errors
- The system performs **rule matching** before any database access

**Critical implication for document-ID writes:**

- `.doc(id).update(...)` and `.doc(id).remove()` only provide `_id` as the write condition
- A rule that depends on another field such as `doc.authorId` or `doc.status` cannot be validated from that request alone
- For document-ID writes, prefer one of these paths:
  - use a simple permission such as `READONLY` when it already matches “public read, creator/admin write”, or
  - keep `doc.field`-based CUSTOM rules and switch to `where(...)` writes that explicitly include the required owner/status fields in the query

**Operation Types Affected:**

The following operation types are subject to rule matching validation:
- **read**: Query conditions must be a subset of the read rule
- **write**: Query conditions must be a subset of the write rule (general write operations)
- **update**: Query conditions must be a subset of the update rule (or write rule if update is not specified)
- **delete**: Query conditions must be a subset of the delete rule (or write rule if delete is not specified)

**Special Case - Create Operations:**

For **create** operations, the system validates whether the **data being written** complies with the security rules, rather than validating query conditions. The written data must satisfy the create rule (or write rule if create is not specified).

**Collection Query Examples:**

```javascript
// Security rule configuration for collection 'test'
// Restricts queries to only records where age > 10
{
    "read": "doc.age > 10"
}

// Complies with security rule
// Query condition (age > 15) is a subset of the rule (age > 10)
const res = await db.collection('test').where({
    age: _.gt(15)
}).get()

// Does not comply with security rule
// Query condition (age > 8) is NOT a subset of the rule (age > 10)
// This would attempt to access records with age between 8-10, which violates the rule
const res = await db.collection('test').where({
    age: _.gt(8)
}).get()

// Complies with security rule (aggregate query)
let res = await db.collection('test').aggregate().match({
    age: _.gt(10)  // Matches the rule exactly
}).project({
    age: 1
}).end()

// Does not comply with security rule (aggregate query)
let res = await db.collection('test').aggregate().match({
    age: _.gt(8)  // Not a subset of age > 10
}).project({
    age: 1
}).end()
```

**Create Operation Example:**

```javascript
// Security rule configuration
{
    "create": "auth.uid != null && request.data.userId == auth.uid"
}

// Complies with security rule
// Written data includes userId matching current user's uid
// Note: _openid is automatically added by SDK, do not include it
await db.collection('userPosts').add({
    userId: currentUser.uid,  // Matches auth.uid
    title: "My Post",
    content: "Post content"
    // _openid is automatically populated by SDK based on authenticated user
})

// Does not comply with security rule
// Written data has userId that doesn't match current user's uid
await db.collection('userPosts').add({
    userId: "other-user-id",  // Does not match auth.uid
    title: "My Post",
    content: "Post content"
})

// Also wrong: Cannot manually set _openid
await db.collection('userPosts').add({
    userId: currentUser.uid,
    _openid: "some-id",  // ERROR: Cannot manually set _openid
    title: "My Post"
})
```

### Template Variables for Automatic Replacement

In query conditions, if the key is `_openid` and the value is `{openid}`, or if the key is `uid` and the value is `{uid}`, the server will automatically replace the value with the actual user's openid or uid.

**Important:** Under basic permission control, query conditions don't need to pass `_openid`, but security rules require explicit passing to ensure query conditions comply with security rules. All query conditions must include openid/uid. You can use template variables `{openid}` or `{uid}` to refer to the current logged-in user's openid or uid.

### Document ID Query Transformation (Migration Required)

**Important:** Security rules require query conditions to be a subset of the rules (all restrictions on `doc` must appear in query conditions and query condition restrictions must be a subset of rule restrictions). This differs from the implicit default behavior of old permission configurations, so developers need to pay attention to the following upgrade/compatibility handling.

**Why Transformation is Needed:**

Since `doc()` operations (doc.get, doc.set, etc.) only specify `_id`, their query conditions only include `{_id: "xxx"}`, which in most cases will not satisfy the subset requirement of security rules (unless reading under `"read": true` or writing under `"write": true`). Therefore, they need to be converted to equivalent forms where query conditions include security rules or their subsets.

**Operation Types Affected:**
- **read, update, delete**: If security rules contain `doc` restrictions, the system will first read the document data from the database once, then judge whether it complies with security rules.
- **create**: Will validate whether the written data complies with security rule restrictions.
- **update**: Only validates existing document data in the database, does not validate written data; does not guarantee atomicity of this operation.

**Transformation Examples:**

```javascript
// Security rule configuration
{
    "read": "doc._openid == auth.openid"
}

// Document with id='ccc' has data: { age: 12, _openid: 'user123' }

// Does not comply with security rules (does not meet subset requirement)
let queryRes = db.collection('collection_a').doc('ccc').get()

// Complies with security rules (rewritten as where query)
let queryRes = db.collection('collection_a')
    .where({
        _id: "ccc", 
        _openid: "{openid}"  // Template variable automatically replaced
    })
    .get()

// For WeChat Mini Program (using openid)
db.collection('posts')
    .where({
        _id: 'postId',
        _openid: '{openid}'  // Auto-replaced with current user's openid
    })
    .get();

// For Web (using uid)
db.collection('posts')
    .where({
        _id: 'postId',
        uid: '{uid}'  // Auto-replaced with current user's uid
    })
    .get();
```

## Common Patterns

### Pattern 1: User-Owned Data (Basic Permission Mapping)

**All users can read, only creator and admin can write:**

For WeChat login:
```json
{
  "read": true,
  "write": "doc._openid == auth.openid"
}
```

For non-WeChat login (Web):
```json
{
  "read": true,
  "write": "doc._openid == auth.uid"
}
```

**Only creator and admin can read/write:**

For WeChat login:
```json
{
  "read": "doc._openid == auth.openid",
  "write": "doc._openid == auth.openid"
}
```

For non-WeChat login (Web):
```json
{
  "read": "doc._openid == auth.uid",
  "write": "doc._openid == auth.uid"
}
```

**All users can read, only admin can write:**
```json
{
  "read": true,
  "write": false
}
```

**Only admin can read/write:**
```json
{
  "read": false,
  "write": false
}
```

### Pattern 2: Public Read, Authenticated Write
```json
{
  "read": true,
  "write": "auth != null"
}
```

### Pattern 3: Public Read, Owner Write
```json
{
  "read": true,
  "create": "auth != null",
  "update": "auth.uid == doc.owner_id",
  "delete": "auth.uid == doc.owner_id"
}
```

### Pattern 4: Immutable After Creation
```json
{
  "read": true,
  "create": "auth != null",
  "update": false,
  "delete": false
}
```

### Pattern 5: Complex Business Logic

**Article Publishing System:**
```json
{
  "read": "doc.published == true || doc.author == auth.uid",
  "create": true,
  "update": "doc.author == auth.uid",
  "delete": "doc.author == auth.uid && doc.published == false"
}
```

**Collaborative Document System:**
```json
{
  "read": "auth.uid in doc.readers || auth.uid in doc.editors || doc.owner == auth.uid",
  "write": "auth.uid in doc.editors || doc.owner == auth.uid"
}
```

### Pattern 6: Time-Based Control

**Time-Limited Activity Data:**
```json
{
  "read": "now >= doc.startTime && now <= doc.endTime",
  "write": "doc.owner == auth.uid && now <= doc.endTime"
}
```

### Pattern 7: Data Owner Pattern
```json
{
  "read": "doc._openid == auth.openid",
  "write": "doc._openid == auth.openid"
}
```

## Propagation And Verification

- Security rule updates may take a short time to propagate through the backend.
- Right after changing a collection to `CUSTOM`, do not assume the first `DATABASE_PERMISSION_DENIED` means the expression is still wrong.
- Keep the same login state, wait briefly, and retry the exact same write before changing the rule again.
- For Web SDK writes, do not treat "no thrown exception" as success.
- Check the returned payload:
  - `update()` should have `updated > 0`
  - `remove()` should have `deleted > 0`
  - if `result.code` or `result.message` exists, especially `DATABASE_PERMISSION_DENIED`, treat it as a real backend rejection

### Pattern 8: Status-Based Permissions
```json
{
  "read": "doc.status == 'published' || doc.author == auth.uid",
  "update": "doc.author == auth.uid && doc.status != 'locked'"
}
```

## Error Handling

When database operations fail due to permissions:

```javascript
try {
  const result = await db.collection('protected').get();
} catch (error) {
  if (error.code === 'PERMISSION_DENIED') {
    console.error('Permission denied: User does not have access');
    // Handle permission error
  }
}
```

## Role-Based Access Control Implementation

You can use CloudBase data models and custom security rules to implement role-based access control in your application.

### Example: Collaborative Writing Application

**Business Requirements:**
- Each story has one owner; stories can be shared with writers
- Writers have all access permissions that commenters have, plus can edit story content
- Owners can edit any part of the story and control other users' access permissions
- Regular users can only view stories and comments, write their own comments, but cannot edit stories

### Data Structure

**stories Collection:**
Each story document:
```json
{
  "id": "storyid",
  "title": "A Great Story",
  "content": "Once upon a time ..."
}
```

**roles Collection:**
Each role document tracks user roles for a story:
```json
{
  "id": "storyid",
  "roles": {
    "alice": "owner",
    "bob": "writer",
    "david": "writer"
    // ...
  }
}
```

**comments Collection:**
Each comment document:
```json
{
  "id": "commentId",
  "storyid": "storyid",
  "user": "alice",
  "content": "I think this is a great story!"
}
```

### Security Rules Configuration

**roles Collection Rules:**
Owners can change roles, allow story writers to read roles:
```json
{
  "write": "doc.roles[auth.uid] === 'owner'",
  "read": "doc.roles[auth.uid] in ['owner', 'writer']"
}
```

**stories Collection Rules:**
Owners and story writers can change stories, others can read stories:
```json
{
  "read": true,
  "write": "get(`database.roles.${doc.id}`).roles[auth.uid] in ['owner', 'writer']"
}
```

**comments Collection Rules:**
Allow everyone to post comments. Only comment owners can update and delete comments:
```json
{
  "read": true,
  "create": true,
  "update": "doc.user == auth.uid",
  "delete": "doc.user == auth.uid"
}
```

### Key Points

- Use a separate `roles` collection to manage user roles for each story
- Use `get()` function to access role information in security rules
- Role-based permissions are checked dynamically based on the roles collection
- This pattern can be extended to more complex permission scenarios

## Permission Selection Guide

### Choose Based on Business Complexity

| Business Scenario | Recommended Solution | Reason |
|------------------|---------------------|--------|
| Simple application | Basic permission control | Simple configuration, meets basic needs |
| Complex business logic | Security rules | Flexible expressions, supports complex judgment |
| Enterprise application | Role permissions + Basic permissions | Organization support, clear permission hierarchy |
| High security requirements | Security rules + Role permissions | Multi-layer protection, fine-grained control |

### Permission Configuration Recommendations

1. **Start Simple:** Use basic permissions first, upgrade gradually as needed
2. **Layered Design:** Basic permissions handle general logic, security rules handle special logic
3. **Test and Verify:** Fully test various permission scenarios in the development environment
4. **Document:** Record permission design ideas and configuration descriptions in detail

Through reasonable permission configuration, you can build a data access control system that is both secure and flexible, meeting various complex business requirements.

## References

- [CloudBase Security Rules Documentation](https://cloud.tencent.com/document/product/876/123478)
- [Security Rules Introduction](/rule/introduce)
- MCP Tool: `managePermissions` - Configure resource permissions
- MCP Tool: `queryPermissions` - Read current resource permissions
