@forumone/throughline-approvals 0.0.1 → 0.2.1
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/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +101 -43
- package/dist/collection.d.ts +18 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +95 -0
- package/dist/collection.js.map +1 -0
- package/dist/endpoints/action.d.ts +31 -0
- package/dist/endpoints/action.d.ts.map +1 -0
- package/dist/endpoints/action.js +205 -0
- package/dist/endpoints/action.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +50 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +28 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +103 -0
- package/dist/plugin.js.map +1 -0
- package/dist/resolver.d.ts +25 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +63 -0
- package/dist/resolver.js.map +1 -0
- package/dist/tokens.d.ts +48 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +103 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tools/get-approval-status.d.ts +11 -0
- package/dist/tools/get-approval-status.d.ts.map +1 -0
- package/dist/tools/get-approval-status.js +47 -0
- package/dist/tools/get-approval-status.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-my-requests.d.ts +11 -0
- package/dist/tools/list-my-requests.d.ts.map +1 -0
- package/dist/tools/list-my-requests.js +55 -0
- package/dist/tools/list-my-requests.js.map +1 -0
- package/dist/tools/list-pending-approvals.d.ts +11 -0
- package/dist/tools/list-pending-approvals.d.ts.map +1 -0
- package/dist/tools/list-pending-approvals.js +62 -0
- package/dist/tools/list-pending-approvals.js.map +1 -0
- package/dist/tools/request-approval.d.ts +13 -0
- package/dist/tools/request-approval.d.ts.map +1 -0
- package/dist/tools/request-approval.js +118 -0
- package/dist/tools/request-approval.js.map +1 -0
- package/dist/tools/respond-to-approval.d.ts +13 -0
- package/dist/tools/respond-to-approval.d.ts.map +1 -0
- package/dist/tools/respond-to-approval.js +107 -0
- package/dist/tools/respond-to-approval.js.map +1 -0
- package/package.json +64 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @forumone/throughline-approvals
|
|
2
|
+
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [a4b5108]
|
|
8
|
+
- @forumone/throughline-core@0.2.1
|
|
9
|
+
- @forumone/throughline-publishing@0.2.2
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 3ef6f6a: Initial release. Conversational approval workflow server with HMAC-signed single-use action tokens, per-group approver resolution, first-decision-wins semantics, version-bound approvals, seven-day default expiration, an HTML confirmation flow on the action endpoint, and five MCP tools (`request_approval`, `respond_to_approval`, `get_approval_status`, `list_pending_approvals`, `list_my_requests`). The plugin's `onInit` attaches the approval resolver to the Payload instance under `Symbol.for('@forumone/throughline/approvals-resolver')` so the publishing server can look it up automatically.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [3ef6f6a]
|
|
20
|
+
- @forumone/throughline-publishing@0.2.1
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Forum One Communications Corporation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,45 +1,103 @@
|
|
|
1
1
|
# @forumone/throughline-approvals
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
4.
|
|
28
|
-
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
3
|
+
Conversational approval workflow server for Throughline. Provides the resolver the publishing server consumes plus MCP tools and email-action endpoints for requesting and responding to approvals.
|
|
4
|
+
|
|
5
|
+
## What this package provides
|
|
6
|
+
|
|
7
|
+
- **Approvals collection** with target / request / decision / workflow-state fields and indexes for the common queries.
|
|
8
|
+
- **Approval resolver** that the publishing server's `approvalStep` calls to check for active granted approvals (auto-attached on the Payload instance via Symbol — no manual wiring needed).
|
|
9
|
+
- **HMAC-signed action tokens** (`generateActionToken` / `verifyActionToken`) for inline-action emails. Single-use enforcement via the per-record `consumedTokens` array prevents replay.
|
|
10
|
+
- **HTTP action endpoint** at `/api/approvals/action` that handles email button clicks: verify token → confirmation page → record decision.
|
|
11
|
+
- **Five MCP tools** served at `/api/approvals/mcp`:
|
|
12
|
+
|
|
13
|
+
| Tool | Audit |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `request_approval` | `approval.requested` |
|
|
16
|
+
| `respond_to_approval` | `approval.granted` / `.declined` / `.changes_requested` |
|
|
17
|
+
| `get_approval_status` | none (read-only) |
|
|
18
|
+
| `list_pending_approvals` | none (read-only) |
|
|
19
|
+
| `list_my_requests` | none (read-only) |
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @forumone/throughline-approvals
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Peers: `payload@^3.0.0`, `inngest@^4.0.0`. Required runtime peer: `@forumone/throughline-core` (audit log).
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { buildConfig } from 'payload'
|
|
33
|
+
import { auditPlugin, createInngestClient } from '@forumone/throughline-core'
|
|
34
|
+
import { approvalsPlugin } from '@forumone/throughline-approvals'
|
|
35
|
+
import { publishingPlugin } from '@forumone/throughline-publishing'
|
|
36
|
+
|
|
37
|
+
const inngest = createInngestClient({ id: 'my-site' })
|
|
38
|
+
|
|
39
|
+
export default buildConfig({
|
|
40
|
+
// collections, db, secret...
|
|
41
|
+
plugins: [
|
|
42
|
+
auditPlugin({ inngest }),
|
|
43
|
+
approvalsPlugin({
|
|
44
|
+
inngest,
|
|
45
|
+
groups: [
|
|
46
|
+
{ slug: 'editorial', name: 'Editorial review' },
|
|
47
|
+
{ slug: 'legal', name: 'Legal review' },
|
|
48
|
+
],
|
|
49
|
+
groupResolver: {
|
|
50
|
+
async resolveUsers(slugs) {
|
|
51
|
+
// Return users belonging to any of the listed groups.
|
|
52
|
+
return payload.find({
|
|
53
|
+
collection: 'users',
|
|
54
|
+
where: { groups: { in: slugs } },
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
tokenSecret: process.env.APPROVAL_TOKEN_SECRET,
|
|
59
|
+
}),
|
|
60
|
+
publishingPlugin({
|
|
61
|
+
inngest,
|
|
62
|
+
collections: [{ slug: 'pages' }],
|
|
63
|
+
// No `approvalResolver` is needed — approvalsPlugin attaches it
|
|
64
|
+
// automatically via Symbol. Pass one explicitly only if you need
|
|
65
|
+
// to override.
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Wiring with the publishing server
|
|
72
|
+
|
|
73
|
+
The publishing server's `approvalStep` does not require an `approvalResolver` in its options. When approvalsPlugin is registered, it attaches the resolver to the Payload instance under `Symbol.for('@forumone/throughline/approvals-resolver')`, and publishing's approval step looks it up at publish time.
|
|
74
|
+
|
|
75
|
+
If you need a custom resolver (e.g. you store approvals in an external system), pass `approvalResolver` directly to `publishingPlugin` — it takes precedence over the symbol lookup.
|
|
76
|
+
|
|
77
|
+
## Phase 1 semantics
|
|
78
|
+
|
|
79
|
+
- **First-decision-wins.** Multi-party approvals (e.g. legal AND communications must both approve) are deferred to Phase 2. The Phase 1 model handles "any one approver from the configured groups," which covers the most common case.
|
|
80
|
+
- **Approvals are tied to versions.** An approval granted against one draft does not apply to a subsequent edit. The resolver checks `targetVersion` against the document version under consideration.
|
|
81
|
+
- **Action tokens are single-use, 14-day validity.** Once an approver clicks an action link, the token is appended to the request's `consumedTokens` array; reusing it returns an error.
|
|
82
|
+
- **Self-approval is blocked.** The respond_to_approval tool refuses if the caller is the requester.
|
|
83
|
+
- **Group resolution is configurable.** Clients define what "editorial" or "legal" means via the `groupResolver.resolveUsers` callback. Core does not hardcode group membership logic.
|
|
84
|
+
|
|
85
|
+
## Action endpoint
|
|
86
|
+
|
|
87
|
+
The plugin registers a GET endpoint at `${routePrefix}/action` (default `/api/approvals/action`) that accepts a `?token=` query param. Flow:
|
|
88
|
+
|
|
89
|
+
1. Verify the HMAC signature and check the token hasn't expired.
|
|
90
|
+
2. Load the approval; bail if it's already decided or the token has been consumed.
|
|
91
|
+
3. First hit: render a confirmation page. Second hit (with `?confirm=true`): record the decision, append the token to `consumedTokens`, fire `approval/decided`, write the audit record.
|
|
92
|
+
|
|
93
|
+
The confirmation page is intentionally minimal. Clients that want a branded action page can register their own endpoint that calls `verifyActionToken` and `previewVerification` directly — both are exported from the package entry point.
|
|
94
|
+
|
|
95
|
+
## The `_meta` payload
|
|
96
|
+
|
|
97
|
+
`request_approval` and `respond_to_approval` accept the framework's `_meta` payload (via `withMeta` from `@forumone/throughline-core`). Audit records carry the prompt and reasoning fields for later "why was this approved?" queries.
|
|
98
|
+
|
|
99
|
+
## Related packages
|
|
100
|
+
|
|
101
|
+
- `@forumone/throughline-core` — required peer; provides the audit log and MCP handler this plugin builds on
|
|
102
|
+
- `@forumone/throughline-publishing` — peer plugin that consumes the resolver this plugin attaches
|
|
103
|
+
- `@forumone/throughline-email` (C11) — will subscribe to `approval/requested` and `approval/decided` to send notifications
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload';
|
|
2
|
+
export interface CreateApprovalsCollectionOptions {
|
|
3
|
+
/** Override the collection slug. Default: 'approvals'. */
|
|
4
|
+
slug?: string;
|
|
5
|
+
/** Slug of the users collection that approver/requester relationships point at. Default: 'users'. */
|
|
6
|
+
usersSlug?: string;
|
|
7
|
+
/** Allowed group slugs from the plugin's `groups` option. Used as the select options on the approverGroups field. */
|
|
8
|
+
groupSlugs: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_APPROVALS_SLUG = "approvals";
|
|
11
|
+
/**
|
|
12
|
+
* Builds the approvals collection config. The collection is read-mostly:
|
|
13
|
+
* `create` is system-only (the plugin's tools mint records via the local
|
|
14
|
+
* API), `update` is admin-only (so a stuck pending record can be corrected),
|
|
15
|
+
* `delete` is denied (audit trail must remain).
|
|
16
|
+
*/
|
|
17
|
+
export declare function createApprovalsCollection(options: CreateApprovalsCollectionOptions): CollectionConfig;
|
|
18
|
+
//# sourceMappingURL=collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,gCAAgC;IAC/C,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qHAAqH;IACrH,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,eAAO,MAAM,sBAAsB,cAAc,CAAA;AAEjD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,gBAAgB,CA4FlB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export const DEFAULT_APPROVALS_SLUG = 'approvals';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the approvals collection config. The collection is read-mostly:
|
|
4
|
+
* `create` is system-only (the plugin's tools mint records via the local
|
|
5
|
+
* API), `update` is admin-only (so a stuck pending record can be corrected),
|
|
6
|
+
* `delete` is denied (audit trail must remain).
|
|
7
|
+
*/
|
|
8
|
+
export function createApprovalsCollection(options) {
|
|
9
|
+
const slug = options.slug ?? DEFAULT_APPROVALS_SLUG;
|
|
10
|
+
const usersSlug = options.usersSlug ?? 'users';
|
|
11
|
+
return {
|
|
12
|
+
slug,
|
|
13
|
+
admin: {
|
|
14
|
+
useAsTitle: 'targetTitle',
|
|
15
|
+
defaultColumns: ['targetTitle', 'status', 'requestedBy', 'requestedAt', 'expiresAt'],
|
|
16
|
+
description: 'Approval workflow state. Read-mostly through the admin; writes happen via the Approvals Server tools and the action endpoint.',
|
|
17
|
+
},
|
|
18
|
+
access: {
|
|
19
|
+
read: ({ req }) => {
|
|
20
|
+
const roles = req.user?.['roles'] ?? [];
|
|
21
|
+
return roles.includes('admin') || roles.includes('editor') || roles.includes('approver');
|
|
22
|
+
},
|
|
23
|
+
create: () => false,
|
|
24
|
+
update: ({ req }) => (req.user?.['roles'] ?? []).includes('admin'),
|
|
25
|
+
delete: () => false,
|
|
26
|
+
},
|
|
27
|
+
fields: [
|
|
28
|
+
// Target
|
|
29
|
+
{ name: 'targetCollection', type: 'text', required: true },
|
|
30
|
+
{ name: 'targetId', type: 'text', required: true },
|
|
31
|
+
{ name: 'targetTitle', type: 'text', required: true },
|
|
32
|
+
{
|
|
33
|
+
name: 'targetVersion',
|
|
34
|
+
type: 'text',
|
|
35
|
+
required: true,
|
|
36
|
+
admin: { description: 'Document version at request time. Approvals are tied to this version.' },
|
|
37
|
+
},
|
|
38
|
+
{ name: 'previewUrl', type: 'text' },
|
|
39
|
+
// Request
|
|
40
|
+
{ name: 'requestedBy', type: 'relationship', relationTo: usersSlug, required: true },
|
|
41
|
+
{
|
|
42
|
+
name: 'requestedAt',
|
|
43
|
+
type: 'date',
|
|
44
|
+
required: true,
|
|
45
|
+
defaultValue: () => new Date().toISOString(),
|
|
46
|
+
},
|
|
47
|
+
{ name: 'requestReason', type: 'textarea' },
|
|
48
|
+
{ name: 'changesSummary', type: 'textarea', required: true },
|
|
49
|
+
{
|
|
50
|
+
name: 'approverGroups',
|
|
51
|
+
type: 'select',
|
|
52
|
+
hasMany: true,
|
|
53
|
+
required: true,
|
|
54
|
+
options: options.groupSlugs.map((s) => ({ label: s, value: s })),
|
|
55
|
+
},
|
|
56
|
+
// Decision
|
|
57
|
+
{
|
|
58
|
+
name: 'status',
|
|
59
|
+
type: 'select',
|
|
60
|
+
required: true,
|
|
61
|
+
defaultValue: 'pending',
|
|
62
|
+
options: [
|
|
63
|
+
{ label: 'Pending', value: 'pending' },
|
|
64
|
+
{ label: 'Granted', value: 'granted' },
|
|
65
|
+
{ label: 'Declined', value: 'declined' },
|
|
66
|
+
{ label: 'Changes requested', value: 'changes-requested' },
|
|
67
|
+
{ label: 'Expired', value: 'expired' },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{ name: 'decidedBy', type: 'relationship', relationTo: usersSlug },
|
|
71
|
+
{ name: 'decidedAt', type: 'date' },
|
|
72
|
+
{ name: 'decisionNotes', type: 'textarea' },
|
|
73
|
+
// Workflow state
|
|
74
|
+
{
|
|
75
|
+
name: 'notifiedApprovers',
|
|
76
|
+
type: 'json',
|
|
77
|
+
defaultValue: [],
|
|
78
|
+
admin: { description: 'IDs of users notified for this request.' },
|
|
79
|
+
},
|
|
80
|
+
{ name: 'expiresAt', type: 'date', required: true },
|
|
81
|
+
{
|
|
82
|
+
name: 'consumedTokens',
|
|
83
|
+
type: 'json',
|
|
84
|
+
defaultValue: [],
|
|
85
|
+
admin: { description: 'Action tokens already consumed; prevents replay.' },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
indexes: [
|
|
89
|
+
{ fields: ['targetCollection', 'targetId', 'status'] },
|
|
90
|
+
{ fields: ['status', 'expiresAt'] },
|
|
91
|
+
{ fields: ['requestedBy', 'requestedAt'] },
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=collection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Endpoint, Payload } from 'payload';
|
|
2
|
+
import type { AuditWriter } from '@forumone/throughline-core';
|
|
3
|
+
import type { ApprovalsPluginOptions } from '../options.js';
|
|
4
|
+
import { type VerifyResult } from '../tokens.js';
|
|
5
|
+
export interface CreateActionEndpointDeps {
|
|
6
|
+
options: ApprovalsPluginOptions & {
|
|
7
|
+
tokenSecret: string;
|
|
8
|
+
};
|
|
9
|
+
auditWriter: AuditWriter;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Builds the GET endpoint that handles inline action links from emails.
|
|
13
|
+
* The flow is:
|
|
14
|
+
* - First request: render a confirmation page so a single misclicked
|
|
15
|
+
* link can't act on the approval.
|
|
16
|
+
* - Second request (with `confirm=true`): verify the token wasn't
|
|
17
|
+
* already consumed and apply the decision.
|
|
18
|
+
*
|
|
19
|
+
* Uses HTML responses (intentionally minimal styling — clients can drop
|
|
20
|
+
* a custom-branded endpoint in front if they want).
|
|
21
|
+
*/
|
|
22
|
+
export declare function createActionEndpoint(deps: CreateActionEndpointDeps): Endpoint;
|
|
23
|
+
/** Lower-level helper exported for test consumers and custom action endpoints. */
|
|
24
|
+
export type ActionVerification = VerifyResult;
|
|
25
|
+
export declare function previewVerification(token: string, payload: Payload, options: ApprovalsPluginOptions & {
|
|
26
|
+
tokenSecret: string;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
verification: VerifyResult;
|
|
29
|
+
approval: Record<string, unknown> | null;
|
|
30
|
+
}>;
|
|
31
|
+
//# sourceMappingURL=action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/endpoints/action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAE3D,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,cAAc,CAAA;AAErB,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,sBAAsB,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,WAAW,EAAE,WAAW,CAAA;CACzB;AAqBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,GAAG,QAAQ,CA+H7E;AAED,kFAAkF;AAClF,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAA;AAE7C,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,sBAAsB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC;IAAE,YAAY,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC,CAQnF"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { DEFAULT_APPROVALS_SLUG } from '../collection.js';
|
|
2
|
+
import { verifyActionToken, } from '../tokens.js';
|
|
3
|
+
const DECISION_STATUS_MAP = {
|
|
4
|
+
approve: 'granted',
|
|
5
|
+
decline: 'declined',
|
|
6
|
+
changes: 'changes-requested',
|
|
7
|
+
};
|
|
8
|
+
const DECISION_AUDIT_MAP = {
|
|
9
|
+
approve: 'approval.granted',
|
|
10
|
+
decline: 'approval.declined',
|
|
11
|
+
changes: 'approval.changes_requested',
|
|
12
|
+
};
|
|
13
|
+
const ACTION_LABEL = {
|
|
14
|
+
approve: 'Approve',
|
|
15
|
+
decline: 'Decline',
|
|
16
|
+
changes: 'Request changes',
|
|
17
|
+
discuss: 'Start discussion',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Builds the GET endpoint that handles inline action links from emails.
|
|
21
|
+
* The flow is:
|
|
22
|
+
* - First request: render a confirmation page so a single misclicked
|
|
23
|
+
* link can't act on the approval.
|
|
24
|
+
* - Second request (with `confirm=true`): verify the token wasn't
|
|
25
|
+
* already consumed and apply the decision.
|
|
26
|
+
*
|
|
27
|
+
* Uses HTML responses (intentionally minimal styling — clients can drop
|
|
28
|
+
* a custom-branded endpoint in front if they want).
|
|
29
|
+
*/
|
|
30
|
+
export function createActionEndpoint(deps) {
|
|
31
|
+
return {
|
|
32
|
+
path: '/approvals/action',
|
|
33
|
+
method: 'get',
|
|
34
|
+
handler: async (req) => {
|
|
35
|
+
const url = new URL(req.url ?? 'http://localhost');
|
|
36
|
+
const token = url.searchParams.get('token');
|
|
37
|
+
if (!token)
|
|
38
|
+
return htmlResponse(renderError('Missing action token'), 400);
|
|
39
|
+
const verification = await verifyActionToken(token, deps.options.tokenSecret);
|
|
40
|
+
if (!verification.ok) {
|
|
41
|
+
return htmlResponse(renderError(verification.error), 401);
|
|
42
|
+
}
|
|
43
|
+
const collectionSlug = deps.options.collectionSlug ?? DEFAULT_APPROVALS_SLUG;
|
|
44
|
+
const approval = (await req.payload.findByID({
|
|
45
|
+
collection: collectionSlug,
|
|
46
|
+
id: verification.token.approvalId,
|
|
47
|
+
}));
|
|
48
|
+
if (!approval)
|
|
49
|
+
return htmlResponse(renderError('Approval not found'), 404);
|
|
50
|
+
const status = String(approval['status']);
|
|
51
|
+
if (status !== 'pending') {
|
|
52
|
+
return htmlResponse(renderInfo(`This request was already ${status}.`));
|
|
53
|
+
}
|
|
54
|
+
const consumed = approval['consumedTokens'] ?? [];
|
|
55
|
+
if (consumed.includes(token)) {
|
|
56
|
+
return htmlResponse(renderError('This action link has already been used'), 400);
|
|
57
|
+
}
|
|
58
|
+
const expiresAtRaw = approval['expiresAt'];
|
|
59
|
+
if (typeof expiresAtRaw === 'string' &&
|
|
60
|
+
Date.parse(expiresAtRaw) < Date.now()) {
|
|
61
|
+
return htmlResponse(renderInfo('This approval request has expired.'));
|
|
62
|
+
}
|
|
63
|
+
const action = verification.token.action;
|
|
64
|
+
const confirm = url.searchParams.get('confirm') === 'true';
|
|
65
|
+
if (!confirm) {
|
|
66
|
+
return htmlResponse(renderConfirmation({
|
|
67
|
+
token,
|
|
68
|
+
action,
|
|
69
|
+
targetTitle: stringOrFallback(approval['targetTitle'], 'this document'),
|
|
70
|
+
changesSummary: stringOrFallback(approval['changesSummary'], ''),
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
if (action === 'discuss') {
|
|
74
|
+
// Discuss isn't a decision — surface a message and don't mutate state.
|
|
75
|
+
// C11 listens for the 'approval/discussed' event to thread it into
|
|
76
|
+
// the email reply UX once that lands.
|
|
77
|
+
await deps.options.inngest.send({
|
|
78
|
+
name: 'approval/discussed',
|
|
79
|
+
data: {
|
|
80
|
+
approvalId: verification.token.approvalId,
|
|
81
|
+
approverId: verification.token.approverId,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
await deps.auditWriter({
|
|
85
|
+
actor: { type: 'user', userId: verification.token.approverId, apiKeyName: 'action-token' },
|
|
86
|
+
action: 'approval.discussed',
|
|
87
|
+
mcpServer: 'approvals',
|
|
88
|
+
mcpTool: 'action-endpoint',
|
|
89
|
+
targetCollection: String(approval['targetCollection']),
|
|
90
|
+
targetId: String(approval['targetId']),
|
|
91
|
+
approvalRequestId: verification.token.approvalId,
|
|
92
|
+
success: true,
|
|
93
|
+
});
|
|
94
|
+
return htmlResponse(renderInfo('Thanks — a discussion thread will follow up by email.'));
|
|
95
|
+
}
|
|
96
|
+
const newStatus = DECISION_STATUS_MAP[action];
|
|
97
|
+
const decidedAt = new Date().toISOString();
|
|
98
|
+
await req.payload.update({
|
|
99
|
+
collection: collectionSlug,
|
|
100
|
+
id: verification.token.approvalId,
|
|
101
|
+
data: {
|
|
102
|
+
status: newStatus,
|
|
103
|
+
decidedBy: verification.token.approverId,
|
|
104
|
+
decidedAt,
|
|
105
|
+
consumedTokens: [...consumed, token],
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
await deps.options.inngest.send({
|
|
109
|
+
name: 'approval/decided',
|
|
110
|
+
data: {
|
|
111
|
+
approvalId: verification.token.approvalId,
|
|
112
|
+
decision: newStatus,
|
|
113
|
+
decidedBy: verification.token.approverId,
|
|
114
|
+
decidedAt,
|
|
115
|
+
targetCollection: String(approval['targetCollection']),
|
|
116
|
+
targetId: String(approval['targetId']),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
await deps.auditWriter({
|
|
120
|
+
actor: {
|
|
121
|
+
type: 'user',
|
|
122
|
+
userId: verification.token.approverId,
|
|
123
|
+
apiKeyName: 'action-token',
|
|
124
|
+
},
|
|
125
|
+
action: DECISION_AUDIT_MAP[action],
|
|
126
|
+
mcpServer: 'approvals',
|
|
127
|
+
mcpTool: 'action-endpoint',
|
|
128
|
+
targetCollection: String(approval['targetCollection']),
|
|
129
|
+
targetId: String(approval['targetId']),
|
|
130
|
+
targetTitle: stringOrFallback(approval['targetTitle'], String(approval['targetId'])),
|
|
131
|
+
approvalRequestId: verification.token.approvalId,
|
|
132
|
+
success: true,
|
|
133
|
+
});
|
|
134
|
+
return htmlResponse(renderSuccess(ACTION_LABEL[action], stringOrFallback(approval['targetTitle'], 'this document')));
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export async function previewVerification(token, payload, options) {
|
|
139
|
+
const verification = await verifyActionToken(token, options.tokenSecret);
|
|
140
|
+
if (!verification.ok)
|
|
141
|
+
return { verification, approval: null };
|
|
142
|
+
const approval = (await payload.findByID({
|
|
143
|
+
collection: options.collectionSlug ?? DEFAULT_APPROVALS_SLUG,
|
|
144
|
+
id: verification.token.approvalId,
|
|
145
|
+
}));
|
|
146
|
+
return { verification, approval };
|
|
147
|
+
}
|
|
148
|
+
function htmlResponse(html, status = 200) {
|
|
149
|
+
return new Response(html, {
|
|
150
|
+
status,
|
|
151
|
+
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function renderConfirmation(args) {
|
|
155
|
+
const label = ACTION_LABEL[args.action];
|
|
156
|
+
const summary = args.changesSummary
|
|
157
|
+
? `<p><strong>What's changing:</strong> ${escape(args.changesSummary)}</p>`
|
|
158
|
+
: '';
|
|
159
|
+
return htmlPage(`
|
|
160
|
+
<h1>${escape(label)}</h1>
|
|
161
|
+
<p>You're about to <strong>${escape(label.toLowerCase())}</strong> the request for <em>${escape(args.targetTitle)}</em>.</p>
|
|
162
|
+
${summary}
|
|
163
|
+
<form method="GET">
|
|
164
|
+
<input type="hidden" name="token" value="${escape(args.token)}" />
|
|
165
|
+
<input type="hidden" name="confirm" value="true" />
|
|
166
|
+
<button type="submit">Confirm: ${escape(label)}</button>
|
|
167
|
+
</form>
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
function renderSuccess(label, title) {
|
|
171
|
+
return htmlPage(`<h1>${escape(label)}</h1><p>Your decision on <em>${escape(title)}</em> has been recorded.</p>`);
|
|
172
|
+
}
|
|
173
|
+
function renderError(message) {
|
|
174
|
+
return htmlPage(`<h1>Action could not be completed</h1><p>${escape(message)}</p>`);
|
|
175
|
+
}
|
|
176
|
+
function renderInfo(message) {
|
|
177
|
+
return htmlPage(`<h1>Notice</h1><p>${escape(message)}</p>`);
|
|
178
|
+
}
|
|
179
|
+
function htmlPage(body) {
|
|
180
|
+
return `<!DOCTYPE html>
|
|
181
|
+
<html lang="en">
|
|
182
|
+
<head>
|
|
183
|
+
<meta charset="utf-8" />
|
|
184
|
+
<title>Approval action</title>
|
|
185
|
+
<style>
|
|
186
|
+
body { font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; max-width: 600px; margin: 3rem auto; padding: 0 1rem; color: #18181b; line-height: 1.6; }
|
|
187
|
+
h1 { font-size: 1.5rem; }
|
|
188
|
+
button { background: #18181b; color: white; padding: 0.75rem 1.5rem; border: none; border-radius: 0.25rem; font-size: 1rem; cursor: pointer; }
|
|
189
|
+
em { font-style: italic; }
|
|
190
|
+
</style>
|
|
191
|
+
</head>
|
|
192
|
+
<body>${body}</body>
|
|
193
|
+
</html>`;
|
|
194
|
+
}
|
|
195
|
+
function escape(text) {
|
|
196
|
+
return String(text ?? '')
|
|
197
|
+
.replace(/&/g, '&')
|
|
198
|
+
.replace(/</g, '<')
|
|
199
|
+
.replace(/>/g, '>')
|
|
200
|
+
.replace(/"/g, '"');
|
|
201
|
+
}
|
|
202
|
+
function stringOrFallback(value, fallback) {
|
|
203
|
+
return typeof value === 'string' ? value : fallback;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/endpoints/action.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { approvalsPlugin } from './plugin.js';
|
|
2
|
+
export type { ApprovalsPluginOptions, ApproverGroup, GroupResolver, ResolvedApprover, } from './options.js';
|
|
3
|
+
export { DEFAULT_APPROVALS_SLUG, createApprovalsCollection, } from './collection.js';
|
|
4
|
+
export type { CreateApprovalsCollectionOptions } from './collection.js';
|
|
5
|
+
export { APPROVALS_RESOLVER_SYMBOL, attachApprovalResolver, createApprovalResolver, } from './resolver.js';
|
|
6
|
+
export type { CreateApprovalResolverOptions } from './resolver.js';
|
|
7
|
+
export { createActionEndpoint } from './endpoints/action.js';
|
|
8
|
+
export type { CreateActionEndpointDeps } from './endpoints/action.js';
|
|
9
|
+
export { generateActionToken, verifyActionToken, buildActionUrl, } from './tokens.js';
|
|
10
|
+
export type { ActionToken, ActionTokenAction, VerifyOptions, VerifyResult, } from './tokens.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,gCAAgC,EAAE,MAAM,iBAAiB,CAAA;AAEvE,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAA;AAElE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAC5D,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAErE,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,YAAY,GACb,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { approvalsPlugin } from './plugin.js';
|
|
2
|
+
export { DEFAULT_APPROVALS_SLUG, createApprovalsCollection, } from './collection.js';
|
|
3
|
+
export { APPROVALS_RESOLVER_SYMBOL, attachApprovalResolver, createApprovalResolver, } from './resolver.js';
|
|
4
|
+
export { createActionEndpoint } from './endpoints/action.js';
|
|
5
|
+
export { generateActionToken, verifyActionToken, buildActionUrl, } from './tokens.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAS7C,OAAO,EACL,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAG5D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,GACf,MAAM,aAAa,CAAA"}
|