ragevault 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/assets.d.ts +14 -0
- package/dist/assets.js +47 -0
- package/dist/assets.js.map +1 -0
- package/dist/audit.d.ts +35 -0
- package/dist/audit.js +79 -0
- package/dist/audit.js.map +1 -0
- package/dist/branding.d.ts +36 -0
- package/dist/branding.js +55 -0
- package/dist/branding.js.map +1 -0
- package/dist/crypto/cipher.d.ts +74 -0
- package/dist/crypto/cipher.js +95 -0
- package/dist/crypto/cipher.js.map +1 -0
- package/dist/crypto/cipher.test.d.ts +7 -0
- package/dist/crypto/cipher.test.js +50 -0
- package/dist/crypto/cipher.test.js.map +1 -0
- package/dist/generator.d.ts +36 -0
- package/dist/generator.js +114 -0
- package/dist/generator.js.map +1 -0
- package/dist/model.d.ts +96 -0
- package/dist/model.js +43 -0
- package/dist/model.js.map +1 -0
- package/dist/porters.d.ts +35 -0
- package/dist/porters.js +175 -0
- package/dist/porters.js.map +1 -0
- package/dist/prompt.d.ts +14 -0
- package/dist/prompt.js +56 -0
- package/dist/prompt.js.map +1 -0
- package/dist/ragevault.d.ts +22 -0
- package/dist/ragevault.js +814 -0
- package/dist/ragevault.js.map +1 -0
- package/dist/server/public/app.js +420 -0
- package/dist/server/public/apple-touch-icon.png +0 -0
- package/dist/server/public/favicon.png +0 -0
- package/dist/server/public/fonts/archivo.woff2 +0 -0
- package/dist/server/public/fonts/space-grotesk.woff2 +0 -0
- package/dist/server/public/icon-192.png +0 -0
- package/dist/server/public/icon-512.png +0 -0
- package/dist/server/public/icon-maskable.png +0 -0
- package/dist/server/public/index.html +225 -0
- package/dist/server/public/manifest.webmanifest +16 -0
- package/dist/server/public/styles.css +362 -0
- package/dist/server/public/sw.js +30 -0
- package/dist/server/server.d.ts +30 -0
- package/dist/server/server.js +410 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/session.d.ts +36 -0
- package/dist/server/session.js +89 -0
- package/dist/server/session.js.map +1 -0
- package/dist/store/vaultfile.d.ts +64 -0
- package/dist/store/vaultfile.js +110 -0
- package/dist/store/vaultfile.js.map +1 -0
- package/dist/store/vaultfile.test.d.ts +5 -0
- package/dist/store/vaultfile.test.js +45 -0
- package/dist/store/vaultfile.test.js.map +1 -0
- package/dist/totp.d.ts +26 -0
- package/dist/totp.js +88 -0
- package/dist/totp.js.map +1 -0
- package/dist/totp.test.d.ts +5 -0
- package/dist/totp.test.js +41 -0
- package/dist/totp.test.js.map +1 -0
- package/dist/tui/ansi.d.ts +34 -0
- package/dist/tui/ansi.js +52 -0
- package/dist/tui/ansi.js.map +1 -0
- package/dist/tui/app.d.ts +83 -0
- package/dist/tui/app.js +607 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/update/channel.d.ts +53 -0
- package/dist/update/channel.js +135 -0
- package/dist/update/channel.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The self-hosted RageVault server — a tiny, dependency-free HTTP server (Node
|
|
3
|
+
* core only) that serves the web UI and a small JSON API used by both the web
|
|
4
|
+
* app and the browser extension.
|
|
5
|
+
*
|
|
6
|
+
* Endpoints (all state-changing ones need the Bearer token from /api/unlock):
|
|
7
|
+
* GET /api/meta product/version, whether a vault exists
|
|
8
|
+
* POST /api/unlock {password} → { token } (401 if wrong)
|
|
9
|
+
* POST /api/lock end the session
|
|
10
|
+
* GET /api/items?domain= item metadata (NO secrets)
|
|
11
|
+
* GET /api/reveal/:id { password, totp } (the secret)
|
|
12
|
+
* POST /api/items {…} add an item
|
|
13
|
+
* POST /api/import?name= <raw file> import from Bitwarden/1Password/Proton/CSV
|
|
14
|
+
* GET /api/gen?mode=&length=&words= generate a password (no auth)
|
|
15
|
+
*
|
|
16
|
+
* Binds to 127.0.0.1 by default. If you bind to all interfaces it loudly warns
|
|
17
|
+
* you to keep it behind a VPN.
|
|
18
|
+
*
|
|
19
|
+
* Part of RageVault.
|
|
20
|
+
* Crafted by SoyRage Agency — https://soyrage.es/
|
|
21
|
+
* MIT licensed (see LICENSE).
|
|
22
|
+
*/
|
|
23
|
+
export interface ServeConfig {
|
|
24
|
+
vaultPath: string;
|
|
25
|
+
host: string;
|
|
26
|
+
port: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function startServer(cfg: ServeConfig): Promise<{
|
|
29
|
+
port: number;
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The self-hosted RageVault server — a tiny, dependency-free HTTP server (Node
|
|
3
|
+
* core only) that serves the web UI and a small JSON API used by both the web
|
|
4
|
+
* app and the browser extension.
|
|
5
|
+
*
|
|
6
|
+
* Endpoints (all state-changing ones need the Bearer token from /api/unlock):
|
|
7
|
+
* GET /api/meta product/version, whether a vault exists
|
|
8
|
+
* POST /api/unlock {password} → { token } (401 if wrong)
|
|
9
|
+
* POST /api/lock end the session
|
|
10
|
+
* GET /api/items?domain= item metadata (NO secrets)
|
|
11
|
+
* GET /api/reveal/:id { password, totp } (the secret)
|
|
12
|
+
* POST /api/items {…} add an item
|
|
13
|
+
* POST /api/import?name= <raw file> import from Bitwarden/1Password/Proton/CSV
|
|
14
|
+
* GET /api/gen?mode=&length=&words= generate a password (no auth)
|
|
15
|
+
*
|
|
16
|
+
* Binds to 127.0.0.1 by default. If you bind to all interfaces it loudly warns
|
|
17
|
+
* you to keep it behind a VPN.
|
|
18
|
+
*
|
|
19
|
+
* Part of RageVault.
|
|
20
|
+
* Crafted by SoyRage Agency — https://soyrage.es/
|
|
21
|
+
* MIT licensed (see LICENSE).
|
|
22
|
+
*/
|
|
23
|
+
import { createServer } from "node:http";
|
|
24
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { networkInterfaces } from "node:os";
|
|
27
|
+
import { dirname, extname, join, normalize, resolve } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import { BRAND } from "../branding.js";
|
|
30
|
+
import { canDo, emptyVault, newId } from "../model.js";
|
|
31
|
+
import { createVault, readVaultFile, rewrapForPassword, saveVault as saveVaultFile, unlockVault } from "../store/vaultfile.js";
|
|
32
|
+
import { generatePassphrase, generatePassword, generatePronounceable, estimateStrength } from "../generator.js";
|
|
33
|
+
import { totp } from "../totp.js";
|
|
34
|
+
import { lock, persist, resolve as resolveSession, unlock } from "./session.js";
|
|
35
|
+
import { importAny } from "../porters.js";
|
|
36
|
+
import { isPackaged, readAssetBuffer } from "../assets.js";
|
|
37
|
+
const PUBLIC_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "public");
|
|
38
|
+
const MIME = {
|
|
39
|
+
".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8",
|
|
40
|
+
".js": "text/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml",
|
|
41
|
+
".webmanifest": "application/manifest+json", ".png": "image/png", ".ico": "image/x-icon",
|
|
42
|
+
".woff2": "font/woff2", ".woff": "font/woff",
|
|
43
|
+
};
|
|
44
|
+
function json(res, status, body) {
|
|
45
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
46
|
+
res.end(JSON.stringify(body));
|
|
47
|
+
}
|
|
48
|
+
function readBody(req) {
|
|
49
|
+
return new Promise((resolvePromise, reject) => {
|
|
50
|
+
let data = "";
|
|
51
|
+
req.on("data", (c) => { data += c; if (data.length > 65_536) {
|
|
52
|
+
reject(new Error("Body too large"));
|
|
53
|
+
req.destroy();
|
|
54
|
+
} });
|
|
55
|
+
req.on("end", () => {
|
|
56
|
+
try {
|
|
57
|
+
if (!data)
|
|
58
|
+
return resolvePromise({});
|
|
59
|
+
// Reject prototype-pollution payloads (__proto__, constructor, prototype)
|
|
60
|
+
// before parsing — defence in depth even though we only read known keys.
|
|
61
|
+
if (/"(?:__proto__|constructor|prototype)"\s*:/.test(data))
|
|
62
|
+
return reject(new Error("Invalid JSON"));
|
|
63
|
+
resolvePromise(JSON.parse(data));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
reject(new Error("Invalid JSON"));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
req.on("error", reject);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Read a raw text body (for file imports, which can exceed the JSON cap). */
|
|
73
|
+
function readText(req, max = 8_388_608) {
|
|
74
|
+
return new Promise((resolvePromise, reject) => {
|
|
75
|
+
let data = "";
|
|
76
|
+
req.on("data", (c) => { data += c; if (data.length > max) {
|
|
77
|
+
reject(new Error("File too large (max 8 MB)."));
|
|
78
|
+
req.destroy();
|
|
79
|
+
} });
|
|
80
|
+
req.on("end", () => resolvePromise(data));
|
|
81
|
+
req.on("error", reject);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Harden every response: strict CSP (no inline/eval), no framing, no sniffing. */
|
|
85
|
+
function setSecurityHeaders(res) {
|
|
86
|
+
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; " +
|
|
87
|
+
"connect-src 'self'; manifest-src 'self'; worker-src 'self'; object-src 'none'; " +
|
|
88
|
+
"base-uri 'none'; form-action 'none'; frame-ancestors 'none'");
|
|
89
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
90
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
91
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
92
|
+
res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=(), interest-cohort=()");
|
|
93
|
+
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
94
|
+
res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
|
|
95
|
+
}
|
|
96
|
+
function bearer(req) {
|
|
97
|
+
const h = req.headers.authorization ?? "";
|
|
98
|
+
return h.startsWith("Bearer ") ? h.slice(7) : undefined;
|
|
99
|
+
}
|
|
100
|
+
/** hostname of an item's url, lowercased, for domain matching. */
|
|
101
|
+
function itemHost(item) {
|
|
102
|
+
try {
|
|
103
|
+
return item.url ? new URL(item.url).hostname.toLowerCase().replace(/^www\./, "") : "";
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Fetch a site's favicon from a public icon service. Only the self-hosted
|
|
110
|
+
* server ever calls this — never the user's browser — and we hit fixed icon
|
|
111
|
+
* endpoints (the domain is only a parameter), so there's no SSRF to arbitrary
|
|
112
|
+
* hosts. Returns PNG/ICO bytes or throws. */
|
|
113
|
+
async function fetchFavicon(domain) {
|
|
114
|
+
const sources = [
|
|
115
|
+
`https://icons.duckduckgo.com/ip3/${domain}.ico`,
|
|
116
|
+
`https://www.google.com/s2/favicons?sz=64&domain=${domain}`,
|
|
117
|
+
];
|
|
118
|
+
for (const src of sources) {
|
|
119
|
+
try {
|
|
120
|
+
const ctl = new AbortController();
|
|
121
|
+
const timer = setTimeout(() => ctl.abort(), 4000);
|
|
122
|
+
const r = await fetch(src, { signal: ctl.signal, headers: { accept: "image/*" } });
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
if (!r.ok)
|
|
125
|
+
continue;
|
|
126
|
+
const buf = Buffer.from(await r.arrayBuffer());
|
|
127
|
+
if (buf.byteLength >= 100)
|
|
128
|
+
return buf; // skip empty/placeholder responses
|
|
129
|
+
}
|
|
130
|
+
catch { /* try the next source */ }
|
|
131
|
+
}
|
|
132
|
+
throw new Error("no favicon");
|
|
133
|
+
}
|
|
134
|
+
export function startServer(cfg) {
|
|
135
|
+
const server = createServer(async (req, res) => {
|
|
136
|
+
setSecurityHeaders(res);
|
|
137
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
138
|
+
const path = url.pathname;
|
|
139
|
+
try {
|
|
140
|
+
if (path === "/api/meta") {
|
|
141
|
+
return json(res, 200, { product: BRAND.product, version: BRAND.version, author: BRAND.author, hasVault: existsSync(cfg.vaultPath) });
|
|
142
|
+
}
|
|
143
|
+
if (path === "/api/gen") {
|
|
144
|
+
const mode = url.searchParams.get("mode");
|
|
145
|
+
const pw = mode === "passphrase" ? generatePassphrase(Number(url.searchParams.get("words") ?? 5))
|
|
146
|
+
: mode === "pronounceable" ? generatePronounceable()
|
|
147
|
+
: generatePassword({ length: Number(url.searchParams.get("length") ?? 20) });
|
|
148
|
+
return json(res, 200, { password: pw, strength: estimateStrength(pw) });
|
|
149
|
+
}
|
|
150
|
+
if (path === "/api/favicon") {
|
|
151
|
+
// Served from our own origin (keeps CSP img-src 'self'). Only the server
|
|
152
|
+
// reaches out to the icon service, so the user's browser never reveals
|
|
153
|
+
// which sites are saved. Requires a valid session (token via ?t= for <img>).
|
|
154
|
+
if (!resolveSession(url.searchParams.get("t") ?? bearer(req))) {
|
|
155
|
+
res.writeHead(401);
|
|
156
|
+
return void res.end();
|
|
157
|
+
}
|
|
158
|
+
const domain = (url.searchParams.get("domain") ?? "").toLowerCase().replace(/^www\./, "");
|
|
159
|
+
if (!/^[a-z0-9][a-z0-9.-]{0,253}$/.test(domain)) {
|
|
160
|
+
res.writeHead(400);
|
|
161
|
+
return void res.end();
|
|
162
|
+
}
|
|
163
|
+
const dir = join(dirname(cfg.vaultPath), "favicons");
|
|
164
|
+
const file = join(dir, `${domain}.png`);
|
|
165
|
+
try {
|
|
166
|
+
let buf;
|
|
167
|
+
if (existsSync(file))
|
|
168
|
+
buf = await readFile(file);
|
|
169
|
+
else {
|
|
170
|
+
buf = await fetchFavicon(domain);
|
|
171
|
+
await mkdir(dir, { recursive: true });
|
|
172
|
+
await writeFile(file, buf);
|
|
173
|
+
}
|
|
174
|
+
// Declare the true type — the global nosniff header stops the browser guessing.
|
|
175
|
+
const type = buf[0] === 0x89 && buf[1] === 0x50 ? "image/png"
|
|
176
|
+
: buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 ? "image/x-icon"
|
|
177
|
+
: buf[0] === 0x47 && buf[1] === 0x49 ? "image/gif"
|
|
178
|
+
: buf.slice(0, 4).toString() === "<svg" || buf.slice(0, 5).toString() === "<?xml" ? "image/svg+xml"
|
|
179
|
+
: "image/x-icon";
|
|
180
|
+
res.writeHead(200, { "Content-Type": type, "Cache-Control": "max-age=604800" });
|
|
181
|
+
return void res.end(buf);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
res.writeHead(404);
|
|
185
|
+
return void res.end();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (path === "/api/init" && req.method === "POST") {
|
|
189
|
+
// First-run vault creation from the UI (used by the desktop app). Refuses
|
|
190
|
+
// if a vault already exists, so it can't clobber your data.
|
|
191
|
+
if (existsSync(cfg.vaultPath))
|
|
192
|
+
return json(res, 409, { error: "A vault already exists here." });
|
|
193
|
+
const body = await readBody(req);
|
|
194
|
+
const password = typeof body.password === "string" ? body.password : "";
|
|
195
|
+
const name = typeof body.name === "string" && body.name.trim() ? body.name.trim() : "My Vault";
|
|
196
|
+
if (password.length < 8)
|
|
197
|
+
return json(res, 400, { error: "Master password must be at least 8 characters." });
|
|
198
|
+
saveVaultFile(cfg.vaultPath, createVault(emptyVault(name, "owner", new Date().toISOString()), password));
|
|
199
|
+
const token = await unlock(cfg.vaultPath, password);
|
|
200
|
+
return json(res, 200, { token });
|
|
201
|
+
}
|
|
202
|
+
if (path === "/api/passwd" && req.method === "POST") {
|
|
203
|
+
const sess = resolveSession(bearer(req));
|
|
204
|
+
if (!sess)
|
|
205
|
+
return json(res, 401, { error: "Locked. Unlock first." });
|
|
206
|
+
if (!canDo(sess.vault.data.users[0]?.role ?? "viewer", "vault.manage"))
|
|
207
|
+
return json(res, 403, { error: "Your role can't change the master password." });
|
|
208
|
+
const body = await readBody(req);
|
|
209
|
+
const current = typeof body.current === "string" ? body.current : "";
|
|
210
|
+
const next = typeof body.next === "string" ? body.next : "";
|
|
211
|
+
if (next.length < 8)
|
|
212
|
+
return json(res, 400, { error: "New master password must be at least 8 characters." });
|
|
213
|
+
try {
|
|
214
|
+
unlockVault(readVaultFile(sess.path), current);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return json(res, 403, { error: "Current master password is incorrect." });
|
|
218
|
+
}
|
|
219
|
+
rewrapForPassword(sess.vault, next);
|
|
220
|
+
persist(sess);
|
|
221
|
+
return json(res, 200, { ok: true });
|
|
222
|
+
}
|
|
223
|
+
if (path === "/api/unlock" && req.method === "POST") {
|
|
224
|
+
const body = await readBody(req);
|
|
225
|
+
if (typeof body.password !== "string")
|
|
226
|
+
return json(res, 400, { error: "password required" });
|
|
227
|
+
try {
|
|
228
|
+
const token = await unlock(cfg.vaultPath, body.password);
|
|
229
|
+
return json(res, 200, { token });
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
return json(res, 401, { error: e.message });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// ---- everything below needs a valid session ----
|
|
236
|
+
const session = resolveSession(bearer(req));
|
|
237
|
+
const guard = () => { json(res, 401, { error: "Locked. Unlock first." }); };
|
|
238
|
+
if (path === "/api/lock" && req.method === "POST") {
|
|
239
|
+
lock(bearer(req));
|
|
240
|
+
return json(res, 200, { ok: true });
|
|
241
|
+
}
|
|
242
|
+
if (path === "/api/items" && req.method === "GET") {
|
|
243
|
+
if (!session)
|
|
244
|
+
return guard();
|
|
245
|
+
const domain = (url.searchParams.get("domain") ?? "").toLowerCase().replace(/^www\./, "");
|
|
246
|
+
const cats = new Map(session.vault.data.categories.map((c) => [c.id, c.name]));
|
|
247
|
+
const items = session.vault.data.items
|
|
248
|
+
// Title/host substrings power the search box; the host clause is a strict
|
|
249
|
+
// exact-or-subdomain match so "github.com.evil.com" can't surface github.com
|
|
250
|
+
// credentials to the extension's one-click autofill.
|
|
251
|
+
.filter((i) => { const h = itemHost(i); return !domain || i.title.toLowerCase().includes(domain) || (!!h && (h.includes(domain) || domain === h || domain.endsWith(`.${h}`))); })
|
|
252
|
+
.map((i) => ({ id: i.id, title: i.title, username: i.username ?? "", url: i.url ?? "", category: cats.get(i.categoryId ?? "") ?? "", favorite: i.favorite, hasTotp: Boolean(i.totp), icon: i.icon ?? "" }));
|
|
253
|
+
return json(res, 200, { items });
|
|
254
|
+
}
|
|
255
|
+
if (path.startsWith("/api/reveal/") && req.method === "GET") {
|
|
256
|
+
if (!session)
|
|
257
|
+
return guard();
|
|
258
|
+
const role = session.vault.data.users[0]?.role ?? "viewer";
|
|
259
|
+
if (!canDo(role, "item.reveal"))
|
|
260
|
+
return json(res, 403, { error: "Your role can't reveal secrets." });
|
|
261
|
+
const id = path.slice("/api/reveal/".length);
|
|
262
|
+
const item = session.vault.data.items.find((i) => i.id === id);
|
|
263
|
+
if (!item)
|
|
264
|
+
return json(res, 404, { error: "Not found" });
|
|
265
|
+
return json(res, 200, { password: item.password ?? "", totp: item.totp ?? null });
|
|
266
|
+
}
|
|
267
|
+
if (path.startsWith("/api/totp/") && req.method === "GET") {
|
|
268
|
+
if (!session)
|
|
269
|
+
return guard();
|
|
270
|
+
if (!canDo(session.vault.data.users[0]?.role ?? "viewer", "item.reveal"))
|
|
271
|
+
return json(res, 403, { error: "Your role can't reveal secrets." });
|
|
272
|
+
const item = session.vault.data.items.find((i) => i.id === path.slice("/api/totp/".length));
|
|
273
|
+
if (!item?.totp)
|
|
274
|
+
return json(res, 404, { error: "No 2FA on this item." });
|
|
275
|
+
try {
|
|
276
|
+
return json(res, 200, totp(item.totp));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return json(res, 400, { error: "Invalid 2FA secret." });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (path === "/api/items" && req.method === "POST") {
|
|
283
|
+
if (!session)
|
|
284
|
+
return guard();
|
|
285
|
+
const role = session.vault.data.users[0]?.role ?? "viewer";
|
|
286
|
+
if (!canDo(role, "item.create"))
|
|
287
|
+
return json(res, 403, { error: "Your role can't add items." });
|
|
288
|
+
const body = await readBody(req);
|
|
289
|
+
if (typeof body.title !== "string" || !body.title.trim())
|
|
290
|
+
return json(res, 400, { error: "title required" });
|
|
291
|
+
const now = new Date().toISOString();
|
|
292
|
+
let categoryId;
|
|
293
|
+
if (typeof body.category === "string" && body.category.trim()) {
|
|
294
|
+
const name = body.category.trim();
|
|
295
|
+
const existing = session.vault.data.categories.find((c) => c.name.toLowerCase() === name.toLowerCase());
|
|
296
|
+
categoryId = existing?.id ?? newId();
|
|
297
|
+
if (!existing)
|
|
298
|
+
session.vault.data.categories.push({ id: categoryId, name });
|
|
299
|
+
}
|
|
300
|
+
const item = {
|
|
301
|
+
id: newId(), title: body.title.trim(),
|
|
302
|
+
username: typeof body.username === "string" ? body.username : undefined,
|
|
303
|
+
url: typeof body.url === "string" ? body.url : undefined,
|
|
304
|
+
password: typeof body.password === "string" ? body.password : "",
|
|
305
|
+
icon: typeof body.icon === "string" && body.icon.trim() ? body.icon.trim().slice(0, 8) : undefined,
|
|
306
|
+
categoryId, tags: [], favorite: false, createdAt: now, updatedAt: now, history: [],
|
|
307
|
+
};
|
|
308
|
+
session.vault.data.items.push(item);
|
|
309
|
+
persist(session);
|
|
310
|
+
return json(res, 200, { ok: true, id: item.id });
|
|
311
|
+
}
|
|
312
|
+
// ---- one-click import from Bitwarden / 1Password / Proton / LastPass / CSV ----
|
|
313
|
+
if (path === "/api/import" && req.method === "POST") {
|
|
314
|
+
if (!session)
|
|
315
|
+
return guard();
|
|
316
|
+
const role = session.vault.data.users[0]?.role ?? "viewer";
|
|
317
|
+
if (!canDo(role, "item.create"))
|
|
318
|
+
return json(res, 403, { error: "Your role can't add items." });
|
|
319
|
+
let content;
|
|
320
|
+
try {
|
|
321
|
+
content = await readText(req);
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
return json(res, 413, { error: e.message });
|
|
325
|
+
}
|
|
326
|
+
if (!content.trim())
|
|
327
|
+
return json(res, 400, { error: "That file looks empty." });
|
|
328
|
+
let parsed;
|
|
329
|
+
try {
|
|
330
|
+
parsed = importAny(content, url.searchParams.get("name") ?? "");
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return json(res, 400, { error: "Couldn't read that file — is it a Bitwarden/1Password/Proton/CSV export?" });
|
|
334
|
+
}
|
|
335
|
+
const now = new Date().toISOString();
|
|
336
|
+
let added = 0;
|
|
337
|
+
for (const raw of parsed.items) {
|
|
338
|
+
if (!raw || typeof raw.title !== "string" || !raw.title.trim())
|
|
339
|
+
continue;
|
|
340
|
+
session.vault.data.items.push({
|
|
341
|
+
...raw, id: newId(), tags: Array.isArray(raw.tags) ? raw.tags : [],
|
|
342
|
+
favorite: Boolean(raw.favorite), createdAt: now, updatedAt: now,
|
|
343
|
+
history: Array.isArray(raw.history) ? raw.history : [],
|
|
344
|
+
});
|
|
345
|
+
added++;
|
|
346
|
+
}
|
|
347
|
+
if (added)
|
|
348
|
+
persist(session);
|
|
349
|
+
return json(res, 200, { ok: true, added, source: parsed.source });
|
|
350
|
+
}
|
|
351
|
+
// ---- static SPA (from disk, or the SEA blob when packaged) ----
|
|
352
|
+
if (req.method === "GET") {
|
|
353
|
+
const rel = path === "/" ? "index.html" : path.replace(/^\/+/, "");
|
|
354
|
+
const target = normalize(join(PUBLIC_DIR, rel));
|
|
355
|
+
if (!target.startsWith(PUBLIC_DIR)) {
|
|
356
|
+
res.writeHead(404);
|
|
357
|
+
return void res.end("Not found");
|
|
358
|
+
}
|
|
359
|
+
let bodyBuf;
|
|
360
|
+
if (isPackaged()) {
|
|
361
|
+
try {
|
|
362
|
+
bodyBuf = readAssetBuffer(`public/${rel}`, target);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
res.writeHead(404);
|
|
366
|
+
return void res.end("Not found");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
if (!existsSync(target)) {
|
|
371
|
+
res.writeHead(404);
|
|
372
|
+
return void res.end("Not found");
|
|
373
|
+
}
|
|
374
|
+
bodyBuf = await readFile(target);
|
|
375
|
+
}
|
|
376
|
+
res.writeHead(200, { "Content-Type": MIME[extname(target)] ?? "application/octet-stream" });
|
|
377
|
+
return void res.end(bodyBuf);
|
|
378
|
+
}
|
|
379
|
+
res.writeHead(405);
|
|
380
|
+
res.end("Method not allowed");
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
json(res, 500, { error: e instanceof Error ? e.message : "server error" });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return new Promise((resolvePromise) => {
|
|
387
|
+
// port 0 asks the OS for a free port (the desktop app uses this and reads it back).
|
|
388
|
+
server.listen(cfg.port, cfg.host, () => {
|
|
389
|
+
const actualPort = server.address()?.port ?? cfg.port;
|
|
390
|
+
process.stdout.write(`\n \x1b[38;5;39m${BRAND.product}\x1b[0m server ready at \x1b[1mhttp://${cfg.host}:${actualPort}\x1b[0m\n`);
|
|
391
|
+
if (cfg.host === "0.0.0.0" || cfg.host === "::") {
|
|
392
|
+
for (const u of reachableUrls(actualPort))
|
|
393
|
+
process.stdout.write(` reachable at ${u}\n`);
|
|
394
|
+
process.stdout.write(` \x1b[33m⚠ Bound to ALL interfaces.\x1b[0m Keep RageVault behind a VPN — never expose it to the Internet.\n`);
|
|
395
|
+
}
|
|
396
|
+
process.stdout.write(` \x1b[2mAuto-locks after 15 min idle. Ctrl-C to stop.\x1b[0m\n\n`);
|
|
397
|
+
resolvePromise({ port: actualPort });
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
function reachableUrls(port) {
|
|
402
|
+
const urls = [`http://127.0.0.1:${port}`];
|
|
403
|
+
for (const addrs of Object.values(networkInterfaces())) {
|
|
404
|
+
for (const a of addrs ?? [])
|
|
405
|
+
if (a.family === "IPv4" && !a.internal)
|
|
406
|
+
urls.push(`http://${a.address}:${port}`);
|
|
407
|
+
}
|
|
408
|
+
return urls;
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory unlock sessions for the self-hosted server.
|
|
3
|
+
*
|
|
4
|
+
* The web UI / extension POST the master password once; the server derives the
|
|
5
|
+
* vault key, holds the *unlocked* vault in memory keyed by a random bearer
|
|
6
|
+
* token, and auto-locks after idle. The password itself is never stored, and
|
|
7
|
+
* only ciphertext is ever written back to disk.
|
|
8
|
+
*
|
|
9
|
+
* SECURITY TRADE-OFF (documented on purpose): in this model the server briefly
|
|
10
|
+
* sees the master password during unlock and holds the data key in memory — so
|
|
11
|
+
* run it **only on a host you trust, behind your VPN**. Browser-side
|
|
12
|
+
* (zero-knowledge) decryption is on the roadmap; it needs a KDF WebCrypto can do
|
|
13
|
+
* (the vault uses scrypt today).
|
|
14
|
+
*
|
|
15
|
+
* Part of RageVault.
|
|
16
|
+
* Crafted by SoyRage Agency — https://soyrage.es/
|
|
17
|
+
* MIT licensed (see LICENSE).
|
|
18
|
+
*/
|
|
19
|
+
import { type OpenVault } from "../store/vaultfile.js";
|
|
20
|
+
interface Session {
|
|
21
|
+
vault: OpenVault;
|
|
22
|
+
path: string;
|
|
23
|
+
/** Epoch ms when this session lapses if untouched. */
|
|
24
|
+
expires: number;
|
|
25
|
+
}
|
|
26
|
+
/** Milliseconds remaining on the current lockout (0 if not locked). */
|
|
27
|
+
export declare function lockoutRemainingMs(): number;
|
|
28
|
+
/** Unlock the vault and start a session. Returns the bearer token. */
|
|
29
|
+
export declare function unlock(path: string, password: string): Promise<string>;
|
|
30
|
+
/** Resolve a token to its live session, sliding the idle timer. Null if gone. */
|
|
31
|
+
export declare function resolve(token: string | undefined): Session | null;
|
|
32
|
+
/** End a session (lock). */
|
|
33
|
+
export declare function lock(token: string | undefined): void;
|
|
34
|
+
/** Persist the current state of a session's vault. */
|
|
35
|
+
export declare function persist(s: Session): void;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory unlock sessions for the self-hosted server.
|
|
3
|
+
*
|
|
4
|
+
* The web UI / extension POST the master password once; the server derives the
|
|
5
|
+
* vault key, holds the *unlocked* vault in memory keyed by a random bearer
|
|
6
|
+
* token, and auto-locks after idle. The password itself is never stored, and
|
|
7
|
+
* only ciphertext is ever written back to disk.
|
|
8
|
+
*
|
|
9
|
+
* SECURITY TRADE-OFF (documented on purpose): in this model the server briefly
|
|
10
|
+
* sees the master password during unlock and holds the data key in memory — so
|
|
11
|
+
* run it **only on a host you trust, behind your VPN**. Browser-side
|
|
12
|
+
* (zero-knowledge) decryption is on the roadmap; it needs a KDF WebCrypto can do
|
|
13
|
+
* (the vault uses scrypt today).
|
|
14
|
+
*
|
|
15
|
+
* Part of RageVault.
|
|
16
|
+
* Crafted by SoyRage Agency — https://soyrage.es/
|
|
17
|
+
* MIT licensed (see LICENSE).
|
|
18
|
+
*/
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
import { readVaultFile, saveVault, unlockVault } from "../store/vaultfile.js";
|
|
22
|
+
const sessions = new Map();
|
|
23
|
+
const IDLE_MS = 15 * 60 * 1000; // auto-lock after 15 minutes idle
|
|
24
|
+
/**
|
|
25
|
+
* Server-side brute-force lockout. This is the authoritative gate — the browser's
|
|
26
|
+
* shake/lockout is only cosmetic and can be bypassed (clear localStorage, or hit
|
|
27
|
+
* the API directly with a proxy). Enforced here, it can't be. After a few bad
|
|
28
|
+
* passwords the server refuses to even try for an escalating window.
|
|
29
|
+
*/
|
|
30
|
+
let failures = 0;
|
|
31
|
+
let lockedUntil = 0;
|
|
32
|
+
/** Milliseconds remaining on the current lockout (0 if not locked). */
|
|
33
|
+
export function lockoutRemainingMs() { return Math.max(0, lockedUntil - Date.now()); }
|
|
34
|
+
function sweep(now) {
|
|
35
|
+
for (const [token, s] of sessions)
|
|
36
|
+
if (s.expires <= now)
|
|
37
|
+
sessions.delete(token);
|
|
38
|
+
}
|
|
39
|
+
/** Unlock the vault and start a session. Returns the bearer token. */
|
|
40
|
+
export async function unlock(path, password) {
|
|
41
|
+
if (!existsSync(path))
|
|
42
|
+
throw new Error("No vault on this server. Run `ragevault init` first.");
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const remaining = lockoutRemainingMs();
|
|
45
|
+
if (remaining > 0)
|
|
46
|
+
throw new Error(`Too many attempts — locked for ${Math.ceil(remaining / 1000)}s.`);
|
|
47
|
+
const file = readVaultFile(path);
|
|
48
|
+
let vault;
|
|
49
|
+
try {
|
|
50
|
+
vault = unlockVault(file, password);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
failures++;
|
|
54
|
+
if (failures >= 5) {
|
|
55
|
+
// Escalating window, capped at 1 hour: 15s, 30s, 1m, 2m, 4m … 60m.
|
|
56
|
+
const secs = Math.min(3600, 15 * 2 ** (failures - 5));
|
|
57
|
+
lockedUntil = Date.now() + secs * 1000;
|
|
58
|
+
throw new Error(`Wrong master password. Too many attempts — locked for ${secs}s.`);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Wrong master password. ${5 - failures} attempt${5 - failures === 1 ? "" : "s"} left before lockout.`);
|
|
61
|
+
}
|
|
62
|
+
failures = 0;
|
|
63
|
+
lockedUntil = 0;
|
|
64
|
+
const token = randomBytes(32).toString("base64url");
|
|
65
|
+
sessions.set(token, { vault, path, expires: now + IDLE_MS });
|
|
66
|
+
return token;
|
|
67
|
+
}
|
|
68
|
+
/** Resolve a token to its live session, sliding the idle timer. Null if gone. */
|
|
69
|
+
export function resolve(token) {
|
|
70
|
+
if (!token)
|
|
71
|
+
return null;
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
sweep(now);
|
|
74
|
+
const s = sessions.get(token);
|
|
75
|
+
if (!s)
|
|
76
|
+
return null;
|
|
77
|
+
s.expires = now + IDLE_MS;
|
|
78
|
+
return s;
|
|
79
|
+
}
|
|
80
|
+
/** End a session (lock). */
|
|
81
|
+
export function lock(token) {
|
|
82
|
+
if (token)
|
|
83
|
+
sessions.delete(token);
|
|
84
|
+
}
|
|
85
|
+
/** Persist the current state of a session's vault. */
|
|
86
|
+
export function persist(s) {
|
|
87
|
+
saveVault(s.path, s.vault);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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"}
|