@harperfast/skills 1.6.0 → 1.7.0

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.
@@ -2,200 +2,189 @@
2
2
  name: checking-authentication
3
3
  description: How to handle user authentication and sessions in Harper Resources.
4
4
  metadata:
5
- mode: synthesized
5
+ mode: generate
6
+ sources:
7
+ - >-
8
+ reference/v5/resources/resource-api.md#`getCurrentUser(): User |
9
+ undefined`
10
+ - reference/v5/resources/resource-api.md#Session and Login from a Resource
11
+ - reference/v5/security/jwt-authentication.md
12
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
13
+ inputHash: fdd9ec3b11011490
6
14
  ---
7
15
 
8
16
  # Checking Authentication
9
17
 
10
- Instructions for the agent to follow when handling authentication and sessions.
18
+ Instructions for the agent to follow when handling user authentication and session management inside Harper Resources.
11
19
 
12
20
  ## When to Use
13
21
 
14
- Use this skill when you need to implement sign-in/sign-out functionality, protect specific resource endpoints, or identify the currently logged-in user in a Harper application.
22
+ Apply this rule when implementing authentication checks, login/logout flows, or token issuance inside a custom Resource. Use it any time a Resource needs to identify the current user, establish a session, or issue JWTs to clients. See [custom-resources.md](custom-resources.md) for the general Resource authoring pattern.
15
23
 
16
24
  ## How It Works
17
25
 
18
- 1. **Configure Harper for Sessions**: Ensure `harper-config.yaml` has sessions enabled and local auto-authorization disabled for testing:
19
- ```yaml
20
- authentication:
21
- authorizeLocal: false
22
- enableSessions: true
23
- ```
24
- 2. **Implement Sign In**: Use `this.getContext().login(username, password)` to create a session:
25
- ```typescript
26
- async post(_target, data) {
27
- const context = this.getContext();
28
- try {
29
- await context.login(data.username, data.password);
30
- } catch {
31
- return new Response('Invalid credentials', { status: 403 });
32
- }
33
- return new Response('Logged in', { status: 200 });
34
- }
35
- ```
36
- 3. **Identify Current User**: Use `this.getCurrentUser()` to access session data:
37
- ```typescript
38
- async get() {
39
- const user = this.getCurrentUser?.();
26
+ 1. **Check the current user** with `getCurrentUser()`. Call it inside any Resource method to retrieve the authenticated user or `undefined` if no user is authenticated. Guard protected endpoints by returning a `401` when the result is `undefined`.
27
+
28
+ ```javascript
29
+ async get(target) {
30
+ const user = this.getCurrentUser();
40
31
  if (!user) return new Response(null, { status: 401 });
41
32
  return { username: user.username, role: user.role };
42
33
  }
43
34
  ```
44
- 4. **Implement Sign Out**: Use `this.getContext().logout()` or delete the session from context:
45
- ```typescript
46
- async post() {
47
- const context = this.getContext();
48
- await context.session?.delete?.(context.session.id);
49
- return new Response('Logged out', { status: 200 });
50
- }
51
- ```
52
- 5. **Protect Routes**: In your Resource, use `allowRead()`, `allowUpdate()`, etc., to enforce authorization logic based on `this.getCurrentUser()`. For privileged actions, verify `user.role.permission.super_user`.
53
-
54
- ## Examples
55
-
56
- ### Sign In Implementation
57
-
58
- ```typescript
59
- async post(_target, data) {
60
- const context = this.getContext();
61
- try {
62
- await context.login(data.username, data.password);
63
- } catch {
64
- return new Response('Invalid credentials', { status: 403 });
65
- }
66
- return new Response('Logged in', { status: 200 });
67
- }
68
- ```
69
35
 
70
- ### Identify Current User
36
+ The returned object exposes `username`, `role`, and `role.permission` flags.
71
37
 
72
- ```typescript
73
- async get() {
74
- const user = this.getCurrentUser?.();
75
- if (!user) return new Response(null, { status: 401 });
76
- return { username: user.username, role: user.role };
77
- }
78
- ```
38
+ 2. **Enable sessions** before using session-based login. Set `authentication.enableSessions: true` in `harperdb-config.yaml`:
79
39
 
80
- ### Sign Out Implementation
40
+ ```yaml
41
+ authentication:
42
+ enableSessions: true
43
+ ```
81
44
 
82
- ```typescript
83
- async post() {
84
- const context = this.getContext();
85
- await context.session?.delete?.(context.session.id);
86
- return new Response('Logged out', { status: 200 });
87
- }
88
- ```
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)`.
48
+
49
+ 4. **Implement sign-in and sign-out Resources** using the context helpers:
50
+
51
+ ```javascript
52
+ export class SignIn extends Resource {
53
+ async post(_target, data) {
54
+ const context = this.getContext();
55
+ try {
56
+ await context.login(data.username, data.password);
57
+ } catch {
58
+ return new Response('Invalid credentials', { status: 403 });
59
+ }
60
+ return new Response('Logged in', { status: 200 });
61
+ }
62
+ }
89
63
 
90
- ## Status code conventions used here
64
+ export class SignOut extends Resource {
65
+ async post() {
66
+ const context = this.getContext();
67
+ if (!context.session) return new Response(null, { status: 401 });
68
+ await context.session.delete(context.session.id);
69
+ return new Response('Logged out', { status: 200 });
70
+ }
71
+ }
72
+ ```
91
73
 
92
- - 200: Successful operation. For `GET /me`, a `200` with empty body means “not signed in”.
93
- - 400: Missing required fields (e.g., username/password on sign-in).
94
- - 401: No current session for an action that requires one (e.g., sign out when not signed in).
95
- - 403: Authenticated but not authorized (bad credentials on login attempt, or insufficient privileges).
74
+ 5. **Issue JWTs for non-browser clients** (CLI tools, mobile apps, service-to-service). Cookie-based sessions are intended for browser clients. For other clients, mint tokens programmatically using `server.operation()`:
75
+
76
+ ```javascript
77
+ import { Resource, server } from 'harper';
78
+
79
+ export class IssueTokens extends Resource {
80
+ static async get(_target, context) {
81
+ const { operation_token, refresh_token } = await server.operation(
82
+ { operation: 'create_authentication_tokens' },
83
+ context,
84
+ true,
85
+ );
86
+ return { operation_token, refresh_token };
87
+ }
88
+
89
+ static async post(_target, data) {
90
+ const { username, password } = await data;
91
+ if (!username || !password) {
92
+ return new Response('username and password required', { status: 400 });
93
+ }
94
+ const { operation_token, refresh_token } = await server.operation({
95
+ operation: 'create_authentication_tokens',
96
+ username,
97
+ password,
98
+ });
99
+ return { operation_token, refresh_token };
100
+ }
101
+ }
96
102
 
97
- ## Client considerations
103
+ export class RefreshJWT extends Resource {
104
+ static async post(_target, data) {
105
+ const { refresh_token } = await data;
106
+ if (!refresh_token) {
107
+ return new Response('refresh_token required', { status: 400 });
108
+ }
109
+ const { operation_token } = await server.operation({
110
+ operation: 'refresh_operation_token',
111
+ refresh_token,
112
+ });
113
+ return { operation_token };
114
+ }
115
+ }
116
+ ```
98
117
 
99
- - Sessions are cookie-based; the server handles setting and reading the cookie via Harper. If you make cross-origin requests, ensure the appropriate `credentials` mode and CORS settings.
100
- - If developing locally, double-check the server config still has `authentication.authorizeLocal: false` to avoid accidental superuser bypass.
118
+ Pass `true` as the third argument to `server.operation()` when the operation should run as the current authenticated user. Omit it or pass `false` when the operation supplies its own credentials.
101
119
 
102
- ## Token-based auth (JWT + refresh token) for non-browser clients
120
+ 6. **Configure JWT token expiry** in `harperdb-config.yaml` under the `authentication` section:
103
121
 
104
- Cookie-backed sessions are great for browser flows. For CLI tools, mobile apps, or other non-browser clients, it’s often easier to use **explicit tokens**:
122
+ ```yaml
123
+ authentication:
124
+ operationTokenTimeout: 1d
125
+ refreshTokenTimeout: 30d
126
+ ```
105
127
 
106
- - **JWT (`operation_token`)**: short-lived bearer token used to authorize API requests.
107
- - **Refresh token (`refresh_token`)**: longer-lived token used to mint a new JWT when it expires.
128
+ Duration strings follow the `jsonwebtoken` package format (e.g., `1d`, `12h`, `60m`).
108
129
 
109
- This project includes two Resource patterns for that flow:
130
+ ## Examples
110
131
 
111
- ### Issuing tokens: `IssueTokens`
132
+ **Protecting a resource endpoint and returning user info:**
112
133
 
113
- **Description / use case:** Generate `{ refreshToken, jwt }` either:
134
+ ```javascript
135
+ async get(target) {
136
+ const user = this.getCurrentUser();
137
+ if (!user) return new Response(null, { status: 401 });
138
+ return { username: user.username, role: user.role };
139
+ }
140
+ ```
114
141
 
115
- - with an existing Authorization token (either Basic Auth or a JWT) and you want to issue new tokens, or
116
- - from an explicit `{ username, password }` payload (useful for direct “login” from a CLI/mobile client).
142
+ **Full session-based sign-in/sign-out flow:**
117
143
 
118
144
  ```javascript
119
- export class IssueTokens extends Resource {
120
- static loadAsInstance = false;
121
-
122
- async get(target) {
123
- const { refresh_token: refreshToken, operation_token: jwt } =
124
- await databases.system.hdb_user.operation(
125
- { operation: 'create_authentication_tokens' },
126
- this.getContext(),
127
- );
128
- return { refreshToken, jwt };
129
- }
130
-
131
- async post(target, data) {
132
- if (!data.username || !data.password) {
133
- throw new Error('username and password are required');
145
+ export class SignIn extends Resource {
146
+ async post(_target, data) {
147
+ const context = this.getContext();
148
+ try {
149
+ await context.login(data.username, data.password);
150
+ } catch {
151
+ return new Response('Invalid credentials', { status: 403 });
134
152
  }
153
+ return new Response('Logged in', { status: 200 });
154
+ }
155
+ }
135
156
 
136
- const { refresh_token: refreshToken, operation_token: jwt } =
137
- await databases.system.hdb_user.operation({
138
- operation: 'create_authentication_tokens',
139
- username: data.username,
140
- password: data.password,
141
- });
142
- return { refreshToken, jwt };
157
+ export class SignOut extends Resource {
158
+ async post() {
159
+ const context = this.getContext();
160
+ if (!context.session) return new Response(null, { status: 401 });
161
+ await context.session.delete(context.session.id);
162
+ return new Response('Logged out', { status: 200 });
143
163
  }
144
164
  }
145
165
  ```
146
166
 
147
- **Recommended documentation notes to include:**
148
-
149
- - `GET` variant: intended for “I already have an Authorization token, give me new tokens”.
150
- - `POST` variant: intended for “I have credentials, give me tokens”.
151
- - Response shape:
152
- - `refreshToken`: store securely (long-lived).
153
- - `jwt`: attach to requests (short-lived).
154
-
155
- ### Refreshing a JWT: `RefreshJWT`
156
-
157
- **Description / use case:** When the JWT expires, the client uses the refresh token to get a new JWT without re-supplying username/password.
167
+ **JWT token refresh endpoint:**
158
168
 
159
169
  ```javascript
160
170
  export class RefreshJWT extends Resource {
161
- static loadAsInstance = false;
162
-
163
- async post(target, data) {
164
- if (!data.refreshToken) {
165
- throw new Error('refreshToken is required');
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 });
166
175
  }
167
-
168
- const { operation_token: jwt } = await databases.system.hdb_user.operation({
176
+ const { operation_token } = await server.operation({
169
177
  operation: 'refresh_operation_token',
170
- refresh_token: data.refreshToken,
178
+ refresh_token,
171
179
  });
172
- return { jwt };
180
+ return { operation_token };
173
181
  }
174
182
  }
175
183
  ```
176
184
 
177
- **Recommended documentation notes to include:**
178
-
179
- - Requires `refreshToken` in the request body.
180
- - Returns a new `{ jwt }`.
181
- - If refresh fails (expired/revoked), client must re-authenticate (e.g., call `IssueTokens.post` again).
182
-
183
- ### Suggested client flow (high-level)
184
-
185
- 1. **Sign in (token flow)**
186
- - POST /IssueTokens/ with a body of `{ "username": "your username", "password": "your password" }` or GET /IssueTokens/ with an existing Authorization token.
187
- - Receive `{ jwt, refreshToken }` in the response
188
- 2. **Call protected APIs**
189
- - Send the JWT with each request in the Authorization header (as your auth mechanism expects)
190
- 3. **JWT expires**
191
- - POST /RefreshJWT/ with a body of `{ "refreshToken": "your refresh token" }`.
192
- - Receive `{ jwt }` in the response and continue
193
-
194
- ## Quick checklist
185
+ ## Notes
195
186
 
196
- - [ ] Public endpoints explicitly `allowRead`/`allowCreate` as needed.
197
- - [ ] Sign-in uses `context.login` and handles 400/403 correctly.
198
- - [ ] Protected routes call `ensureSuperUser(this.getCurrentUser())` (or another role check) before doing work.
199
- - [ ] Sign-out verifies a session and deletes it.
200
- - [ ] `authentication.authorizeLocal` is `false` and `enableSessions` is `true` in Harper config.
201
- - [ ] If using tokens: `IssueTokens` issues `{ jwt, refreshToken }`, `RefreshJWT` refreshes `{ jwt }` with a `refreshToken`.
187
+ - `getCurrentUser()` and `getContext()` are instance methods; call them with `this` inside non-static Resource methods.
188
+ - `enableSessions` must be `true` in config before `context.login` or `context.session` will function.
189
+ - Cookie-based sessions target browser clients. Use JWT issuance via `server.operation()` for all other client types.
190
+ - When both `operation_token` and `refresh_token` have expired, the client must call `create_authentication_tokens` again with credentials.
@@ -2,103 +2,121 @@
2
2
  name: deploying-to-harper-fabric
3
3
  description: How to deploy a Harper application to the Harper Fabric cloud.
4
4
  metadata:
5
- mode: synthesized
5
+ mode: generate
6
+ sources:
7
+ - reference/v5/components/applications.md#Remote Management
8
+ - >-
9
+ fabric/cluster-creation-management.md#Connecting the Harper CLI to a
10
+ Cluster
11
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
12
+ inputHash: ccdefbbf8f3b8657
6
13
  ---
7
14
 
8
15
  # Deploying to Harper Fabric
9
16
 
10
- Instructions for the agent to follow when deploying to Harper Fabric.
17
+ Instructions for the agent to follow when deploying a Harper application to the Harper Fabric cloud using the Harper CLI.
11
18
 
12
19
  ## When to Use
13
20
 
14
- Use this skill when you are ready to move your Harper application from local development to a cloud-hosted environment.
21
+ Apply this rule when deploying a Harper application to a remote Harper instance or Harper Fabric cluster. This covers interactive deployments, CI/CD pipelines, and any scenario where the agent must push a local or remote package to a target environment.
15
22
 
16
23
  ## How It Works
17
24
 
18
- 1. **Sign up**: Follow the [creating-a-fabric-account-and-cluster](creating-a-fabric-account-and-cluster.md) rule to create a Harper Fabric account, organization, and cluster.
19
- 2. **Configure Environment**: Add your cluster credentials and cluster application URL to `.env`:
25
+ 1. **Authenticate with the remote target**: Run `harper login` once to store an authentication token. The CLI writes `HARPER_CLI_TARGET` to a local `.env` so subsequent commands do not need credentials repeated. Find the **Application URL** on the cluster's **Config → Overview** page (see [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md)).
26
+
27
+ ```bash
28
+ harper login <Application URL>
29
+ # Provide cluster username and password when prompted
30
+ ```
31
+
32
+ 2. **Deploy the application**: Run `harper deploy` with the required parameters. After logging in, no credentials are needed inline.
33
+
34
+ ```bash
35
+ harper deploy \
36
+ project=<name> \
37
+ package=<package> \
38
+ target=<remote> \
39
+ restart=true \
40
+ replicated=true
41
+ ```
42
+
43
+ 3. **Choose a package source**: Set the `package` parameter to any valid npm dependency value, or omit it to package and deploy the current local directory.
44
+
45
+ | Value | Effect |
46
+ | ---------------------------------------------------- | ------------------------------------------------ |
47
+ | _(omitted)_ | Packages and deploys the current local directory |
48
+ | `"@harperdb/status-check"` | npm package |
49
+ | `"HarperDB/status-check"` | GitHub repo (short form) |
50
+ | `"https://github.com/HarperDB/status-check"` | GitHub repo (full URL) |
51
+ | `"git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo via SSH |
52
+ | `"https://example.com/application.tar.gz"` | Remote tarball |
53
+
54
+ For git tags, use the `semver` directive for reliable versioning:
55
+
56
+ ```
57
+ HarperDB/application-template#semver:v1.0.0
58
+ ```
59
+
60
+ 4. **Authenticate for CI/CD pipelines**: Use environment variables instead of interactive login. Set credentials before running `harper deploy`.
61
+
20
62
  ```bash
21
- CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
22
- CLI_TARGET_PASSWORD='YOUR_CLUSTER_PASSWORD'
23
- CLI_TARGET='YOUR_CLUSTER_URL'
63
+ export HARPER_CLI_USERNAME=<username>
64
+ export HARPER_CLI_PASSWORD=<password>
65
+ harper deploy \
66
+ project=<name> \
67
+ package=<package> \
68
+ target=<remote> \
69
+ restart=true \
70
+ replicated=true
24
71
  ```
25
- 3. **Deploy From Local Environment**: Run `npm run deploy`.
26
- 4. **Set up CI/CD**: Configure `.github/workflows/deploy.yaml` and set repository secrets for automated deployments.
27
72
 
28
- ## Manual Setup for Existing Apps
73
+ 5. **Register SSH keys for private repos**: Before deploying from an SSH-based private repository, use the Add SSH Key operation to register the key with the remote instance.
74
+
75
+ ## Examples
29
76
 
30
- If your application was not created with `npm create harper`, you'll need to manually configure the deployment scripts and CI/CD workflow.
77
+ **Interactive login then deploy (recommended):**
31
78
 
32
- ### 1. Update `package.json`
79
+ ```bash
80
+ # Log in once
81
+ harper login <remote>
82
+ # Provide your username and password when prompted
33
83
 
34
- Add the following scripts and dependencies to your `package.json`:
84
+ # Subsequently deploy without credentials
85
+ harper deploy \
86
+ project=<name> \
87
+ package=<package> \
88
+ target=<remote> \
89
+ restart=true \
90
+ replicated=true
91
+ ```
35
92
 
36
- ```json
37
- {
38
- "scripts": {
39
- "deploy": "dotenv -- npm run deploy:component",
40
- "deploy:component": "harper deploy_component . restart=rolling replicated=true"
41
- },
42
- "devDependencies": {
43
- "dotenv-cli": "^11.0.0",
44
- "harper": "^5.0.0"
45
- }
46
- }
93
+ **Deploy with inline credentials (not recommended for production):**
94
+
95
+ ```bash
96
+ harper deploy \
97
+ project=<name> \
98
+ package=<package> \
99
+ username=<username> \
100
+ password=<password> \
101
+ target=<remote> \
102
+ restart=true \
103
+ replicated=true
47
104
  ```
48
105
 
49
- #### Why split the scripts?
50
-
51
- The `deploy` script is separated from `deploy:component` to ensure environment variables from your `.env` file are properly loaded and passed to the Harper CLI.
52
-
53
- - `deploy`: Uses `dotenv-cli` to load environment variables (like `CLI_TARGET`, `CLI_TARGET_USERNAME`, and `CLI_TARGET_PASSWORD`) before executing the next command.
54
- - `deploy:component`: The actual command that performs the deployment.
55
-
56
- By using `dotenv -- npm run deploy:component`, the environment variables are correctly set in the shell session before `harper deploy_component` is called, allowing it to authenticate with your cluster.
57
-
58
- ### 2. Configure GitHub Actions
59
-
60
- Create a `.github/workflows/deploy.yaml` file with the following content:
61
-
62
- ```yaml
63
- name: Deploy to Harper Fabric
64
- on:
65
- workflow_dispatch:
66
- # push:
67
- # branches:
68
- # - main
69
- concurrency:
70
- group: main
71
- cancel-in-progress: false
72
- jobs:
73
- deploy:
74
- runs-on: ubuntu-latest
75
- steps:
76
- - name: Checkout code
77
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
78
- with:
79
- fetch-depth: 0
80
- fetch-tags: true
81
- - name: Set up Node.js
82
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
83
- with:
84
- cache: 'npm'
85
- node-version-file: '.nvmrc'
86
- - name: Install dependencies
87
- run: npm ci
88
- - name: Run unit tests
89
- run: npm test
90
- - name: Run lint
91
- run: npm run lint
92
- - name: Deploy
93
- run: npm run deploy
94
- env:
95
- CLI_TARGET: ${{ secrets.CLI_TARGET }}
96
- CLI_TARGET_USERNAME: ${{ secrets.CLI_TARGET_USERNAME }}
97
- CLI_TARGET_PASSWORD: ${{ secrets.CLI_TARGET_PASSWORD }}
106
+ **Deploy a specific GitHub release by semver tag:**
107
+
108
+ ```bash
109
+ harper deploy \
110
+ project=my-app \
111
+ package="HarperDB/application-template#semver:v1.0.0" \
112
+ target=<remote> \
113
+ restart=true \
114
+ replicated=true
98
115
  ```
99
116
 
100
- Be sure to set the following repository secrets in your GitHub repository's /settings/secrets/actions:
117
+ ## Notes
101
118
 
102
- - `CLI_TARGET`
103
- - `CLI_TARGET_USERNAME`
104
- - `CLI_TARGET_PASSWORD`
119
+ - Always prefer `harper login` for interactive use and environment variables (`HARPER_CLI_USERNAME`, `HARPER_CLI_PASSWORD`) for CI/CD. Avoid inline `username`/`password` parameters in production.
120
+ - Omitting `package` causes the CLI to package the current local directory. Specifying a local file path creates a symlink, so changes are picked up between restarts without redeploying.
121
+ - Harper generates a `package.json` from component configurations and resolves dependencies using a form of `npm install`.
122
+ - For SSH-based private repos, register keys with the Add SSH Key operation before deploying.