2017-07-26 16:52:55 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const Joi = require('joi');
|
|
|
|
const MongoPaging = require('mongo-cursor-pagination');
|
|
|
|
const ObjectID = require('mongodb').ObjectID;
|
|
|
|
|
|
|
|
module.exports = (db, server, userHandler) => {
|
|
|
|
server.post('/authenticate', (req, res, next) => {
|
|
|
|
res.charSet('utf-8');
|
|
|
|
|
|
|
|
const schema = Joi.object().keys({
|
2017-09-01 19:50:53 +08:00
|
|
|
username: Joi.alternatives()
|
|
|
|
.try(
|
|
|
|
Joi.string()
|
|
|
|
.lowercase()
|
|
|
|
.regex(/^[a-z](?:\.?[a-z0-9]+)*$/, 'username')
|
|
|
|
.min(3)
|
|
|
|
.max(30),
|
|
|
|
Joi.string().email()
|
|
|
|
)
|
|
|
|
.required(),
|
|
|
|
password: Joi.string()
|
|
|
|
.max(256)
|
|
|
|
.required(),
|
2017-07-26 16:52:55 +08:00
|
|
|
|
|
|
|
protocol: Joi.string().default('API'),
|
|
|
|
scope: Joi.string().default('master'),
|
|
|
|
|
|
|
|
ip: Joi.string().ip({
|
|
|
|
version: ['ipv4', 'ipv6'],
|
|
|
|
cidr: 'forbidden'
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
const result = Joi.validate(req.params, schema, {
|
|
|
|
abortEarly: false,
|
|
|
|
convert: true
|
|
|
|
});
|
|
|
|
|
|
|
|
if (result.error) {
|
|
|
|
res.json({
|
|
|
|
error: result.error.message
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
let meta = {
|
|
|
|
protocol: result.value.protocol,
|
|
|
|
ip: result.value.ip
|
|
|
|
};
|
|
|
|
|
|
|
|
userHandler.authenticate(result.value.username, result.value.password, result.value.scope, meta, (err, authData) => {
|
|
|
|
if (err) {
|
|
|
|
res.json({
|
|
|
|
error: err.message
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!authData) {
|
|
|
|
res.json({
|
|
|
|
error: 'Authentication failed'
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
2017-10-10 16:19:10 +08:00
|
|
|
let authResponse = {
|
2017-07-26 16:52:55 +08:00
|
|
|
success: true,
|
|
|
|
id: authData.user,
|
|
|
|
username: authData.username,
|
|
|
|
scope: authData.scope,
|
2017-08-05 20:55:29 +08:00
|
|
|
require2fa: authData.require2fa,
|
|
|
|
requirePasswordChange: authData.requirePasswordChange
|
2017-10-10 16:19:10 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
if (authData.u2fAuthRequest) {
|
|
|
|
authResponse.u2fAuthRequest = authData.u2fAuthRequest;
|
|
|
|
}
|
|
|
|
|
|
|
|
res.json(authResponse);
|
2017-07-26 16:52:55 +08:00
|
|
|
|
|
|
|
return next();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
server.get({ name: 'authlog', path: '/users/:user/authlog' }, (req, res, next) => {
|
|
|
|
res.charSet('utf-8');
|
|
|
|
|
|
|
|
const schema = Joi.object().keys({
|
2017-09-01 19:50:53 +08:00
|
|
|
user: Joi.string()
|
|
|
|
.hex()
|
|
|
|
.lowercase()
|
|
|
|
.length(24)
|
|
|
|
.required(),
|
|
|
|
action: Joi.string()
|
|
|
|
.trim()
|
|
|
|
.lowercase()
|
|
|
|
.empty('')
|
|
|
|
.max(100),
|
|
|
|
limit: Joi.number()
|
|
|
|
.default(20)
|
|
|
|
.min(1)
|
|
|
|
.max(250),
|
|
|
|
next: Joi.string()
|
|
|
|
.empty('')
|
|
|
|
.alphanum()
|
2017-09-04 19:49:04 +08:00
|
|
|
.max(1024),
|
2017-09-01 19:50:53 +08:00
|
|
|
previous: Joi.string()
|
|
|
|
.empty('')
|
|
|
|
.alphanum()
|
2017-09-04 19:49:04 +08:00
|
|
|
.max(1024),
|
2017-09-01 19:50:53 +08:00
|
|
|
page: Joi.number()
|
|
|
|
.empty('')
|
|
|
|
.default(1)
|
2017-07-26 16:52:55 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
req.query.user = req.params.user;
|
|
|
|
|
|
|
|
const result = Joi.validate(req.query, schema, {
|
|
|
|
abortEarly: false,
|
|
|
|
convert: true,
|
2017-07-30 03:08:43 +08:00
|
|
|
allowUnknown: false
|
2017-07-26 16:52:55 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
if (result.error) {
|
|
|
|
res.json({
|
|
|
|
error: result.error.message
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
let user = new ObjectID(result.value.user);
|
|
|
|
let limit = result.value.limit;
|
|
|
|
let action = result.value.action;
|
|
|
|
let page = result.value.page;
|
|
|
|
let pageNext = result.value.next;
|
2017-07-30 03:08:43 +08:00
|
|
|
let pagePrevious = result.value.previous;
|
2017-07-26 16:52:55 +08:00
|
|
|
|
2017-07-27 21:24:42 +08:00
|
|
|
db.users.collection('users').findOne({
|
2017-07-26 16:52:55 +08:00
|
|
|
_id: user
|
|
|
|
}, {
|
|
|
|
fields: {
|
|
|
|
_id: true
|
|
|
|
}
|
|
|
|
}, (err, userData) => {
|
|
|
|
if (err) {
|
|
|
|
res.json({
|
|
|
|
error: 'MongoDB Error: ' + err.message
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
if (!userData) {
|
|
|
|
res.json({
|
|
|
|
error: 'This user does not exist'
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
let filter = action
|
|
|
|
? {
|
|
|
|
user,
|
|
|
|
action
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
user
|
|
|
|
};
|
|
|
|
|
|
|
|
db.database.collection('authlog').count(filter, (err, total) => {
|
|
|
|
if (err) {
|
|
|
|
res.json({
|
|
|
|
error: err.message
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
let opts = {
|
|
|
|
limit,
|
|
|
|
query: filter,
|
|
|
|
sortAscending: false
|
|
|
|
};
|
|
|
|
|
|
|
|
if (pageNext) {
|
|
|
|
opts.next = pageNext;
|
2017-07-30 03:08:43 +08:00
|
|
|
} else if (pagePrevious) {
|
|
|
|
opts.previous = pagePrevious;
|
2017-07-26 16:52:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
MongoPaging.find(db.users.collection('authlog'), opts, (err, result) => {
|
|
|
|
if (err) {
|
|
|
|
res.json({
|
|
|
|
error: result.error.message
|
|
|
|
});
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!result.hasPrevious) {
|
|
|
|
page = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
let response = {
|
|
|
|
success: true,
|
|
|
|
action,
|
|
|
|
total,
|
|
|
|
page,
|
2017-07-30 03:08:43 +08:00
|
|
|
previousCursor: result.hasPrevious ? result.previous : false,
|
|
|
|
nextCursor: result.hasNext ? result.next : false,
|
2017-07-26 16:52:55 +08:00
|
|
|
results: (result.results || []).map(resultData => {
|
|
|
|
let response = {
|
|
|
|
id: resultData._id
|
|
|
|
};
|
|
|
|
Object.keys(resultData).forEach(key => {
|
|
|
|
if (!['_id', 'user'].includes(key)) {
|
|
|
|
response[key] = resultData[key];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return response;
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
|
|
|
res.json(response);
|
|
|
|
return next();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|