src/core/auth.js

/**
 * 验证模块
 * @module core/auth
 */

const ajv = new (require('ajv'))();
const {errorsEnum, coreOkay, coreValidate, coreThrow, coreAssert} = require('./errors');

const authenticateSchema = {
  basic: ajv.compile({
    type: 'object',
    required: ['strategy', 'payload'],
    properties: {
      strategy: {type: 'string', enum: ['username', 'email', 'jwt']},
      payload: {type: 'object'}
    },
    additionalProperties: false
  }),
  username: ajv.compile({
    type: 'object',
    required: ['username', 'password'],
    properties: {
      username: {type: 'string', pattern: '^[a-zA-Z_0-9]+$'},
      password: {type: 'string', minLength: 8}
    },
    additionalProperties: false
  }),
  email: ajv.compile({
    type: 'object',
    required: ['email', 'password'],
    properties: {
      email: {type: 'string', format: 'email'},
      password: {type: 'string', minLength: 8}
    },
    additionalProperties: false
  }),
  jwt: ajv.compile({
    type: 'object',
    required: ['jwt'],
    properties: {
      jwt: {type: 'string'}
    },
    additionalProperties: false
  })
};

/**
 * 验证用户,并颁发JWT。通过以下两种方式暴露:
 *   - ajax: POST /api/auth
 *   - socket.io: emit auth
 * @param params {object} 请求数据
 *   - data {object} 请求的data
 *     - strategy {string} 可以为`username`、`email`、`jwt`
 *     - payload {object} 如果`strategy`为`local`,则为{username,password}或
 *       {email,password};如果`strategy`为`{jwt}`则重新续`jwt`的使用时间
 * @param global {object}
 * @return {Promise<object>} data 为新的JWT。
 */
async function authenticate(params, global) {
  const {jwt, users} = global;
  coreValidate(authenticateSchema.basic, params.data);
  coreValidate(authenticateSchema[params.data.strategy], params.data.payload);
  if (params.data.strategy === 'jwt') {
    let data;
    try {
      data = await jwt.verify(params.data.payload.jwt);
    } catch (err) {
      coreThrow(errorsEnum.INVALID, err.message);
    }
    await jwt.revoke(data.uid, data.jti);
    const user = await users.findById(data.uid).notDeleted();
    coreAssert(user, errorsEnum.INVALID, 'User does not exist');
    coreAssert(user.blocked !== true, errorsEnum.INVALID, 'User blocked');
    const token = await jwt.sign({uid: data.uid, role: user.roles}, {
      expiresIn: '10d'
    });
    return coreOkay({data: token});
  } else {
    const password = params.data.payload.password;
    delete params.data.payload.password;
    const user = await users.findOne(params.data.payload).notDeleted();
    coreAssert(user, errorsEnum.INVALID, 'User does not exist');
    coreAssert(await user.checkPassword(password), errorsEnum.INVALID, 'Wrong password');
    coreAssert(user.blocked !== true, errorsEnum.INVALID, 'User blocked');
    const token = await jwt.sign({uid: user._id.toString(), role: user.roles}, {
      expiresIn: '10d'
    });
    return coreOkay({data: token});
  }
}

module.exports = {
  authenticate
};