You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

323 lines
8.4 KiB

/*!
* lib/auth/authorizations-manager.js
* Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
*/
'use strict'
const validator = require('validator')
const jwt = require('jsonwebtoken')
const network = require('../bitcoin/network')
const keys = require('../../keys/')[network.key]
const errors = require('../errors')
const Logger = require('../logger')
/**
* A singleton managing authorizations the API
*/
class AuthorizationsManager {
/**
* Constructor
*/
constructor() {
try {
// Constants
this.ISS = 'Samourai Wallet backend'
this.TOKEN_TYPE_ACCESS = 'access-token'
this.TOKEN_TYPE_REFRESH = 'refresh-token'
this.TOKEN_PROFILE_API = 'api'
this.TOKEN_PROFILE_ADMIN = 'admin'
this.authActive = (keys.auth.activeStrategy != null)
this._secret = keys.auth.jwt.secret
this.isMandatory = keys.auth.mandatory
this.accessTokenExpires = keys.auth.jwt.accessToken.expires
this.refreshTokenExpires = keys.auth.jwt.refreshToken.expires
} catch(e) {
this._secret = null
Logger.error(e, errors.auth.INVALID_CONF)
}
}
/**
* Middleware generating authorization token
* @param {Object} req - http request object
* @param {Object} res - http response object
* @param {function} next - callback
*/
generateAuthorizations(req, res, next) {
if (!(req.user && req.user.authenticated))
return next(errors.auth.TECH_ISSUE)
// Generates an access token
const accessToken = this._generateAccessToken(req.user)
// Generates a refresh token
const refreshToken = this._generateRefreshToken(req.user)
// Stores the tokens in the request
req.authorizations = {
access_token: accessToken,
refresh_token: refreshToken
}
next()
}
/**
* Middleware refreshing authorizations
* @param {Object} req - http request object
* @param {Object} res - http response object
* @param {function} next - callback
*/
refreshAuthorizations(req, res, next) {
// Check if authentication is activated
if (!this.authActive)
return next()
// Authentication is activated
// A refresh token is required
const refreshToken = this._extractRefreshToken(req)
if (!refreshToken)
return next(errors.auth.MISSING_JWT)
try {
const decodedRefrehToken = this._verifyRefreshToken(refreshToken)
if (req.user == null)
req.user = {}
req.user['profile'] = decodedRefrehToken['prf']
} catch(e) {
Logger.error(e, `${errors.auth.INVALID_JWT}: ${refreshToken}`)
return next(errors.auth.INVALID_JWT)
}
// Generates a new access token
const accessToken = this._generateAccessToken(req.user)
// Stores the access token in the request
req.authorizations = {
access_token: accessToken
}
next()
}
/**
* Middleware revoking authorizations
* @param {Object} req - http request object
* @param {Object} res - http response object
* @param {function} next - callback
*/
revokeAuthorizations(req, res, next) {
// Nothing to do (for now)
}
/**
* Middleware checking if user is authenticated
* @param {Object} req - http request object
* @param {Object} res - http response object
* @param {function} next - callback
* @returns {boolean} returns true if user is authenticated, false otherwise
*/
checkAuthentication(req, res, next) {
// Check if authentication is activated
if (!this.authActive)
return next()
// Authentication is activated
// A JSON web token is required
const token = this._extractAccessToken(req)
if (this.isMandatory || token) {
try {
const decodedToken = this.isAuthenticated(token)
req.authorizations = {decoded_access_token: decodedToken}
next()
} catch (e) {
return next(e)
}
} else {
next()
}
}
/**
* Middleware checking if user is authenticated and has admin profile
* @param {Object} req - http request object
* @param {Object} res - http response object
* @param {function} next - callback
* @returns {boolean} returns true if user is authenticated and has admin profile, false otherwise
*/
checkHasAdminProfile(req, res, next) {
// Check if authentication is activated
if (!this.authActive)
return next()
// Authentication is activated
// A JSON web token is required
const token = this._extractAccessToken(req)
try {
const decodedToken = this.isAuthenticated(token)
if (decodedToken['prf'] == this.TOKEN_PROFILE_ADMIN) {
req.authorizations = {decoded_access_token: decodedToken}
next()
} else {
return next(errors.auth.INVALID_PRF)
}
} catch (e) {
return next(e)
}
}
/**
* Check if user is authenticated
* (i.e. we have received a valid json web token)
* @param {string} token - json web token
* @returns {boolean} returns the decoded token if valid
* throws an exception otherwise
*/
isAuthenticated(token) {
if (!token) {
Logger.error(null, `${errors.auth.MISSING_JWT}`)
throw errors.auth.MISSING_JWT
}
try {
return this._verifyAccessToken(token)
} catch(e) {
//Logger.error(e, `${errors.auth.INVALID_JWT}: ${token}`)
throw errors.auth.INVALID_JWT
}
}
/**
* Generate an access token
* @param {Object} user - user's information
* @returns {Object} returns a json web token
*/
_generateAccessToken(user) {
// Builds claims
const claims = {
'iss': this.ISS,
'type': this.TOKEN_TYPE_ACCESS,
'prf': user['profile']
}
// Builds and signs the access token
return jwt.sign(
claims,
this._secret,
{expiresIn: this.accessTokenExpires}
)
}
/**
* Extract the access token from the http request
* @param {Object} req - http request object
* @returns {Object} returns the json web token
*/
_extractAccessToken(req) {
const token = this._extractBearerAuthorizationHeader(req)
if (token)
return token
if (req.body && req.body.at && validator.isJWT(req.body.at))
return req.body.at
if (req.query && req.query.at && validator.isJWT(req.query.at))
return req.query.at
return null
}
/**
* Verify an access token
* @param {Object} token - json web token
* @returns {Object} payload of the json web token
*/
_verifyAccessToken(token) {
const payload = jwt.verify(token, this._secret, {})
if (payload['type'] != this.TOKEN_TYPE_ACCESS)
throw errors.auth.INVALID_JWT
return payload
}
/**
* Generate an refresh token
* @param {Object} user - user's information
* @returns {Object} returns a json web token
*/
_generateRefreshToken(user) {
// Builds claims
const claims = {
'iss': this.ISS,
'type': this.TOKEN_TYPE_REFRESH,
'prf': user['profile']
}
// Builds and signs the access token
return jwt.sign(
claims,
this._secret,
{expiresIn: this.refreshTokenExpires}
)
}
/**
* Extract the refresh token from the http request
* @param {Object} req - http request object
* @returns {Object} returns the json web token
*/
_extractRefreshToken(req) {
const token = this._extractBearerAuthorizationHeader(req)
if (token)
return token
if (req.body && req.body.rt && validator.isJWT(req.body.rt))
return req.body.rt
if (req.query && req.query.rt && validator.isJWT(req.query.rt))
return req.query.rt
return null
}
/**
* Verify a refresh token
* @param {Object} token - json web token
* @returns {Object} payload of the json web token
*/
_verifyRefreshToken(token) {
const payload = jwt.verify(token, this._secret, {})
if (payload['type'] != this.TOKEN_TYPE_REFRESH)
throw errors.auth.INVALID_JWT
return payload
}
/**
* Extract a bearer JWT auth token
* from the Authorization HTTP header
* Returns null if it doesn't exist or is an onvalid JWT
* @param {Object} req - http request object
* @returns {Object} returns the json web token
*/
_extractBearerAuthorizationHeader(req) {
if (req.get('Authorization')) {
const authHeader = req.get('Authorization')
if (authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7)
if (validator.isJWT(token))
return token
}
}
return null
}
}
module.exports = new AuthorizationsManager()