Refactor codebase
This commit is contained in:
parent
490d289330
commit
0230f32aab
@ -1,2 +1,2 @@
|
|||||||
onepassword_data
|
*.opvault
|
||||||
lib
|
lib
|
131
.eslintrc
131
.eslintrc
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": ["@typescript-eslint"],
|
"plugins": ["@typescript-eslint", "import"],
|
||||||
"env": {
|
"env": {
|
||||||
"node": true,
|
"node": true,
|
||||||
"browser": true
|
"browser": true
|
||||||
@ -9,11 +9,57 @@
|
|||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:react/recommended"
|
"plugin:react/recommended",
|
||||||
|
"plugin:import/errors",
|
||||||
|
"plugin:import/typescript",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"prettier"
|
||||||
],
|
],
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
},
|
||||||
|
"import/parsers": {
|
||||||
|
"@typescript-eslint/parser": [".ts", ".tsx"]
|
||||||
|
},
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"alwaysTryTypes": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/ban-types": "off",
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"disallowTypeAnnotations": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/ban-types": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"extendDefaults": false,
|
||||||
|
"types": {
|
||||||
|
"String": {
|
||||||
|
"message": "Use string instead",
|
||||||
|
"fixWith": "string"
|
||||||
|
},
|
||||||
|
"Number": {
|
||||||
|
"message": "Use number instead",
|
||||||
|
"fixWith": "number"
|
||||||
|
},
|
||||||
|
"Boolean": {
|
||||||
|
"message": "Use boolean instead",
|
||||||
|
"fixWith": "boolean"
|
||||||
|
},
|
||||||
|
"Symbol": {
|
||||||
|
"message": "Use symbol instead",
|
||||||
|
"fixWith": "symbol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
@ -23,22 +69,70 @@
|
|||||||
"@typescript-eslint/no-use-before-define": "off",
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
"@typescript-eslint/triple-slash-reference": "off",
|
"@typescript-eslint/triple-slash-reference": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "off",
|
||||||
"arrow-body-style": ["error", "as-needed"],
|
"arrow-body-style": ["error", "as-needed"],
|
||||||
"class-methods-use-this": ["warn", { "exceptMethods": ["toString"] }],
|
"class-methods-use-this": [
|
||||||
"complexity": ["warn", { "max": 100 }],
|
"warn",
|
||||||
|
{
|
||||||
|
"exceptMethods": ["toString", "shouldComponentUpdate"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"complexity": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
"curly": ["error", "multi-line", "consistent"],
|
"curly": ["error", "multi-line", "consistent"],
|
||||||
"eqeqeq": ["error", "smart"],
|
"eqeqeq": ["error", "smart"],
|
||||||
"no-async-promise-executor": "off",
|
"no-async-promise-executor": "off",
|
||||||
"no-case-declarations": "off",
|
"no-case-declarations": "off",
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"no-constant-condition": [
|
||||||
"no-empty": ["error", { "allowEmptyCatch": true }],
|
"error",
|
||||||
|
{
|
||||||
|
"checkLoops": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-debugger": "off",
|
||||||
|
"no-empty": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowEmptyCatch": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-inner-declarations": "off",
|
"no-inner-declarations": "off",
|
||||||
"no-lonely-if": "error",
|
"no-lonely-if": "error",
|
||||||
|
"no-template-curly-in-string": "error",
|
||||||
"no-var": "error",
|
"no-var": "error",
|
||||||
"object-shorthand": "error",
|
"import/export": "off",
|
||||||
"one-var": ["error", { "var": "never", "let": "never" }],
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"groups": ["builtin", "external"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"object-shorthand": [
|
||||||
|
"error",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"ignoreConstructors": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"one-var": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"var": "never",
|
||||||
|
"let": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
"prefer-arrow-callback": "error",
|
"prefer-arrow-callback": "error",
|
||||||
"prefer-const": ["error", { "destructuring": "all" }],
|
"prefer-const": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"destructuring": "all"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"prefer-destructuring": "warn",
|
||||||
"prefer-object-spread": "error",
|
"prefer-object-spread": "error",
|
||||||
"prefer-rest-params": "warn",
|
"prefer-rest-params": "warn",
|
||||||
"prefer-spread": "warn",
|
"prefer-spread": "warn",
|
||||||
@ -46,7 +140,20 @@
|
|||||||
"react/display-name": "off",
|
"react/display-name": "off",
|
||||||
"react/no-children-prop": "off",
|
"react/no-children-prop": "off",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"spaced-comment": "error",
|
"react/react-in-jsx-scope": "off",
|
||||||
"yoda": ["error", "never", { "exceptRange": true }]
|
"spaced-comment": [
|
||||||
|
"error",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"markers": ["/"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yoda": [
|
||||||
|
"error",
|
||||||
|
"never",
|
||||||
|
{
|
||||||
|
"exceptRange": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
mochawesome-report
|
||||||
lib
|
lib
|
||||||
docs
|
docs
|
||||||
ref
|
ref
|
||||||
|
7
.mocharc.json
Normal file
7
.mocharc.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/mocharc",
|
||||||
|
"reporter": "mochawesome",
|
||||||
|
"extension": [".ts"],
|
||||||
|
"require": ["ts-node/register", "tsconfig-paths/register", "./test/before.ts"],
|
||||||
|
"timeout": 10000
|
||||||
|
}
|
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
// Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
|
||||||
|
// Pointez pour afficher la description des attributs existants.
|
||||||
|
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "REPL",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"program": "repl.ts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Mocha",
|
||||||
|
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||||
|
"args": ["test/**/*.test.ts"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"protocol": "inspector"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@ -1,3 +1,12 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"cSpell.ignorePaths": [
|
||||||
|
"**/package-lock.json",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/vscode-extension/**",
|
||||||
|
"**/.git/objects/**",
|
||||||
|
".vscode",
|
||||||
|
".vscode-insiders",
|
||||||
|
"i18n.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
# opvault.js
|
# opvault.js
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
wget -qO- https://cache.agilebits.com/security-kb/freddy-2013-12-04.tar.gz | tar xvz -
|
||||||
|
mv onepassword_data freddy-2013-12-04.opvault
|
||||||
|
pnpm run test
|
||||||
|
```
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
### Reporting Security Issues
|
### Reporting Security Issues
|
||||||
|
15
package.json
15
package.json
@ -7,27 +7,38 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c; prettier --write lib >/dev/null",
|
"build": "rollup -c; prettier --write lib >/dev/null",
|
||||||
"build:docs": "typedoc --out docs src/index.ts --excludePrivate",
|
"build:docs": "typedoc --out docs src/index.ts --excludePrivate",
|
||||||
|
"test": "mocha test/**/*.test.ts",
|
||||||
"repl": "node -r ts-node/register/transpile-only src/repl.ts"
|
"repl": "node -r ts-node/register/transpile-only src/repl.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@types/chai": "^4.2.19",
|
"@types/chai": "^4.2.19",
|
||||||
|
"@types/chai-as-promised": "^7.1.4",
|
||||||
"@types/fs-extra": "^9.0.11",
|
"@types/fs-extra": "^9.0.11",
|
||||||
"@types/mocha": "github:whitecolor/mocha-types#da22474cf43f48a56c86f8c23a5a0ea36e295768",
|
"@types/mocha": "github:whitecolor/mocha-types#da22474cf43f48a56c86f8c23a5a0ea36e295768",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.0.0",
|
||||||
"@types/prompts": "^2.0.13",
|
"@types/prompts": "^2.0.13",
|
||||||
"@typescript-eslint/eslint-plugin": "4.28.1",
|
"@typescript-eslint/eslint-plugin": "4.28.2",
|
||||||
"@typescript-eslint/parser": "4.28.1",
|
"@typescript-eslint/parser": "4.28.2",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"eslint": "7.30.0",
|
"eslint": "7.30.0",
|
||||||
|
"eslint-config-prettier": "8.3.0",
|
||||||
|
"eslint-import-resolver-typescript": "2.4.0",
|
||||||
|
"eslint-plugin-import": "2.23.4",
|
||||||
"eslint-plugin-react": "7.24.0",
|
"eslint-plugin-react": "7.24.0",
|
||||||
|
"eslint-plugin-react-hooks": "4.2.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
|
"memfs": "^3.2.2",
|
||||||
"mocha": "^9.0.2",
|
"mocha": "^9.0.2",
|
||||||
|
"mochawesome": "^6.2.2",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"prompts": "^2.4.1",
|
"prompts": "^2.4.1",
|
||||||
"rollup": "^2.52.7",
|
"rollup": "^2.52.7",
|
||||||
"rollup-plugin-ts": "^1.4.0",
|
"rollup-plugin-ts": "^1.4.0",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
|
"tsconfig-paths": "^3.9.0",
|
||||||
"typedoc": "^0.21.2",
|
"typedoc": "^0.21.2",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
|
731
pnpm-lock.yaml
generated
731
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
repl.ts
1
repl.ts
@ -24,6 +24,7 @@ async function main(args: string[]) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
vault.unlock(password)
|
vault.unlock(password)
|
||||||
|
console.log(vault.overviews.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
main(process.argv.slice(2))
|
main(process.argv.slice(2))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import ts from "rollup-plugin-ts"
|
|
||||||
import { builtinModules } from "module"
|
import { builtinModules } from "module"
|
||||||
|
import ts from "rollup-plugin-ts"
|
||||||
|
import json from "@rollup/plugin-json"
|
||||||
import { dependencies } from "./package.json"
|
import { dependencies } from "./package.json"
|
||||||
|
|
||||||
/** @returns {import("rollup").RollupOptions} */
|
/** @returns {import("rollup").RollupOptions} */
|
||||||
@ -10,5 +11,5 @@ export default () => ({
|
|||||||
file: "lib/index.js",
|
file: "lib/index.js",
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
},
|
},
|
||||||
plugins: [ts()],
|
plugins: [ts(), json()],
|
||||||
})
|
})
|
||||||
|
26
src/i18n/index.ts
Normal file
26
src/i18n/index.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import json from "./res.json"
|
||||||
|
|
||||||
|
const locale =
|
||||||
|
process.env.LOCALE || Intl.DateTimeFormat().resolvedOptions().locale.split("-")[0]
|
||||||
|
|
||||||
|
const mapValue = <T, R>(
|
||||||
|
object: Record<string, T>,
|
||||||
|
fn: (value: T, key: string) => R
|
||||||
|
): Record<string, R> => {
|
||||||
|
const res = Object.create(null)
|
||||||
|
Object.entries(object).forEach(([key, value]) => {
|
||||||
|
res[key] = fn(value, key)
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type json = typeof json
|
||||||
|
type i18n = {
|
||||||
|
[K in keyof json]: {
|
||||||
|
[L in keyof json[K]]: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const i18n: i18n = mapValue(json, dict =>
|
||||||
|
mapValue(dict, (value: any) => value[locale] ?? value.en)
|
||||||
|
) as any
|
16
src/i18n/res.json
Normal file
16
src/i18n/res.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"invalidPassword": {
|
||||||
|
"en": "Invalid password",
|
||||||
|
"fr": "Mot de passe invalide"
|
||||||
|
},
|
||||||
|
"vaultIsLocked": {
|
||||||
|
"en": "This vault is locked",
|
||||||
|
"fr": "Ce coffre est verrouillé."
|
||||||
|
},
|
||||||
|
"cannotDecryptOverviewItem": {
|
||||||
|
"en": "Failed to decrypt overview item",
|
||||||
|
"fr": "Impossible de déchiffrer cet aperçu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { resolve } from "path"
|
import { resolve } from "path"
|
||||||
import { getDefaultFileSystem, IFileSystem } from "./fs"
|
import type { IFileSystem } from "./fs"
|
||||||
|
import { getDefaultFileSystem } from "./fs"
|
||||||
import { Vault } from "./vault"
|
import { Vault } from "./vault"
|
||||||
|
|
||||||
export type { Vault } from "./vault"
|
export type { Vault } from "./vault"
|
||||||
|
@ -17,7 +17,7 @@ export enum Category {
|
|||||||
Rewards = 107,
|
Rewards = 107,
|
||||||
SSN = 108,
|
SSN = 108,
|
||||||
Router = 109,
|
Router = 109,
|
||||||
Server = 100,
|
Server = 110,
|
||||||
Email = 111,
|
Email = 111,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
src/vault.ts
69
src/vault.ts
@ -1,5 +1,7 @@
|
|||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import { IFileSystem, OnePasswordFileManager } from "./fs"
|
import type { IFileSystem } from "./fs"
|
||||||
|
import { OnePasswordFileManager } from "./fs"
|
||||||
|
import { i18n } from "./i18n"
|
||||||
|
|
||||||
import type { Profile, Band, Overview, EncryptedItem, Item } from "./types"
|
import type { Profile, Band, Overview, EncryptedItem, Item } from "./types"
|
||||||
|
|
||||||
@ -10,8 +12,8 @@ type FoldersMap = { [uuid: string]: Band }
|
|||||||
*/
|
*/
|
||||||
export class Vault {
|
export class Vault {
|
||||||
// Ciphers
|
// Ciphers
|
||||||
#master: Cipher
|
#master?: Cipher
|
||||||
#overview: Cipher
|
#overview?: Cipher
|
||||||
|
|
||||||
// File system interface
|
// File system interface
|
||||||
#files: OnePasswordFileManager
|
#files: OnePasswordFileManager
|
||||||
@ -35,10 +37,6 @@ export class Vault {
|
|||||||
this.#profile = profile
|
this.#profile = profile
|
||||||
this.#folders = folders
|
this.#folders = folders
|
||||||
this.#bands = bands
|
this.#bands = bands
|
||||||
|
|
||||||
this.#files
|
|
||||||
this.#folders
|
|
||||||
this.#items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,6 +57,24 @@ export class Vault {
|
|||||||
return new Vault(files, profile, folders, bands)
|
return new Vault(files, profile, folders, bands)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly overviews = Object.freeze({
|
||||||
|
get: (condition: string | ((overview: Overview) => boolean)) => {
|
||||||
|
this.#assertUnlocked()
|
||||||
|
|
||||||
|
if (typeof condition === "string") {
|
||||||
|
const title = condition
|
||||||
|
condition = overview => overview.title === title
|
||||||
|
}
|
||||||
|
for (const value of this.#overviews.values()) {
|
||||||
|
if (condition(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
values: () => Array.from(this.#overviews.values()),
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock this OnePassword vault.
|
* Unlock this OnePassword vault.
|
||||||
* @param masterPassword User provided master password. Only the derived
|
* @param masterPassword User provided master password. Only the derived
|
||||||
@ -82,10 +98,13 @@ export class Vault {
|
|||||||
this.#overview = decryptKeys(profile.overviewKey, cipher)
|
this.#overview = decryptKeys(profile.overviewKey, cipher)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof HMACAssertionError) {
|
if (e instanceof HMACAssertionError) {
|
||||||
throw new Error("Invalid password.")
|
throw new Error(i18n.error.invalidPassword)
|
||||||
}
|
}
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#readOverviews()
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,10 +114,17 @@ export class Vault {
|
|||||||
this.#master = null!
|
this.#master = null!
|
||||||
this.#overview = null!
|
this.#overview = null!
|
||||||
this.#overviews.clear()
|
this.#overviews.clear()
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLocked() {
|
get isLocked() {
|
||||||
return Boolean(this.#master.key)
|
return this.#master?.key == null
|
||||||
|
}
|
||||||
|
|
||||||
|
#assertUnlocked() {
|
||||||
|
if (this.isLocked) {
|
||||||
|
throw new Error(i18n.error.vaultIsLocked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getOverview(title: string): Overview
|
getOverview(title: string): Overview
|
||||||
@ -117,36 +143,38 @@ export class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getItem(uuid: string) {
|
getItem(uuid: string) {
|
||||||
|
this.#assertUnlocked()
|
||||||
const encrypted = uuid ? this.#bands.get(uuid[0])![uuid] : undefined
|
const encrypted = uuid ? this.#bands.get(uuid[0])![uuid] : undefined
|
||||||
return encrypted && this.decryptItem(encrypted)
|
return encrypted && decryptItem(encrypted, this.#master!)
|
||||||
}
|
}
|
||||||
|
|
||||||
readItems() {
|
#readOverviews() {
|
||||||
|
this.#assertUnlocked()
|
||||||
this.#bands.forEach(value => {
|
this.#bands.forEach(value => {
|
||||||
for (const [uuid, item] of Object.entries(value)) {
|
for (const [uuid, item] of Object.entries(value)) {
|
||||||
const overview = this.decryptOverview(item)
|
const overview = decryptOverview(item, this.#overview!)
|
||||||
overview.uuid = uuid
|
overview.uuid = uuid
|
||||||
this.#overviews.set(uuid, overview)
|
this.#overviews.set(uuid, overview)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return this
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private decryptOverview(item: EncryptedItem) {
|
function decryptOverview(item: EncryptedItem, overviewCipher: Cipher) {
|
||||||
try {
|
try {
|
||||||
const overview = decryptOPData(toBuffer(item.o), this.#overview)
|
const overview = decryptOPData(toBuffer(item.o), overviewCipher)
|
||||||
return JSON.parse(overview.toString("utf8")) as Overview
|
return JSON.parse(overview.toString("utf8")) as Overview
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to decrypt overview item.")
|
console.error(i18n.error.cannotDecryptOverviewItem)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private decryptItem(item: EncryptedItem): Item {
|
function decryptItem(item: EncryptedItem, master: Cipher): Item {
|
||||||
const k = toBuffer(item.k)
|
const k = toBuffer(item.k)
|
||||||
const data = k.slice(0, -32)
|
const data = k.slice(0, -32)
|
||||||
assertHMac(data, this.#master.hmac, k.slice(-32))
|
assertHMac(data, master.hmac, k.slice(-32))
|
||||||
const derivedKey = decryptData(this.#master.key, data.slice(0, 16), data.slice(16))
|
const derivedKey = decryptData(master.key, data.slice(0, 16), data.slice(16))
|
||||||
|
|
||||||
const detail = decryptOPData(
|
const detail = decryptOPData(
|
||||||
/* cipherText */ toBuffer(item.d),
|
/* cipherText */ toBuffer(item.d),
|
||||||
@ -154,7 +182,6 @@ export class Vault {
|
|||||||
)
|
)
|
||||||
return JSON.parse(detail.toString("utf-8"))
|
return JSON.parse(detail.toString("utf-8"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Encryption and MAC */
|
/** Encryption and MAC */
|
||||||
interface Cipher {
|
interface Cipher {
|
||||||
@ -189,7 +216,7 @@ function decryptOPData(cipherText: Buffer, cipher: Cipher) {
|
|||||||
function assertHMac(data: Buffer, key: Buffer, expected: Buffer) {
|
function assertHMac(data: Buffer, key: Buffer, expected: Buffer) {
|
||||||
const actual = crypto.createHmac("sha256", key).update(data).digest()
|
const actual = crypto.createHmac("sha256", key).update(data).digest()
|
||||||
if (!actual.equals(expected)) {
|
if (!actual.equals(expected)) {
|
||||||
throw new HMACAssertionError("HMAC assertion failed.")
|
throw new HMACAssertionError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
test/.prettierrc
Normal file
3
test/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"semi": true
|
||||||
|
}
|
5
test/before.ts
Normal file
5
test/before.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import chai from "chai";
|
||||||
|
import chaiAsPromised from "chai-as-promised";
|
||||||
|
|
||||||
|
process.env.LOCALE = "en";
|
||||||
|
chai.use(chaiAsPromised);
|
42
test/profile.test.ts
Normal file
42
test/profile.test.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { describe, it, beforeEach } from "mocha";
|
||||||
|
import { expect } from "chai";
|
||||||
|
// import { fs, vol } from "memfs"
|
||||||
|
|
||||||
|
import type { Vault } from "../src/index";
|
||||||
|
import { OnePassword } from "../src/index";
|
||||||
|
|
||||||
|
describe("OnePassword", () => {
|
||||||
|
const freddy = resolve(__dirname, "../freddy-2013-12-04.opvault");
|
||||||
|
|
||||||
|
describe("getProfileNames", () => {
|
||||||
|
it("freddy", async () => {
|
||||||
|
const instance = new OnePassword({ path: freddy });
|
||||||
|
expect(await instance.getProfileNames()).to.deep.equal(["default"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip("ignores faulty folders", async () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("unlock", () => {
|
||||||
|
let vault: Vault;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vault = await new OnePassword({ path: freddy }).getProfile("default");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts correct password", () => {
|
||||||
|
expect(() => vault.unlock("freddy")).to.not.throw();
|
||||||
|
expect(vault.isLocked).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects wrong password", () => {
|
||||||
|
["Freddy", "_freddy", ""].forEach((password) => {
|
||||||
|
expect(() => vault.unlock(password)).to.throw("Invalid password");
|
||||||
|
expect(vault.isLocked).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("lock", () => {});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user