@indecisivedev/openplan 0.2.0-alpha.2
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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/access-policy.d.ts +35 -0
- package/dist/access-policy.js +107 -0
- package/dist/access-policy.js.map +1 -0
- package/dist/access-store.d.ts +33 -0
- package/dist/access-store.js +212 -0
- package/dist/access-store.js.map +1 -0
- package/dist/admin.d.ts +1 -0
- package/dist/admin.js +13 -0
- package/dist/admin.js.map +1 -0
- package/dist/api.d.ts +2 -0
- package/dist/api.js +867 -0
- package/dist/api.js.map +1 -0
- package/dist/bootstrap-admin.d.ts +1 -0
- package/dist/bootstrap-admin.js +35 -0
- package/dist/bootstrap-admin.js.map +1 -0
- package/dist/browser-security.d.ts +5 -0
- package/dist/browser-security.js +53 -0
- package/dist/browser-security.js.map +1 -0
- package/dist/celld-quickstart.d.ts +51 -0
- package/dist/celld-quickstart.js +1175 -0
- package/dist/celld-quickstart.js.map +1 -0
- package/dist/cli-api-client.d.ts +78 -0
- package/dist/cli-api-client.js +272 -0
- package/dist/cli-api-client.js.map +1 -0
- package/dist/cli-credentials.d.ts +47 -0
- package/dist/cli-credentials.js +447 -0
- package/dist/cli-credentials.js.map +1 -0
- package/dist/cli-endpoint.d.ts +19 -0
- package/dist/cli-endpoint.js +56 -0
- package/dist/cli-endpoint.js.map +1 -0
- package/dist/cli-program.d.ts +11 -0
- package/dist/cli-program.js +934 -0
- package/dist/cli-program.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-ip.d.ts +4 -0
- package/dist/client-ip.js +49 -0
- package/dist/client-ip.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +113 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +3 -0
- package/dist/crypto.js +11 -0
- package/dist/crypto.js.map +1 -0
- package/dist/database.d.ts +15 -0
- package/dist/database.js +155 -0
- package/dist/database.js.map +1 -0
- package/dist/db.d.ts +31 -0
- package/dist/db.js +388 -0
- package/dist/db.js.map +1 -0
- package/dist/drafts.d.ts +43 -0
- package/dist/drafts.js +90 -0
- package/dist/drafts.js.map +1 -0
- package/dist/feature-settings.d.ts +7 -0
- package/dist/feature-settings.js +32 -0
- package/dist/feature-settings.js.map +1 -0
- package/dist/generated/annotation-client.d.ts +1 -0
- package/dist/generated/annotation-client.js +2 -0
- package/dist/generated/annotation-client.js.map +1 -0
- package/dist/html-policy.d.ts +15 -0
- package/dist/html-policy.js +172 -0
- package/dist/html-policy.js.map +1 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +10 -0
- package/dist/ids.js.map +1 -0
- package/dist/local-auth.d.ts +36 -0
- package/dist/local-auth.js +215 -0
- package/dist/local-auth.js.map +1 -0
- package/dist/maintenance.d.ts +1 -0
- package/dist/maintenance.js +18 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/oidc.d.ts +39 -0
- package/dist/oidc.js +194 -0
- package/dist/oidc.js.map +1 -0
- package/dist/public-url.d.ts +17 -0
- package/dist/public-url.js +96 -0
- package/dist/public-url.js.map +1 -0
- package/dist/rate-limit.d.ts +25 -0
- package/dist/rate-limit.js +55 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/reconcile.d.ts +5 -0
- package/dist/reconcile.js +18 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/render-annotations.d.ts +10 -0
- package/dist/render-annotations.js +73 -0
- package/dist/render-annotations.js.map +1 -0
- package/dist/render-web.d.ts +74 -0
- package/dist/render-web.js +439 -0
- package/dist/render-web.js.map +1 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +85 -0
- package/dist/render.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +26 -0
- package/dist/server.js.map +1 -0
- package/dist/shoo.d.ts +26 -0
- package/dist/shoo.js +92 -0
- package/dist/shoo.js.map +1 -0
- package/dist/sqlite-migrations.d.ts +6 -0
- package/dist/sqlite-migrations.js +214 -0
- package/dist/sqlite-migrations.js.map +1 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +86 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upload-state.d.ts +19 -0
- package/dist/upload-state.js +118 -0
- package/dist/upload-state.js.map +1 -0
- package/dist/web-auth.d.ts +33 -0
- package/dist/web-auth.js +122 -0
- package/dist/web-auth.js.map +1 -0
- package/dist/web.d.ts +2 -0
- package/dist/web.js +550 -0
- package/dist/web.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 t3dotgg
|
|
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
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Openplan
|
|
2
|
+
|
|
3
|
+
Openplan publishes authenticated static HTML drafts for agents and teams. This
|
|
4
|
+
source release includes a TypeScript CLI, a Node.js self-hosted runtime, and a
|
|
5
|
+
complete celld Worker project.
|
|
6
|
+
|
|
7
|
+
**New authenticated plans are private by default.** Public sharing is an explicit
|
|
8
|
+
owner choice; configure browser identity before expecting private reads. Existing
|
|
9
|
+
policies and historical legacy defaults are retained on upgrade.
|
|
10
|
+
|
|
11
|
+
Start with [Getting started](./docs/README.md). Agents can use
|
|
12
|
+
[`llms.txt`](./llms.txt) for setup paths, CLI commands, and verification steps.
|
|
13
|
+
|
|
14
|
+
## Choose a deployment path
|
|
15
|
+
|
|
16
|
+
The alpha focuses on two deployment paths:
|
|
17
|
+
|
|
18
|
+
- **[Docker Compose on Linux — recommended first setup](./docs/self-hosted.md):**
|
|
19
|
+
run the app, SQLite, and SeaweedFS on one machine. The guide includes private
|
|
20
|
+
configuration generation, image builds, administrator bootstrap, browser
|
|
21
|
+
sign-in, and commands through the first verified upload and restart.
|
|
22
|
+
- **[Dedicated celld](./docs/celld.md):** install on an existing healthy fleet.
|
|
23
|
+
The quickstart supports celld 0.5.1 and the legacy 0.3.x/0.4.x paths, with
|
|
24
|
+
version-specific configuration. This does not provision the fleet itself.
|
|
25
|
+
|
|
26
|
+
The celld quickstart includes a neutral Wrangler configuration, protected
|
|
27
|
+
variable template, D1 migrations, reload/restart requirements, readiness
|
|
28
|
+
checks, bootstrap authentication, and a first upload. Do not deploy it onto a
|
|
29
|
+
shared fleet that already serves another application.
|
|
30
|
+
|
|
31
|
+
## Install the CLI
|
|
32
|
+
|
|
33
|
+
The CLI alpha supports **Linux with Node.js 24.x**. macOS and Windows are not
|
|
34
|
+
supported yet; saved credentials require Linux's `/proc` filesystem. Deployment
|
|
35
|
+
guides below build from source. For an existing service, the planned npm channel
|
|
36
|
+
is `@indecisivedev/openplan@next`; once the public release is published, install it with:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npm install --global @indecisivedev/openplan@next
|
|
40
|
+
openplan --version
|
|
41
|
+
openplan endpoint set https://openplan.example
|
|
42
|
+
openplan auth login
|
|
43
|
+
openplan whoami
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For a one-off invocation, use `npx --package=@indecisivedev/openplan@next openplan --help`.
|
|
47
|
+
Replace the example origin with your service. These commands describe the release
|
|
48
|
+
channel and do not establish that it is published. Before publication, use the
|
|
49
|
+
source build below. See [Connect the CLI](./docs/connect.md) for verification,
|
|
50
|
+
key enrollment, and the first upload.
|
|
51
|
+
|
|
52
|
+
The same CLI is also prepared for JSR as `@aarons7/openplan`. Once the alpha is
|
|
53
|
+
published, Linux users with Deno 2.9.6 can run:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
deno run -A jsr:@aarons7/openplan@0.2.0-alpha.2/cli --help
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
See the [JSR CLI guide](https://github.com/aaronS7/openplan/blob/main/jsr/README.md) for permissions, setup and runtime
|
|
60
|
+
requirements. The source distribution includes both registry configurations;
|
|
61
|
+
these commands do not establish that either version has been published.
|
|
62
|
+
|
|
63
|
+
## Build the source CLI
|
|
64
|
+
|
|
65
|
+
For a first installation, follow your deployment guide above in order; it
|
|
66
|
+
includes the required builds. These are the standalone source build checks:
|
|
67
|
+
|
|
68
|
+
[Node.js 24.x](https://nodejs.org/en/download) is the supported runtime for this
|
|
69
|
+
alpha. Other Node.js majors are outside the support scope. Confirm
|
|
70
|
+
`node --version` and `npm --version` before continuing.
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npm ci
|
|
74
|
+
npm run check
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Annotation bundles are reproducible from the included client code through
|
|
78
|
+
`npm run generate:annotations`.
|
|
79
|
+
|
|
80
|
+
## Setup and publishing
|
|
81
|
+
|
|
82
|
+
- [Connect to an existing service](./docs/connect.md): install or build the CLI, select an
|
|
83
|
+
endpoint, authenticate, and verify a first upload.
|
|
84
|
+
- [Publish and update](./docs/publishing.md): static HTML sample, byte
|
|
85
|
+
verification, versions, and sharing.
|
|
86
|
+
- [CLI reference](./docs/cli.md) and [configuration](./docs/configuration.md):
|
|
87
|
+
exact commands and runtime-specific variables.
|
|
88
|
+
- [OpenID Connect](./docs/oidc.md): configure browser SSO with your identity provider.
|
|
89
|
+
- [Troubleshooting](./docs/troubleshooting.md): recover from a failed step.
|
|
90
|
+
|
|
91
|
+
## CLI credentials and upgrades
|
|
92
|
+
|
|
93
|
+
CLI keys are bound to a canonical origin. Login uses masked TTY input;
|
|
94
|
+
noninteractive enrollment uses `auth set --stdin --api-url <origin>`, never a
|
|
95
|
+
positional key. Both verify fresh input and save only the binding. To also change
|
|
96
|
+
the default, run `endpoint set <origin>` separately; the former combined behavior
|
|
97
|
+
is intentionally removed. See [safe enrollment](./docs/cli.md#enroll-a-key-without-changing-the-default).
|
|
98
|
+
|
|
99
|
+
Environment keys require independently configured `OPENPLAN_API_KEY_ORIGIN`;
|
|
100
|
+
missing/mismatched binding fails without fallback. Default uploads require
|
|
101
|
+
authentication; anonymous upload is explicit. Older global keys remain inactive.
|
|
102
|
+
[Upgrade and re-enroll every client](./docs/troubleshooting.md#upgrade-and-re-enroll-older-clients)
|
|
103
|
+
and retire old writers before resuming jobs. These instructions do not claim a
|
|
104
|
+
live rollout or public release has been qualified or published.
|
|
105
|
+
|
|
106
|
+
## Help and contributions
|
|
107
|
+
|
|
108
|
+
See [Contributing](./CONTRIBUTING.md) for installation reports, bug reports,
|
|
109
|
+
and proposed changes. Use [the security policy](./SECURITY.md) to report a
|
|
110
|
+
vulnerability privately.
|
|
111
|
+
|
|
112
|
+
## Project lineage
|
|
113
|
+
|
|
114
|
+
Openplan is a fork of Postplan by Theo Browne. New interfaces use the Openplan
|
|
115
|
+
name, while some Postplan identifiers remain as deprecated compatibility
|
|
116
|
+
aliases. The upstream copyright notice is preserved in `LICENSE`.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Request } from "express";
|
|
2
|
+
export type AccessPolicyMode = "private" | "password" | "public";
|
|
3
|
+
export type ResolvedAccessMode = AccessPolicyMode | "authenticated";
|
|
4
|
+
export interface AccessPolicyRecord {
|
|
5
|
+
id: string;
|
|
6
|
+
mode: AccessPolicyMode;
|
|
7
|
+
password_algorithm: string | null;
|
|
8
|
+
password_iterations: number | null;
|
|
9
|
+
password_salt: string | null;
|
|
10
|
+
password_hash: string | null;
|
|
11
|
+
credential_revision: number;
|
|
12
|
+
}
|
|
13
|
+
export interface AccessPolicyView {
|
|
14
|
+
mode: ResolvedAccessMode;
|
|
15
|
+
inherited: boolean;
|
|
16
|
+
legacy: boolean;
|
|
17
|
+
passwordSet: boolean;
|
|
18
|
+
policyId: string | null;
|
|
19
|
+
credentialRevision: number | null;
|
|
20
|
+
}
|
|
21
|
+
export interface PasswordMaterial {
|
|
22
|
+
algorithm: "pbkdf2-sha256-v1";
|
|
23
|
+
iterations: number;
|
|
24
|
+
salt: string;
|
|
25
|
+
hash: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function nodeLegacyAccessMode(): ResolvedAccessMode;
|
|
28
|
+
export declare function policyView(policy: AccessPolicyRecord | null, inherited: boolean, legacyMode: ResolvedAccessMode): AccessPolicyView;
|
|
29
|
+
export declare function derivePassword(password: string): PasswordMaterial;
|
|
30
|
+
export declare function verifyPassword(password: string, policy: AccessPolicyRecord): boolean;
|
|
31
|
+
export declare function createUnlockToken(policy: AccessPolicyRecord, draftId: string, versionId: string): string;
|
|
32
|
+
export declare function hasValidUnlock(req: Request, policy: AccessPolicyRecord, draftId: string, versionId: string, versionNumber?: number): boolean;
|
|
33
|
+
export declare function unlockCookie(token: string, draftId: string, versionNumber?: number): string;
|
|
34
|
+
export declare function unlockCookieName(draftId: string, versionNumber?: number): string;
|
|
35
|
+
export declare function requireShareSecret(): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { pbkdf2Sync, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { config } from "./config.js";
|
|
3
|
+
import { signToken, verifyToken } from "./web-auth.js";
|
|
4
|
+
const UNLOCK_TTL_SECONDS = 12 * 60 * 60;
|
|
5
|
+
export function nodeLegacyAccessMode() {
|
|
6
|
+
return "public";
|
|
7
|
+
}
|
|
8
|
+
export function policyView(policy, inherited, legacyMode) {
|
|
9
|
+
return {
|
|
10
|
+
mode: policy?.mode || legacyMode,
|
|
11
|
+
inherited,
|
|
12
|
+
legacy: !policy,
|
|
13
|
+
passwordSet: policy?.mode === "password" && Boolean(policy.password_hash),
|
|
14
|
+
policyId: policy?.id || null,
|
|
15
|
+
credentialRevision: policy?.credential_revision ?? null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function derivePassword(password) {
|
|
19
|
+
const secret = requireShareSecret();
|
|
20
|
+
const salt = randomBytes(16).toString("base64url");
|
|
21
|
+
return {
|
|
22
|
+
algorithm: "pbkdf2-sha256-v1",
|
|
23
|
+
iterations: config.passwordIterations,
|
|
24
|
+
salt,
|
|
25
|
+
hash: passwordHash(password, secret, salt, config.passwordIterations)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function verifyPassword(password, policy) {
|
|
29
|
+
if (password.length > 512 ||
|
|
30
|
+
policy.mode !== "password" ||
|
|
31
|
+
policy.password_algorithm !== "pbkdf2-sha256-v1" ||
|
|
32
|
+
!policy.password_iterations ||
|
|
33
|
+
!policy.password_salt ||
|
|
34
|
+
!policy.password_hash)
|
|
35
|
+
return false;
|
|
36
|
+
const actual = Buffer.from(passwordHash(password, requireShareSecret(), policy.password_salt, policy.password_iterations), "utf8");
|
|
37
|
+
const expected = Buffer.from(policy.password_hash, "utf8");
|
|
38
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
39
|
+
}
|
|
40
|
+
export function createUnlockToken(policy, draftId, versionId) {
|
|
41
|
+
return signToken({
|
|
42
|
+
draftId,
|
|
43
|
+
versionId,
|
|
44
|
+
policyId: policy.id,
|
|
45
|
+
credentialRevision: policy.credential_revision
|
|
46
|
+
}, requireShareSecret(), UNLOCK_TTL_SECONDS);
|
|
47
|
+
}
|
|
48
|
+
export function hasValidUnlock(req, policy, draftId, versionId, versionNumber) {
|
|
49
|
+
const header = req.get("authorization") || "";
|
|
50
|
+
const headerMatch = header.match(/^Openplan-Share\s+(.+)$/i);
|
|
51
|
+
const token = headerMatch?.[1]?.trim() || readCookie(req, unlockCookieName(draftId, versionNumber));
|
|
52
|
+
const payload = token ? verifyToken(token, requireShareSecret()) : null;
|
|
53
|
+
return isUnlockPayload(payload) &&
|
|
54
|
+
payload.draftId === draftId &&
|
|
55
|
+
payload.versionId === versionId &&
|
|
56
|
+
payload.policyId === policy.id &&
|
|
57
|
+
payload.credentialRevision === policy.credential_revision;
|
|
58
|
+
}
|
|
59
|
+
export function unlockCookie(token, draftId, versionNumber) {
|
|
60
|
+
const attributes = [
|
|
61
|
+
`${unlockCookieName(draftId, versionNumber)}=${encodeURIComponent(token)}`,
|
|
62
|
+
"Path=/",
|
|
63
|
+
"HttpOnly",
|
|
64
|
+
"SameSite=Lax",
|
|
65
|
+
`Max-Age=${UNLOCK_TTL_SECONDS}`
|
|
66
|
+
];
|
|
67
|
+
if (process.env.NODE_ENV !== "development")
|
|
68
|
+
attributes.push("Secure");
|
|
69
|
+
return attributes.join("; ");
|
|
70
|
+
}
|
|
71
|
+
export function unlockCookieName(draftId, versionNumber) {
|
|
72
|
+
return `openplan_unlock_${draftId}_${versionNumber === undefined ? "current" : `v${versionNumber}`}`;
|
|
73
|
+
}
|
|
74
|
+
export function requireShareSecret() {
|
|
75
|
+
if (!config.shareSecret)
|
|
76
|
+
throw new Error("OPENPLAN_SHARE_SECRET is not configured.");
|
|
77
|
+
return config.shareSecret;
|
|
78
|
+
}
|
|
79
|
+
function passwordHash(password, secret, salt, iterations) {
|
|
80
|
+
return pbkdf2Sync(`${password}\0${secret}`, Buffer.from(salt, "base64url"), iterations, 32, "sha256")
|
|
81
|
+
.toString("base64url");
|
|
82
|
+
}
|
|
83
|
+
function readCookie(req, name) {
|
|
84
|
+
const header = req.get("cookie") || "";
|
|
85
|
+
for (const part of header.split(";")) {
|
|
86
|
+
const separator = part.indexOf("=");
|
|
87
|
+
if (separator === -1 || part.slice(0, separator).trim() !== name)
|
|
88
|
+
continue;
|
|
89
|
+
try {
|
|
90
|
+
return decodeURIComponent(part.slice(separator + 1).trim());
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function isUnlockPayload(value) {
|
|
99
|
+
if (!value || typeof value !== "object")
|
|
100
|
+
return false;
|
|
101
|
+
const payload = value;
|
|
102
|
+
return typeof payload.draftId === "string" &&
|
|
103
|
+
typeof payload.versionId === "string" &&
|
|
104
|
+
typeof payload.policyId === "string" &&
|
|
105
|
+
typeof payload.credentialRevision === "number";
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=access-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-policy.js","sourceRoot":"","sources":["../src/access-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DatabaseClient } from "./database.js";
|
|
2
|
+
import { type AccessPolicyMode, type AccessPolicyRecord, type AccessPolicyView, type ResolvedAccessMode } from "./access-policy.js";
|
|
3
|
+
export interface DraftAccessTarget {
|
|
4
|
+
draftId: string;
|
|
5
|
+
ownerAccountId: string;
|
|
6
|
+
title: string;
|
|
7
|
+
versionId: string;
|
|
8
|
+
versionNumber: number;
|
|
9
|
+
objectKey: string;
|
|
10
|
+
contentHash: string;
|
|
11
|
+
inherited: boolean;
|
|
12
|
+
policy: AccessPolicyRecord | null;
|
|
13
|
+
mode: ResolvedAccessMode;
|
|
14
|
+
}
|
|
15
|
+
export interface DraftAccessOverview {
|
|
16
|
+
plan: AccessPolicyView;
|
|
17
|
+
versions: Map<number, AccessPolicyView>;
|
|
18
|
+
}
|
|
19
|
+
export interface AccessMutationInput {
|
|
20
|
+
accountId: string;
|
|
21
|
+
apiKeyId?: string | null;
|
|
22
|
+
draftId: string;
|
|
23
|
+
versionNumber?: number;
|
|
24
|
+
mode?: AccessPolicyMode;
|
|
25
|
+
password?: string;
|
|
26
|
+
inherit?: boolean;
|
|
27
|
+
requestId?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function createDefaultPolicy(client: DatabaseClient, accountId: string, mode: AccessPolicyMode): Promise<string>;
|
|
30
|
+
export declare function findDraftAccessTarget(draftId: string, versionNumber?: number): Promise<DraftAccessTarget | null>;
|
|
31
|
+
export declare function canAccountReadDraft(accountId: string, ownerAccountId: string): Promise<boolean>;
|
|
32
|
+
export declare function getDraftAccessOverview(accountId: string, draftId: string): Promise<DraftAccessOverview | null>;
|
|
33
|
+
export declare function setAccessPolicy(input: AccessMutationInput): Promise<AccessPolicyView | null>;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { derivePassword, nodeLegacyAccessMode, policyView } from "./access-policy.js";
|
|
2
|
+
import { newInternalId } from "./ids.js";
|
|
3
|
+
import { newEventId, pool, withTransaction } from "./db.js";
|
|
4
|
+
export async function createDefaultPolicy(client, accountId, mode) {
|
|
5
|
+
return insertPolicy(client, accountId, mode);
|
|
6
|
+
}
|
|
7
|
+
export async function findDraftAccessTarget(draftId, versionNumber) {
|
|
8
|
+
const versionClause = versionNumber === undefined
|
|
9
|
+
? "v.id = d.current_version_id"
|
|
10
|
+
: "v.draft_id = d.id AND v.version_number = $2";
|
|
11
|
+
const values = versionNumber === undefined ? [draftId] : [draftId, versionNumber];
|
|
12
|
+
const result = await pool.query(`
|
|
13
|
+
SELECT d.id AS draft_id, d.account_id, d.title,
|
|
14
|
+
v.id AS version_id, v.version_number, v.object_key, v.content_hash,
|
|
15
|
+
v.access_policy_id AS version_policy_id,
|
|
16
|
+
COALESCE(v.access_policy_id, d.default_access_policy_id) AS effective_policy_id,
|
|
17
|
+
p.id AS policy_id, p.mode, p.password_algorithm,
|
|
18
|
+
p.password_iterations, p.password_salt, p.password_hash,
|
|
19
|
+
p.credential_revision
|
|
20
|
+
FROM drafts d
|
|
21
|
+
JOIN draft_versions v ON ${versionClause}
|
|
22
|
+
LEFT JOIN draft_access_policies p
|
|
23
|
+
ON p.id = COALESCE(v.access_policy_id, d.default_access_policy_id)
|
|
24
|
+
WHERE d.id = $1
|
|
25
|
+
AND d.deleted_at IS NULL
|
|
26
|
+
AND d.disabled_at IS NULL
|
|
27
|
+
AND v.status = 'ready'
|
|
28
|
+
LIMIT 1
|
|
29
|
+
`, values);
|
|
30
|
+
return mapTarget(result.rows[0]);
|
|
31
|
+
}
|
|
32
|
+
export async function canAccountReadDraft(accountId, ownerAccountId) {
|
|
33
|
+
if (accountId === ownerAccountId)
|
|
34
|
+
return true;
|
|
35
|
+
const result = await pool.query(`
|
|
36
|
+
SELECT 1 FROM account_access_grants g
|
|
37
|
+
JOIN api_keys grant_key ON grant_key.id = g.created_by_api_key_id
|
|
38
|
+
AND grant_key.revoked_at IS NULL AND grant_key.account_id = g.resource_account_id
|
|
39
|
+
WHERE viewer_account_id = $1 AND resource_account_id = $2
|
|
40
|
+
LIMIT 1
|
|
41
|
+
`, [accountId, ownerAccountId]);
|
|
42
|
+
return Boolean(result.rowCount);
|
|
43
|
+
}
|
|
44
|
+
export async function getDraftAccessOverview(accountId, draftId) {
|
|
45
|
+
const draft = await pool.query(`SELECT id, default_access_policy_id FROM drafts
|
|
46
|
+
WHERE id = $1 AND account_id = $2 AND deleted_at IS NULL LIMIT 1`, [draftId, accountId]);
|
|
47
|
+
const row = draft.rows[0];
|
|
48
|
+
if (!row)
|
|
49
|
+
return null;
|
|
50
|
+
const policies = await pool.query(`
|
|
51
|
+
SELECT CAST(NULL AS INTEGER) AS version_number, CAST(NULL AS TEXT) AS version_policy_id,
|
|
52
|
+
d.default_access_policy_id AS effective_policy_id,
|
|
53
|
+
p.id AS policy_id, p.mode, p.password_algorithm, p.password_iterations,
|
|
54
|
+
p.password_salt, p.password_hash, p.credential_revision
|
|
55
|
+
FROM drafts d
|
|
56
|
+
LEFT JOIN draft_access_policies p ON p.id = d.default_access_policy_id
|
|
57
|
+
WHERE d.id = $1
|
|
58
|
+
UNION ALL
|
|
59
|
+
SELECT v.version_number, v.access_policy_id AS version_policy_id,
|
|
60
|
+
COALESCE(v.access_policy_id, d.default_access_policy_id) AS effective_policy_id,
|
|
61
|
+
p.id AS policy_id, p.mode, p.password_algorithm, p.password_iterations,
|
|
62
|
+
p.password_salt, p.password_hash, p.credential_revision
|
|
63
|
+
FROM draft_versions v
|
|
64
|
+
JOIN drafts d ON d.id = v.draft_id
|
|
65
|
+
LEFT JOIN draft_access_policies p
|
|
66
|
+
ON p.id = COALESCE(v.access_policy_id, d.default_access_policy_id)
|
|
67
|
+
WHERE v.draft_id = $1 AND v.status = 'ready'
|
|
68
|
+
`, [draftId]);
|
|
69
|
+
const [planRow, ...versionRows] = policies.rows;
|
|
70
|
+
const planPolicy = policyFromRow(planRow);
|
|
71
|
+
const legacyMode = nodeLegacyAccessMode();
|
|
72
|
+
const versions = new Map();
|
|
73
|
+
for (const version of versionRows) {
|
|
74
|
+
if (version.version_number === null)
|
|
75
|
+
continue;
|
|
76
|
+
versions.set(Number(version.version_number), policyView(policyFromRow(version), version.version_policy_id === null, legacyMode));
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
plan: policyView(planPolicy, false, legacyMode),
|
|
80
|
+
versions
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function setAccessPolicy(input) {
|
|
84
|
+
return withTransaction(async (client) => {
|
|
85
|
+
const draft = await client.query(`SELECT id, account_id, default_access_policy_id FROM drafts
|
|
86
|
+
WHERE id = $1 AND account_id = $2 AND deleted_at IS NULL LIMIT 1`, [input.draftId, input.accountId]);
|
|
87
|
+
const owned = draft.rows[0];
|
|
88
|
+
if (!owned)
|
|
89
|
+
return null;
|
|
90
|
+
let versionId = null;
|
|
91
|
+
let previousPolicyId = owned.default_access_policy_id;
|
|
92
|
+
if (input.versionNumber !== undefined) {
|
|
93
|
+
const version = await client.query(`SELECT id, access_policy_id FROM draft_versions
|
|
94
|
+
WHERE draft_id = $1 AND version_number = $2 LIMIT 1`, [input.draftId, input.versionNumber]);
|
|
95
|
+
const versionRow = version.rows[0];
|
|
96
|
+
if (!versionRow)
|
|
97
|
+
return null;
|
|
98
|
+
versionId = versionRow.id;
|
|
99
|
+
previousPolicyId = versionRow.access_policy_id;
|
|
100
|
+
if (input.inherit) {
|
|
101
|
+
await client.query("UPDATE draft_versions SET access_policy_id = NULL WHERE id = $1", [versionId]);
|
|
102
|
+
const view = await currentPlanView(client, input.draftId, true);
|
|
103
|
+
await recordEvent(client, input, versionId, await modeForPolicy(client, previousPolicyId), view.mode, view.policyId, view.credentialRevision);
|
|
104
|
+
return view;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (input.inherit) {
|
|
108
|
+
throw new Error("Plan access cannot inherit.");
|
|
109
|
+
}
|
|
110
|
+
if (!input.mode)
|
|
111
|
+
throw new Error("Access mode is required.");
|
|
112
|
+
const previousPolicy = previousPolicyId ? await policyById(client, previousPolicyId) : null;
|
|
113
|
+
const credentialRevision = input.mode === "password" && previousPolicy?.mode === "password"
|
|
114
|
+
? previousPolicy.credential_revision + 1
|
|
115
|
+
: 1;
|
|
116
|
+
const policyId = await insertPolicy(client, input.accountId, input.mode, input.password, credentialRevision);
|
|
117
|
+
if (versionId) {
|
|
118
|
+
await client.query("UPDATE draft_versions SET access_policy_id = $1 WHERE id = $2", [policyId, versionId]);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
await client.query("UPDATE drafts SET default_access_policy_id = $1, updated_at = now() WHERE id = $2", [policyId, input.draftId]);
|
|
122
|
+
}
|
|
123
|
+
const policy = await policyById(client, policyId);
|
|
124
|
+
const view = policyView(policy, false, nodeLegacyAccessMode());
|
|
125
|
+
await recordEvent(client, input, versionId, await modeForPolicy(client, previousPolicyId), view.mode, policyId, policy?.credential_revision ?? null);
|
|
126
|
+
return view;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function insertPolicy(client, accountId, mode, password, credentialRevision = 1) {
|
|
130
|
+
if (mode === "password" && (!password || password.length < 8 || password.length > 512)) {
|
|
131
|
+
throw new Error("Password must be between 8 and 512 characters.");
|
|
132
|
+
}
|
|
133
|
+
const material = mode === "password" ? derivePassword(password) : null;
|
|
134
|
+
const id = newInternalId();
|
|
135
|
+
await client.query(`
|
|
136
|
+
INSERT INTO draft_access_policies (
|
|
137
|
+
id, mode, password_algorithm, password_iterations, password_salt,
|
|
138
|
+
password_hash, credential_revision, created_by_account_id
|
|
139
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
140
|
+
`, [
|
|
141
|
+
id,
|
|
142
|
+
mode,
|
|
143
|
+
material?.algorithm || null,
|
|
144
|
+
material?.iterations || null,
|
|
145
|
+
material?.salt || null,
|
|
146
|
+
material?.hash || null,
|
|
147
|
+
credentialRevision,
|
|
148
|
+
accountId
|
|
149
|
+
]);
|
|
150
|
+
return id;
|
|
151
|
+
}
|
|
152
|
+
async function currentPlanView(client, draftId, inherited) {
|
|
153
|
+
const result = await client.query(`SELECT d.default_access_policy_id AS effective_policy_id, p.id AS policy_id, p.*
|
|
154
|
+
FROM drafts d LEFT JOIN draft_access_policies p ON p.id = d.default_access_policy_id WHERE d.id = $1`, [draftId]);
|
|
155
|
+
return policyView(policyFromRow(result.rows[0]), inherited, nodeLegacyAccessMode());
|
|
156
|
+
}
|
|
157
|
+
async function policyById(client, policyId) {
|
|
158
|
+
const result = await client.query("SELECT * FROM draft_access_policies WHERE id = $1 LIMIT 1", [policyId]);
|
|
159
|
+
return policyFromRow(result.rows[0]);
|
|
160
|
+
}
|
|
161
|
+
async function modeForPolicy(client, policyId) {
|
|
162
|
+
if (!policyId)
|
|
163
|
+
return null;
|
|
164
|
+
const result = await client.query("SELECT mode FROM draft_access_policies WHERE id = $1", [policyId]);
|
|
165
|
+
return result.rows[0]?.mode || null;
|
|
166
|
+
}
|
|
167
|
+
async function recordEvent(client, input, versionId, previousMode, newMode, policyId, credentialRevision) {
|
|
168
|
+
await client.query(`INSERT INTO access_policy_events (
|
|
169
|
+
id, draft_id, draft_version_id, actor_account_id, actor_api_key_id,
|
|
170
|
+
previous_mode, new_mode, policy_id, credential_revision, request_id
|
|
171
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [newEventId(), input.draftId, versionId, input.accountId, input.apiKeyId || null,
|
|
172
|
+
previousMode, newMode, policyId, credentialRevision, input.requestId || null]);
|
|
173
|
+
}
|
|
174
|
+
function mapTarget(row) {
|
|
175
|
+
if (!row)
|
|
176
|
+
return null;
|
|
177
|
+
const policy = policyFromRow(row);
|
|
178
|
+
return {
|
|
179
|
+
draftId: row.draft_id,
|
|
180
|
+
ownerAccountId: row.account_id,
|
|
181
|
+
title: row.title,
|
|
182
|
+
versionId: row.version_id,
|
|
183
|
+
versionNumber: Number(row.version_number),
|
|
184
|
+
objectKey: row.object_key,
|
|
185
|
+
contentHash: row.content_hash,
|
|
186
|
+
inherited: row.version_policy_id === null,
|
|
187
|
+
policy,
|
|
188
|
+
mode: policy?.mode || nodeLegacyAccessMode()
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function policyFromRow(row) {
|
|
192
|
+
// Only an absent pointer is legacy. A broken explicit policy is not public.
|
|
193
|
+
if (typeof row?.effective_policy_id === "string" && (typeof row.policy_id !== "string" ||
|
|
194
|
+
(row.mode !== "private" && row.mode !== "password" && row.mode !== "public")))
|
|
195
|
+
throw new Error("Draft access policy is unavailable.");
|
|
196
|
+
if (!row || typeof row.policy_id !== "string" && typeof row.id !== "string")
|
|
197
|
+
return null;
|
|
198
|
+
const id = typeof row.policy_id === "string" ? row.policy_id : String(row.id);
|
|
199
|
+
if (row.mode !== "private" && row.mode !== "password" && row.mode !== "public")
|
|
200
|
+
return null;
|
|
201
|
+
return {
|
|
202
|
+
id,
|
|
203
|
+
mode: row.mode,
|
|
204
|
+
password_algorithm: typeof row.password_algorithm === "string" ? row.password_algorithm : null,
|
|
205
|
+
password_iterations: row.password_iterations === null || row.password_iterations === undefined
|
|
206
|
+
? null : Number(row.password_iterations),
|
|
207
|
+
password_salt: typeof row.password_salt === "string" ? row.password_salt : null,
|
|
208
|
+
password_hash: typeof row.password_hash === "string" ? row.password_hash : null,
|
|
209
|
+
credential_revision: Number(row.credential_revision || 1)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=access-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-store.js","sourceRoot":"","sources":["../src/access-store.ts"],"names":[],"mappings":"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"}
|
package/dist/admin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isAdminAccount(accountId: string | undefined | null): Promise<boolean>;
|
package/dist/admin.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { config } from "./config.js";
|
|
2
|
+
import { pool } from "./db.js";
|
|
3
|
+
export async function isAdminAccount(accountId) {
|
|
4
|
+
if (!accountId)
|
|
5
|
+
return false;
|
|
6
|
+
if (config.adminAccountIds.includes(accountId))
|
|
7
|
+
return true;
|
|
8
|
+
if (config.runtimeProfile !== "self-hosted")
|
|
9
|
+
return false;
|
|
10
|
+
const result = await pool.query("SELECT is_admin FROM accounts WHERE id = $1 LIMIT 1", [accountId]);
|
|
11
|
+
return Boolean(result.rows[0]?.is_admin);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAoC;IACvE,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,IAAI,MAAM,CAAC,cAAc,KAAK,aAAa;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,qDAAqD,EACrD,CAAC,SAAS,CAAC,CACZ,CAAC;IACF,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC"}
|
package/dist/api.d.ts
ADDED