mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-12 16:15:31 +08:00
experimental user token generation on auth
This commit is contained in:
parent
bc59ac1451
commit
5fffe6fb79
8 changed files with 95 additions and 6 deletions
19
api.js
19
api.js
|
@ -247,6 +247,25 @@ server.use(
|
|||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
} else if (tokenData.ttl && isNaN(tokenData.ttl) && Number(tokenData.ttl) > 0) {
|
||||
let tokenTTL = Number(tokenData.ttl);
|
||||
let tokenLifetime = config.api.accessControl.tokenLifetime || 30 * 24 * 3600;
|
||||
|
||||
// check if token is not too old
|
||||
if (tokenLifetime < (Date.now() - Number(tokenData.created)) / 1000) {
|
||||
// token is still usable, increase session length
|
||||
try {
|
||||
await db.redis
|
||||
.multi()
|
||||
.expire('tn:token:' + tokenHash, tokenTTL)
|
||||
.expire('tn:user:' + tokenData.user, tokenTTL)
|
||||
.exec();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
req.role = tokenData.role;
|
||||
req.user = tokenData.user;
|
||||
}
|
||||
} else {
|
||||
req.role = tokenData.role;
|
||||
req.user = tokenData.user;
|
||||
|
|
|
@ -253,6 +253,12 @@
|
|||
},
|
||||
|
||||
"auth": {
|
||||
"authentication": {
|
||||
"create:any": ["*", "!token"]
|
||||
}
|
||||
},
|
||||
|
||||
"tokenAuth": {
|
||||
"authentication": {
|
||||
"create:any": ["*"]
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-02-26T12:32:31.900Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
});
|
||||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-03-20T21:18:58.140Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-02-26T12:32:31.900Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
}
|
||||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-03-20T21:18:58.140Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
}
|
||||
|
|
|
@ -32,6 +32,7 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiParam {String} password Password
|
||||
* @apiParam {String} [protocol] Application identifier for security logs
|
||||
* @apiParam {String} [scope="master"] Required scope. One of <code>master</code>, <code>imap</code>, <code>smtp</code>, <code>pop3</code>
|
||||
* @apiParam {Boolean} [token=false] If true then generates a temporary access token that is valid for this user
|
||||
* @apiParam {String} [sess] Session identifier for the logs
|
||||
* @apiParam {String} [ip] IP address for the logs
|
||||
*
|
||||
|
@ -41,6 +42,7 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiSuccess {String} scope The scope this authentication is valid for
|
||||
* @apiSuccess {String[]} require2fa List of enabled 2FA mechanisms
|
||||
* @apiSuccess {Boolean} requirePasswordChange Indicates if account hassword has been reset and should be replaced
|
||||
* @apiSuccess {String} [token] If access token was requested then this is the value to use as access token when making API requests on behalf of logged in user
|
||||
*
|
||||
* @apiError error Description of the error
|
||||
* @apiError [code] Error code
|
||||
|
@ -103,6 +105,11 @@ module.exports = (db, server, userHandler) => {
|
|||
.empty('')
|
||||
.uri(),
|
||||
|
||||
token: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
|
||||
.default(false),
|
||||
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
|
@ -123,8 +130,13 @@ module.exports = (db, server, userHandler) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
let permission = roles.can(req.role).createAny('authentication');
|
||||
|
||||
// permissions check
|
||||
req.validate(roles.can(req.role).createAny('authentication'));
|
||||
req.validate(permission);
|
||||
|
||||
// filter out unallowed fields
|
||||
result.value = permission.filter(result.value);
|
||||
|
||||
let meta = {
|
||||
protocol: result.value.protocol,
|
||||
|
@ -175,11 +187,25 @@ module.exports = (db, server, userHandler) => {
|
|||
requirePasswordChange: authData.requirePasswordChange
|
||||
};
|
||||
|
||||
if (result.value.token) {
|
||||
try {
|
||||
authResponse.token = await userHandler.generateAuthToken(authData.user);
|
||||
} catch (err) {
|
||||
let response = {
|
||||
error: err.message,
|
||||
code: 'AuthFailed' || err.code,
|
||||
id: user.toString()
|
||||
};
|
||||
res.json(response);
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
if (authData.u2fAuthRequest) {
|
||||
authResponse.u2fAuthRequest = authData.u2fAuthRequest;
|
||||
}
|
||||
|
||||
res.json(authResponse);
|
||||
res.json(permission.filter(authResponse));
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
|
|
@ -3323,6 +3323,44 @@ class UserHandler {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
async generateAuthToken(user) {
|
||||
let accessToken = crypto.randomBytes(20).toString('hex');
|
||||
let tokenHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(accessToken)
|
||||
.digest('hex');
|
||||
let key = 'tn:token:' + tokenHash;
|
||||
let ttl = config.api.accessControl.tokenTTL || 3600;
|
||||
|
||||
let tokenData = {
|
||||
user: user.toString(),
|
||||
role: 'user',
|
||||
created: Date.now(),
|
||||
ttl,
|
||||
// signature
|
||||
s: crypto
|
||||
.createHmac('sha256', config.api.accessControl.secret)
|
||||
.update(
|
||||
JSON.stringify({
|
||||
token: accessToken,
|
||||
user: user.toString(),
|
||||
role: 'user'
|
||||
})
|
||||
)
|
||||
.digest('hex')
|
||||
};
|
||||
|
||||
await this.redis
|
||||
.multi()
|
||||
.hmset(key, tokenData)
|
||||
.sadd('tn:user:' + user, tokenHash)
|
||||
.expire(key, ttl)
|
||||
.expire('tn:user:' + user, ttl)
|
||||
.exec();
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
function rateLimitResponse(res, callback) {
|
||||
|
|
Loading…
Add table
Reference in a new issue