autho 0.0.2 → 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,10 +1,11 @@
1
1
  {
2
2
  "name": "autho",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
- "bin":{
7
- "autho": "./src/cli/bin.js"},
6
+ "bin": {
7
+ "autho": "src/cli/bin.js"
8
+ },
8
9
  "scripts": {
9
10
  "test": "echo \"Error: no test specified\" && exit 1"
10
11
  },
package/src/cli/bin.js CHANGED
@@ -3,38 +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
- const choices = existingSecrets.map((secret) => ({
17
- value: secret.id,
18
- name: secret.name,
19
- }));
16
+ //type AppOptions = {
17
+ // masterPasswordHash?: string;
18
+ // masterPassword?: string;
19
+ // appFolder?: string;
20
+ // };
20
21
 
21
- const { id: secretId } = await prompt({
22
- name: "id",
23
- message: "Secrets:",
24
- type: "list",
25
- choices,
26
- required: true,
27
- });
28
-
29
- const secrets = new Secrets(config);
30
- const secret = await secrets.get(secretId);
31
-
32
- if (!secret) {
33
- 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);
34
35
  }
35
36
 
36
- return secret;
37
- };
37
+ }
38
38
 
39
39
  program
40
40
  .name("autho")
@@ -46,19 +46,13 @@ program
46
46
  const masterPassword = args.password
47
47
  ? args.password
48
48
  : await ask({
49
- name: "masterPassword",
50
- message: "Password:",
51
- type: "password",
52
- required: true,
53
- });
54
- const masterPasswordHash = Cipher.hash(masterPassword);
55
- const config = new Config({ encryptionKey: masterPasswordHash });
56
- let salt = config.get();
49
+ name: "masterPassword",
50
+ message: "Password:",
51
+ type: "password",
52
+ required: true,
53
+ });
57
54
 
58
- if (!salt) {
59
- salt = Cipher.random();
60
- config.set("salt", salt);
61
- }
55
+ const app = new App({ masterPassword });
62
56
 
63
57
  let choices = [
64
58
  { value: "create", name: "Create new secret" },
@@ -72,31 +66,52 @@ program
72
66
  choices,
73
67
  required: true,
74
68
  });
69
+
75
70
  switch (action) {
76
71
  case "create":
77
- await createSecret(config, masterPasswordHash);
72
+ await createSecret(app);
78
73
  break;
79
74
 
80
75
  case "read":
81
- const readSecret = await getSecret(config);
82
-
83
- switch (readSecret.type) {
84
- case "otp":
85
- const otp = new OTP(config, readSecret, masterPasswordHash);
86
- console.log(otp.generate());
87
- setTimeout(() => {
88
- console.log("Expired");
89
- process.exit(0);
90
- }, 30000);
91
- break;
92
- }
93
-
76
+ const readSecret = await getSecret(app);
77
+ let encryptionKey = app.db.encryptionKey
78
+ if (readSecret.protected) {
79
+ encryptionKey = await getEncryptionKey()
80
+ }
81
+
82
+ readSecret.value = Cipher.decrypt(
83
+ readSecret.value,
84
+ readSecret.publicKey,
85
+ encryptionKey,
86
+ readSecret.signature
87
+ );
88
+ switch (readSecret.type) {
89
+ case "password":
90
+ console.log("Username:", readSecret.typeOptions.username);
91
+ console.log("Password:", readSecret.value);
92
+ break;
93
+ case "note":
94
+ console.log("Note:", readSecret.value);
95
+ break;
96
+ case "otp":
97
+ const otp = new OTP(readSecret);
98
+ console.log("OTP code:", otp.generate());
99
+ setTimeout(() => {
100
+ console.log("Expired");
101
+ process.exit(0);
102
+ }, 30000);
103
+ break;
104
+ }
105
+
94
106
  break;
95
107
  case "delete":
96
- const deleteSecret = getSecret(config);
97
- const secrets = new Secrets(config);
98
- secrets.remove(deleteSecret.id)
99
- break;
108
+ const deleteSecret = await getSecret(app);
109
+ await app.secrets.remove(deleteSecret.id);
110
+ console.log("Removed");
111
+ process.exit(0);
112
+ default:
113
+ console.log("Unknown action:", action);
114
+ process.exit(1);
100
115
  }
101
116
  } catch (error) {
102
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",
@@ -12,44 +13,94 @@ const wizard = async (config, masterPasswordHash) => {
12
13
  {
13
14
  name: "type",
14
15
  message: "type:",
15
- type: "choice",
16
- default: "otp",
17
- choices: ["otp"],
16
+ type: "list",
17
+ default: "password",
18
+ choices: ["password", "otp", "note"],
19
+ required: true,
20
+ },
21
+ {
22
+ name: "protected",
23
+ message: "protected:",
24
+ type: "confirm",
25
+ default: false,
18
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) {
38
+ case "password":
39
+ const password = await prompt([
40
+ {
41
+ name: "username",
42
+ message: "username:",
43
+ type: "input",
44
+ required: true,
45
+ },
46
+ {
47
+ name: "value",
48
+ message: "password:",
49
+ type: "password",
50
+ required: true,
51
+ },
52
+ ]);
53
+ newSecret = {
54
+ ...info,
55
+ value: password.value,
56
+ typeOptions: {
57
+ username: password.username,
58
+ },
59
+ };
60
+ break;
61
+ case "note":
62
+ const note = await prompt([
63
+ {
64
+ name: "value",
65
+ message: "note:",
66
+ type: "password",
67
+ required: true,
68
+ },
69
+ ]);
70
+ newSecret = {
71
+ ...info,
72
+ value: note.value,
73
+ typeOptions: {
74
+
75
+ },
76
+ };
77
+ break;
25
78
  case "otp":
26
- const secret = await prompt([
27
- {
28
- name: "username",
29
- message: "username:",
30
- type: "input",
31
- required: true,
32
- },
33
- {
34
- name: "value",
35
- message: "value:",
36
- type: "password",
37
- required: true,
38
- },
39
- ]);
40
- newSecret = {
41
- name: info.name,
42
- type: info.type,
43
- value: secret.value,
44
- typeOptions: {
45
- username: secret.username,
46
- },
47
- };
79
+ const otp = await prompt([
80
+ {
81
+ name: "username",
82
+ message: "username:",
83
+ type: "input",
84
+ required: true,
85
+ },
86
+ {
87
+ name: "value",
88
+ message: "value:",
89
+ type: "password",
90
+ required: true,
91
+ },
92
+ ]);
93
+ newSecret = {
94
+ ...info,
95
+ value: otp.value,
96
+ typeOptions: {
97
+ username: otp.username,
98
+ },
99
+ };
48
100
  break;
49
101
  }
50
102
 
51
- const secrets = new Secrets(config);
52
- await secrets.add(newSecret, masterPasswordHash);
103
+ await secrets.add(newSecret, encryptionKey);
53
104
  };
54
105
 
55
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;
@@ -2,10 +2,10 @@ import Joi from 'joi';
2
2
  import Cipher from "../sdk/cipher.js";
3
3
 
4
4
  export const createSecretSchema = Joi.object({
5
- id: Joi.string().default(Cipher.random()),
5
+ id: Joi.string().default(Cipher.randomString()),
6
6
  protected: Joi.boolean().default(false), // ask for password
7
7
  name: Joi.string().required(),
8
- type: Joi.string().required().valid('otp'),
8
+ type: Joi.string().required().valid('otp', 'password', 'note'),
9
9
  value: Joi.string().required(),
10
10
  typeOptions: Joi.object().default({}),
11
11
  createdAt: Joi.date().default(new Date()),
package/src/sdk/cipher.js CHANGED
@@ -1,49 +1,69 @@
1
1
  import crypto from "crypto";
2
2
 
3
3
  export default class Cipher {
4
- constructor(config) {
5
- this.config = config;
6
- this.salt = config.get("salt", "");
7
- }
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() {
17
- const rnd = crypto.randomBytes(16).toString("hex");
12
+ static random(size = 16) {
13
+ const rnd = crypto.randomBytes(size);
14
+
15
+ return rnd;
16
+ }
17
+
18
+ static randomString() {
19
+ const rnd = Cipher.random().toString("hex");
18
20
 
19
21
  return rnd;
20
22
  }
21
23
 
22
- static createKey(salt="") {
23
- const rnd = Cipher.random(salt)
24
+ static sign(text) {
25
+ const hash = Cipher.hash(text)
26
+ const signature = `${hash.substring(0, 10)}:${hash.substring(hash.length - 10)}`;
24
27
 
25
- return Cipher.hash(salt+rnd);
28
+ return signature;
26
29
  }
27
30
 
28
- encrypt(text, password) {
29
- const publicKey = Cipher.createKey(this.salt);
30
- const cipher = crypto.createCipher(
31
- "aes-256-cbc",
32
- publicKey + password
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") {
38
+ const publicKey = Cipher.randomString();
39
+ let cipher = crypto.createCipheriv(
40
+ algorithm,
41
+ Buffer.from(encryptionKey, "hex"),
42
+ Buffer.from(publicKey, "hex"),
33
43
  );
34
- let encrypted = cipher.update(text, "utf8", "hex");
35
- encrypted += cipher.final("hex");
44
+ let encrypted = cipher.update(text);
45
+ encrypted = Buffer.concat([encrypted, cipher.final()]);
46
+ encrypted = encrypted.toString("hex");
36
47
 
37
- return { publicKey, encrypted };
48
+ return { publicKey, encrypted, algorithm, signature: Cipher.sign(text) };
38
49
  }
39
50
 
40
- decrypt(encryptedText, publicKey, password) {
41
- const decipher = crypto.createDecipher(
42
- "aes-256-cbc",
43
- publicKey + password
51
+ static decrypt(encryptedText, publicKey, encryptionKey, signature, algorithm = "aes-256-ctr") {
52
+ encryptedText = Buffer.from(encryptedText, "hex");
53
+
54
+ let decipher = crypto.createDecipheriv(
55
+ algorithm,
56
+ Buffer.from(encryptionKey, "hex"),
57
+ Buffer.from(publicKey, "hex")
44
58
  );
45
- let decrypted = decipher.update(encryptedText, "hex", "utf8");
46
- decrypted += decipher.final("utf8");
59
+
60
+ let decrypted = decipher.update(encryptedText);
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
+ }
47
67
 
48
68
  return decrypted;
49
69
  }
package/src/sdk/db.js ADDED
@@ -0,0 +1,23 @@
1
+ import Conf from "conf";
2
+
3
+ const { AUTHO_ENCRYPTION_KEY = "", AUTHO_NAME = 'default' } = process.env;
4
+
5
+ export default class DB {
6
+ constructor({
7
+ encryptionKey = AUTHO_ENCRYPTION_KEY,
8
+ configName = AUTHO_NAME
9
+ }) {
10
+ this.encryptionKey = encryptionKey;
11
+ this.client = new Conf({ projectName: "autho", encryptionKey, configName });
12
+ }
13
+
14
+ get(key, defaultValue) {
15
+ return this.client.get(key, defaultValue);
16
+ }
17
+
18
+ set(key, value) {
19
+ this.client.set(key, value);
20
+ }
21
+
22
+ }
23
+
package/src/sdk/otp.js CHANGED
@@ -1,16 +1,15 @@
1
1
  import * as OTPAuth from "otpauth";
2
- import Cipher from "./cipher.js";
3
2
 
4
3
  export default class OTP {
5
- constructor(config, secret, password) {
6
- const cipher = new Cipher(config);
4
+ constructor(secret) {
5
+
7
6
  const options = {
8
- issuer: "ACME",
7
+ issuer: "Autho",
9
8
  label: secret.name,
10
9
  algorithm: "SHA1",
11
10
  digits: 6,
12
11
  period: 30,
13
- secret: cipher.decrypt(secret.value, secret.publicKey, password),
12
+ secret: secret.value,
14
13
  };
15
14
 
16
15
  this.totp = new OTPAuth.TOTP(options);
@@ -1,38 +1,39 @@
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;
7
- this.cipher = new Cipher(config);
5
+ constructor(app) {
6
+ this.db = app.db;
8
7
  }
9
8
 
10
9
  get secrets() {
11
- return this.config.get("secrets", []);
10
+ return this.db.get("secrets", []);
12
11
  }
13
12
 
14
13
  set secrets(value) {
15
- this.config.set("secrets", value);
14
+ this.db.set("secrets", value);
16
15
  }
17
16
 
18
17
  async get(id) {
19
18
  return this.secrets.find((secret) => secret.id == id);
20
19
  }
21
20
 
22
- async add(secret, password) {
21
+ async add(secret, encryptionKey) {
23
22
  const { value, error } = createSecretSchema.validate(secret);
24
23
  if (error) {
25
24
  throw new Error(error);
26
25
  }
27
26
 
28
- const { publicKey, encrypted } = this.cipher.encrypt(value.value, password);
29
- value.value = encrypted;
30
- value.publicKey = publicKey;
27
+ const { encrypted, ...encryption } = Cipher.encrypt(value.value, encryptionKey);
31
28
 
32
- this.secrets = [...this.secrets, value];
29
+ this.secrets = [...this.secrets, { ...value, ...encryption, value: encrypted }];
33
30
  }
34
31
 
35
32
  async remove(id) {
36
33
  this.secrets = this.secrets.filter((secret) => secret.id != id);
37
34
  }
35
+
36
+ async clear() {
37
+ this.secrets = [];
38
+ }
38
39
  }
package/src/sdk/config.js DELETED
@@ -1,20 +0,0 @@
1
- import Conf from "conf";
2
-
3
- export default class Config {
4
- constructor({
5
- encryptionKey,
6
- configName='default'
7
- }) {
8
- this.config = new Conf({ projectName: "autho", encryptionKey, configName });
9
- }
10
-
11
- get(key, defaultValue) {
12
- return this.config.get(key, defaultValue);
13
- }
14
-
15
- set(key, value) {
16
- this.config.set(key, value);
17
- }
18
-
19
- }
20
-