Example #1
0
internals.writeHead = function (response) {

    const res = response.request.raw.res;
    const headers = Object.keys(response.headers);
    let i = 0;

    try {
        for (; i < headers.length; ++i) {
            const header = headers[i];
            const value = response.headers[header];
            if (value !== undefined) {
                res.setHeader(header, value);
            }
        }
    }
    catch (err) {
        for (--i; i >= 0; --i) {
            res.removeHeader(headers[i]);       // Undo headers
        }

        throw Boom.boomify(err);
    }

    if (response.settings.message) {
        res.statusMessage = response.settings.message;
    }

    try {
        res.writeHead(response.statusCode);
    }
    catch (err) {
        throw Boom.boomify(err);
    }
};
Example #2
0
    static wrap(result, request) {

        if (result instanceof internals.Response ||
            typeof result === 'symbol') {

            return result;
        }

        if (result instanceof Error) {
            return Boom.boomify(result);
        }

        return new internals.Response(result, request);
    }
Example #3
0
    _prepare() {

        this._passThrough();

        if (!this._processors.prepare) {
            return this;
        }

        try {
            return this._processors.prepare(this);
        }
        catch (err) {
            throw Boom.boomify(err);
        }
    }
Example #4
0
internals.end = function (env, event, err) {

    const { request, stream, team } = env;

    if (!team) {                                                        // Used instead of cleaning up emitter listeners
        return;
    }

    env.team = null;

    if (request.raw.res.finished) {
        if (event !== 'aborted') {
            request.info.responded = Date.now();
        }

        team.attend();
        return;
    }

    if (err) {
        request.raw.res.destroy();
        Response.drain(stream);
    }

    err = err || new Boom(`Request ${event}`, { statusCode: request.route.settings.response.disconnectStatusCode });
    const error = internals.error(Boom.boomify(err), request);
    request._setResponse(error);

    if (request.raw.res[Config.symbol]) {
        request.raw.res.statusCode = error.statusCode;
        request.raw.res[Config.symbol].result = error.source;       // Force injected response to error
    }

    if (event) {
        request._log(['response', 'error', event]);
    }
    else {
        request._log(['response', 'error'], err);
    }

    request.raw.res.end();                                          // Triggers injection promise resolve
    team.attend();
};
Example #5
0
    async _marshal() {

        let source = this.source;

        // Processor marshal

        if (this._processors.marshal) {
            try {
                source = await this._processors.marshal(this);
            }
            catch (err) {
                throw Boom.boomify(err);
            }
        }

        // Stream source

        if (source instanceof Stream) {
            if (typeof source._read !== 'function') {
                throw Boom.badImplementation('Stream must have a readable interface');
            }

            if (source._readableState.objectMode) {
                throw Boom.badImplementation('Cannot reply with stream in object mode');
            }

            this._payload = source;
            return;
        }

        // Plain source (non string or null)

        const jsonify = (this.variety === 'plain' && source !== null && typeof source !== 'string');

        if (!jsonify &&
            this.settings.stringify) {

            throw Boom.badImplementation('Cannot set formatting options on non object response');
        }

        let payload = source;

        if (jsonify) {
            const options = this.settings.stringify || {};
            const space = options.space || this.request.route.settings.json.space;
            const replacer = options.replacer || this.request.route.settings.json.replacer;
            const suffix = options.suffix || this.request.route.settings.json.suffix || '';
            const escape = this.request.route.settings.json.escape || false;

            try {
                if (replacer || space) {
                    payload = JSON.stringify(payload, replacer, space);
                }
                else {
                    payload = JSON.stringify(payload);
                }
            }
            catch (err) {
                throw Boom.boomify(err);
            }

            if (suffix) {
                payload = payload + suffix;
            }

            if (escape) {
                payload = Hoek.escapeJson(payload);
            }
        }

        this._payload = new internals.Response.Payload(payload, this.settings);
    }
Example #6
0
exports.authenticate = async function (req, credentialsFunc, options) {

    options = options || {};

    // Default options

    options.timestampSkewSec = options.timestampSkewSec || 60;                                                  // 60 seconds

    // Application time

    const now = Utils.now(options.localtimeOffsetMsec);                           // Measure now before any other processing

    // Convert node Http request object to a request configuration object

    const request = Utils.parseRequest(req, options);

    // Parse HTTP Authorization header

    const attributes = Utils.parseAuthorizationHeader(request.authorization);

    // Construct artifacts container

    const artifacts = {
        method: request.method,
        host: request.host,
        port: request.port,
        resource: request.url,
        ts: attributes.ts,
        nonce: attributes.nonce,
        hash: attributes.hash,
        ext: attributes.ext,
        app: attributes.app,
        dlg: attributes.dlg,
        mac: attributes.mac,
        id: attributes.id
    };

    // Verify required header attributes

    if (!attributes.id ||
        !attributes.ts ||
        !attributes.nonce ||
        !attributes.mac) {

        throw Boom.badRequest('Missing attributes', { decorate: { artifacts } });
    }

    // Fetch Hawk credentials

    try {
        var credentials = await credentialsFunc(attributes.id);
    }
    catch (err) {
        throw Boom.boomify(err, { decorate: { artifacts } });
    }

    if (!credentials) {
        throw Object.assign(Utils.unauthorized('Unknown credentials'), { artifacts });
    }

    const result = { credentials, artifacts };

    if (!credentials.key ||
        !credentials.algorithm) {

        throw new Boom('Invalid credentials', { decorate: result });
    }

    if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
        throw new Boom('Unknown algorithm', { decorate: result });
    }

    // Calculate MAC

    const mac = Crypto.calculateMac('header', credentials, artifacts);
    if (!Cryptiles.fixedTimeComparison(mac, attributes.mac)) {
        throw Object.assign(Utils.unauthorized('Bad mac'), result);
    }

    // Check payload hash

    if (options.payload ||
        options.payload === '') {

        if (!attributes.hash) {
            throw Object.assign(Utils.unauthorized('Missing required payload hash'), result);
        }

        const hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, request.contentType);
        if (!Cryptiles.fixedTimeComparison(hash, attributes.hash)) {
            throw Object.assign(Utils.unauthorized('Bad payload hash'), result);
        }
    }

    // Check nonce

    if (options.nonceFunc) {
        try {
            await options.nonceFunc(credentials.key, attributes.nonce, attributes.ts);
        }
        catch (err) {
            throw Object.assign(Utils.unauthorized('Invalid nonce'), result);
        }
    }

    // Check timestamp staleness

    if (Math.abs((attributes.ts * 1000) - now) > (options.timestampSkewSec * 1000)) {
        const tsm = Crypto.timestampMessage(credentials, options.localtimeOffsetMsec);
        throw Object.assign(Utils.unauthorized('Stale timestamp', tsm), result);
    }

    // Successful authentication

    return result;
};