autho 0.0.1
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 +24 -0
- package/src/Readme.md +63 -0
- package/src/cli/bin.js +111 -0
- package/src/cli/utils.js +15 -0
- package/src/cli/wizards/createSecret.js +55 -0
- package/src/models/Secret.js +12 -0
- package/src/sdk/cipher.js +50 -0
- package/src/sdk/config.js +20 -0
- package/src/sdk/otp.js +26 -0
- package/src/sdk/secrets.js +38 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autho",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin":{
|
|
7
|
+
"autho": "./cli/bin.js"},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^12.0.0",
|
|
16
|
+
"conf": "^12.0.0",
|
|
17
|
+
"inquirer": "^9.2.17",
|
|
18
|
+
"inquirer-autocomplete-standalone": "^0.8.1",
|
|
19
|
+
"joi": "^17.12.2",
|
|
20
|
+
"otpauth": "^9.2.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {},
|
|
23
|
+
"description": "Open Source Authentication and Password Management Tool"
|
|
24
|
+
}
|
package/src/Readme.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Autho: Open Source Authentication and Password Management Tool
|
|
2
|
+
|
|
3
|
+
Autho is an open-source, self-hosted alternative to services like Authy, providing One-Time Password (OTP) generation and password management functionalities. With Autho, users can securely manage their authentication tokens and passwords while maintaining full control over their data.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **OTP Generation**: Autho allows users to generate One-Time Passwords (OTPs) for two-factor authentication (2FA) using industry-standard algorithms.
|
|
8
|
+
|
|
9
|
+
- **Password Management**: Autho provides a secure vault for users to store and manage their passwords, ensuring easy access and strong encryption.
|
|
10
|
+
|
|
11
|
+
- **Self-Hosted**: Autho can be self-hosted, giving users complete control over their data and eliminating reliance on third-party services.
|
|
12
|
+
|
|
13
|
+
- **Open Source**: Autho is open-source software, allowing users to inspect, modify, and contribute to its codebase, ensuring transparency and security.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
To install Autho globally, use npm:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g autho
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
After installing Autho, you can run the `autho` command in your terminal to access its functionalities:
|
|
26
|
+
|
|
27
|
+
This will start the Autho CLI, where you can generate OTPs, manage passwords, and configure settings as needed.
|
|
28
|
+
|
|
29
|
+
## Getting Started
|
|
30
|
+
|
|
31
|
+
1. **Setting Up Autho**: After installation, run the `autho` command to set up Autho for the first time. Follow the on-screen instructions to configure your master password and other settings.
|
|
32
|
+
|
|
33
|
+
2. **Generating OTPs**: Use Autho to generate OTPs for your accounts by providing the associated account name or label. Autho will generate a time-based OTP using a secure algorithm.
|
|
34
|
+
|
|
35
|
+
3. **Managing Passwords**: Autho provides a secure vault for storing and managing your passwords. You can add, view, update, and delete passwords using the CLI interface.
|
|
36
|
+
|
|
37
|
+
4. **Self-Hosting**: If you prefer self-hosting, deploy Autho on your own server by following the instructions provided in the documentation.
|
|
38
|
+
|
|
39
|
+
## Security Considerations
|
|
40
|
+
|
|
41
|
+
- **Encryption**: Autho employs strong encryption algorithms to protect user data, ensuring that passwords and OTPs are securely stored.
|
|
42
|
+
|
|
43
|
+
- **Master Password**: Users are required to set a master password during setup, which is used to encrypt and decrypt their data. Choose a strong and unique master password to enhance security.
|
|
44
|
+
|
|
45
|
+
- **Self-Hosting**: By self-hosting Autho, users maintain control over their data and reduce reliance on external services, minimizing the risk of data breaches.
|
|
46
|
+
|
|
47
|
+
- **Regular Updates**: Keep Autho and its dependencies up to date to ensure that security vulnerabilities are addressed promptly.
|
|
48
|
+
|
|
49
|
+
## Contributing
|
|
50
|
+
|
|
51
|
+
Autho is an open-source project, and contributions are welcome! Feel free to report issues, suggest features, or submit pull requests on the project's GitHub repository.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
Autho is licensed under the [MIT License](link-to-license), allowing for unrestricted use, modification, and distribution.
|
|
56
|
+
|
|
57
|
+
## Support
|
|
58
|
+
|
|
59
|
+
For support or inquiries, please reach out to the [official Autho community](link-to-community) for assistance.
|
|
60
|
+
|
|
61
|
+
## Acknowledgments
|
|
62
|
+
|
|
63
|
+
Autho is built upon various open-source libraries and technologies. We extend our gratitude to the developers and contributors of these projects.
|
package/src/cli/bin.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { prompt, ask } from "./utils.js";
|
|
6
|
+
import Config from "../sdk/config.js";
|
|
7
|
+
import Cipher from "../sdk/cipher.js";
|
|
8
|
+
import createSecret from "./wizards/createSecret.js";
|
|
9
|
+
import Secrets from "../sdk/secrets.js";
|
|
10
|
+
import OTP from "../sdk/otp.js";
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
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
|
+
}));
|
|
20
|
+
|
|
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");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return secret;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.name("autho")
|
|
41
|
+
.description("Secrets manager")
|
|
42
|
+
.version("0.0.1")
|
|
43
|
+
.option("-p, --password <password>", "Master password")
|
|
44
|
+
.action(async (args) => {
|
|
45
|
+
try {
|
|
46
|
+
const masterPassword = args.password
|
|
47
|
+
? args.password
|
|
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();
|
|
57
|
+
|
|
58
|
+
if (!salt) {
|
|
59
|
+
salt = Cipher.random();
|
|
60
|
+
config.set("salt", salt);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let choices = [
|
|
64
|
+
{ value: "create", name: "Create new secret" },
|
|
65
|
+
{ value: "read", name: "Read secret" },
|
|
66
|
+
{ value: "delete", name: "Delete secret" },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const { action } = await prompt({
|
|
70
|
+
name: "action",
|
|
71
|
+
type: "list",
|
|
72
|
+
choices,
|
|
73
|
+
required: true,
|
|
74
|
+
});
|
|
75
|
+
switch (action) {
|
|
76
|
+
case "create":
|
|
77
|
+
await createSecret(config, masterPasswordHash);
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
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
|
+
|
|
94
|
+
break;
|
|
95
|
+
case "delete":
|
|
96
|
+
const deleteSecret = getSecret(config);
|
|
97
|
+
const secrets = new Secrets(config);
|
|
98
|
+
secrets.remove(deleteSecret.id)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(
|
|
103
|
+
chalk.redBright("Something went wrong, Error: "),
|
|
104
|
+
error.message
|
|
105
|
+
);
|
|
106
|
+
console.log(error.stack);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
program.parse();
|
package/src/cli/utils.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
|
|
3
|
+
export const prompt = inquirer.prompt;
|
|
4
|
+
|
|
5
|
+
export const ask = async ({ name = "", message = "", type = "input" }) => {
|
|
6
|
+
const answers = await inquirer.prompt([
|
|
7
|
+
{
|
|
8
|
+
name,
|
|
9
|
+
message,
|
|
10
|
+
type,
|
|
11
|
+
},
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
return answers[name];
|
|
15
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { prompt } from "../utils.js";
|
|
2
|
+
import Secrets from "../../sdk/secrets.js";
|
|
3
|
+
|
|
4
|
+
const wizard = async (config, masterPasswordHash) => {
|
|
5
|
+
const info = await prompt([
|
|
6
|
+
{
|
|
7
|
+
name: "name",
|
|
8
|
+
message: "name:",
|
|
9
|
+
type: "input",
|
|
10
|
+
required: true,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "type",
|
|
14
|
+
message: "type:",
|
|
15
|
+
type: "choice",
|
|
16
|
+
default: "otp",
|
|
17
|
+
choices: ["otp"],
|
|
18
|
+
required: true,
|
|
19
|
+
}
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
let newSecret = {};
|
|
23
|
+
|
|
24
|
+
switch (info.type) {
|
|
25
|
+
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
|
+
};
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const secrets = new Secrets(config);
|
|
52
|
+
await secrets.add(newSecret, masterPasswordHash);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default wizard;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import Cipher from "../sdk/cipher.js";
|
|
3
|
+
|
|
4
|
+
export const createSecretSchema = Joi.object({
|
|
5
|
+
id: Joi.string().default(Cipher.random()),
|
|
6
|
+
protected: Joi.boolean().default(false), // ask for password
|
|
7
|
+
name: Joi.string().required(),
|
|
8
|
+
type: Joi.string().required().valid('otp'),
|
|
9
|
+
value: Joi.string().required(),
|
|
10
|
+
typeOptions: Joi.object().default({}),
|
|
11
|
+
createdAt: Joi.date().default(new Date()),
|
|
12
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
|
|
3
|
+
export default class Cipher {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
this.salt = config.get("salt", "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static hash(text) {
|
|
10
|
+
const hash = crypto.createHash("sha256");
|
|
11
|
+
hash.update(text);
|
|
12
|
+
|
|
13
|
+
return hash.digest("hex");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static random() {
|
|
17
|
+
const rnd = crypto.randomBytes(16).toString("hex");
|
|
18
|
+
|
|
19
|
+
return rnd;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static createKey(salt="") {
|
|
23
|
+
const rnd = Cipher.random(salt)
|
|
24
|
+
|
|
25
|
+
return Cipher.hash(salt+rnd);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
encrypt(text, password) {
|
|
29
|
+
const publicKey = Cipher.createKey(this.salt);
|
|
30
|
+
const cipher = crypto.createCipher(
|
|
31
|
+
"aes-256-cbc",
|
|
32
|
+
publicKey + password
|
|
33
|
+
);
|
|
34
|
+
let encrypted = cipher.update(text, "utf8", "hex");
|
|
35
|
+
encrypted += cipher.final("hex");
|
|
36
|
+
|
|
37
|
+
return { publicKey, encrypted };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
decrypt(encryptedText, publicKey, password) {
|
|
41
|
+
const decipher = crypto.createDecipher(
|
|
42
|
+
"aes-256-cbc",
|
|
43
|
+
publicKey + password
|
|
44
|
+
);
|
|
45
|
+
let decrypted = decipher.update(encryptedText, "hex", "utf8");
|
|
46
|
+
decrypted += decipher.final("utf8");
|
|
47
|
+
|
|
48
|
+
return decrypted;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
|
package/src/sdk/otp.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as OTPAuth from "otpauth";
|
|
2
|
+
import Cipher from "./cipher.js";
|
|
3
|
+
|
|
4
|
+
export default class OTP {
|
|
5
|
+
constructor(config, secret, password) {
|
|
6
|
+
const cipher = new Cipher(config);
|
|
7
|
+
const options = {
|
|
8
|
+
issuer: "ACME",
|
|
9
|
+
label: secret.name,
|
|
10
|
+
algorithm: "SHA1",
|
|
11
|
+
digits: 6,
|
|
12
|
+
period: 30,
|
|
13
|
+
secret: cipher.decrypt(secret.value, secret.publicKey, password),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
this.totp = new OTPAuth.TOTP(options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
generate() {
|
|
20
|
+
return this.totp.generate();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
validate(token, window = 1) {
|
|
24
|
+
return this.totp.validate({ token, window });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createSecretSchema } from "../models/Secret.js";
|
|
2
|
+
import Cipher from "./cipher.js";
|
|
3
|
+
|
|
4
|
+
export default class Secrets {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.cipher = new Cipher(config);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get secrets() {
|
|
11
|
+
return this.config.get("secrets", []);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
set secrets(value) {
|
|
15
|
+
this.config.set("secrets", value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async get(id) {
|
|
19
|
+
return this.secrets.find((secret) => secret.id == id);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async add(secret, password) {
|
|
23
|
+
const { value, error } = createSecretSchema.validate(secret);
|
|
24
|
+
if (error) {
|
|
25
|
+
throw new Error(error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { publicKey, encrypted } = this.cipher.encrypt(value.value, password);
|
|
29
|
+
value.value = encrypted;
|
|
30
|
+
value.publicKey = publicKey;
|
|
31
|
+
|
|
32
|
+
this.secrets = [...this.secrets, value];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async remove(id) {
|
|
36
|
+
this.secrets = this.secrets.filter((secret) => secret.id != id);
|
|
37
|
+
}
|
|
38
|
+
}
|