zyket 1.0.6 → 1.0.8
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/bin/cli.js +29 -11
- package/package.json +1 -1
- package/src/services/index.js +1 -1
- package/src/services/template-manager/index.js +19 -13
- package/src/templates/auth/src/db-models/User.js +53 -0
- package/src/templates/auth/src/db-models/hooks/User.js +46 -0
- package/src/templates/auth/src/handlers/auth/login.js +15 -0
- package/src/templates/auth/src/handlers/auth/logout.js +18 -0
- package/src/templates/auth/src/handlers/auth/register.js +25 -0
package/bin/cli.js
CHANGED
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const prompts = require('prompts');
|
|
3
|
+
const TemplateManager = require('../src/services/template-manager');
|
|
4
|
+
const templateManager = new TemplateManager();
|
|
3
5
|
|
|
4
6
|
(async () => {
|
|
5
7
|
process.stdout.write("\u001b[2J\u001b[0;0H");
|
|
8
|
+
await templateManager.boot();
|
|
6
9
|
const response = await prompts({
|
|
7
10
|
type: 'select',
|
|
8
11
|
name: 'value',
|
|
9
12
|
message: '[ZYKET] What do you want to do?',
|
|
10
13
|
choices: [
|
|
11
14
|
{ title: 'Install Template', value: 'install-template', description: 'Install a new template', disabled: false },
|
|
12
|
-
{ title: 'Remove Template', value: 'remove-template', description: 'Remove an existing template', disabled: false }
|
|
15
|
+
/*{ title: 'Remove Template', value: 'remove-template', description: 'Remove an existing template', disabled: false },*/
|
|
13
16
|
],
|
|
14
17
|
initial: 0
|
|
15
18
|
});
|
|
16
19
|
|
|
17
20
|
const actions = {
|
|
18
21
|
'install-template': async () => {
|
|
19
|
-
|
|
22
|
+
const templates = templateManager.getTemplates();
|
|
23
|
+
const response = await prompts({
|
|
24
|
+
type: 'select',
|
|
25
|
+
name: 'templateToInstall',
|
|
26
|
+
message: '[ZYKET] What template would you like to install?',
|
|
27
|
+
choices: [
|
|
28
|
+
...templates.map((template) => ({ title: template.toUpperCase(), value: template, description: '', disabled: false })),
|
|
29
|
+
],
|
|
30
|
+
initial: 0
|
|
31
|
+
});
|
|
32
|
+
if(!templateManager.exists(response.templateToInstall)) throw new Error(`Template ${response.templateToInstall} not found`);
|
|
33
|
+
templateManager.installTemplate(response.templateToInstall);
|
|
20
34
|
},
|
|
21
|
-
'remove-template': async () => {
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
/*'remove-template': async () => {
|
|
36
|
+
const response = await prompts({
|
|
37
|
+
type: 'select',
|
|
38
|
+
name: 'templateToRemove',
|
|
39
|
+
message: '[ZYKET] What template would you like to remove?',
|
|
40
|
+
choices: [
|
|
41
|
+
{ title: 'Auth', value: 'auth', description: 'Authentication template', disabled: false },
|
|
42
|
+
{ title: 'Chat', value: 'chat', description: 'Chat template', disabled: false },
|
|
43
|
+
],
|
|
44
|
+
initial: 0
|
|
45
|
+
});
|
|
46
|
+
console.log(`Removing template: ${response.templateToRemove}`);
|
|
47
|
+
}*/
|
|
24
48
|
};
|
|
25
49
|
|
|
26
50
|
await actions[response.value]();
|
|
27
|
-
|
|
28
|
-
await new Promise((resolve) => {
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
resolve();
|
|
31
|
-
}, 2000);
|
|
32
|
-
});
|
|
33
51
|
})();
|
package/package.json
CHANGED
package/src/services/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const { SocketIO } = require("./socketio");
|
|
|
5
5
|
|
|
6
6
|
module.exports = [
|
|
7
7
|
["logger", require("./Logger"), ["@service_container", process.env.LOG_DIRECTORY || `${process.cwd()}/logs`, process.env.DEBUG === "true"]],
|
|
8
|
-
["template-manager", require("./template-manager"), [
|
|
8
|
+
["template-manager", require("./template-manager"), []],
|
|
9
9
|
process.env.DATABASE_URL ? ["database", Database, ["@service_container", process.env.DATABASE_URL]] : null,
|
|
10
10
|
process.env.CACHE_URL ? ["cache", Cache, ["@service_container", process.env.CACHE_URL]] : null,
|
|
11
11
|
(process.env.S3_ENDPOINT && process.env.S3_ACCESS_KEY && process.env.S3_SECRET_KEY) ? ["s3", S3, ["@service_container", process.env.S3_ENDPOINT, process.env.S3_PORT, process.env.S3_USE_SSL === "true", process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY]] : null,
|
|
@@ -4,12 +4,10 @@ const path = require('path');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
|
|
6
6
|
module.exports = class TemplateManager extends Service {
|
|
7
|
-
#container;
|
|
8
7
|
templates = {}
|
|
9
8
|
|
|
10
|
-
constructor(
|
|
9
|
+
constructor() {
|
|
11
10
|
super('template-manager');
|
|
12
|
-
this.#container = container;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
async boot() {
|
|
@@ -17,19 +15,19 @@ module.exports = class TemplateManager extends Service {
|
|
|
17
15
|
cwd: path.join(__dirname, '../../templates'),
|
|
18
16
|
});
|
|
19
17
|
for (const template of zyketTemplates) {
|
|
20
|
-
// need to copy full file and relation with the name on templates variable
|
|
21
18
|
const templatePath = path.join(__dirname, '../../templates', template);
|
|
22
19
|
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
23
|
-
this.templates[template.replace('.js', '')] =
|
|
20
|
+
this.templates[template.replace('.js', '')] = {
|
|
21
|
+
route: template,
|
|
22
|
+
content: templateContent
|
|
23
|
+
};
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
this.#container.get('logger').info(`Loaded ${this.templates.length} templates`);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
installFile(fileName, location) {
|
|
30
28
|
const template = this.templates[fileName];
|
|
31
29
|
if (!template) throw new Error(`Template ${fileName} not found`);
|
|
32
|
-
return fs.writeFileSync(location, template);
|
|
30
|
+
return fs.writeFileSync(location, template?.content);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
installTemplate(templateName) {
|
|
@@ -38,19 +36,23 @@ module.exports = class TemplateManager extends Service {
|
|
|
38
36
|
const files = this.getTemplate(templateName);
|
|
39
37
|
|
|
40
38
|
for (const file of files) {
|
|
41
|
-
const fileName = file.split('/').slice(1).join('/');
|
|
39
|
+
const fileName = file?.route.split('/').slice(1).join('/');
|
|
42
40
|
const fileLocation = path.join(process.cwd(), fileName);
|
|
43
41
|
if (fs.existsSync(fileLocation)) throw new Error(`File ${file} already exists`);
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
for (const file of files) {
|
|
47
|
-
const fileName = file.split('/').slice(1).join('/');
|
|
48
|
-
const template = this.templates[file];
|
|
45
|
+
const fileName = file?.route.split('/').slice(1).join('/');
|
|
49
46
|
const fileLocation = path.join(process.cwd(), fileName);
|
|
50
|
-
|
|
47
|
+
const folderLocation = path.join(process.cwd(), fileName.split('/').slice(0, -1).join('/'));
|
|
48
|
+
fs.mkdirSync(folderLocation, { recursive: true });
|
|
49
|
+
fs.writeFileSync(fileLocation, file?.content);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
uninstallTemplate(templateName) {
|
|
54
|
+
}
|
|
55
|
+
|
|
54
56
|
getTemplates() {
|
|
55
57
|
const uniqueTemplates = new Set();
|
|
56
58
|
for (const template of Object.keys(this.templates)) {
|
|
@@ -62,6 +64,10 @@ module.exports = class TemplateManager extends Service {
|
|
|
62
64
|
getTemplate(templateName) {
|
|
63
65
|
const files = Object.keys(this.templates).filter((t) => t.startsWith(templateName));
|
|
64
66
|
if (files.length === 0) throw new Error(`Template ${templateName} not found`);
|
|
65
|
-
return files
|
|
67
|
+
return files.map((file) => this.templates[file]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exists(templateName) {
|
|
71
|
+
return this.getTemplates().some((t) => t === templateName);
|
|
66
72
|
}
|
|
67
73
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module.exports = ({sequelize, container, Sequelize}) => {
|
|
2
|
+
const User = sequelize.define('user', {
|
|
3
|
+
id: {
|
|
4
|
+
type: Sequelize.UUID,
|
|
5
|
+
defaultValue: Sequelize.UUIDV4,
|
|
6
|
+
allowNull: false,
|
|
7
|
+
primaryKey: true
|
|
8
|
+
},
|
|
9
|
+
email: {
|
|
10
|
+
type: Sequelize.STRING,
|
|
11
|
+
allowNull: false
|
|
12
|
+
},
|
|
13
|
+
password: {
|
|
14
|
+
type: Sequelize.STRING,
|
|
15
|
+
allowNull: false
|
|
16
|
+
},
|
|
17
|
+
}, {
|
|
18
|
+
timestamps: true,
|
|
19
|
+
createdAt: 'created_at',
|
|
20
|
+
updatedAt: 'updated_at',
|
|
21
|
+
indexes: [
|
|
22
|
+
{
|
|
23
|
+
unique: true,
|
|
24
|
+
fields: ['email']
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
User.associate = models => {
|
|
30
|
+
// User is associated to an application through ApplicationUser
|
|
31
|
+
User.belongsToMany(models.Application, {
|
|
32
|
+
through: models.ApplicationUser,
|
|
33
|
+
as: 'applications',
|
|
34
|
+
foreignKey: 'user_id',
|
|
35
|
+
otherKey: 'application_id'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
User.addScope('defaultScope', {
|
|
39
|
+
attributes: { exclude: ['password'] },
|
|
40
|
+
include: [
|
|
41
|
+
{
|
|
42
|
+
model: models.Application,
|
|
43
|
+
as: 'applications',
|
|
44
|
+
through: { attributes: [] }
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
require('./hooks/User')(User, container);
|
|
51
|
+
|
|
52
|
+
return User;
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint-disable no-async-promise-executor */
|
|
2
|
+
const crypto = require('crypto')
|
|
3
|
+
|
|
4
|
+
module.exports = (User, container) => {
|
|
5
|
+
User.beforeCreate(async (user, options) => {
|
|
6
|
+
const salt = process.env.ENCRYPTION_SALT || ''
|
|
7
|
+
user.password = crypto.pbkdf2Sync(user.password, salt, 1000, 64, 'sha512').toString('hex')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Verify if the password is valid
|
|
12
|
+
* @param {string} password - The password to verify
|
|
13
|
+
* @returns {Boolean} - Boolean
|
|
14
|
+
*/
|
|
15
|
+
User.prototype.isValidPassword = async function (password) {
|
|
16
|
+
const { User } = container.get('database').models
|
|
17
|
+
const user = await User.findByPk(this.id, {
|
|
18
|
+
attributes: ['password']
|
|
19
|
+
})
|
|
20
|
+
const hash = crypto.pbkdf2Sync(password, process.env.ENCRYPTION_SALT, 1000, 64, 'sha512').toString('hex')
|
|
21
|
+
return user.password === hash
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
User.prototype.generateAuthToken = async function () {
|
|
25
|
+
const token = crypto.randomBytes(32).toString('hex')
|
|
26
|
+
const cache = container.get('cache')
|
|
27
|
+
await cache.set(`auth:${token}`, JSON.stringify({
|
|
28
|
+
...this.toJSON()
|
|
29
|
+
}))
|
|
30
|
+
await cache.expire(`auth:${token}`, 60 * 60 * 24 * 7)
|
|
31
|
+
return token
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
User.prototype.removeAuthToken = function (token) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
try {
|
|
37
|
+
const cache = container.get('cache')
|
|
38
|
+
cache.del(`auth:${token}`).then(() => {
|
|
39
|
+
resolve()
|
|
40
|
+
}).catch(err => reject(err))
|
|
41
|
+
} catch (err) {
|
|
42
|
+
reject(err)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module.exports = async ({ container, socket, data }) => {
|
|
2
|
+
const {email, password} = data
|
|
3
|
+
const { User } = container.get('database').models
|
|
4
|
+
const user = await User.findOne({ where: { email } })
|
|
5
|
+
|
|
6
|
+
if(!user) return socket.emit("auth.login", { error: "User not found" })
|
|
7
|
+
if(!await user.isValidPassword(password)) return socket.emit("auth.login", { error: "Invalid password" })
|
|
8
|
+
|
|
9
|
+
const token = await user.generateAuthToken()
|
|
10
|
+
|
|
11
|
+
socket.user = user
|
|
12
|
+
socket.token = token
|
|
13
|
+
|
|
14
|
+
socket.emit("auth.login", { ...user.toJSON(), token })
|
|
15
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const validator = require('validator');
|
|
2
|
+
|
|
3
|
+
module.exports = async ({ container, socket, data }) => {
|
|
4
|
+
if(!socket.user) return socket.emit('auth.logout', { error: 'Not authenticated' });
|
|
5
|
+
const { token } = socket;
|
|
6
|
+
if (!token) return socket.emit('auth.logout', { error: 'Not authenticated' });
|
|
7
|
+
|
|
8
|
+
const cache = container.get('cache');
|
|
9
|
+
const tokenData = await cache.get(`auth:${token}`);
|
|
10
|
+
if (!tokenData) return socket.emit('auth.logout', { error: 'Not authenticated' });
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await cache.del(`auth:${token}`);
|
|
14
|
+
socket.emit('auth.logout', { success: true });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
socket.emit('auth.logout', { error: error.message });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const validator = require('validator');
|
|
2
|
+
|
|
3
|
+
module.exports = async ({ container, socket, data }) => {
|
|
4
|
+
const { email, password } = data;
|
|
5
|
+
|
|
6
|
+
if(!email) return socket.emit('auth.register', { error: 'Email is required' });
|
|
7
|
+
if(validator.isEmpty(email)) return socket.emit('auth.register', { error: 'Email is invalid' });
|
|
8
|
+
if(!validator.isEmail(email)) return socket.emit('auth.register', { error: 'Email is invalid' });
|
|
9
|
+
|
|
10
|
+
if(!password) return socket.emit('auth.register', { error: 'Password is required' });
|
|
11
|
+
if(validator.isEmpty(password)) return socket.emit('auth.register', { error: 'Password is invalid' });
|
|
12
|
+
if(!validator.isLength(password, { min: 6 })) return socket.emit('auth.register', { error: 'Password must be at least 6 characters' });
|
|
13
|
+
|
|
14
|
+
const { User } = container.get('database').models;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
await User.create({ email, password })
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return socket.emit('auth.register', { error: 'Email already exists' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const user = await User.findOne({ where: { email } });
|
|
23
|
+
|
|
24
|
+
socket.emit('auth.register', { user });
|
|
25
|
+
};
|