@rewind-ai/cli 0.6.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/.env.example +8 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/src/action/capture.d.ts +15 -0
- package/dist/src/action/capture.js +215 -0
- package/dist/src/action/capture.js.map +1 -0
- package/dist/src/action/mediate.d.ts +28 -0
- package/dist/src/action/mediate.js +109 -0
- package/dist/src/action/mediate.js.map +1 -0
- package/dist/src/action/mutate.d.ts +11 -0
- package/dist/src/action/mutate.js +189 -0
- package/dist/src/action/mutate.js.map +1 -0
- package/dist/src/action/objects.d.ts +5 -0
- package/dist/src/action/objects.js +50 -0
- package/dist/src/action/objects.js.map +1 -0
- package/dist/src/action/store.d.ts +24 -0
- package/dist/src/action/store.js +149 -0
- package/dist/src/action/store.js.map +1 -0
- package/dist/src/action/types.d.ts +84 -0
- package/dist/src/action/types.js +3 -0
- package/dist/src/action/types.js.map +1 -0
- package/dist/src/action/undo.d.ts +11 -0
- package/dist/src/action/undo.js +42 -0
- package/dist/src/action/undo.js.map +1 -0
- package/dist/src/adapters/filesystem.d.ts +35 -0
- package/dist/src/adapters/filesystem.js +499 -0
- package/dist/src/adapters/filesystem.js.map +1 -0
- package/dist/src/adapters/index.d.ts +7 -0
- package/dist/src/adapters/index.js +19 -0
- package/dist/src/adapters/index.js.map +1 -0
- package/dist/src/adapters/types.d.ts +54 -0
- package/dist/src/adapters/types.js +7 -0
- package/dist/src/adapters/types.js.map +1 -0
- package/dist/src/api/server.d.ts +25 -0
- package/dist/src/api/server.js +786 -0
- package/dist/src/api/server.js.map +1 -0
- package/dist/src/approval/store.d.ts +35 -0
- package/dist/src/approval/store.js +113 -0
- package/dist/src/approval/store.js.map +1 -0
- package/dist/src/checkpoint.d.ts +9 -0
- package/dist/src/checkpoint.js +140 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/cli/v5.d.ts +15 -0
- package/dist/src/cli/v5.js +110 -0
- package/dist/src/cli/v5.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +823 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/clients/types.d.ts +90 -0
- package/dist/src/clients/types.js +239 -0
- package/dist/src/clients/types.js.map +1 -0
- package/dist/src/commercial/auth/provider.d.ts +61 -0
- package/dist/src/commercial/auth/provider.js +227 -0
- package/dist/src/commercial/auth/provider.js.map +1 -0
- package/dist/src/commercial/entitlement/provider.d.ts +27 -0
- package/dist/src/commercial/entitlement/provider.js +192 -0
- package/dist/src/commercial/entitlement/provider.js.map +1 -0
- package/dist/src/commercial/entitlement/signing.d.ts +61 -0
- package/dist/src/commercial/entitlement/signing.js +124 -0
- package/dist/src/commercial/entitlement/signing.js.map +1 -0
- package/dist/src/commercial/env.d.ts +10 -0
- package/dist/src/commercial/env.js +40 -0
- package/dist/src/commercial/env.js.map +1 -0
- package/dist/src/commercial/product.d.ts +26 -0
- package/dist/src/commercial/product.js +39 -0
- package/dist/src/commercial/product.js.map +1 -0
- package/dist/src/commercial/store.d.ts +46 -0
- package/dist/src/commercial/store.js +104 -0
- package/dist/src/commercial/store.js.map +1 -0
- package/dist/src/commercial/stripe/checkout.d.ts +104 -0
- package/dist/src/commercial/stripe/checkout.js +469 -0
- package/dist/src/commercial/stripe/checkout.js.map +1 -0
- package/dist/src/core/compatibility.d.ts +15 -0
- package/dist/src/core/compatibility.js +226 -0
- package/dist/src/core/compatibility.js.map +1 -0
- package/dist/src/core/engine.d.ts +33 -0
- package/dist/src/core/engine.js +432 -0
- package/dist/src/core/engine.js.map +1 -0
- package/dist/src/core/lifecycle.d.ts +17 -0
- package/dist/src/core/lifecycle.js +56 -0
- package/dist/src/core/lifecycle.js.map +1 -0
- package/dist/src/core/types.d.ts +140 -0
- package/dist/src/core/types.js +37 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/destroy.d.ts +21 -0
- package/dist/src/destroy.js +219 -0
- package/dist/src/destroy.js.map +1 -0
- package/dist/src/diff.d.ts +13 -0
- package/dist/src/diff.js +251 -0
- package/dist/src/diff.js.map +1 -0
- package/dist/src/enforcement/detect.d.ts +13 -0
- package/dist/src/enforcement/detect.js +75 -0
- package/dist/src/enforcement/detect.js.map +1 -0
- package/dist/src/enforcement/hook-policy.d.ts +38 -0
- package/dist/src/enforcement/hook-policy.js +111 -0
- package/dist/src/enforcement/hook-policy.js.map +1 -0
- package/dist/src/enforcement/index.d.ts +8 -0
- package/dist/src/enforcement/index.js +8 -0
- package/dist/src/enforcement/index.js.map +1 -0
- package/dist/src/enforcement/perf.d.ts +6 -0
- package/dist/src/enforcement/perf.js +18 -0
- package/dist/src/enforcement/perf.js.map +1 -0
- package/dist/src/enforcement/policy.d.ts +12 -0
- package/dist/src/enforcement/policy.js +84 -0
- package/dist/src/enforcement/policy.js.map +1 -0
- package/dist/src/enforcement/readonly.d.ts +17 -0
- package/dist/src/enforcement/readonly.js +124 -0
- package/dist/src/enforcement/readonly.js.map +1 -0
- package/dist/src/enforcement/sandbox-clone.d.ts +6 -0
- package/dist/src/enforcement/sandbox-clone.js +11 -0
- package/dist/src/enforcement/sandbox-clone.js.map +1 -0
- package/dist/src/enforcement/setup.d.ts +33 -0
- package/dist/src/enforcement/setup.js +168 -0
- package/dist/src/enforcement/setup.js.map +1 -0
- package/dist/src/enforcement/trust.d.ts +10 -0
- package/dist/src/enforcement/trust.js +71 -0
- package/dist/src/enforcement/trust.js.map +1 -0
- package/dist/src/enforcement/types.d.ts +40 -0
- package/dist/src/enforcement/types.js +3 -0
- package/dist/src/enforcement/types.js.map +1 -0
- package/dist/src/entitlement/provider.d.ts +48 -0
- package/dist/src/entitlement/provider.js +95 -0
- package/dist/src/entitlement/provider.js.map +1 -0
- package/dist/src/hash.d.ts +9 -0
- package/dist/src/hash.js +23 -0
- package/dist/src/hash.js.map +1 -0
- package/dist/src/ledger/query.d.ts +19 -0
- package/dist/src/ledger/query.js +158 -0
- package/dist/src/ledger/query.js.map +1 -0
- package/dist/src/manifest.d.ts +32 -0
- package/dist/src/manifest.js +242 -0
- package/dist/src/manifest.js.map +1 -0
- package/dist/src/mcp/server.d.ts +7 -0
- package/dist/src/mcp/server.js +364 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/paths.d.ts +94 -0
- package/dist/src/paths.js +354 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/permission/engine.d.ts +15 -0
- package/dist/src/permission/engine.js +147 -0
- package/dist/src/permission/engine.js.map +1 -0
- package/dist/src/permission/types.d.ts +42 -0
- package/dist/src/permission/types.js +3 -0
- package/dist/src/permission/types.js.map +1 -0
- package/dist/src/product/setup.d.ts +35 -0
- package/dist/src/product/setup.js +300 -0
- package/dist/src/product/setup.js.map +1 -0
- package/dist/src/protect/audit.d.ts +5 -0
- package/dist/src/protect/audit.js +26 -0
- package/dist/src/protect/audit.js.map +1 -0
- package/dist/src/protect/capture.d.ts +7 -0
- package/dist/src/protect/capture.js +250 -0
- package/dist/src/protect/capture.js.map +1 -0
- package/dist/src/protect/fingerprint.d.ts +4 -0
- package/dist/src/protect/fingerprint.js +49 -0
- package/dist/src/protect/fingerprint.js.map +1 -0
- package/dist/src/protect/hook-cli.d.ts +2 -0
- package/dist/src/protect/hook-cli.js +107 -0
- package/dist/src/protect/hook-cli.js.map +1 -0
- package/dist/src/protect/index.d.ts +12 -0
- package/dist/src/protect/index.js +12 -0
- package/dist/src/protect/index.js.map +1 -0
- package/dist/src/protect/install.d.ts +34 -0
- package/dist/src/protect/install.js +293 -0
- package/dist/src/protect/install.js.map +1 -0
- package/dist/src/protect/intercept.d.ts +11 -0
- package/dist/src/protect/intercept.js +390 -0
- package/dist/src/protect/intercept.js.map +1 -0
- package/dist/src/protect/protect.d.ts +17 -0
- package/dist/src/protect/protect.js +194 -0
- package/dist/src/protect/protect.js.map +1 -0
- package/dist/src/protect/root-paths.d.ts +13 -0
- package/dist/src/protect/root-paths.js +140 -0
- package/dist/src/protect/root-paths.js.map +1 -0
- package/dist/src/protect/shell-policy.d.ts +17 -0
- package/dist/src/protect/shell-policy.js +269 -0
- package/dist/src/protect/shell-policy.js.map +1 -0
- package/dist/src/protect/status.d.ts +11 -0
- package/dist/src/protect/status.js +165 -0
- package/dist/src/protect/status.js.map +1 -0
- package/dist/src/protect/store.d.ts +20 -0
- package/dist/src/protect/store.js +170 -0
- package/dist/src/protect/store.js.map +1 -0
- package/dist/src/protect/types.d.ts +116 -0
- package/dist/src/protect/types.js +3 -0
- package/dist/src/protect/types.js.map +1 -0
- package/dist/src/protect/undo-session.d.ts +14 -0
- package/dist/src/protect/undo-session.js +182 -0
- package/dist/src/protect/undo-session.js.map +1 -0
- package/dist/src/restore.d.ts +39 -0
- package/dist/src/restore.js +141 -0
- package/dist/src/restore.js.map +1 -0
- package/dist/src/sandbox.d.ts +23 -0
- package/dist/src/sandbox.js +198 -0
- package/dist/src/sandbox.js.map +1 -0
- package/dist/src/session/ambient.d.ts +10 -0
- package/dist/src/session/ambient.js +53 -0
- package/dist/src/session/ambient.js.map +1 -0
- package/dist/src/session/attribution.d.ts +27 -0
- package/dist/src/session/attribution.js +201 -0
- package/dist/src/session/attribution.js.map +1 -0
- package/dist/src/session/index.d.ts +25 -0
- package/dist/src/session/index.js +472 -0
- package/dist/src/session/index.js.map +1 -0
- package/dist/src/session/process.d.ts +34 -0
- package/dist/src/session/process.js +234 -0
- package/dist/src/session/process.js.map +1 -0
- package/dist/src/session/store.d.ts +14 -0
- package/dist/src/session/store.js +125 -0
- package/dist/src/session/store.js.map +1 -0
- package/dist/src/session/types.d.ts +111 -0
- package/dist/src/session/types.js +3 -0
- package/dist/src/session/types.js.map +1 -0
- package/dist/src/session/watcher.d.ts +32 -0
- package/dist/src/session/watcher.js +186 -0
- package/dist/src/session/watcher.js.map +1 -0
- package/dist/src/timing.d.ts +7 -0
- package/dist/src/timing.js +19 -0
- package/dist/src/timing.js.map +1 -0
- package/dist/src/types.d.ts +101 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/verify.d.ts +8 -0
- package/dist/src/verify.js +126 -0
- package/dist/src/verify.js.map +1 -0
- package/docs/ADAPTERS.md +37 -0
- package/docs/APPROVALS.md +32 -0
- package/docs/ARCHITECTURE.md +57 -0
- package/docs/ATTRIBUTION.md +179 -0
- package/docs/COMMERCIAL.md +51 -0
- package/docs/CURSOR-ENFORCEMENT.md +73 -0
- package/docs/ENFORCEMENT.md +134 -0
- package/docs/FUTURE.md +91 -0
- package/docs/HUMAN-SETUP.md +98 -0
- package/docs/IDENTITY-AUTHORITY.md +35 -0
- package/docs/MCP.md +107 -0
- package/docs/PACKAGING.md +77 -0
- package/docs/PERMISSIONS.md +53 -0
- package/docs/PRODUCT-V5.md +63 -0
- package/docs/PRODUCT-V6.md +69 -0
- package/docs/PROTECTED-SESSIONS.md +155 -0
- package/docs/THREAT-MODEL.md +84 -0
- package/docs/TRANSACTIONS.md +57 -0
- package/package.json +85 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication for REWIND Personal.
|
|
3
|
+
*
|
|
4
|
+
* WHY this design:
|
|
5
|
+
* - Browser-based login — never paste passwords into the CLI
|
|
6
|
+
* - Local-first developer product: OIDC/OAuth Authorization Code + PKCE
|
|
7
|
+
* - Dev/test: LocalDevAuthProvider completes login on loopback without external IdP
|
|
8
|
+
* - Production: configure REWIND_OIDC_* for Auth0 / Clerk / any OIDC provider
|
|
9
|
+
*
|
|
10
|
+
* Secrets never live in the distributed app binary — only client_id + redirect.
|
|
11
|
+
* Refresh tokens stored under ~/.rewind/credentials with mode 0600
|
|
12
|
+
* (macOS Keychain preferred when `security` is available — best-effort).
|
|
13
|
+
*/
|
|
14
|
+
import http from "node:http";
|
|
15
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
16
|
+
import { mkdir, readFile, writeFile, rename, rm } from "node:fs/promises";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { execFile } from "node:child_process";
|
|
19
|
+
import { promisify } from "node:util";
|
|
20
|
+
import { upsertUserByEmail, registerInstallation } from "../store.js";
|
|
21
|
+
import { getUserRewindDir } from "../../paths.js";
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
function credsDir() {
|
|
24
|
+
return getUserRewindDir();
|
|
25
|
+
}
|
|
26
|
+
function credsPath() {
|
|
27
|
+
return path.join(credsDir(), "credentials.json");
|
|
28
|
+
}
|
|
29
|
+
async function writeSecure(file, data) {
|
|
30
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
31
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
32
|
+
await writeFile(tmp, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 });
|
|
33
|
+
await rename(tmp, file);
|
|
34
|
+
}
|
|
35
|
+
export async function loadSession() {
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readFile(credsPath(), "utf8");
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function saveSession(session) {
|
|
45
|
+
await writeSecure(credsPath(), session);
|
|
46
|
+
// Best-effort Keychain mirror on macOS (non-fatal)
|
|
47
|
+
if (process.platform === "darwin") {
|
|
48
|
+
try {
|
|
49
|
+
await execFileAsync("security", [
|
|
50
|
+
"add-generic-password",
|
|
51
|
+
"-U",
|
|
52
|
+
"-a",
|
|
53
|
+
"rewind",
|
|
54
|
+
"-s",
|
|
55
|
+
"rewind-auth",
|
|
56
|
+
"-w",
|
|
57
|
+
session.accessToken,
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* optional */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function clearSession() {
|
|
66
|
+
await rm(credsPath(), { force: true });
|
|
67
|
+
if (process.platform === "darwin") {
|
|
68
|
+
try {
|
|
69
|
+
await execFileAsync("security", [
|
|
70
|
+
"delete-generic-password",
|
|
71
|
+
"-a",
|
|
72
|
+
"rewind",
|
|
73
|
+
"-s",
|
|
74
|
+
"rewind-auth",
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* optional */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function pkce() {
|
|
83
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
84
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
85
|
+
return { verifier, challenge };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* LocalDevAuthProvider — browser opens loopback page; user enters email (no password).
|
|
89
|
+
* Suitable for tests and Jack's machine without Auth0 credentials.
|
|
90
|
+
* Production must use OidcAuthProvider with a real IdP.
|
|
91
|
+
*/
|
|
92
|
+
export class LocalDevAuthProvider {
|
|
93
|
+
id = "local-dev";
|
|
94
|
+
async currentSession() {
|
|
95
|
+
return loadSession();
|
|
96
|
+
}
|
|
97
|
+
async logout() {
|
|
98
|
+
await clearSession();
|
|
99
|
+
}
|
|
100
|
+
async login(opts = {}) {
|
|
101
|
+
// Non-interactive path for tests
|
|
102
|
+
if (opts.email) {
|
|
103
|
+
return this.complete(opts.email);
|
|
104
|
+
}
|
|
105
|
+
const { challenge } = pkce();
|
|
106
|
+
const state = randomBytes(16).toString("hex");
|
|
107
|
+
let resolveEmail;
|
|
108
|
+
const emailPromise = new Promise((r) => {
|
|
109
|
+
resolveEmail = r;
|
|
110
|
+
});
|
|
111
|
+
const server = http.createServer((req, res) => {
|
|
112
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
113
|
+
if (url.pathname === "/" && req.method === "GET") {
|
|
114
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
115
|
+
res.end(`<!DOCTYPE html><html><head><title>REWIND Sign in</title>
|
|
116
|
+
<style>
|
|
117
|
+
body{font-family:system-ui;background:#141210;color:#f2ebe3;display:flex;min-height:100vh;align-items:center;justify-content:center}
|
|
118
|
+
form{background:#1c1915;padding:2rem;border:1px solid #2c2824;max-width:360px}
|
|
119
|
+
h1{letter-spacing:.12em;font-size:1.2rem}input,button{width:100%;padding:.6rem;margin:.4rem 0;font:inherit}
|
|
120
|
+
button{background:#c4a574;border:0;cursor:pointer;color:#141210;font-weight:600}
|
|
121
|
+
p{color:#9a9086;font-size:.85rem}
|
|
122
|
+
</style></head><body><form method="GET" action="/callback">
|
|
123
|
+
<h1>REWIND</h1><p>Sign in (local test mode — no password)</p>
|
|
124
|
+
<input type="hidden" name="state" value="${state}"/>
|
|
125
|
+
<input type="hidden" name="code_challenge" value="${challenge}"/>
|
|
126
|
+
<input name="email" type="email" required placeholder="you@example.com" autofocus/>
|
|
127
|
+
<button type="submit">Continue</button>
|
|
128
|
+
</form></body></html>`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (url.pathname === "/callback") {
|
|
132
|
+
if (url.searchParams.get("state") !== state) {
|
|
133
|
+
res.writeHead(400);
|
|
134
|
+
res.end("Invalid state");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const email = url.searchParams.get("email")?.trim();
|
|
138
|
+
if (!email) {
|
|
139
|
+
res.writeHead(400);
|
|
140
|
+
res.end("Email required");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
144
|
+
res.end(`<!DOCTYPE html><html><body style="font-family:system-ui;background:#141210;color:#f2ebe3;padding:3rem">
|
|
145
|
+
<h1>REWIND</h1><p>✓ Signed in as ${email}</p><p>You can close this window and return to the terminal.</p></body></html>`);
|
|
146
|
+
resolveEmail(email);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
res.writeHead(404);
|
|
150
|
+
res.end("Not found");
|
|
151
|
+
});
|
|
152
|
+
const port = await new Promise((resolve, reject) => {
|
|
153
|
+
server.listen(0, "127.0.0.1", () => {
|
|
154
|
+
const addr = server.address();
|
|
155
|
+
if (addr && typeof addr === "object")
|
|
156
|
+
resolve(addr.port);
|
|
157
|
+
else
|
|
158
|
+
reject(new Error("bind failed"));
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
162
|
+
console.log("REWIND");
|
|
163
|
+
console.log("");
|
|
164
|
+
console.log("Opening browser…");
|
|
165
|
+
console.log(url);
|
|
166
|
+
console.log("");
|
|
167
|
+
if (opts.openBrowser !== false) {
|
|
168
|
+
const openCmd = process.platform === "darwin"
|
|
169
|
+
? "open"
|
|
170
|
+
: process.platform === "win32"
|
|
171
|
+
? "start"
|
|
172
|
+
: "xdg-open";
|
|
173
|
+
execFile(openCmd, [url], () => undefined);
|
|
174
|
+
}
|
|
175
|
+
const email = await emailPromise;
|
|
176
|
+
server.close();
|
|
177
|
+
return this.complete(email);
|
|
178
|
+
}
|
|
179
|
+
async complete(email) {
|
|
180
|
+
const user = await upsertUserByEmail(email);
|
|
181
|
+
const inst = await registerInstallation(user.id);
|
|
182
|
+
const session = {
|
|
183
|
+
userId: user.id,
|
|
184
|
+
email: user.email,
|
|
185
|
+
accessToken: randomBytes(24).toString("hex"),
|
|
186
|
+
refreshToken: randomBytes(24).toString("hex"),
|
|
187
|
+
expiresAt: new Date(Date.now() + 30 * 86400_000).toISOString(),
|
|
188
|
+
provider: "local-dev",
|
|
189
|
+
installationId: inst.id,
|
|
190
|
+
};
|
|
191
|
+
await saveSession(session);
|
|
192
|
+
return session;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* OIDC Authorization Code + PKCE skeleton.
|
|
197
|
+
* Activates when REWIND_OIDC_ISSUER, REWIND_OIDC_CLIENT_ID are set.
|
|
198
|
+
* Full token exchange requires Jack's IdP credentials — see docs/HUMAN-SETUP.md.
|
|
199
|
+
*/
|
|
200
|
+
export class OidcAuthProvider {
|
|
201
|
+
id = "oidc";
|
|
202
|
+
async currentSession() {
|
|
203
|
+
return loadSession();
|
|
204
|
+
}
|
|
205
|
+
async logout() {
|
|
206
|
+
await clearSession();
|
|
207
|
+
}
|
|
208
|
+
async login() {
|
|
209
|
+
const issuer = process.env.REWIND_OIDC_ISSUER;
|
|
210
|
+
const clientId = process.env.REWIND_OIDC_CLIENT_ID;
|
|
211
|
+
if (!issuer || !clientId) {
|
|
212
|
+
throw new Error("OIDC not configured. Set REWIND_OIDC_ISSUER and REWIND_OIDC_CLIENT_ID, or use local-dev auth (default).");
|
|
213
|
+
}
|
|
214
|
+
// Production wiring: redirect user to authorize URL with PKCE, exchange code.
|
|
215
|
+
// Until credentials exist, fall through message:
|
|
216
|
+
throw new Error(`OIDC provider configured (${issuer}) but interactive token exchange requires ` +
|
|
217
|
+
`REWIND_OIDC_CLIENT_SECRET (confidential) or public PKCE client setup. ` +
|
|
218
|
+
`See docs/HUMAN-SETUP.md. For local product testing use LocalDevAuthProvider.`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export function getAuthProvider() {
|
|
222
|
+
if (process.env.REWIND_OIDC_ISSUER && process.env.REWIND_OIDC_CLIENT_ID) {
|
|
223
|
+
return new OidcAuthProvider();
|
|
224
|
+
}
|
|
225
|
+
return new LocalDevAuthProvider();
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../../src/commercial/auth/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commercial / signed entitlement provider for Core.
|
|
3
|
+
* INVARIANT: allowUndoExisting is always true — never paywall recovery.
|
|
4
|
+
*/
|
|
5
|
+
import { type EntitlementProvider, type EntitlementSnapshot } from "../../entitlement/provider.js";
|
|
6
|
+
import { type SignedEntitlement } from "../entitlement/signing.js";
|
|
7
|
+
/** Bundled / env public key for verification. Dev keys generated into ~/.rewind/keys if missing. */
|
|
8
|
+
export declare function ensureDevKeyPair(): Promise<{
|
|
9
|
+
publicKeyPem: string;
|
|
10
|
+
privateKeyPem: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function loadCachedEntitlement(): Promise<SignedEntitlement | null>;
|
|
13
|
+
export declare function saveCachedEntitlement(token: SignedEntitlement): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Issue a signed entitlement for a commercial user id (webhook / CLI).
|
|
16
|
+
* Writes machine cache when this is the signed-in user, or when no session
|
|
17
|
+
* exists (local single-user proof).
|
|
18
|
+
*/
|
|
19
|
+
export declare function issueEntitlementForUser(userId: string): Promise<SignedEntitlement>;
|
|
20
|
+
export declare function issueEntitlementForCurrentUser(): Promise<SignedEntitlement>;
|
|
21
|
+
export declare class SignedEntitlementProvider implements EntitlementProvider {
|
|
22
|
+
private readonly publicKeyPem?;
|
|
23
|
+
private readonly now;
|
|
24
|
+
readonly id = "signed";
|
|
25
|
+
constructor(publicKeyPem?: string | undefined, now?: () => Date);
|
|
26
|
+
check(): Promise<EntitlementSnapshot>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commercial / signed entitlement provider for Core.
|
|
3
|
+
* INVARIANT: allowUndoExisting is always true — never paywall recovery.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdir, readFile, writeFile, rename } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { buildClaims, mediationAllowedFromClaims, signEntitlement, verifyEntitlement, allowUndoExistingAlways, } from "../entitlement/signing.js";
|
|
8
|
+
import { getSubscriptionForUser, loadStore } from "../store.js";
|
|
9
|
+
import { loadSession } from "../auth/provider.js";
|
|
10
|
+
import { PLAN_MATRIX } from "../product.js";
|
|
11
|
+
import { getUserRewindDir } from "../../paths.js";
|
|
12
|
+
function cachePath() {
|
|
13
|
+
return path.join(getUserRewindDir(), "entitlement.cache.json");
|
|
14
|
+
}
|
|
15
|
+
/** Bundled / env public key for verification. Dev keys generated into ~/.rewind/keys if missing. */
|
|
16
|
+
export async function ensureDevKeyPair() {
|
|
17
|
+
const dir = path.join(getUserRewindDir(), "keys");
|
|
18
|
+
const pubPath = path.join(dir, "entitlement.pub.pem");
|
|
19
|
+
const privPath = path.join(dir, "entitlement.priv.pem");
|
|
20
|
+
try {
|
|
21
|
+
const publicKeyPem = await readFile(pubPath, "utf8");
|
|
22
|
+
const privateKeyPem = await readFile(privPath, "utf8");
|
|
23
|
+
return { publicKeyPem, privateKeyPem };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
const { generateEntitlementKeyPair } = await import("../entitlement/signing.js");
|
|
27
|
+
const kp = generateEntitlementKeyPair();
|
|
28
|
+
await mkdir(dir, { recursive: true });
|
|
29
|
+
await writeFile(pubPath, kp.publicKeyPem, { mode: 0o644 });
|
|
30
|
+
await writeFile(privPath, kp.privateKeyPem, { mode: 0o600 });
|
|
31
|
+
return kp;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function loadCachedEntitlement() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = await readFile(cachePath(), "utf8");
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function saveCachedEntitlement(token) {
|
|
44
|
+
await mkdir(path.dirname(cachePath()), { recursive: true });
|
|
45
|
+
const tmp = `${cachePath()}.tmp`;
|
|
46
|
+
await writeFile(tmp, JSON.stringify(token, null, 2) + "\n", { mode: 0o600 });
|
|
47
|
+
await rename(tmp, cachePath());
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Issue a signed entitlement for a commercial user id (webhook / CLI).
|
|
51
|
+
* Writes machine cache when this is the signed-in user, or when no session
|
|
52
|
+
* exists (local single-user proof).
|
|
53
|
+
*/
|
|
54
|
+
export async function issueEntitlementForUser(userId) {
|
|
55
|
+
const store = await loadStore();
|
|
56
|
+
const user = store.users.find((u) => u.id === userId);
|
|
57
|
+
if (!user) {
|
|
58
|
+
throw new Error(`Unknown user ${userId}`);
|
|
59
|
+
}
|
|
60
|
+
const session = await loadSession();
|
|
61
|
+
const sub = await getSubscriptionForUser(userId);
|
|
62
|
+
const plan = sub && (sub.status === "active" || sub.status === "trialing")
|
|
63
|
+
? "personal"
|
|
64
|
+
: "free";
|
|
65
|
+
const keys = await ensureDevKeyPair();
|
|
66
|
+
const privateKeyPem = process.env.REWIND_ENTITLEMENT_PRIVATE_KEY?.replace(/\\n/g, "\n") ??
|
|
67
|
+
keys.privateKeyPem;
|
|
68
|
+
const installationId = session?.userId === userId
|
|
69
|
+
? session.installationId
|
|
70
|
+
: (store.installations.find((i) => i.userId === userId)?.id ?? null);
|
|
71
|
+
const claims = buildClaims({
|
|
72
|
+
userId,
|
|
73
|
+
email: user.email,
|
|
74
|
+
plan,
|
|
75
|
+
installationId,
|
|
76
|
+
});
|
|
77
|
+
const token = signEntitlement(claims, privateKeyPem);
|
|
78
|
+
// Refresh local cache for the active machine user (local proof / same user).
|
|
79
|
+
if (!session || session.userId === userId) {
|
|
80
|
+
await saveCachedEntitlement(token);
|
|
81
|
+
}
|
|
82
|
+
return token;
|
|
83
|
+
}
|
|
84
|
+
export async function issueEntitlementForCurrentUser() {
|
|
85
|
+
const session = await loadSession();
|
|
86
|
+
if (!session) {
|
|
87
|
+
throw new Error("Not signed in. Run: rewind login");
|
|
88
|
+
}
|
|
89
|
+
return issueEntitlementForUser(session.userId);
|
|
90
|
+
}
|
|
91
|
+
function tierFromPlan(plan) {
|
|
92
|
+
if (plan === "personal")
|
|
93
|
+
return "PERSONAL";
|
|
94
|
+
if (plan === "free")
|
|
95
|
+
return "FREE";
|
|
96
|
+
if (plan === "development")
|
|
97
|
+
return "DEVELOPMENT";
|
|
98
|
+
return "UNKNOWN";
|
|
99
|
+
}
|
|
100
|
+
export class SignedEntitlementProvider {
|
|
101
|
+
publicKeyPem;
|
|
102
|
+
now;
|
|
103
|
+
id = "signed";
|
|
104
|
+
constructor(publicKeyPem, now = () => new Date()) {
|
|
105
|
+
this.publicKeyPem = publicKeyPem;
|
|
106
|
+
this.now = now;
|
|
107
|
+
}
|
|
108
|
+
async check() {
|
|
109
|
+
const keys = await ensureDevKeyPair();
|
|
110
|
+
const pub = this.publicKeyPem ??
|
|
111
|
+
process.env.REWIND_ENTITLEMENT_PUBLIC_KEY?.replace(/\\n/g, "\n") ??
|
|
112
|
+
keys.publicKeyPem;
|
|
113
|
+
let token = await loadCachedEntitlement();
|
|
114
|
+
const session = await loadSession();
|
|
115
|
+
// Refresh if missing or past refreshAfter and online session exists
|
|
116
|
+
if (session &&
|
|
117
|
+
(!token ||
|
|
118
|
+
this.now().getTime() > Date.parse(token.claims.refreshAfter))) {
|
|
119
|
+
try {
|
|
120
|
+
token = await issueEntitlementForCurrentUser();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* keep cache */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!token) {
|
|
127
|
+
// Anonymous free demo — still allows mediation + always undo
|
|
128
|
+
const features = PLAN_MATRIX.free;
|
|
129
|
+
return {
|
|
130
|
+
provider: this.id,
|
|
131
|
+
tier: "FREE",
|
|
132
|
+
label: "Free (not signed in)",
|
|
133
|
+
allowMediatedMutations: features.allowNewMediation,
|
|
134
|
+
allowUndoExisting: allowUndoExistingAlways(),
|
|
135
|
+
checkedAt: this.now().toISOString(),
|
|
136
|
+
notes: [
|
|
137
|
+
"Sign in with: rewind login",
|
|
138
|
+
"UNDO of recorded transactions is never paywalled.",
|
|
139
|
+
],
|
|
140
|
+
plan: "free",
|
|
141
|
+
offline: true,
|
|
142
|
+
inGrace: false,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const verified = verifyEntitlement(token, pub);
|
|
146
|
+
if (!verified.ok || !verified.claims) {
|
|
147
|
+
return {
|
|
148
|
+
provider: this.id,
|
|
149
|
+
tier: "UNLICENSED",
|
|
150
|
+
label: `Invalid entitlement (${verified.reason})`,
|
|
151
|
+
allowMediatedMutations: PLAN_MATRIX.free.allowNewMediation,
|
|
152
|
+
allowUndoExisting: allowUndoExistingAlways(),
|
|
153
|
+
checkedAt: this.now().toISOString(),
|
|
154
|
+
notes: [
|
|
155
|
+
`Signature verification failed: ${verified.reason}`,
|
|
156
|
+
"Falling back to free mediation limits.",
|
|
157
|
+
"UNDO of recorded transactions remains available.",
|
|
158
|
+
],
|
|
159
|
+
plan: "free",
|
|
160
|
+
offline: true,
|
|
161
|
+
inGrace: false,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const med = mediationAllowedFromClaims(verified.claims, this.now());
|
|
165
|
+
const claims = verified.claims;
|
|
166
|
+
// Mark offline if past refresh and we couldn't refresh
|
|
167
|
+
const offline = claims.offline ||
|
|
168
|
+
this.now().getTime() > Date.parse(claims.refreshAfter);
|
|
169
|
+
return {
|
|
170
|
+
provider: this.id,
|
|
171
|
+
tier: tierFromPlan(claims.plan),
|
|
172
|
+
label: `${claims.features.label}${med.inGrace ? " (grace)" : ""}${offline ? " [cached]" : ""}`,
|
|
173
|
+
allowMediatedMutations: med.allow,
|
|
174
|
+
allowUndoExisting: allowUndoExistingAlways(),
|
|
175
|
+
checkedAt: this.now().toISOString(),
|
|
176
|
+
notes: [
|
|
177
|
+
med.reason,
|
|
178
|
+
`User: ${claims.email ?? claims.sub}`,
|
|
179
|
+
"UNDO of already-recorded transactions is always allowed.",
|
|
180
|
+
...(med.expired && !med.allow
|
|
181
|
+
? ["Subscribe: rewind subscribe (Stripe test mode)"]
|
|
182
|
+
: []),
|
|
183
|
+
],
|
|
184
|
+
plan: claims.plan,
|
|
185
|
+
offline,
|
|
186
|
+
inGrace: med.inGrace,
|
|
187
|
+
expiresAt: claims.expiresAt,
|
|
188
|
+
graceUntil: claims.graceUntil,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../../src/commercial/entitlement/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed entitlement tokens (asymmetric).
|
|
3
|
+
* Server holds private key; distributed app holds public key only.
|
|
4
|
+
*/
|
|
5
|
+
import type { PlanFeatures } from "../product.js";
|
|
6
|
+
export declare const ENTITLEMENT_TOKEN_VERSION: 1;
|
|
7
|
+
export interface EntitlementClaims {
|
|
8
|
+
v: typeof ENTITLEMENT_TOKEN_VERSION;
|
|
9
|
+
sub: string;
|
|
10
|
+
email: string | null;
|
|
11
|
+
plan: PlanFeatures["plan"];
|
|
12
|
+
features: PlanFeatures;
|
|
13
|
+
installationId: string | null;
|
|
14
|
+
issuedAt: string;
|
|
15
|
+
/** Soft refresh hint — not hard expiry for undo. */
|
|
16
|
+
refreshAfter: string;
|
|
17
|
+
/** Hard expiry for NEW mediation only when past grace. */
|
|
18
|
+
expiresAt: string;
|
|
19
|
+
/** Extra grace after expiresAt during which cached personal still allows new mediation. */
|
|
20
|
+
graceUntil: string;
|
|
21
|
+
offline: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface SignedEntitlement {
|
|
24
|
+
claims: EntitlementClaims;
|
|
25
|
+
/** base64url signature over canonical JSON of claims */
|
|
26
|
+
signature: string;
|
|
27
|
+
algorithm: "ed25519";
|
|
28
|
+
}
|
|
29
|
+
export declare function generateEntitlementKeyPair(): {
|
|
30
|
+
publicKeyPem: string;
|
|
31
|
+
privateKeyPem: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function signEntitlement(claims: EntitlementClaims, privateKeyPem: string): SignedEntitlement;
|
|
34
|
+
export declare function verifyEntitlement(token: SignedEntitlement, publicKeyPem: string): {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
reason?: string;
|
|
37
|
+
claims?: EntitlementClaims;
|
|
38
|
+
};
|
|
39
|
+
export declare function buildClaims(input: {
|
|
40
|
+
userId: string;
|
|
41
|
+
email: string | null;
|
|
42
|
+
plan: PlanFeatures["plan"];
|
|
43
|
+
installationId: string | null;
|
|
44
|
+
now?: Date;
|
|
45
|
+
ttlHours?: number;
|
|
46
|
+
graceHours?: number;
|
|
47
|
+
refreshHours?: number;
|
|
48
|
+
offline?: boolean;
|
|
49
|
+
}): EntitlementClaims;
|
|
50
|
+
/**
|
|
51
|
+
* Evaluate whether NEW mediation is allowed given verified claims + clock.
|
|
52
|
+
* Undo of existing work is NEVER denied here.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mediationAllowedFromClaims(claims: EntitlementClaims, now?: Date): {
|
|
55
|
+
allow: boolean;
|
|
56
|
+
reason: string;
|
|
57
|
+
inGrace: boolean;
|
|
58
|
+
expired: boolean;
|
|
59
|
+
};
|
|
60
|
+
/** Architectural invariant — always true. */
|
|
61
|
+
export declare function allowUndoExistingAlways(): true;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed entitlement tokens (asymmetric).
|
|
3
|
+
* Server holds private key; distributed app holds public key only.
|
|
4
|
+
*/
|
|
5
|
+
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey, } from "node:crypto";
|
|
6
|
+
import { PLAN_MATRIX } from "../product.js";
|
|
7
|
+
export const ENTITLEMENT_TOKEN_VERSION = 1;
|
|
8
|
+
export function generateEntitlementKeyPair() {
|
|
9
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
10
|
+
return {
|
|
11
|
+
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
12
|
+
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function canonical(claims) {
|
|
16
|
+
return JSON.stringify(claims, Object.keys(claims).sort());
|
|
17
|
+
}
|
|
18
|
+
export function signEntitlement(claims, privateKeyPem) {
|
|
19
|
+
const key = createPrivateKey(privateKeyPem);
|
|
20
|
+
const sig = sign(null, Buffer.from(canonical(claims), "utf8"), key);
|
|
21
|
+
return {
|
|
22
|
+
claims,
|
|
23
|
+
signature: sig.toString("base64url"),
|
|
24
|
+
algorithm: "ed25519",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function verifyEntitlement(token, publicKeyPem) {
|
|
28
|
+
if (token.algorithm !== "ed25519") {
|
|
29
|
+
return { ok: false, reason: "unsupported algorithm" };
|
|
30
|
+
}
|
|
31
|
+
if (token.claims.v !== ENTITLEMENT_TOKEN_VERSION) {
|
|
32
|
+
return { ok: false, reason: "unsupported token version" };
|
|
33
|
+
}
|
|
34
|
+
let key;
|
|
35
|
+
try {
|
|
36
|
+
key = createPublicKey(publicKeyPem);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { ok: false, reason: "invalid public key" };
|
|
40
|
+
}
|
|
41
|
+
const ok = verify(null, Buffer.from(canonical(token.claims), "utf8"), key, Buffer.from(token.signature, "base64url"));
|
|
42
|
+
if (!ok)
|
|
43
|
+
return { ok: false, reason: "invalid signature" };
|
|
44
|
+
// Tamper check: features.plan must match claims.plan
|
|
45
|
+
if (token.claims.features.plan !== token.claims.plan) {
|
|
46
|
+
return { ok: false, reason: "features.plan mismatch" };
|
|
47
|
+
}
|
|
48
|
+
// Undo invariant must never be false in a valid token
|
|
49
|
+
if (token.claims.features.allowUndoExisting !== true) {
|
|
50
|
+
return { ok: false, reason: "token violates undo invariant" };
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, claims: token.claims };
|
|
53
|
+
}
|
|
54
|
+
export function buildClaims(input) {
|
|
55
|
+
const now = input.now ?? new Date();
|
|
56
|
+
const ttl = input.ttlHours ?? 24;
|
|
57
|
+
const grace = input.graceHours ?? 72;
|
|
58
|
+
const refresh = input.refreshHours ?? 12;
|
|
59
|
+
const expires = new Date(now.getTime() + ttl * 3600_000);
|
|
60
|
+
const graceUntil = new Date(expires.getTime() + grace * 3600_000);
|
|
61
|
+
const refreshAfter = new Date(now.getTime() + refresh * 3600_000);
|
|
62
|
+
const features = { ...PLAN_MATRIX[input.plan] };
|
|
63
|
+
return {
|
|
64
|
+
v: ENTITLEMENT_TOKEN_VERSION,
|
|
65
|
+
sub: input.userId,
|
|
66
|
+
email: input.email,
|
|
67
|
+
plan: input.plan,
|
|
68
|
+
features,
|
|
69
|
+
installationId: input.installationId,
|
|
70
|
+
issuedAt: now.toISOString(),
|
|
71
|
+
refreshAfter: refreshAfter.toISOString(),
|
|
72
|
+
expiresAt: expires.toISOString(),
|
|
73
|
+
graceUntil: graceUntil.toISOString(),
|
|
74
|
+
offline: input.offline ?? false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate whether NEW mediation is allowed given verified claims + clock.
|
|
79
|
+
* Undo of existing work is NEVER denied here.
|
|
80
|
+
*/
|
|
81
|
+
export function mediationAllowedFromClaims(claims, now = new Date()) {
|
|
82
|
+
const t = now.getTime();
|
|
83
|
+
const expired = t > Date.parse(claims.expiresAt);
|
|
84
|
+
const pastGrace = t > Date.parse(claims.graceUntil);
|
|
85
|
+
if (!claims.features.allowNewMediation) {
|
|
86
|
+
return {
|
|
87
|
+
allow: false,
|
|
88
|
+
reason: "plan does not allow new mediation",
|
|
89
|
+
inGrace: false,
|
|
90
|
+
expired,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (!expired) {
|
|
94
|
+
return { allow: true, reason: "valid entitlement", inGrace: false, expired: false };
|
|
95
|
+
}
|
|
96
|
+
if (!pastGrace) {
|
|
97
|
+
return {
|
|
98
|
+
allow: true,
|
|
99
|
+
reason: "within offline/grace period",
|
|
100
|
+
inGrace: true,
|
|
101
|
+
expired: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Past grace: free mediation soft-cap still available as free tier fallback
|
|
105
|
+
if (claims.plan === "personal") {
|
|
106
|
+
return {
|
|
107
|
+
allow: false,
|
|
108
|
+
reason: "Personal entitlement expired past grace — new mediation limited; existing undos remain available",
|
|
109
|
+
inGrace: false,
|
|
110
|
+
expired: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
allow: true,
|
|
115
|
+
reason: "free/trial mediation",
|
|
116
|
+
inGrace: false,
|
|
117
|
+
expired,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Architectural invariant — always true. */
|
|
121
|
+
export function allowUndoExistingAlways() {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../../../../src/commercial/entitlement/signing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,mBAAmB,EACnB,IAAI,EACJ,MAAM,EACN,eAAe,EACf,gBAAgB,GAEjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAU,CAAC;AA0BpD,MAAM,UAAU,0BAA0B;IAIxC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjE,OAAO;QACL,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC1E,aAAa,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE;KAC9E,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,MAAyB;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,MAAyB,EACzB,aAAqB;IAErB,MAAM,GAAG,GAAG,gBAAgB,CAAC,aAAa,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;IACpE,OAAO;QACL,MAAM;QACN,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;QACpC,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAwB,EACxB,YAAoB;IAEpB,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAClC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IACxD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,yBAAyB,EAAE,CAAC;QACjD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,GAAc,CAAC;IACnB,IAAI,CAAC;QACH,GAAG,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,CACf,IAAI,EACJ,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAC5C,GAAG,EACH,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAC1C,CAAC;IACF,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAE3D,qDAAqD;IACrD,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;IACzD,CAAC;IACD,sDAAsD;IACtD,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAU3B;IACC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,GAAG,QAAQ,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,QAAQ,CAAC,CAAC;IAClE,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAChD,OAAO;QACL,CAAC,EAAE,yBAAyB;QAC5B,GAAG,EAAE,KAAK,CAAC,MAAM;QACjB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ;QACR,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,QAAQ,EAAE,GAAG,CAAC,WAAW,EAAE;QAC3B,YAAY,EAAE,YAAY,CAAC,WAAW,EAAE;QACxC,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE;QAChC,UAAU,EAAE,UAAU,CAAC,WAAW,EAAE;QACpC,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,KAAK;KAChC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAyB,EACzB,GAAG,GAAG,IAAI,IAAI,EAAE;IAEhB,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QACvC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,mCAAmC;YAC3C,OAAO,EAAE,KAAK;YACd,OAAO;SACR,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,6BAA6B;YACrC,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,4EAA4E;IAC5E,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EACJ,kGAAkG;YACpG,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,sBAAsB;QAC9B,OAAO,EAAE,KAAK;QACd,OAAO;KACR,CAAC;AACJ,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,uBAAuB;IACrC,OAAO,IAAI,CAAC;AACd,CAAC"}
|