// Load modules

var Url = require('url'); var Hoek = require('hoek'); var Cryptiles = require('cryptiles'); var Crypto = require('./crypto'); var Utils = require('./utils');

// Declare internals

var internals = {};

// Generate an Authorization header for a given request

/*

uri: 'http://example.com/resource?a=b' or object from Url.parse()
method: HTTP verb (e.g. 'GET', 'POST')
options: {
    // Required
    credentials: {
        id: 'dh37fgj492je',
        key: 'aoijedoaijsdlaksjdl',
        algorithm: 'sha256'                                 // 'sha1', 'sha256'
    },
    // Optional
    ext: 'application-specific',                        // Application specific data sent via the ext attribute
    timestamp: Date.now(),                              // A pre-calculated timestamp
    nonce: '2334f34f',                                  // A pre-generated nonce
    localtimeOffsetMsec: 400,                           // Time offset to sync with server time (ignored if timestamp provided)
    payload: '{"some":"payload"}',                      // UTF-8 encoded string for body hash generation (ignored if hash provided)
    contentType: 'application/json',                    // Payload content-type (ignored if hash provided)
    hash: 'U4MKKSmiVxk37JCCrAVIjV=',                    // Pre-calculated payload hash
    app: '24s23423f34dx',                               // Oz application id
    dlg: '234sz34tww3sd'                                // Oz delegated-by application id
}

*/

exports.header = function (uri, method, options) {

var result = {
    field: '',
    artifacts: {}
};
// Validate inputs
if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') ||
    !method || typeof method !== 'string' ||
    !options || typeof options !== 'object') {
    result.err = 'Invalid argument type';
    return result;
}
// Application time
var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);
// Validate credentials
var credentials = options.credentials;
if (!credentials ||
    !credentials.id ||
    !credentials.key ||
    !credentials.algorithm) {
    result.err = 'Invalid credential object';
    return result;
}
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
    result.err = 'Unknown algorithm';
    return result;
}
// Parse URI
if (typeof uri === 'string') {
    uri = Url.parse(uri);
}
// Calculate signature
var artifacts = {
    ts: timestamp,
    nonce: options.nonce || Cryptiles.randomString(6),
    method: method,
    resource: uri.pathname + (uri.search || ''),                            // Maintain trailing '?'
    host: uri.hostname,
    port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
    hash: options.hash,
    ext: options.ext,
    app: options.app,
    dlg: options.dlg
};
result.artifacts = artifacts;
// Calculate payload hash
if (!artifacts.hash &&
    (options.payload || options.payload === '')) {
    artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
}
var mac = Crypto.calculateMac('header', credentials, artifacts);
// Construct header
var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== '';       // Other falsey values allowed
var header = 'Hawk id="' + credentials.id +
             '", ts="' + artifacts.ts +
             '", nonce="' + artifacts.nonce +
             (artifacts.hash ? '", hash="' + artifacts.hash : '') +
             (hasExt ? '", ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) : '') +
             '", mac="' + mac + '"';
if (artifacts.app) {
    header += ', app="' + artifacts.app +
              (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';
}
result.field = header;
return result;

};

// Validate server response

/*

res:        node's response object
artifacts:  object received from header().artifacts
options: {
    payload:    optional payload received
    required:   specifies if a Server-Authorization header is required. Defaults to 'false'
}

*/

exports.authenticate = function (res, credentials, artifacts, options) {

artifacts = Hoek.clone(artifacts);
options = options || {};
if (res.headers['www-authenticate']) {
    // Parse HTTP WWW-Authenticate header
    var wwwAttributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']);
    if (wwwAttributes instanceof Error) {
        return false;
    }
    // Validate server timestamp (not used to update clock since it is done via the SNPT client)
    if (wwwAttributes.ts) {
        var tsm = Crypto.calculateTsMac(wwwAttributes.ts, credentials);
        if (tsm !== wwwAttributes.tsm) {
            return false;
        }
    }
}
// Parse HTTP Server-Authorization header
if (!res.headers['server-authorization'] &&
    !options.required) {
    return true;
}
var attributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']);
if (attributes instanceof Error) {
    return false;
}
artifacts.ext = attributes.ext;
artifacts.hash = attributes.hash;
var mac = Crypto.calculateMac('response', credentials, artifacts);
if (mac !== attributes.mac) {
    return false;
}
if (!options.payload &&
    options.payload !== '') {
    return true;
}
if (!attributes.hash) {
    return false;
}
var calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']);
return (calculatedHash === attributes.hash);

};

// Generate a bewit value for a given URI

/*

uri: 'http://example.com/resource?a=b' or object from Url.parse()
options: {
    // Required
    credentials: {
        id: 'dh37fgj492je',
        key: 'aoijedoaijsdlaksjdl',
        algorithm: 'sha256'                             // 'sha1', 'sha256'
    },
    ttlSec: 60 * 60,                                    // TTL in seconds
    // Optional
    ext: 'application-specific',                        // Application specific data sent via the ext attribute
    localtimeOffsetMsec: 400                            // Time offset to sync with server time
};

*/

exports.getBewit = function (uri, options) {

// Validate inputs
if (!uri ||
    (typeof uri !== 'string' && typeof uri !== 'object') ||
    !options ||
    typeof options !== 'object' ||
    !options.ttlSec) {
    return '';
}
options.ext = (options.ext === null || options.ext === undefined ? '' : options.ext);       // Zero is valid value
// Application time
var now = Utils.now(options.localtimeOffsetMsec);
// Validate credentials
var credentials = options.credentials;
if (!credentials ||
    !credentials.id ||
    !credentials.key ||
    !credentials.algorithm) {
    return '';
}
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
    return '';
}
// Parse URI
if (typeof uri === 'string') {
    uri = Url.parse(uri);
}
// Calculate signature
var exp = Math.floor(now / 1000) + options.ttlSec;
var mac = Crypto.calculateMac('bewit', credentials, {
    ts: exp,
    nonce: '',
    method: 'GET',
    resource: uri.pathname + (uri.search || ''),                            // Maintain trailing '?'
    host: uri.hostname,
    port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
    ext: options.ext
});
// Construct bewit: id\exp\mac\ext
var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext;
return Hoek.base64urlEncode(bewit);

};

// Generate an authorization string for a message

/*

host: 'example.com',
port: 8000,
message: '{"some":"payload"}',                          // UTF-8 encoded string for body hash generation
options: {
    // Required
    credentials: {
        id: 'dh37fgj492je',
        key: 'aoijedoaijsdlaksjdl',
        algorithm: 'sha256'                             // 'sha1', 'sha256'
    },
    // Optional
    timestamp: Date.now(),                              // A pre-calculated timestamp
    nonce: '2334f34f',                                  // A pre-generated nonce
    localtimeOffsetMsec: 400,                           // Time offset to sync with server time (ignored if timestamp provided)
}

*/

exports.message = function (host, port, message, options) {

// Validate inputs
if (!host || typeof host !== 'string' ||
    !port || typeof port !== 'number' ||
    message === null || message === undefined || typeof message !== 'string' ||
    !options || typeof options !== 'object') {
    return null;
}
// Application time
var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);
// Validate credentials
var credentials = options.credentials;
if (!credentials ||
    !credentials.id ||
    !credentials.key ||
    !credentials.algorithm) {
    // Invalid credential object
    return null;
}
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
    return null;
}
// Calculate signature
var artifacts = {
    ts: timestamp,
    nonce: options.nonce || Cryptiles.randomString(6),
    host: host,
    port: port,
    hash: Crypto.calculatePayloadHash(message, credentials.algorithm)
};
// Construct authorization
var result = {
    id: credentials.id,
    ts: artifacts.ts,
    nonce: artifacts.nonce,
    hash: artifacts.hash,
    mac: Crypto.calculateMac('message', credentials, artifacts)
};
return result;

};