autho 0.0.2 → 0.0.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/package.json +4 -3
- package/src/cli/bin.js +36 -28
- package/src/cli/wizards/createSecret.js +67 -25
- package/src/models/Secret.js +2 -2
- package/src/sdk/cipher.js +27 -23
- package/src/sdk/config.js +4 -2
- package/src/sdk/otp.js +4 -5
- package/src/sdk/secrets.js +5 -2
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autho",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin":{
|
|
7
|
-
"autho": "
|
|
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
|
@@ -13,9 +13,14 @@ const program = new Command();
|
|
|
13
13
|
|
|
14
14
|
const getSecret = async (config) => {
|
|
15
15
|
const existingSecrets = config.get("secrets", []);
|
|
16
|
+
|
|
17
|
+
if (existingSecrets.length === 0) {
|
|
18
|
+
throw new Error("No secrets found");
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
const choices = existingSecrets.map((secret) => ({
|
|
17
22
|
value: secret.id,
|
|
18
|
-
name: secret.name
|
|
23
|
+
name: `${secret.name} (${secret.typeOptions.username || secret.id})`,
|
|
19
24
|
}));
|
|
20
25
|
|
|
21
26
|
const { id: secretId } = await prompt({
|
|
@@ -25,7 +30,6 @@ const getSecret = async (config) => {
|
|
|
25
30
|
choices,
|
|
26
31
|
required: true,
|
|
27
32
|
});
|
|
28
|
-
|
|
29
33
|
const secrets = new Secrets(config);
|
|
30
34
|
const secret = await secrets.get(secretId);
|
|
31
35
|
|
|
@@ -46,19 +50,13 @@ program
|
|
|
46
50
|
const masterPassword = args.password
|
|
47
51
|
? args.password
|
|
48
52
|
: await ask({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
name: "masterPassword",
|
|
54
|
+
message: "Password:",
|
|
55
|
+
type: "password",
|
|
56
|
+
required: true,
|
|
57
|
+
});
|
|
54
58
|
const masterPasswordHash = Cipher.hash(masterPassword);
|
|
55
59
|
const config = new Config({ encryptionKey: masterPasswordHash });
|
|
56
|
-
let salt = config.get();
|
|
57
|
-
|
|
58
|
-
if (!salt) {
|
|
59
|
-
salt = Cipher.random();
|
|
60
|
-
config.set("salt", salt);
|
|
61
|
-
}
|
|
62
60
|
|
|
63
61
|
let choices = [
|
|
64
62
|
{ value: "create", name: "Create new secret" },
|
|
@@ -79,23 +77,33 @@ program
|
|
|
79
77
|
|
|
80
78
|
case "read":
|
|
81
79
|
const readSecret = await getSecret(config);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
readSecret.value = Cipher.decrypt(readSecret.value, readSecret.publicKey, masterPasswordHash)
|
|
81
|
+
|
|
82
|
+
switch (readSecret.type) {
|
|
83
|
+
case "password":
|
|
84
|
+
console.log("Username:", readSecret.typeOptions.username);
|
|
85
|
+
console.log("Password:", readSecret.value);
|
|
86
|
+
break;
|
|
87
|
+
case "note":
|
|
88
|
+
console.log("Note:", readSecret.value);
|
|
89
|
+
break;
|
|
90
|
+
case "otp":
|
|
91
|
+
const otp = new OTP(readSecret);
|
|
92
|
+
console.log("OTP code:", otp.generate());
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
console.log("Expired");
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}, 30000);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
break;
|
|
95
101
|
case "delete":
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
const deleteSecret = await getSecret(config);
|
|
103
|
+
const secrets = new Secrets(config);
|
|
104
|
+
await secrets.remove(deleteSecret.id)
|
|
105
|
+
console.log("Removed");
|
|
106
|
+
process.exit(0);
|
|
99
107
|
break;
|
|
100
108
|
}
|
|
101
109
|
} catch (error) {
|
|
@@ -12,9 +12,9 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
12
12
|
{
|
|
13
13
|
name: "type",
|
|
14
14
|
message: "type:",
|
|
15
|
-
type: "
|
|
16
|
-
default: "
|
|
17
|
-
choices: ["otp"],
|
|
15
|
+
type: "list",
|
|
16
|
+
default: "password",
|
|
17
|
+
choices: ["password", "otp", "note"],
|
|
18
18
|
required: true,
|
|
19
19
|
}
|
|
20
20
|
]);
|
|
@@ -22,29 +22,71 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
22
22
|
let newSecret = {};
|
|
23
23
|
|
|
24
24
|
switch (info.type) {
|
|
25
|
+
case "password":
|
|
26
|
+
const password = await prompt([
|
|
27
|
+
{
|
|
28
|
+
name: "username",
|
|
29
|
+
message: "username:",
|
|
30
|
+
type: "input",
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "value",
|
|
35
|
+
message: "password:",
|
|
36
|
+
type: "password",
|
|
37
|
+
required: true,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
newSecret = {
|
|
41
|
+
name: info.name,
|
|
42
|
+
type: info.type,
|
|
43
|
+
value: password.value,
|
|
44
|
+
typeOptions: {
|
|
45
|
+
username: password.username,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
break;
|
|
49
|
+
case "note":
|
|
50
|
+
const note = await prompt([
|
|
51
|
+
{
|
|
52
|
+
name: "value",
|
|
53
|
+
message: "note:",
|
|
54
|
+
type: "password",
|
|
55
|
+
required: true,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
newSecret = {
|
|
59
|
+
name: info.name,
|
|
60
|
+
type: info.type,
|
|
61
|
+
value: note.value,
|
|
62
|
+
typeOptions: {
|
|
63
|
+
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
break;
|
|
25
67
|
case "otp":
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
const otp = await prompt([
|
|
69
|
+
{
|
|
70
|
+
name: "username",
|
|
71
|
+
message: "username:",
|
|
72
|
+
type: "input",
|
|
73
|
+
required: true,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "value",
|
|
77
|
+
message: "value:",
|
|
78
|
+
type: "password",
|
|
79
|
+
required: true,
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
newSecret = {
|
|
83
|
+
name: info.name,
|
|
84
|
+
type: info.type,
|
|
85
|
+
value: otp.value,
|
|
86
|
+
typeOptions: {
|
|
87
|
+
username: otp.username,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
48
90
|
break;
|
|
49
91
|
}
|
|
50
92
|
|
package/src/models/Secret.js
CHANGED
|
@@ -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.
|
|
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,10 +1,10 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
|
|
3
|
+
const algorithm = "aes-256-ctr";
|
|
4
|
+
const IV_LENGTH = 16;
|
|
5
|
+
|
|
3
6
|
export default class Cipher {
|
|
4
|
-
constructor(
|
|
5
|
-
this.config = config;
|
|
6
|
-
this.salt = config.get("salt", "");
|
|
7
|
-
}
|
|
7
|
+
constructor() { }
|
|
8
8
|
|
|
9
9
|
static hash(text) {
|
|
10
10
|
const hash = crypto.createHash("sha256");
|
|
@@ -13,38 +13,42 @@ export default class Cipher {
|
|
|
13
13
|
return hash.digest("hex");
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
static random() {
|
|
17
|
-
const rnd = crypto.randomBytes(
|
|
16
|
+
static random(size = IV_LENGTH) {
|
|
17
|
+
const rnd = crypto.randomBytes(size);
|
|
18
18
|
|
|
19
19
|
return rnd;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
static
|
|
23
|
-
const rnd = Cipher.random(
|
|
22
|
+
static randomString() {
|
|
23
|
+
const rnd = Cipher.random().toString("hex");
|
|
24
24
|
|
|
25
|
-
return
|
|
25
|
+
return rnd;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
encrypt(text, password) {
|
|
29
|
-
const publicKey = Cipher.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
static encrypt(text, password) {
|
|
29
|
+
const publicKey = Cipher.randomString();
|
|
30
|
+
let cipher = crypto.createCipheriv(
|
|
31
|
+
algorithm,
|
|
32
|
+
Buffer.from(password, "hex"),
|
|
33
|
+
Buffer.from(publicKey, "hex"),
|
|
33
34
|
);
|
|
34
|
-
let encrypted = cipher.update(text
|
|
35
|
-
encrypted
|
|
35
|
+
let encrypted = cipher.update(text);
|
|
36
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
37
|
+
encrypted = encrypted.toString("hex");
|
|
36
38
|
|
|
37
39
|
return { publicKey, encrypted };
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
decrypt(encryptedText, publicKey, password) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
static decrypt(encryptedText, publicKey, password) {
|
|
43
|
+
encryptedText = Buffer.from(encryptedText, "hex");
|
|
44
|
+
let decipher = crypto.createDecipheriv(
|
|
45
|
+
algorithm,
|
|
46
|
+
Buffer.from(password, "hex"),
|
|
47
|
+
Buffer.from(publicKey, "hex")
|
|
44
48
|
);
|
|
45
|
-
let decrypted = decipher.update(encryptedText
|
|
46
|
-
decrypted
|
|
49
|
+
let decrypted = decipher.update(encryptedText);
|
|
50
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
47
51
|
|
|
48
|
-
return decrypted;
|
|
52
|
+
return decrypted.toString();
|
|
49
53
|
}
|
|
50
54
|
}
|
package/src/sdk/config.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import Conf from "conf";
|
|
2
2
|
|
|
3
|
+
const { AUTHO_ENCRYPTION_KEY = "", AUTHO_NAME = 'default' } = process.env;
|
|
4
|
+
|
|
3
5
|
export default class Config {
|
|
4
6
|
constructor({
|
|
5
|
-
encryptionKey,
|
|
6
|
-
configName=
|
|
7
|
+
encryptionKey = AUTHO_ENCRYPTION_KEY,
|
|
8
|
+
configName = AUTHO_NAME
|
|
7
9
|
}) {
|
|
8
10
|
this.config = new Conf({ projectName: "autho", encryptionKey, configName });
|
|
9
11
|
}
|
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(
|
|
6
|
-
|
|
4
|
+
constructor(secret) {
|
|
5
|
+
|
|
7
6
|
const options = {
|
|
8
|
-
issuer: "
|
|
7
|
+
issuer: "Autho",
|
|
9
8
|
label: secret.name,
|
|
10
9
|
algorithm: "SHA1",
|
|
11
10
|
digits: 6,
|
|
12
11
|
period: 30,
|
|
13
|
-
secret:
|
|
12
|
+
secret: secret.value,
|
|
14
13
|
};
|
|
15
14
|
|
|
16
15
|
this.totp = new OTPAuth.TOTP(options);
|
package/src/sdk/secrets.js
CHANGED
|
@@ -4,7 +4,6 @@ import Cipher from "./cipher.js";
|
|
|
4
4
|
export default class Secrets {
|
|
5
5
|
constructor(config) {
|
|
6
6
|
this.config = config;
|
|
7
|
-
this.cipher = new Cipher(config);
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
get secrets() {
|
|
@@ -25,7 +24,7 @@ export default class Secrets {
|
|
|
25
24
|
throw new Error(error);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
const { publicKey, encrypted } =
|
|
27
|
+
const { publicKey, encrypted } = Cipher.encrypt(value.value, password);
|
|
29
28
|
value.value = encrypted;
|
|
30
29
|
value.publicKey = publicKey;
|
|
31
30
|
|
|
@@ -35,4 +34,8 @@ export default class Secrets {
|
|
|
35
34
|
async remove(id) {
|
|
36
35
|
this.secrets = this.secrets.filter((secret) => secret.id != id);
|
|
37
36
|
}
|
|
37
|
+
|
|
38
|
+
async clear() {
|
|
39
|
+
this.secrets = [];
|
|
40
|
+
}
|
|
38
41
|
}
|