@vulkano/core 1.22.0 → 1.22.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/README.md +7 -3
- package/examples/controllers/api/AuthController.js +56 -0
- package/examples/services/Auth.js +111 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ GET /users/edit/1
|
|
|
116
116
|
|
|
117
117
|
A controller method key is `<path tail>` on its own, or `'<verb> <path tail>'` when the verb isn't `GET`. The auto-router only reassigns the HTTP method when the key has a space-separated verb prefix — otherwise it defaults to **GET**.
|
|
118
118
|
|
|
119
|
-
- A **custom action name with no verb prefix** (no space in the key) is still `GET`, e.g. `me(req, res)` on `AuthController` → `GET /auth/
|
|
119
|
+
- A **custom action name with no verb prefix** (no space in the key) is still `GET`, e.g. `me(req, res)` on `AuthController` → `GET /auth/current`. Don't write `'get current'`; it's redundant.
|
|
120
120
|
- A **custom action that isn't `GET`** needs the verb spelled out, e.g. `'post login'` → `POST /auth/login`.
|
|
121
121
|
- The path tail can carry arbitrary nested segments and multiple params:
|
|
122
122
|
|
|
@@ -134,8 +134,8 @@ module.exports = {
|
|
|
134
134
|
// controllers/api/AuthController.js
|
|
135
135
|
module.exports = {
|
|
136
136
|
|
|
137
|
-
// GET /api/auth/
|
|
138
|
-
|
|
137
|
+
// GET /api/auth/current — no verb prefix needed, GET is the default
|
|
138
|
+
current(req, res) { },
|
|
139
139
|
|
|
140
140
|
// POST /api/auth/login
|
|
141
141
|
'post login': (req, res) => { },
|
|
@@ -304,6 +304,10 @@ module.exports = {
|
|
|
304
304
|
|
|
305
305
|
NOTE: To find examples with the best practices for available methods ahd hooks, look in `examples/models` and read the file `Example.js`, and Scaffold Model API `ExampleWithScaffold.js`.
|
|
306
306
|
|
|
307
|
+
### Vulkano models — don't hand-roll `createdAt` or `updatedAt`
|
|
308
|
+
|
|
309
|
+
`@vulkano/core`'s `database/mongodb.js` auto-injects `createdAt: Date` and `updatedAt: Date` attributes into every model schema if the model doesn't already define them (`if (!attributes.createdAt) { ... }`, same for `updatedAt`). Never add a manual timestamp field (`at`, `date`, `timestamp`, `createdAt`, `updatedAt`, etc.) to a model's `attributes` — they're already automatic in Vulkano, so a hand-rolled one is redundant, and if named anything other than `createdAt`/`updatedAt` it also fights the framework's own sort/index defaults (`database/scaffold.js` defaults `sort: 'createdAt|DESC'`). Use `createdAt` and `updatedAt` directly in indexes, sort strings, and business logic.
|
|
310
|
+
|
|
307
311
|
---
|
|
308
312
|
|
|
309
313
|
## Key conventions
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* global Auth, Jwt */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AuthController.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
|
|
9
|
+
'get current': (req, res) => {
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
auth
|
|
13
|
+
} = req || {};
|
|
14
|
+
|
|
15
|
+
res.vsr(Auth.getCurrent(auth));
|
|
16
|
+
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
'post login': (req, res) => {
|
|
20
|
+
|
|
21
|
+
Auth.login(req.body)
|
|
22
|
+
.then(({ user, token }) => {
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
cookieName
|
|
26
|
+
} = Jwt.getConfig();
|
|
27
|
+
|
|
28
|
+
res.cookie(cookieName || 'token', token, {
|
|
29
|
+
httpOnly: true,
|
|
30
|
+
// secure: app.PRODUCTION,
|
|
31
|
+
// sameSite: 'lax',
|
|
32
|
+
maxAge: Auth.SESSION_MS,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
res.vsr(Promise.resolve(user));
|
|
36
|
+
|
|
37
|
+
})
|
|
38
|
+
.catch((err) => {
|
|
39
|
+
res.vsr(Promise.reject(err));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
'post logout': (req, res) => {
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
cookieName
|
|
48
|
+
} = Jwt.getConfig();
|
|
49
|
+
|
|
50
|
+
res.clearCookie(cookieName || 'token');
|
|
51
|
+
|
|
52
|
+
res.vsr(Promise.resolve({ success: true }));
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* global User, Auth, VSError */
|
|
2
|
+
|
|
3
|
+
const bcrypt = require('bcryptjs');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
|
|
7
|
+
SESSION_MS: 1000 * 60 * 60 * 24 * 365 * 10,
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Verify a plain-text password against a stored hash.
|
|
11
|
+
* @param {String} plain
|
|
12
|
+
* @param {String} hash
|
|
13
|
+
* @returns {Boolean}
|
|
14
|
+
*/
|
|
15
|
+
verifyPassword(plain, hash) {
|
|
16
|
+
return bcrypt.compareSync(`${process.env.SALT_KEY || ''}-${plain}`, hash);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Look up a user by email and verify their password. The only place in
|
|
21
|
+
* the model that explicitly loads the password hash.
|
|
22
|
+
* @param {String} email
|
|
23
|
+
* @param {String} password
|
|
24
|
+
* @returns {Promise<Object>} the authenticated User document
|
|
25
|
+
*/
|
|
26
|
+
login({ email, password }) {
|
|
27
|
+
|
|
28
|
+
const normalizedEmail = String(email || '')
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.trim();
|
|
31
|
+
|
|
32
|
+
return User.findOne({ email: normalizedEmail, active: true })
|
|
33
|
+
.select('+password')
|
|
34
|
+
.then((user) => {
|
|
35
|
+
|
|
36
|
+
if (!user || !Auth.verifyPassword(password || '', user.password)) {
|
|
37
|
+
return VSError.reject('Invalid credentials', 401);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Auth.setToken(user);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate token
|
|
47
|
+
* @param {Object} props
|
|
48
|
+
* @returns {Object}
|
|
49
|
+
*/
|
|
50
|
+
setToken(u) {
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
_id
|
|
54
|
+
} = u || {};
|
|
55
|
+
|
|
56
|
+
if (!_id) {
|
|
57
|
+
return VSError.reject('The user and/or password are incorrect', 400);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return Promise.resolve({
|
|
61
|
+
|
|
62
|
+
user: {
|
|
63
|
+
_id: u._id || '',
|
|
64
|
+
name: u.name || ''
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
token: Jwt.encode({
|
|
68
|
+
_id: u._id || '',
|
|
69
|
+
name: u.name || '',
|
|
70
|
+
email: u.email,
|
|
71
|
+
role: u.role || '',
|
|
72
|
+
expiration: Auth.SESSION_MS + Date.now()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get current user logged
|
|
80
|
+
*
|
|
81
|
+
* @param {Object} auth
|
|
82
|
+
* @returns Promise
|
|
83
|
+
*/
|
|
84
|
+
getCurrent(auth) {
|
|
85
|
+
|
|
86
|
+
const {
|
|
87
|
+
_id
|
|
88
|
+
} = auth || {};
|
|
89
|
+
|
|
90
|
+
if (!_id) {
|
|
91
|
+
return VSError.reject('Invalid token', 401);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return User
|
|
95
|
+
.getUser(_id)
|
|
96
|
+
.then( (u) => {
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
active
|
|
100
|
+
} = u || {};
|
|
101
|
+
|
|
102
|
+
if (String(active || '') !== 'true' || !_id) {
|
|
103
|
+
return VSError.reject('Invalid ID. User not found', 401);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return u;
|
|
107
|
+
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulkano/core",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.1",
|
|
4
4
|
"description": "A MVC framework using Express 4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vulkano Team",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@socket.io/mongo-adapter": "^0.4.0",
|
|
46
46
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
47
|
+
"bcryptjs": "^3.0.3",
|
|
47
48
|
"compression": "^1.8.1",
|
|
48
49
|
"connect-timeout": "^1.9.1",
|
|
49
50
|
"cookie-parser": "^1.4.7",
|