@harperfast/template-react-ts-studio 1.12.2 → 1.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harper-best-practices/AGENTS.md +632 -474
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +131 -55
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +155 -61
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +63 -85
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +111 -52
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +38 -70
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +81 -69
- package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +82 -96
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +212 -0
- package/agent/skills/harper-best-practices/AGENTS.md +632 -474
- package/agent/skills/harper-best-practices/rules/automatic-apis.md +131 -55
- package/agent/skills/harper-best-practices/rules/checking-authentication.md +155 -61
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +63 -85
- package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +111 -52
- package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +38 -70
- package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +81 -69
- package/agent/skills/harper-best-practices/rules/v5-upgrade.md +82 -96
- package/agent/skills/harper-best-practices/rules.manifest.yaml +212 -0
- package/package.json +1 -1
- package/skills-lock.json +1 -1
|
@@ -4,23 +4,29 @@ description: How to use Harper's automatically generated REST and WebSocket APIs
|
|
|
4
4
|
metadata:
|
|
5
5
|
mode: generate
|
|
6
6
|
sources:
|
|
7
|
-
- reference/v5/rest/overview.md
|
|
7
|
+
- reference/v5/rest/overview.md#How the REST Interface Works
|
|
8
|
+
- reference/v5/rest/overview.md#Configuration
|
|
9
|
+
- reference/v5/rest/overview.md#Tables and Their Automatic Endpoints
|
|
10
|
+
- reference/v5/rest/overview.md#URL Structure
|
|
11
|
+
- reference/v5/rest/overview.md#HTTP Methods
|
|
12
|
+
- reference/v5/rest/overview.md#Content Types
|
|
13
|
+
- reference/v5/rest/overview.md#OpenAPI
|
|
8
14
|
- reference/v5/rest/websockets.md
|
|
9
|
-
sourceCommit:
|
|
10
|
-
inputHash:
|
|
15
|
+
sourceCommit: 677ad213d67822e109c83619e181ca23a59823db
|
|
16
|
+
inputHash: a9356e92dd3dc106
|
|
11
17
|
---
|
|
12
18
|
|
|
13
19
|
# Automatic APIs
|
|
14
20
|
|
|
15
|
-
Instructions for the agent to follow when
|
|
21
|
+
Instructions for the agent to follow when using Harper's automatically generated REST and WebSocket APIs for exported tables and resources.
|
|
16
22
|
|
|
17
23
|
## When to Use
|
|
18
24
|
|
|
19
|
-
Apply this rule when
|
|
25
|
+
Apply this rule when enabling HTTP REST endpoints or WebSocket subscriptions for Harper tables without writing custom handler code. Use it whenever a schema type needs to be served over HTTP, when configuring real-time subscriptions, or when setting up conditional caching behavior for REST responses.
|
|
20
26
|
|
|
21
27
|
## How It Works
|
|
22
28
|
|
|
23
|
-
1. **Enable
|
|
29
|
+
1. **Enable REST in `config.yaml`**: Add `rest: true` to the application configuration file. This registers REST endpoints and, by default, WebSocket subscriptions for all exported resources.
|
|
24
30
|
|
|
25
31
|
```yaml
|
|
26
32
|
rest: true
|
|
@@ -34,26 +40,47 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
|
|
|
34
40
|
webSocket: false # disables automatic WebSocket support (enabled by default)
|
|
35
41
|
```
|
|
36
42
|
|
|
37
|
-
2. **Export
|
|
43
|
+
2. **Export the table in the schema**: Add `@export` to the type definition. Without `@export`, Harper registers no REST route and callers receive `404`. Without `rest: true`, even an exported table does not respond to HTTP requests. Both are required.
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
```graphql
|
|
46
|
+
type Product @table @export {
|
|
47
|
+
id: Long @primaryKey
|
|
48
|
+
name: String
|
|
49
|
+
price: Float
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Reference the schema file in `config.yaml`:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
graphqlSchema:
|
|
57
|
+
files: schema.graphql
|
|
58
|
+
rest: true
|
|
59
|
+
```
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
| -------------------------------------------- | ----------------------------------------------------------- |
|
|
43
|
-
| `/my-resource` | Returns resource metadata |
|
|
44
|
-
| `/my-resource/` | Collection — all records; append query parameters to search |
|
|
45
|
-
| `/my-resource/record-id` | Specific record by primary key |
|
|
46
|
-
| `/my-resource/record-id/` | Collection of records with the given id prefix |
|
|
47
|
-
| `/my-resource/record-id/with/multiple/parts` | Record id with multiple path segments |
|
|
61
|
+
3. **Use the automatically registered endpoints**: Harper serves the following endpoints on the application HTTP server port (default `9926`) with no route definitions or handler code required.
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
| Endpoint | Description |
|
|
64
|
+
| ---------------------------- | --------------------------------------------------------------------------- |
|
|
65
|
+
| `GET /Product` | Returns resource description (table name, database, attributes) |
|
|
66
|
+
| `GET /Product/` | Returns the record collection; append query parameters to filter |
|
|
67
|
+
| `GET /Product/{id}` | Returns a single record by primary key; `404` if not found |
|
|
68
|
+
| `GET /Product/{id}.property` | Returns a single declared property of one record |
|
|
69
|
+
| `POST /Product/` | Creates a record; responds `201`; primary key returned in `Location` header |
|
|
70
|
+
| `PUT /Product/{id}` | Creates or replaces the record at `{id}` (upsert) |
|
|
71
|
+
| `PATCH /Product/{id}` | Merges body into existing record (shallow, top-level only) |
|
|
72
|
+
| `DELETE /Product/{id}` | Deletes the record at `{id}` |
|
|
73
|
+
| `DELETE /Product/?query` | Deletes every record matching the query |
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
4. **Handle `POST` primary key and `Location`**: On a successful `POST`, the new record's primary key is returned in the `Location` response header — the value the body supplied if it carried the primary-key property, otherwise a Harper-assigned key. The header carries the bare key value, not a URL.
|
|
76
|
+
|
|
77
|
+
5. **Understand `PUT` write behavior**: `PUT` replaces the stored record exactly. Three exceptions always apply: a `@createdTime` attribute keeps the original value, an `@updatedTime` attribute is re-stamped with the time of the write, and the primary key is forced to match the `{id}` in the URL.
|
|
78
|
+
|
|
79
|
+
6. **Use conditional requests for caching**: GET responses include an `ETag` header encoding the record's version/last-modification time. Send `If-None-Match` on subsequent requests with the cached `ETag` value. If the record has not changed, Harper returns `304 Not Modified` with no body.
|
|
80
|
+
|
|
81
|
+
7. **Select content type with `Accept`**: Use the `Accept` header to request a specific response format. The suffixes `.json`, `.cbor`, `.msgpack`, and `.csv` are reserved as content-type selectors on property paths and take precedence over property names. See [querying-rest-apis.md](querying-rest-apis.md) for query syntax details.
|
|
82
|
+
|
|
83
|
+
8. **Connect via WebSocket**: WebSocket support is enabled automatically when `rest` is enabled. Connecting to a resource URL subscribes to changes for that resource. See [real-time-apps.md](real-time-apps.md) for real-time patterns.
|
|
57
84
|
|
|
58
85
|
```javascript
|
|
59
86
|
let ws = new WebSocket('wss://server/my-resource/341');
|
|
@@ -62,29 +89,84 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
|
|
|
62
89
|
};
|
|
63
90
|
```
|
|
64
91
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
7. **Retrieve the OpenAPI spec**: Harper auto-generates an OpenAPI specification for all exported resources, available at:
|
|
92
|
+
9. **Implement a custom `connect()` handler** when default subscription behavior is insufficient. The method must return an async iterable that produces messages to send to the client.
|
|
68
93
|
|
|
69
|
-
```
|
|
70
|
-
|
|
94
|
+
```javascript
|
|
95
|
+
export class Echo extends Resource {
|
|
96
|
+
async *connect(incomingMessages) {
|
|
97
|
+
for await (let message of incomingMessages) {
|
|
98
|
+
yield message; // echo each message back
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
71
102
|
```
|
|
72
103
|
|
|
73
104
|
## Examples
|
|
74
105
|
|
|
75
|
-
|
|
106
|
+
### Full schema and config setup
|
|
76
107
|
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
108
|
+
```graphql
|
|
109
|
+
# schema.graphql
|
|
110
|
+
type Product @table @export {
|
|
111
|
+
id: Long @primaryKey
|
|
112
|
+
name: String
|
|
113
|
+
price: Float
|
|
84
114
|
}
|
|
85
115
|
```
|
|
86
116
|
|
|
87
|
-
|
|
117
|
+
```yaml
|
|
118
|
+
# config.yaml
|
|
119
|
+
graphqlSchema:
|
|
120
|
+
files: schema.graphql
|
|
121
|
+
rest: true
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Conditional GET with ETag caching
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
GET /Product/123
|
|
128
|
+
# Response includes:
|
|
129
|
+
# ETag: "abc123"
|
|
130
|
+
|
|
131
|
+
GET /Product/123
|
|
132
|
+
If-None-Match: "abc123"
|
|
133
|
+
# Response: 304 Not Modified (no body transferred)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### POST and read the Location header
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
POST /Product/
|
|
140
|
+
Content-Type: application/json
|
|
141
|
+
|
|
142
|
+
{ "name": "Widget", "price": 9.99 }
|
|
143
|
+
|
|
144
|
+
# Response:
|
|
145
|
+
# 201 Created
|
|
146
|
+
# Location: 7f3a9c
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### PATCH (shallow merge only)
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
PATCH /Product/123
|
|
153
|
+
Content-Type: application/json
|
|
154
|
+
|
|
155
|
+
{ "price": 12.99 }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Only `price` is updated; other top-level properties are preserved. Nested objects in the body replace the stored sub-object wholesale — deep merge does not occur.
|
|
159
|
+
|
|
160
|
+
### Request MessagePack response
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
GET /Product/123
|
|
164
|
+
Accept: application/msgpack
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Alternatively, use the `.msgpack` suffix on the URL path where supported.
|
|
168
|
+
|
|
169
|
+
### WebSocket with custom outgoing messages
|
|
88
170
|
|
|
89
171
|
```javascript
|
|
90
172
|
export class Example extends Resource {
|
|
@@ -96,7 +178,7 @@ export class Example extends Resource {
|
|
|
96
178
|
}, 1000);
|
|
97
179
|
|
|
98
180
|
incomingMessages.on('data', (message) => {
|
|
99
|
-
outgoingMessages.send(message);
|
|
181
|
+
outgoingMessages.send(message);
|
|
100
182
|
});
|
|
101
183
|
|
|
102
184
|
outgoingMessages.on('close', () => {
|
|
@@ -108,26 +190,20 @@ export class Example extends Resource {
|
|
|
108
190
|
}
|
|
109
191
|
```
|
|
110
192
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
GET /MyTable/123
|
|
115
|
-
GET /MyTable/?name=Harper
|
|
116
|
-
PUT /MyTable/123
|
|
117
|
-
PATCH /MyTable/123
|
|
118
|
-
DELETE /MyTable/?status=archived
|
|
119
|
-
```
|
|
193
|
+
### Disable WebSocket while keeping REST
|
|
120
194
|
|
|
121
|
-
```
|
|
122
|
-
|
|
195
|
+
```yaml
|
|
196
|
+
rest:
|
|
197
|
+
webSocket: false
|
|
123
198
|
```
|
|
124
199
|
|
|
125
200
|
## Notes
|
|
126
201
|
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
202
|
+
- The trailing slash is significant: `/Product` addresses the resource itself; `/Product/` addresses its record collection. `POST /Product` (no trailing slash) returns `404`.
|
|
203
|
+
- `HEAD` is served as `GET` with the body omitted. `QUERY` is accepted on the collection path and reads its search from the request body.
|
|
204
|
+
- A `POST` to an existing primary key fails with `409` — it does not overwrite.
|
|
205
|
+
- A component directory with **no configuration file** gets REST enabled by Harper's built-in default. As soon as a `config.yaml` exists it is used verbatim — add `rest: true` explicitly or REST is off.
|
|
206
|
+
- Do not apply `@export` to a schema type and also export a same-named JavaScript subclass of that table — this produces conflicting endpoints.
|
|
207
|
+
- Server-Sent Events subscriptions are served on the same paths, negotiated via `Accept: text/event-stream`. They are not affected by the `webSocket` option.
|
|
208
|
+
- Every non-hidden exported resource is included in the generated OpenAPI document at `GET /openapi`. Mark a type `@hidden` or set `static hidden = true` on a programmatic Resource to omit it.
|
|
209
|
+
- MQTT over WebSockets requires the sub-protocol header `Sec-WebSocket-Protocol: mqtt`.
|
|
@@ -8,22 +8,31 @@ metadata:
|
|
|
8
8
|
reference/v5/resources/resource-api.md#`getCurrentUser(): User |
|
|
9
9
|
undefined`
|
|
10
10
|
- reference/v5/resources/resource-api.md#Session and Login from a Resource
|
|
11
|
-
- reference/v5/security/jwt-authentication.md
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
- reference/v5/security/jwt-authentication.md#Create Authentication Tokens
|
|
12
|
+
- reference/v5/security/jwt-authentication.md#Using the Operation Token
|
|
13
|
+
- reference/v5/security/jwt-authentication.md#Refreshing the Operation Token
|
|
14
|
+
- reference/v5/security/jwt-authentication.md#Scoped Tokens (Inline Role)
|
|
15
|
+
- >-
|
|
16
|
+
reference/v5/security/jwt-authentication.md#Issuing Tokens from a Custom
|
|
17
|
+
Resource
|
|
18
|
+
- reference/v5/security/jwt-authentication.md#Token Expiry Configuration
|
|
19
|
+
- reference/v5/security/jwt-authentication.md#When to Use JWT Auth
|
|
20
|
+
- reference/v5/security/jwt-authentication.md#Security Notes
|
|
21
|
+
sourceCommit: 677ad213d67822e109c83619e181ca23a59823db
|
|
22
|
+
inputHash: 084363f039abfe73
|
|
14
23
|
---
|
|
15
24
|
|
|
16
25
|
# Checking Authentication
|
|
17
26
|
|
|
18
|
-
Instructions for the agent to
|
|
27
|
+
Instructions for the agent to handle user authentication, sessions, and JWT token issuance in Harper Resources.
|
|
19
28
|
|
|
20
29
|
## When to Use
|
|
21
30
|
|
|
22
|
-
Apply this rule when implementing
|
|
31
|
+
Apply this rule when implementing login/logout flows, protecting Resource endpoints by checking the current user, issuing or refreshing JWT tokens, or configuring token expiry in Harper. Use it whenever a custom Resource needs to authenticate callers or mint credentials for downstream consumers. See [custom-resources.md](custom-resources.md) for the broader Resource authoring context.
|
|
23
32
|
|
|
24
33
|
## How It Works
|
|
25
34
|
|
|
26
|
-
1. **Check the current user
|
|
35
|
+
1. **Check the current authenticated user**: Call `getCurrentUser()` inside any Resource method. It returns the user object (with `username`, `role`, and `role.permission`) or `undefined` if unauthenticated. Guard endpoints by returning a 401 when no user is present.
|
|
27
36
|
|
|
28
37
|
```javascript
|
|
29
38
|
async get(target) {
|
|
@@ -33,20 +42,9 @@ Apply this rule when implementing authentication checks, login/logout flows, or
|
|
|
33
42
|
}
|
|
34
43
|
```
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
2. **Enable sessions** before using session-based login. Set `authentication.enableSessions: true` in `harperdb-config.yaml`:
|
|
39
|
-
|
|
40
|
-
```yaml
|
|
41
|
-
authentication:
|
|
42
|
-
enableSessions: true
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
3. **Access login and session helpers** via `getContext()`. The context object exposes `context.login` and `context.session` for sign-in/out flows.
|
|
46
|
-
- Call `context.login(username, password)` to verify credentials and establish a session cookie on success.
|
|
47
|
-
- To end a session, delete it via `context.session.delete(context.session.id)`.
|
|
45
|
+
2. **Enable sessions before using login/logout**: Set `authentication.enableSessions: true` in `harperdb-config.yaml`. Without this, `context.login` and `context.session` are unavailable.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
3. **Implement login via `getContext()`**: Call `this.getContext()` to obtain the request context, then call `context.login(username, password)` to verify credentials and establish a session cookie.
|
|
50
48
|
|
|
51
49
|
```javascript
|
|
52
50
|
export class SignIn extends Resource {
|
|
@@ -60,7 +58,11 @@ Apply this rule when implementing authentication checks, login/logout flows, or
|
|
|
60
58
|
return new Response('Logged in', { status: 200 });
|
|
61
59
|
}
|
|
62
60
|
}
|
|
61
|
+
```
|
|
63
62
|
|
|
63
|
+
4. **Implement logout**: Delete the session via `context.session.delete(context.session.id)`.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
64
66
|
export class SignOut extends Resource {
|
|
65
67
|
async post() {
|
|
66
68
|
const context = this.getContext();
|
|
@@ -71,7 +73,96 @@ Apply this rule when implementing authentication checks, login/logout flows, or
|
|
|
71
73
|
}
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
Cookie-based sessions are intended for browser clients. For non-browser clients, use JWT issuance (steps below).
|
|
77
|
+
|
|
78
|
+
5. **Create authentication tokens**: Call `create_authentication_tokens` with credentials. No `Authorization` header is required for this operation.
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"operation": "create_authentication_tokens",
|
|
83
|
+
"username": "username",
|
|
84
|
+
"password": "password"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Response:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"operation_token": "<jwt-operation-token>",
|
|
93
|
+
"refresh_token": "<jwt-refresh-token>"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
6. **Use the operation token**: Pass it as a `Bearer` token in the `Authorization` header on subsequent requests.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
curl --location --request POST 'http://localhost:9925' \
|
|
101
|
+
--header 'Content-Type: application/json' \
|
|
102
|
+
--header 'Authorization: Bearer <operation_token>' \
|
|
103
|
+
--data-raw '{
|
|
104
|
+
"operation": "search_by_hash",
|
|
105
|
+
"schema": "dev",
|
|
106
|
+
"table": "dog",
|
|
107
|
+
"hash_values": [1],
|
|
108
|
+
"get_attributes": ["*"]
|
|
109
|
+
}'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
7. **Refresh an expired operation token**: When the `operation_token` expires, use `refresh_operation_token` and pass the `refresh_token` as `Bearer <refresh_token>`.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
curl --location --request POST 'http://localhost:9925' \
|
|
116
|
+
--header 'Content-Type: application/json' \
|
|
117
|
+
--header 'Authorization: Bearer <refresh_token>' \
|
|
118
|
+
--data-raw '{
|
|
119
|
+
"operation": "refresh_operation_token"
|
|
120
|
+
}'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Response:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"operation_token": "<new-jwt-operation-token>"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
When both tokens have expired, call `create_authentication_tokens` again with username and password.
|
|
132
|
+
|
|
133
|
+
8. **Mint scoped tokens for limited access**: A super user can embed an inline role in `create_authentication_tokens` using the same `permission` structure as `add_role`. Include `expires_in` to control lifetime. Do not include a `password` field. The `username` is attribution only and must not match an existing user.
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"operation": "create_authentication_tokens",
|
|
138
|
+
"username": "reporting-service",
|
|
139
|
+
"role": {
|
|
140
|
+
"permission": {
|
|
141
|
+
"operations": ["read_only"],
|
|
142
|
+
"dev": {
|
|
143
|
+
"tables": {
|
|
144
|
+
"dog": {
|
|
145
|
+
"read": true,
|
|
146
|
+
"insert": false,
|
|
147
|
+
"update": false,
|
|
148
|
+
"delete": false,
|
|
149
|
+
"attribute_permissions": []
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"expires_in": "7d"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Key constraints for scoped tokens:
|
|
160
|
+
- No refresh token is issued; no user record is created.
|
|
161
|
+
- Scoped tokens cannot be revoked before expiry — choose short `expires_in` values.
|
|
162
|
+
- `super_user` and `cluster_user` are always forced to `false` in the embedded role.
|
|
163
|
+
- In mixed-version clusters, only nodes with scoped-token support accept these tokens; older nodes return 401.
|
|
164
|
+
|
|
165
|
+
9. **Issue tokens from a custom Resource using `server.operation`**: Import `server` from `harper` and call `server.operation()` to mint tokens programmatically. Pass `true` as the **third argument** to run the operation as the current authenticated user; omit it when supplying credentials directly.
|
|
75
166
|
|
|
76
167
|
```javascript
|
|
77
168
|
import { Resource, server } from 'harper';
|
|
@@ -115,31 +206,50 @@ Apply this rule when implementing authentication checks, login/logout flows, or
|
|
|
115
206
|
}
|
|
116
207
|
```
|
|
117
208
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
6. **Configure JWT token expiry** in `harperdb-config.yaml` under the `authentication` section:
|
|
121
|
-
|
|
122
|
-
```yaml
|
|
123
|
-
authentication:
|
|
124
|
-
operationTokenTimeout: 1d
|
|
125
|
-
refreshTokenTimeout: 30d
|
|
126
|
-
```
|
|
209
|
+
10. **Configure token expiry**: Set timeouts in `harper-config.yaml` under the `authentication` section. Values follow the `jsonwebtoken` duration string format (e.g., `1d`, `12h`, `60m`).
|
|
127
210
|
|
|
128
|
-
|
|
211
|
+
```yaml
|
|
212
|
+
authentication:
|
|
213
|
+
operationTokenTimeout: 1d # Default: 1 day
|
|
214
|
+
refreshTokenTimeout: 30d # Default: 30 days
|
|
215
|
+
```
|
|
129
216
|
|
|
130
217
|
## Examples
|
|
131
218
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
219
|
+
### Full JWT flow via cURL
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Step 1: Create tokens
|
|
223
|
+
curl --location --request POST 'http://localhost:9925' \
|
|
224
|
+
--header 'Content-Type: application/json' \
|
|
225
|
+
--data-raw '{
|
|
226
|
+
"operation": "create_authentication_tokens",
|
|
227
|
+
"username": "username",
|
|
228
|
+
"password": "password"
|
|
229
|
+
}'
|
|
230
|
+
|
|
231
|
+
# Step 2: Use operation token
|
|
232
|
+
curl --location --request POST 'http://localhost:9925' \
|
|
233
|
+
--header 'Content-Type: application/json' \
|
|
234
|
+
--header 'Authorization: Bearer <operation_token>' \
|
|
235
|
+
--data-raw '{
|
|
236
|
+
"operation": "search_by_hash",
|
|
237
|
+
"schema": "dev",
|
|
238
|
+
"table": "dog",
|
|
239
|
+
"hash_values": [1],
|
|
240
|
+
"get_attributes": ["*"]
|
|
241
|
+
}'
|
|
242
|
+
|
|
243
|
+
# Step 3: Refresh when operation token expires
|
|
244
|
+
curl --location --request POST 'http://localhost:9925' \
|
|
245
|
+
--header 'Content-Type: application/json' \
|
|
246
|
+
--header 'Authorization: Bearer <refresh_token>' \
|
|
247
|
+
--data-raw '{
|
|
248
|
+
"operation": "refresh_operation_token"
|
|
249
|
+
}'
|
|
140
250
|
```
|
|
141
251
|
|
|
142
|
-
|
|
252
|
+
### Session-based login/logout Resource
|
|
143
253
|
|
|
144
254
|
```javascript
|
|
145
255
|
export class SignIn extends Resource {
|
|
@@ -164,27 +274,11 @@ export class SignOut extends Resource {
|
|
|
164
274
|
}
|
|
165
275
|
```
|
|
166
276
|
|
|
167
|
-
**JWT token refresh endpoint:**
|
|
168
|
-
|
|
169
|
-
```javascript
|
|
170
|
-
export class RefreshJWT extends Resource {
|
|
171
|
-
static async post(_target, data) {
|
|
172
|
-
const { refresh_token } = await data;
|
|
173
|
-
if (!refresh_token) {
|
|
174
|
-
return new Response('refresh_token required', { status: 400 });
|
|
175
|
-
}
|
|
176
|
-
const { operation_token } = await server.operation({
|
|
177
|
-
operation: 'refresh_operation_token',
|
|
178
|
-
refresh_token,
|
|
179
|
-
});
|
|
180
|
-
return { operation_token };
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
277
|
## Notes
|
|
186
278
|
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
279
|
+
- JWT authentication is **preferred over Basic Auth** when you want to avoid sending credentials on every request, when the client can store tokens, or when making multiple sequential requests. For simple or server-to-server scenarios, use Basic Authentication.
|
|
280
|
+
- Always use **HTTPS** in production to protect tokens in transit.
|
|
281
|
+
- Treat tokens like passwords. If a token is compromised, it remains valid until expiry — use shorter `operationTokenTimeout` values in high-security environments.
|
|
282
|
+
- `enableSessions` must be `true` in config before `context.login` or `context.session` will work.
|
|
283
|
+
- The `third argument` (`true`) to `server.operation` controls whether the operation runs as the current authenticated user. Omit it when the operation body supplies its own credentials.
|
|
284
|
+
- Scoped tokens have a 12KB limit when encoded into an `Authorization` header.
|