Skip to content

Commit 2855ece

Browse files
committed
feat: uses create(de)Cipheriv instead of deprecated methods
BREAKING CHANGE: min node is 12.14.x, upgrades internal dependencies and hardens minimum shared secret requirements. Introduces new token format of <version><iv><payload>, so that in the future breaking changes are easier to mitigate. For back-compatibility introduces ability to specify legacy secret with a length of 24, which is supported by createCipher/createDecipher.
1 parent 976c9dd commit 2855ece

19 files changed

+3189
-2923
lines changed

.eslintrc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"extends": "makeomatic",
3-
"parser": "babel-eslint",
43
"rules": {
54
"object-curly-newline": 0
6-
}
5+
}
76
}

.mdeprc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"node": "10.16.0"
2+
"node": "12.14.1",
3+
"auto_compose": true,
4+
"with_local_compose": true
35
}

.mocharc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"require": "@babel/register",
3+
"timeout": 10000,
4+
"bail": true
5+
}

.releaserc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"branch": "master",
2+
"branches": ["master"],
33
"analyzeCommits": {
44
"preset": "angular",
55
"releaseRules": [

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const tokenManager = new TokenManager({
4040
},
4141
encrypt: {
4242
algorithm: 'aes256',
43-
sharedSecret: Buffer.from('incredibly-long-secret'),
43+
sharedSecret: Buffer.from('incredibly-long-secret-ooooohooo'),
4444
},
4545
});
4646
```

package.json

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,42 @@
3030
},
3131
"homepage": "https://github.com/makeomatic/ms-token#readme",
3232
"peerDependencies": {
33-
"ioredis": "3.x.x || 4.x.x"
33+
"ioredis": "4.x.x"
3434
},
3535
"engine": {
36-
"node": ">= 8.9.0"
36+
"node": ">= 12.14.0"
3737
},
3838
"devDependencies": {
39-
"@babel/cli": "^7.4.4",
40-
"@babel/core": "^7.4.5",
41-
"@babel/plugin-proposal-class-properties": "^7.4.4",
42-
"@babel/plugin-proposal-object-rest-spread": "^7.4.4",
43-
"@babel/plugin-transform-strict-mode": "^7.2.0",
44-
"@babel/register": "^7.4.4",
45-
"@makeomatic/deploy": "^8.4.4",
46-
"babel-eslint": "^10.0.2",
47-
"babel-plugin-istanbul": "^5.1.4",
48-
"codecov": "^3.5.0",
49-
"cross-env": "^5.2.0",
50-
"eslint": "^6.0.1",
51-
"eslint-config-makeomatic": "^3.0.0",
52-
"eslint-plugin-import": "^2.18.0",
53-
"eslint-plugin-mocha": "^5.3.0",
39+
"@babel/cli": "^7.8.3",
40+
"@babel/core": "^7.8.3",
41+
"@babel/plugin-proposal-class-properties": "^7.8.3",
42+
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
43+
"@babel/plugin-transform-strict-mode": "^7.8.3",
44+
"@babel/register": "^7.8.3",
45+
"@makeomatic/deploy": "^10.0.1",
46+
"babel-plugin-istanbul": "^6.0.0",
47+
"codecov": "^3.6.2",
48+
"cross-env": "^6.0.3",
49+
"eslint": "^6.8.0",
50+
"eslint-config-makeomatic": "^4.0.0",
51+
"eslint-plugin-import": "^2.20.0",
52+
"eslint-plugin-mocha": "^6.2.2",
5453
"eslint-plugin-promise": "^4.2.1",
55-
"ioredis": "^4.10.0",
56-
"mocha": "^6.1.4",
57-
"nyc": "^14.1.1",
58-
"rimraf": "^2.6.3"
54+
"ioredis": "^4.14.1",
55+
"mocha": "^7.0.0",
56+
"nyc": "^15.0.0",
57+
"rimraf": "^3.0.0"
5958
},
6059
"dependencies": {
61-
"@hapi/joi": "^15.0.0",
62-
"base64-url": "^2.2.2",
63-
"chance": "^1.0.18",
60+
"@hapi/joi": "^17.1.0",
61+
"base64-url": "^2.3.3",
62+
"chance": "^1.1.4",
6463
"get-value": "^3.0.1",
65-
"glob": "^7.1.4",
64+
"glob": "^7.1.6",
6665
"is": "^3.3.0",
6766
"lodash.compact": "^3.0.1",
6867
"lodash.omit": "^4.5.0",
69-
"uuid": "^3.3.2"
68+
"uuid": "^3.4.0"
7069
},
7170
"files": [
7271
"src/",

src/actions/create.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@ const schema = Joi
2323
.min(0)
2424
.max(Joi.ref('ttl')),
2525
Joi.boolean()
26-
.only(true)
26+
.valid(true)
2727
),
2828

2929
metadata: Joi.any(),
3030

31+
legacy: Joi.boolean().default(false),
32+
3133
secret: Joi.alternatives()
3234
.try(
3335
Joi.boolean(),
3436
Joi.object({
3537
type: Joi.string()
36-
.only(['alphabet', 'number', 'uuid'])
38+
.valid('alphabet', 'number', 'uuid')
3739
.required(),
3840

3941
alphabet: Joi.any()
@@ -45,16 +47,16 @@ const schema = Joi
4547

4648
length: Joi.any()
4749
.when('type', {
48-
is: Joi.string().only(['alphabet', 'number']),
50+
is: Joi.string().valid('alphabet', 'number'),
4951
then: Joi.number().integer().min(1).required(),
5052
otherwise: Joi.forbidden(),
5153
}),
5254

5355
encrypt: Joi.boolean()
5456
.when('type', {
5557
is: 'uuid',
56-
then: Joi.default(true),
57-
otherwise: Joi.default(false),
58+
then: Joi.any().default(true),
59+
otherwise: Joi.any().default(false),
5860
}),
5961
})
6062
)
@@ -63,7 +65,7 @@ const schema = Joi
6365
regenerate: Joi.boolean()
6466
.when('secret', {
6567
is: false,
66-
then: Joi.only(false),
68+
then: Joi.valid(false),
6769
otherwise: Joi.optional(),
6870
}),
6971
})
@@ -96,7 +98,7 @@ function getSecret(_secret) {
9698
module.exports = async function create(args) {
9799
const opts = Joi.attempt(args, schema);
98100

99-
const { action, id, ttl, metadata } = opts;
101+
const { action, id, ttl, metadata, legacy } = opts;
100102
const throttle = getThrottle(opts.throttle, ttl);
101103
const uid = opts.regenerate ? uuid.v4() : false;
102104
const secret = getSecret(opts.secret);
@@ -125,7 +127,7 @@ module.exports = async function create(args) {
125127

126128
if (secret) {
127129
settings.secret = secret;
128-
output.secret = crypto.secret(this.encrypt, secret, { id, action, uid });
130+
output.secret = await crypto.secret(this.encrypt, secret, { id, action, uid }, legacy);
129131
}
130132

131133
await this.backend.create(settings, output);

src/actions/info.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const schema = Joi.alternatives()
4242
.required(),
4343

4444
encrypt: Joi.bool()
45-
.only(true)
45+
.valid(true)
4646
.required(),
4747
}),
4848

@@ -59,7 +59,7 @@ const schema = Joi.alternatives()
5959
.required(),
6060

6161
encrypt: Joi.bool()
62-
.only(false)
62+
.valid(false)
6363
.required(),
6464
})
6565
);

src/actions/regenerate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const schema = Joi.alternatives()
1818
);
1919

2020
// helper function used to generate new secret
21-
const generateSecret = encrypt => (id, action, uid, secret) => (
21+
const generateSecret = (encrypt) => async (id, action, uid, secret) => (
2222
crypto.secret(encrypt, secret, { id, action, uid })
2323
);
2424

src/actions/verify.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@ const optsSchema = Joi.object({
3333
.default({}),
3434
});
3535

36-
/**
37-
* Enriches error and includes args into it
38-
* @param {Error} e
39-
*/
40-
function enrichError(e) {
41-
e.args = this;
42-
throw e;
43-
}
44-
4536
/**
4637
* Parses input options
4738
* @param {Function}
@@ -83,12 +74,13 @@ function assertControlOptions(args, opts) {
8374
* @param {Object} [_opts={}]
8475
* @return {Promise}
8576
*/
86-
module.exports = async function create(_args, _opts = {}) {
77+
module.exports = async function verify(_args, _opts = {}) {
8778
const { args, opts } = parseInput(this.decrypt, _args, _opts);
8879
assertControlOptions(args, opts);
8980
try {
9081
return await this.backend.verify(args, opts);
9182
} catch (e) {
92-
return enrichError.call(args, e);
83+
e.args = args;
84+
throw e;
9385
}
9486
};

src/backends/redis/redis.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,14 @@ class RedisBackend {
3131
}
3232
}
3333

34-
static RESERVED_PROPS = {
35-
id: String,
36-
action: String,
37-
secret: String,
38-
uid: String,
39-
created: Number,
40-
verified: Number,
41-
isFirstVerification: Boolean,
42-
throttleKey: String,
43-
};
44-
45-
// static instance of error
46-
static Unauthorized = new Error(403);
47-
4834
// quick helpers
49-
static serialize = data => JSON.stringify(data);
35+
static serialize(data) {
36+
return JSON.stringify(data);
37+
}
5038

51-
static deserialize = data => JSON.parse(data);
39+
static deserialize(data) {
40+
return JSON.parse(data);
41+
}
5242

5343
// redis key helpers
5444
key(...args) {
@@ -116,7 +106,7 @@ class RedisBackend {
116106
const { id, action, uid } = data;
117107
const secretSettings = RedisBackend.deserialize(data.settings);
118108
const oldSecret = data.secret;
119-
const newSecret = updateSecret(id, action, uid, secretSettings);
109+
const newSecret = await updateSecret(id, action, uid, secretSettings);
120110

121111
// redis keys
122112
const idKey = this.key(action, id);
@@ -212,4 +202,18 @@ class RedisBackend {
212202
}
213203
}
214204

205+
RedisBackend.RESERVED_PROPS = {
206+
id: String,
207+
action: String,
208+
secret: String,
209+
uid: String,
210+
created: Number,
211+
verified: Number,
212+
isFirstVerification: Boolean,
213+
throttleKey: String,
214+
};
215+
216+
// static instance of error
217+
RedisBackend.Unauthorized = new Error(403);
218+
215219
module.exports = RedisBackend;

src/defaults.js

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,50 @@
11
const Joi = require('@hapi/joi');
2+
const Redis = require('ioredis');
23
const glob = require('glob');
34
const path = require('path');
45
const pkg = require('../package.json');
56

67
const backends = glob
78
.sync('*', { cwd: path.join(__dirname, 'backends') })
8-
.map(filename => path.basename(filename, '.js'));
9-
10-
const schema = Joi
11-
.object({
12-
backend: {
13-
name: Joi.string()
14-
.only(backends)
15-
.required(),
16-
17-
connection: Joi.any()
18-
.required()
19-
.when('name', {
20-
is: 'redis',
21-
then: Joi.lazy(() => {
22-
const Redis = require('ioredis');
23-
24-
return Joi.alternatives()
25-
.try(
26-
Joi.object().type(Redis),
27-
Joi.object().type(Redis.Cluster)
28-
);
29-
}),
30-
}),
31-
32-
prefix: Joi.string()
33-
.default(`{ms-token!${pkg.version}}`),
34-
},
35-
36-
encrypt: {
37-
38-
algorithm: Joi.string()
39-
.required(),
40-
41-
sharedSecret: Joi.binary()
42-
.encoding('utf8')
43-
.min(24)
44-
.required(),
45-
},
46-
})
47-
.requiredKeys(['', 'backend', 'encrypt']);
48-
49-
module.exports = opts => Joi.attempt(opts, schema);
9+
.map((filename) => path.basename(filename, '.js'));
10+
11+
const secret = Joi.binary()
12+
.encoding('utf8')
13+
.required();
14+
15+
const schema = Joi.object({
16+
backend: Joi.object({
17+
name: Joi.string()
18+
.valid(...backends)
19+
.required(),
20+
21+
connection: Joi.any()
22+
.required()
23+
.when('name', {
24+
is: 'redis',
25+
then: Joi.alternatives().try(
26+
Joi.object().instance(Redis),
27+
Joi.object().instance(Redis.Cluster)
28+
),
29+
}),
30+
31+
prefix: Joi.string()
32+
.default(`{ms-token!${pkg.version}}`),
33+
}).required(),
34+
35+
encrypt: Joi.object({
36+
37+
algorithm: Joi.string()
38+
.required(),
39+
40+
sharedSecret: Joi.alternatives().try(
41+
secret.min(32),
42+
Joi.object({
43+
legacy: secret.min(24),
44+
current: secret.min(32),
45+
})
46+
).required(),
47+
}).required(),
48+
}).required();
49+
50+
module.exports = (opts) => Joi.attempt(opts, schema);

0 commit comments

Comments
 (0)