autho 0.0.3 → 0.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autho",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/bin.js CHANGED
@@ -3,42 +3,38 @@
3
3
  import { Command } from "commander";
4
4
  import chalk from "chalk";
5
5
  import { prompt, ask } from "./utils.js";
6
- import Config from "../sdk/config.js";
6
+ import DB from "../sdk/db.js";
7
7
  import Cipher from "../sdk/cipher.js";
8
8
  import createSecret from "./wizards/createSecret.js";
9
+ import getEncryptionKey from "./wizards/getEncryptionKey.js";
10
+ import getSecret from "./wizards/getSecret.js";
9
11
  import Secrets from "../sdk/secrets.js";
10
12
  import OTP from "../sdk/otp.js";
11
13
 
12
14
  const program = new Command();
13
15
 
14
- const getSecret = async (config) => {
15
- const existingSecrets = config.get("secrets", []);
16
+ //type AppOptions = {
17
+ // masterPasswordHash?: string;
18
+ // masterPassword?: string;
19
+ // appFolder?: string;
20
+ // };
16
21
 
17
- if (existingSecrets.length === 0) {
18
- throw new Error("No secrets found");
19
- }
20
-
21
- const choices = existingSecrets.map((secret) => ({
22
- value: secret.id,
23
- name: `${secret.name} (${secret.typeOptions.username || secret.id})`,
24
- }));
25
-
26
- const { id: secretId } = await prompt({
27
- name: "id",
28
- message: "Secrets:",
29
- type: "list",
30
- choices,
31
- required: true,
32
- });
33
- const secrets = new Secrets(config);
34
- const secret = await secrets.get(secretId);
35
-
36
- if (!secret) {
37
- throw new Error("Secret not found");
22
+ class App {
23
+ constructor(options = {}) {
24
+ let masterPasswordHash =
25
+ options.masterPasswordHash || process.env.AUTHO_MASTER_PASSWORD_HASH;
26
+ const masterPassword = options.masterPassword || process.env.AUTHO_MASTER_PASSWORD;
27
+ if (!masterPasswordHash && !masterPassword) {
28
+ throw new Error("Master password or master password hash is required")
29
+ }
30
+ masterPasswordHash =
31
+ masterPasswordHash ||
32
+ Cipher.hash(masterPassword);
33
+ this.db = new DB({ encryptionKey: masterPasswordHash });
34
+ this.secrets = new Secrets(this);
38
35
  }
39
36
 
40
- return secret;
41
- };
37
+ }
42
38
 
43
39
  program
44
40
  .name("autho")
@@ -55,8 +51,8 @@ program
55
51
  type: "password",
56
52
  required: true,
57
53
  });
58
- const masterPasswordHash = Cipher.hash(masterPassword);
59
- const config = new Config({ encryptionKey: masterPasswordHash });
54
+
55
+ const app = new App({ masterPassword });
60
56
 
61
57
  let choices = [
62
58
  { value: "create", name: "Create new secret" },
@@ -70,15 +66,25 @@ program
70
66
  choices,
71
67
  required: true,
72
68
  });
69
+
73
70
  switch (action) {
74
71
  case "create":
75
- await createSecret(config, masterPasswordHash);
72
+ await createSecret(app);
76
73
  break;
77
74
 
78
75
  case "read":
79
- const readSecret = await getSecret(config);
80
- readSecret.value = Cipher.decrypt(readSecret.value, readSecret.publicKey, masterPasswordHash)
76
+ const readSecret = await getSecret(app);
77
+ let encryptionKey = app.db.encryptionKey
78
+ if (readSecret.protected) {
79
+ encryptionKey = await getEncryptionKey()
80
+ }
81
81
 
82
+ readSecret.value = Cipher.decrypt(
83
+ readSecret.value,
84
+ readSecret.publicKey,
85
+ encryptionKey,
86
+ readSecret.signature
87
+ );
82
88
  switch (readSecret.type) {
83
89
  case "password":
84
90
  console.log("Username:", readSecret.typeOptions.username);
@@ -99,12 +105,13 @@ program
99
105
 
100
106
  break;
101
107
  case "delete":
102
- const deleteSecret = await getSecret(config);
103
- const secrets = new Secrets(config);
104
- await secrets.remove(deleteSecret.id)
108
+ const deleteSecret = await getSecret(app);
109
+ await app.secrets.remove(deleteSecret.id);
105
110
  console.log("Removed");
106
111
  process.exit(0);
107
- break;
112
+ default:
113
+ console.log("Unknown action:", action);
114
+ process.exit(1);
108
115
  }
109
116
  } catch (error) {
110
117
  console.log(
@@ -1,7 +1,8 @@
1
1
  import { prompt } from "../utils.js";
2
- import Secrets from "../../sdk/secrets.js";
2
+ import getEncryptionKey from "./getEncryptionKey.js";
3
3
 
4
- const wizard = async (config, masterPasswordHash) => {
4
+ const wizard = async (app) => {
5
+ const secrets = app.secrets;
5
6
  const info = await prompt([
6
7
  {
7
8
  name: "name",
@@ -16,10 +17,22 @@ const wizard = async (config, masterPasswordHash) => {
16
17
  default: "password",
17
18
  choices: ["password", "otp", "note"],
18
19
  required: true,
20
+ },
21
+ {
22
+ name: "protected",
23
+ message: "protected:",
24
+ type: "confirm",
25
+ default: false,
26
+ required: true,
19
27
  }
20
28
  ]);
21
29
 
22
30
  let newSecret = {};
31
+ let encryptionKey = app.db.encryptionKey
32
+
33
+ if (info.protected) {
34
+ encryptionKey = await getEncryptionKey(true)
35
+ }
23
36
 
24
37
  switch (info.type) {
25
38
  case "password":
@@ -38,8 +51,7 @@ const wizard = async (config, masterPasswordHash) => {
38
51
  },
39
52
  ]);
40
53
  newSecret = {
41
- name: info.name,
42
- type: info.type,
54
+ ...info,
43
55
  value: password.value,
44
56
  typeOptions: {
45
57
  username: password.username,
@@ -56,8 +68,7 @@ const wizard = async (config, masterPasswordHash) => {
56
68
  },
57
69
  ]);
58
70
  newSecret = {
59
- name: info.name,
60
- type: info.type,
71
+ ...info,
61
72
  value: note.value,
62
73
  typeOptions: {
63
74
 
@@ -80,8 +91,7 @@ const wizard = async (config, masterPasswordHash) => {
80
91
  },
81
92
  ]);
82
93
  newSecret = {
83
- name: info.name,
84
- type: info.type,
94
+ ...info,
85
95
  value: otp.value,
86
96
  typeOptions: {
87
97
  username: otp.username,
@@ -90,8 +100,7 @@ const wizard = async (config, masterPasswordHash) => {
90
100
  break;
91
101
  }
92
102
 
93
- const secrets = new Secrets(config);
94
- await secrets.add(newSecret, masterPasswordHash);
103
+ await secrets.add(newSecret, encryptionKey);
95
104
  };
96
105
 
97
106
  export default wizard;
@@ -0,0 +1,30 @@
1
+ import Cipher from "../../sdk/cipher.js";
2
+ import { prompt } from "../utils.js";
3
+
4
+ const wizard = async (confirm = false) => {
5
+ const questions = [{
6
+ name: "password",
7
+ message: "password:",
8
+ type: "password",
9
+ required: true,
10
+ }
11
+ ]
12
+ if (confirm) {
13
+ questions.push({
14
+ name: "confirmPassword",
15
+ message: "confirm password:",
16
+ type: "password",
17
+ required: true,
18
+ });
19
+ }
20
+ const input = await prompt(questions);
21
+
22
+ if (input.confirmPassword && input.password !== input.confirmPassword) {
23
+ throw new Error("Passwords do not match");
24
+ }
25
+
26
+ return Cipher.hash(input.password);
27
+ }
28
+
29
+
30
+ export default wizard;
@@ -0,0 +1,31 @@
1
+ import { prompt } from "../utils.js";
2
+
3
+ const wizard = async (app) => {
4
+ const existingSecrets = app.db.get("secrets", []);
5
+
6
+ if (existingSecrets.length === 0) {
7
+ throw new Error("No secrets found");
8
+ }
9
+
10
+ const choices = existingSecrets.map((secret) => ({
11
+ value: secret.id,
12
+ name: `${secret.name} (${secret.typeOptions.username || secret.id})`,
13
+ }));
14
+
15
+ const { id: secretId } = await prompt({
16
+ name: "id",
17
+ message: "Secrets:",
18
+ type: "list",
19
+ choices,
20
+ required: true,
21
+ });
22
+ const secret = await app.secrets.get(secretId);
23
+
24
+ if (!secret) {
25
+ throw new Error("Secret not found");
26
+ }
27
+
28
+ return secret;
29
+ };
30
+
31
+ export default wizard;
package/src/sdk/cipher.js CHANGED
@@ -1,19 +1,15 @@
1
1
  import crypto from "crypto";
2
2
 
3
- const algorithm = "aes-256-ctr";
4
- const IV_LENGTH = 16;
5
-
6
3
  export default class Cipher {
7
- constructor() { }
8
4
 
9
- static hash(text) {
10
- const hash = crypto.createHash("sha256");
5
+ static hash(text, algorithm = "sha256") {
6
+ const hash = crypto.createHash(algorithm);
11
7
  hash.update(text);
12
8
 
13
9
  return hash.digest("hex");
14
10
  }
15
11
 
16
- static random(size = IV_LENGTH) {
12
+ static random(size = 16) {
17
13
  const rnd = crypto.randomBytes(size);
18
14
 
19
15
  return rnd;
@@ -25,30 +21,50 @@ export default class Cipher {
25
21
  return rnd;
26
22
  }
27
23
 
28
- static encrypt(text, password) {
24
+ static sign(text) {
25
+ const hash = Cipher.hash(text)
26
+ const signature = `${hash.substring(0, 10)}:${hash.substring(hash.length - 10)}`;
27
+
28
+ return signature;
29
+ }
30
+
31
+ static verify(text, signature) {
32
+ const expectedSignature = Cipher.sign(text)
33
+
34
+ return expectedSignature === signature;
35
+ }
36
+
37
+ static encrypt(text, encryptionKey, algorithm = "aes-256-ctr") {
29
38
  const publicKey = Cipher.randomString();
30
39
  let cipher = crypto.createCipheriv(
31
40
  algorithm,
32
- Buffer.from(password, "hex"),
41
+ Buffer.from(encryptionKey, "hex"),
33
42
  Buffer.from(publicKey, "hex"),
34
43
  );
35
44
  let encrypted = cipher.update(text);
36
45
  encrypted = Buffer.concat([encrypted, cipher.final()]);
37
46
  encrypted = encrypted.toString("hex");
38
47
 
39
- return { publicKey, encrypted };
48
+ return { publicKey, encrypted, algorithm, signature: Cipher.sign(text) };
40
49
  }
41
50
 
42
- static decrypt(encryptedText, publicKey, password) {
51
+ static decrypt(encryptedText, publicKey, encryptionKey, signature, algorithm = "aes-256-ctr") {
43
52
  encryptedText = Buffer.from(encryptedText, "hex");
53
+
44
54
  let decipher = crypto.createDecipheriv(
45
55
  algorithm,
46
- Buffer.from(password, "hex"),
56
+ Buffer.from(encryptionKey, "hex"),
47
57
  Buffer.from(publicKey, "hex")
48
58
  );
59
+
49
60
  let decrypted = decipher.update(encryptedText);
50
61
  decrypted = Buffer.concat([decrypted, decipher.final()]);
62
+ decrypted = decrypted.toString();
63
+
64
+ if (!Cipher.verify(decrypted, signature)) {
65
+ throw new Error("Invalid signature")
66
+ }
51
67
 
52
- return decrypted.toString();
68
+ return decrypted;
53
69
  }
54
70
  }
@@ -2,20 +2,21 @@ import Conf from "conf";
2
2
 
3
3
  const { AUTHO_ENCRYPTION_KEY = "", AUTHO_NAME = 'default' } = process.env;
4
4
 
5
- export default class Config {
5
+ export default class DB {
6
6
  constructor({
7
7
  encryptionKey = AUTHO_ENCRYPTION_KEY,
8
8
  configName = AUTHO_NAME
9
9
  }) {
10
- this.config = new Conf({ projectName: "autho", encryptionKey, configName });
10
+ this.encryptionKey = encryptionKey;
11
+ this.client = new Conf({ projectName: "autho", encryptionKey, configName });
11
12
  }
12
13
 
13
14
  get(key, defaultValue) {
14
- return this.config.get(key, defaultValue);
15
+ return this.client.get(key, defaultValue);
15
16
  }
16
17
 
17
18
  set(key, value) {
18
- this.config.set(key, value);
19
+ this.client.set(key, value);
19
20
  }
20
21
 
21
22
  }
@@ -1,34 +1,32 @@
1
1
  import { createSecretSchema } from "../models/Secret.js";
2
- import Cipher from "./cipher.js";
2
+ import Cipher from "../sdk/cipher.js";
3
3
 
4
4
  export default class Secrets {
5
- constructor(config) {
6
- this.config = config;
5
+ constructor(app) {
6
+ this.db = app.db;
7
7
  }
8
8
 
9
9
  get secrets() {
10
- return this.config.get("secrets", []);
10
+ return this.db.get("secrets", []);
11
11
  }
12
12
 
13
13
  set secrets(value) {
14
- this.config.set("secrets", value);
14
+ this.db.set("secrets", value);
15
15
  }
16
16
 
17
17
  async get(id) {
18
18
  return this.secrets.find((secret) => secret.id == id);
19
19
  }
20
20
 
21
- async add(secret, password) {
21
+ async add(secret, encryptionKey) {
22
22
  const { value, error } = createSecretSchema.validate(secret);
23
23
  if (error) {
24
24
  throw new Error(error);
25
25
  }
26
26
 
27
- const { publicKey, encrypted } = Cipher.encrypt(value.value, password);
28
- value.value = encrypted;
29
- value.publicKey = publicKey;
27
+ const { encrypted, ...encryption } = Cipher.encrypt(value.value, encryptionKey);
30
28
 
31
- this.secrets = [...this.secrets, value];
29
+ this.secrets = [...this.secrets, { ...value, ...encryption, value: encrypted }];
32
30
  }
33
31
 
34
32
  async remove(id) {