Skip to content

Commit 4609a8f

Browse files
committed
better expiration implementation
1 parent fb973fd commit 4609a8f

File tree

5 files changed

+61
-59
lines changed

5 files changed

+61
-59
lines changed

dist/cli.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,8 @@ var VaultFolder = class _VaultFolder {
388388
this.key = key;
389389
this.metadata = metadata ?? { s: [], f: [] };
390390
}
391-
async serialize() {
392-
return await this.key.encryptObject(this.metadata);
391+
async serialize(extra) {
392+
return await this.key.encryptObject(extra ? { ...extra, ...this.metadata } : this.metadata);
393393
}
394394
handle(name) {
395395
return {
@@ -604,25 +604,25 @@ var Vault = class _Vault {
604604
}
605605
static async deserialize(data, password, io) {
606606
const parts = data.split("~");
607-
if (parts.length !== 2 && parts.length !== 3) {
607+
if (parts.length !== 2) {
608608
return null;
609609
}
610610
const imp = await CryptoKey.importWithPassword(parts[0], password);
611611
if (!imp) {
612612
return null;
613613
}
614614
const { key, difficulty } = imp;
615-
if (parts.length === 3) {
616-
const exp = parseFloat(await key.decryptString(parts[2]));
617-
const now = Math.floor(Date.now() / 6e4);
618-
if (now >= exp) {
619-
return null;
620-
}
621-
}
622615
const root = await VaultFolder.deserialize(-1, key, parts[1]);
623616
if (!root) {
624617
return null;
625618
}
619+
const metadata = root.metadata;
620+
if ("x" in metadata && typeof metadata.x === "number") {
621+
const now = Math.floor(Date.now() / 6e4);
622+
if (now >= metadata.x) {
623+
return null;
624+
}
625+
}
626626
return new _Vault(root, difficulty, io);
627627
}
628628
containerDirty() {
@@ -631,15 +631,13 @@ var Vault = class _Vault {
631631
// expiration is enforced on the client-side... so not 100% secure but at least it's something
632632
async serialize(password, difficulty = 0, expiresInMinutes = 0) {
633633
const d = difficulty > 0 ? difficulty : this.difficulty > 0 ? this.difficulty : _Vault.DEFAULT_DIFFICULTY;
634-
const parts = [
635-
await this.root.key.exportWithPassword(password, d),
636-
await this.root.serialize()
637-
];
634+
let more = void 0;
638635
if (expiresInMinutes > 0) {
639-
const exp = Math.ceil(Date.now() / 6e4) + expiresInMinutes;
640-
parts.push(await this.root.key.encryptString(`${exp}`));
636+
more = { x: Math.ceil(Date.now() / 6e4) + expiresInMinutes };
641637
}
642-
return parts.join("~");
638+
const k = await this.root.key.exportWithPassword(password, d);
639+
const r = await this.root.serialize(more);
640+
return `${k}~${r}`;
643641
}
644642
async save(forceEverything = false) {
645643
const saveFile = async (file) => {
@@ -1097,7 +1095,11 @@ Missing destination directory`);
10971095
}
10981096
for (const file of vault.listFiles()) {
10991097
const bytes = await vault.getFile(file);
1100-
await fs2.writeFile(path.join(dir, file), bytes);
1098+
if (!bytes) {
1099+
console.warn(`Warning: Failed to decrypt file: ${vault.getPath()}/${file}`);
1100+
} else {
1101+
await fs2.writeFile(path.join(dir, file), bytes);
1102+
}
11011103
}
11021104
};
11031105
await walk(destination);

dist/index.js

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ var StaticVault = (() => {
329329
this.key = key;
330330
this.metadata = metadata ?? { s: [], f: [] };
331331
}
332-
async serialize() {
333-
return await this.key.encryptObject(this.metadata);
332+
async serialize(extra) {
333+
return await this.key.encryptObject(extra ? { ...extra, ...this.metadata } : this.metadata);
334334
}
335335
handle(name) {
336336
return {
@@ -545,25 +545,25 @@ var StaticVault = (() => {
545545
}
546546
static async deserialize(data, password, io) {
547547
const parts = data.split("~");
548-
if (parts.length !== 2 && parts.length !== 3) {
548+
if (parts.length !== 2) {
549549
return null;
550550
}
551551
const imp = await CryptoKey.importWithPassword(parts[0], password);
552552
if (!imp) {
553553
return null;
554554
}
555555
const { key, difficulty } = imp;
556-
if (parts.length === 3) {
557-
const exp = parseFloat(await key.decryptString(parts[2]));
558-
const now = Math.floor(Date.now() / 6e4);
559-
if (now >= exp) {
560-
return null;
561-
}
562-
}
563556
const root = await VaultFolder.deserialize(-1, key, parts[1]);
564557
if (!root) {
565558
return null;
566559
}
560+
const metadata = root.metadata;
561+
if ("x" in metadata && typeof metadata.x === "number") {
562+
const now = Math.floor(Date.now() / 6e4);
563+
if (now >= metadata.x) {
564+
return null;
565+
}
566+
}
567567
return new _Vault(root, difficulty, io);
568568
}
569569
containerDirty() {
@@ -572,15 +572,13 @@ var StaticVault = (() => {
572572
// expiration is enforced on the client-side... so not 100% secure but at least it's something
573573
async serialize(password, difficulty = 0, expiresInMinutes = 0) {
574574
const d = difficulty > 0 ? difficulty : this.difficulty > 0 ? this.difficulty : _Vault.DEFAULT_DIFFICULTY;
575-
const parts = [
576-
await this.root.key.exportWithPassword(password, d),
577-
await this.root.serialize()
578-
];
575+
let more = void 0;
579576
if (expiresInMinutes > 0) {
580-
const exp = Math.ceil(Date.now() / 6e4) + expiresInMinutes;
581-
parts.push(await this.root.key.encryptString(`${exp}`));
577+
more = { x: Math.ceil(Date.now() / 6e4) + expiresInMinutes };
582578
}
583-
return parts.join("~");
579+
const k = await this.root.key.exportWithPassword(password, d);
580+
const r = await this.root.serialize(more);
581+
return `${k}~${r}`;
584582
}
585583
async save(forceEverything = false) {
586584
const saveFile = async (file) => {

dist/index.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Vault.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ class VaultFolder {
9191
this.metadata = metadata ?? { s: [], f: [] };
9292
}
9393

94-
async serialize(): Promise<string> {
95-
return await this.key.encryptObject(this.metadata);
94+
async serialize(extra?: object): Promise<string> {
95+
return await this.key.encryptObject(extra ? {...extra, ...this.metadata} : this.metadata);
9696
}
9797

9898
handle(name: string): IVaultMetadataHandle {
@@ -359,26 +359,26 @@ export class Vault {
359359
io: FileIO
360360
): Promise<Vault | null> {
361361
const parts = data.split('~');
362-
if (parts.length !== 2 && parts.length !== 3) {
362+
if (parts.length !== 2) {
363363
return null;
364364
}
365365
const imp = await CryptoKey.importWithPassword(parts[0], password);
366366
if (!imp) {
367367
return null;
368368
}
369369
const { key, difficulty } = imp;
370-
if (parts.length === 3) {
371-
const exp = parseFloat(await key.decryptString(parts[2]));
370+
const root = await VaultFolder.deserialize(-1, key, parts[1]);
371+
if (!root) {
372+
return null;
373+
}
374+
const metadata: object = root.metadata;
375+
if ('x' in metadata && typeof metadata.x === 'number') {
372376
const now = Math.floor(Date.now() / 60000);
373-
if (now >= exp) {
377+
if (now >= metadata.x) {
374378
// expired
375379
return null;
376380
}
377381
}
378-
const root = await VaultFolder.deserialize(-1, key, parts[1]);
379-
if (!root) {
380-
return null;
381-
}
382382
return new Vault(root, difficulty, io);
383383
}
384384

@@ -394,15 +394,13 @@ export class Vault {
394394
: this.difficulty > 0
395395
? this.difficulty
396396
: Vault.DEFAULT_DIFFICULTY;
397-
const parts = [
398-
await this.root.key.exportWithPassword(password, d),
399-
await this.root.serialize()
400-
];
397+
let more: object | undefined = undefined;
401398
if (expiresInMinutes > 0) {
402-
const exp = Math.ceil(Date.now() / 60000) + expiresInMinutes;
403-
parts.push(await this.root.key.encryptString(`${exp}`));
399+
more = { x: Math.ceil(Date.now() / 60000) + expiresInMinutes };
404400
}
405-
return parts.join('~');
401+
const k = await this.root.key.exportWithPassword(password, d);
402+
const r = await this.root.serialize(more);
403+
return `${k}~${r}`;
406404
}
407405

408406
async save(forceEverything = false) {

src/cli.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Vault } from './Vault';
1313
import * as fs from 'node:fs/promises';
1414
import * as path from 'node:path';
1515
import { fileURLToPath } from 'node:url';
16-
import readline from 'readline';
16+
import * as readline from 'readline';
1717

1818
const __filename = fileURLToPath(import.meta.url);
1919
const __dirname = path.dirname(__filename);
@@ -98,20 +98,20 @@ function printUsage(filter?: string) {
9898
}
9999
}
100100

101-
async function promptPassword(prompt = 'Password: ') {
101+
async function promptPassword(prompt = 'Password: '): Promise<string> {
102102
if (!process.stdin.isTTY) {
103103
console.error(`Password prompt requires a TTY`);
104104
process.exit(1);
105105
}
106106
process.stdout.write(prompt + ': ');
107-
return await new Promise((resolve) => {
107+
return await new Promise<string>((resolve) => {
108108
const stdin = process.stdin;
109-
const input = [];
109+
const input: string[] = [];
110110
stdin.setRawMode(true);
111111
stdin.resume();
112112
stdin.setEncoding('utf8');
113113

114-
const onData = (char) => {
114+
const onData = (char: string) => {
115115
if (char === '\r' || char === '\n') {
116116
stdin.setRawMode(false);
117117
stdin.pause();
@@ -275,7 +275,11 @@ async function cmdDump(args: string[]): Promise<number> {
275275
}
276276
for (const file of vault.listFiles()) {
277277
const bytes = await vault.getFile(file);
278-
await fs.writeFile(path.join(dir, file), bytes);
278+
if (!bytes) {
279+
console.warn(`Warning: Failed to decrypt file: ${vault.getPath()}/${file}`);
280+
} else {
281+
await fs.writeFile(path.join(dir, file), bytes);
282+
}
279283
}
280284
};
281285
await walk(destination);

0 commit comments

Comments
 (0)