Standardize JWT authentication and finalize Activity Log V3 migration
This commit is contained in:
@@ -17,7 +17,36 @@ This guide explains how to update or create frontend functions to interact with
|
||||
|
||||
---
|
||||
|
||||
## 2. Implementing V3 CRUD Functions
|
||||
## 2. Authentication and Security (Mandatory in V3)
|
||||
|
||||
As of January 2026, the V3 architecture enforces strict **Multi-Tenant Isolation**. Most requests now require valid authentication to prevent data leakage between accounts.
|
||||
|
||||
### A. Authentication Requirement
|
||||
Almost all V3 CRUD endpoints require a standard Bearer token in the `Authorization` header.
|
||||
|
||||
* **Mandatory:** You must provide a valid JWT for nearly all requests.
|
||||
* **Account Isolation:** The backend automatically filters all results based on the `account_id` found in your JWT. You cannot access data belonging to another account even if you know the random ID.
|
||||
* **Status Codes:**
|
||||
* `401 Unauthorized`: Your JWT is invalid or expired.
|
||||
* `403 Forbidden`: No authentication provided, or you attempted to access an object belonging to a different account.
|
||||
|
||||
**Example Request Header:**
|
||||
```http
|
||||
Authorization: Bearer <your_jwt_token>
|
||||
```
|
||||
|
||||
### B. The "Bootstrap Paradox" Exception (`site_domain`)
|
||||
There is one critical exception to strict authentication: **`site_domain` search**.
|
||||
|
||||
Because the frontend needs to lookup the site configuration (to know which account it's on) *before* a user can log in, the following endpoint allows unauthenticated (guest) access:
|
||||
|
||||
**Endpoint:** `POST /v3/crud/site_domain/search`
|
||||
|
||||
This is the only V3 search allowed without a JWT. All other object types (journal, account, post, etc.) will return `403 Forbidden` if accessed without a token.
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementing V3 CRUD Functions
|
||||
|
||||
### A. List & Single Object (GET)
|
||||
Support for view selection allows fetching richer data models when needed.
|
||||
@@ -60,41 +89,21 @@ export async function search_ae_obj_v3({
|
||||
}
|
||||
```
|
||||
|
||||
### C. Standardized Global Search
|
||||
Use the `q` property in your search body for a general keyword search across indexed columns.
|
||||
### C. Standardized Global Search (`q`)
|
||||
Use the `q` property in your search body for a keyword search.
|
||||
- **Wildcard Support**: Setting `q: "%"` will bypass all text filtering and return all records (respecting only logical filters like `enable`).
|
||||
- **Fallback**: If the table lacks a `default_qry_str` column, the API automatically performs a `LIKE` search across all searchable fields.
|
||||
|
||||
```json
|
||||
// POST /v3/crud/journal/search
|
||||
{
|
||||
"q": "Annual Meeting",
|
||||
"and": [
|
||||
{ "field": "enable", "op": "eq", "value": true }
|
||||
]
|
||||
"and": [{ "field": "enable", "op": "eq", "value": true }]
|
||||
}
|
||||
```
|
||||
|
||||
### D. Supported Search Operators
|
||||
The `op` property in a `SearchFilter` supports the following values:
|
||||
|
||||
| Operator | SQL equivalent | Description |
|
||||
| --- | --- | --- |
|
||||
| `eq` | `=` | Equal to |
|
||||
| `ne` | `!=` | Not equal to |
|
||||
| `gt` | `>` | Greater than |
|
||||
| `gte` | `>=` | Greater than or equal to |
|
||||
| `lt` | `<` | Less than |
|
||||
| `lte` | `<=` | Less than or equal to |
|
||||
| `in` | `IN (...)` | Matches any value in a provided list |
|
||||
| `is_null` | `IS NULL` | Field is null (ignores `value`) |
|
||||
| `is_not_null` | `IS NOT NULL` | Field is not null (ignores `value`) |
|
||||
| `like` | `LIKE` | Standard SQL LIKE (requires manual `%` in `value`) |
|
||||
| `contains` | `LIKE %val%` | Wraps value in `%` automatically |
|
||||
| `startswith` | `LIKE val%` | Appends `%` to value automatically |
|
||||
| `endswith` | `LIKE %val` | Prepends `%` to value automatically |
|
||||
|
||||
---
|
||||
|
||||
## 3. Create, Update, & Delete (POST, PATCH, DELETE)
|
||||
## 4. Create, Update, & Delete (POST, PATCH, DELETE)
|
||||
|
||||
V3 supports both top-level operations and nested parent/child operations.
|
||||
|
||||
@@ -103,14 +112,12 @@ When creating objects, V3 strictly validates the incoming JSON against the `mdl_
|
||||
|
||||
```ts
|
||||
// POST /v3/crud/{obj_type}/
|
||||
// POST /v3/crud/journal/
|
||||
export async function create_ae_obj_v3({ api_cfg, obj_type, data }) {
|
||||
const endpoint = `/v3/crud/${obj_type}/`;
|
||||
return await post_object({ api_cfg, endpoint, data });
|
||||
}
|
||||
|
||||
// POST /v3/crud/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/
|
||||
// POST /v3/crud/journal/EIAC-40-76-82/journal_entry/
|
||||
// Note: Parent ID is automatically injected into the child record.
|
||||
export async function create_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, data }) {
|
||||
const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`;
|
||||
@@ -127,13 +134,6 @@ export async function update_ae_obj_v3({ api_cfg, obj_type, obj_id, data }) {
|
||||
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
|
||||
return await patch_object({ api_cfg, endpoint, data });
|
||||
}
|
||||
|
||||
// PATCH /v3/crud/{parent_type}/{parent_id}/{child_type}/{child_id}
|
||||
// Verification: The backend ensures the child actually belongs to the parent before updating.
|
||||
export async function update_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, child_id, data }) {
|
||||
const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/${child_id}`;
|
||||
return await patch_object({ api_cfg, endpoint, data });
|
||||
}
|
||||
```
|
||||
|
||||
### C. Delete (DELETE)
|
||||
@@ -149,37 +149,42 @@ export async function delete_ae_obj_v3({ api_cfg, obj_type, obj_id }) {
|
||||
|
||||
---
|
||||
|
||||
## 4. Authentication in V3
|
||||
## 5. Specialized & Context Endpoints
|
||||
|
||||
V3 supports multiple authentication methods. The backend resolves these automatically.
|
||||
### A. Context Resolution (FQDN)
|
||||
Used during initial load to find the `account_id` associated with the domain.
|
||||
|
||||
### A. Standard Requests (Header)
|
||||
For most API calls, use the standard Bearer token in the `Authorization` header.
|
||||
**Legacy Method:** `GET /crud/site/domain/{fqdn}?use_alt_table=true&use_alt_base=true`
|
||||
**Modern V3 Method (Preferred):** `POST /v3/crud/site_domain/search` with `{ "q": "your-domain.com" }`
|
||||
|
||||
### B. Schema Discovery
|
||||
**Path**: `GET /v3/crud/{obj_type}/schema`
|
||||
|
||||
Used for developer tools or dynamic UI builders to understand the structure of an AE object.
|
||||
|
||||
```ts
|
||||
// Example: Setting the JWT in headers
|
||||
headers: {
|
||||
"Authorization": `Bearer ${user_jwt_token}`
|
||||
// GET /v3/crud/account/schema
|
||||
export async function get_obj_schema_v3({ api_cfg, obj_type }) {
|
||||
const endpoint = `/v3/crud/${obj_type}/schema`;
|
||||
return await get_object({ api_cfg, endpoint });
|
||||
}
|
||||
```
|
||||
|
||||
### B. Secure File Downloads (URL Parameter)
|
||||
**Crucial for `hosted_file` and `event_file`**: To allow browsers to download files without complex header modifications, you can pass the JWT directly in the URL.
|
||||
---
|
||||
|
||||
## 6. Secure File Downloads (URL Parameter)
|
||||
For `hosted_file` and `event_file`, browsers often need to download files without complex header modifications. In these cases, you can pass the JWT directly in the URL.
|
||||
|
||||
```ts
|
||||
// Example: Creating a secure download link
|
||||
// Example: Creating a secure download link for a browser
|
||||
// GET /v3/crud/hosted_file/{id}/?jwt={token}
|
||||
const downloadUrl = `${BASE_URL}/hosted_file/${fileId}/?jwt=${jwtToken}`;
|
||||
const downloadUrl = `${BASE_URL}/v3/crud/hosted_file/${fileId}/?jwt=${jwtToken}`;
|
||||
```
|
||||
|
||||
### C. Legacy Fallback (X-Account-ID)
|
||||
For development and backward compatibility, the `X-Account-ID` header is still supported but should be phased out in favor of JWT.
|
||||
|
||||
---
|
||||
|
||||
## 5. Best Practices for V3
|
||||
## 7. Best Practices for V3
|
||||
|
||||
1. **Use `view` for Rich Data**: Instead of manually joining data in separate calls, use `?view=enriched` or `?view=detail` if defined in the backend.
|
||||
2. **Hybrid Search**: Use query parameters for simple toggles (enabled/hidden) and the POST body for complex logic.
|
||||
3. **Global Search**: Always prefer the `q` property for text search instead of manually targeting `default_qry_str`.
|
||||
4. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`).
|
||||
1. **Use `view` for Rich Data**: Instead of manually joining data in separate calls, use `?view=enriched` or `?view=detail`.
|
||||
2. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`).
|
||||
3. **Strict Typing**: Ensure your `data` objects match the backend models to avoid `400 Bad Request` validation errors.
|
||||
|
||||
Reference in New Issue
Block a user