

var _ = require('underscore');
var BasicStrategy = require('passport-http').BasicStrategy;
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
var oauth2orize = require('oauth2orize');
var passport = require('passport');

var log = require('oae-logger').logger('oae-authentication');
var OAE = require('oae-util/lib/oae');
var OaeServer = require('oae-util/lib/server');

var OAuthDAO = require('./internal/dao');
var OAuthAPI = require('./api');


/////////////////
// OAuth setup //
/////////////////

// The OAuth instance that will take care of creating authorization codes and access tokens
// As we're only use the Client Credentials Grant, there is no need yet to add client (de)serializers
var server = oauth2orize.createServer();


//////////////////////////////
// Client Credentials Grant //
//////////////////////////////

/*!
 * The following exchange is called the "Client Credentials Grant". In it, a client
 * exchanges its id and secret for an access token. Typically this is only used to update
 * the client's information. However, within OAE all API requests need to be either anonymous
 * or authenticated as a user. We deviate from the specification slightly and will bind each
 * client to a user. When a client requests an access token via the Client Credentials Grant,
 * we will grant an access token for the user who "owns" the client.
 *
 * By the time this method gets called, the client credentials have already been validated
 * and we can simply return an "Access Token".
 *
 * @param  {Client}         client              The client who desires an access token
 * @param  {[type]}         [scope]             The scope of access requested by the client. This is unused within OAE
 * @param  {Function}       callback            Standard callback function
 * @param  {Object}         callback.err        An error that occurred, if any
 * @param  {AccessToken}    callback.token      An access token that can be used to interact with the OAE apis as a user
 */
server.exchange(oauth2orize.exchange.clientCredentials({'userProperty': 'oaeAuthInfo'}, function(client, scope, callback) {
    // In theory, each client should cache their access token, but that's probably a pipedream
    // We should check if this client has a token already so we don't generate a new one each time
    OAuthDAO.AccessTokens.getAccessTokenForUserAndClient(client.userId, client.id, function(err, accessToken) {
        if (err) {
            return callback(err);

        // This client has a token, return it
        } else if (accessToken) {
            return callback(null, accessToken.token);
        }

        // This is the first time this client is requesting a token, we'll need to generate one
        var token = OAuthAPI.generateToken(256);
        OAuthDAO.AccessTokens.createAccessToken(token, client.userId, client.id, function(err) {
            if (err) {
                return callback(err);
            }

            // Return an access token to the client
            log().info({'client': client.id, 'user': client.userId}, 'An access token has been handed out via Client Credentials');
            return callback(null, token);
        });
    });
}));


//////////////////
// Access Token //
//////////////////

/**
 * Verifies that the passed in client ID and secret are correct
 *
 * @param  {String}     clientId            The ID of the client to check
 * @param  {String}     clientSecret        The secret to check
 * @param  {Function}   callback            Standard callback function
 * @param  {Object}     callback.err        An error that occurred, if any
 * @param  {Client}     callback.client     The authenticated client
 * @api private
 */
var verifyClientAuthentication = function(clientId, clientSecret, callback) {
    OAuthDAO.Clients.getClientById(clientId, function(err, client) {
        if (err) {
            return callback(err);
        } else if (!client) {
            return callback(null, false);
        } else if (client.secret != clientSecret) {
            log().warn({'client': client.id}, 'A client attempted to authenticate with the wrong secret');
            return callback(null, false);
        }

        return callback(null, client);
    });
};

/*!
 * BasicStrategy & ClientPasswordStrategy
 *
 * These strategies are used to authenticate registered OAuth clients.  They are
 * employed to protect the `token` endpoint, which consumers use to obtain
 * access tokens.  The OAuth 2.0 specification suggests that clients use the
 * HTTP Basic scheme to authenticate.  Use of the client password strategy
 * allows clients to send the same credentials in the request body (as opposed
 * to the `Authorization` header).  While this approach is not recommended by
 * the specification, in practice it is quite common.
 */
passport.use(new BasicStrategy(verifyClientAuthentication));
passport.use(new ClientPasswordStrategy(verifyClientAuthentication));

/**
 * @REST postAuthOauthV2Token
 *
 * Obtain an OAuth access token
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /auth/oauth/v2/token
 * @FormParam   {string}        grant_type      The authorization request type     [client_credentials]
 * @FormParam   {string}        client_id       The id of the OAuth client
 * @FormParam   {string}        client_secret   The secret of the OAuth client
 * @Return      {void}
 * @HttpResponse                200             token available
 * @HttpResponse                401             Unauthorized
 */
OAE.tenantRouter.on('post', '/api/auth/oauth/v2/token',
    [
        // OAuth allows for 2 possible strategies to authenticate an "Access Token Request" HTTP request
        // Currently we only support the "Client Credentials Grant".
        passport.authenticate(['basic', 'oauth2-client-password'], { 'session': false }),

        // If authentication was succesful, hand out a token
        server.token(),

        // Take care of any errors that were triggered by the token handler
        server.errorHandler()
    ]
);

/*
 * At this point, the client will not be authenticated yet, so we need
 * to add the CSRF exception
 */
OaeServer.addSafePathPrefix('/api/auth/oauth/v2/token');


//////////////////////
// Client endpoints //
//////////////////////

/**
 * @REST postAuthOauthClientsUserId
 *
 * Create an OAuth client
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /auth/oauth/clients/{userId}
 * @PathParam   {string}            userId          The id of the user for which to create an OAuth client
 * @FormParam   {string}            displayName     The name of the OAuth client
 * @Return      {OAuthClient}                       The created OAuth client
 * @HttpResponse                    201             client created
 * @HttpResponse                    400             Invalid user id was specified
 * @HttpResponse                    400             Missing or invalid displayname was provided
 * @HttpResponse                    401             Unauthorized
 */
OAE.tenantRouter.on('post', '/api/auth/oauth/clients/:userId', function(req, res) {
    OAuthAPI.Clients.createClient(req.ctx, req.params.userId, req.body.displayName, function(err, client) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        res.send(201, client);
    });
});

/**
 * @REST getAuthOauthClientsUserId
 *
 * Get all OAuth clients for a user
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /auth/oauth/clients/{userId}
 * @PathParam   {string}            userId          The id of the user for which to get the registered OAuth clients
 * @Return      {OAuthClientList}                   The registerd OAuth clients for the user
 * @HttpResponse                    200             clients available
 * @HttpResponse                    401             Unauthorized
 */
OAE.tenantRouter.on('get', '/api/auth/oauth/clients/:userId', function(req, res) {
    OAuthAPI.Clients.getClients(req.ctx, req.params.userId, function(err, clients) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        res.send(200, {'results': clients});
    });
});

/**
 * @REST postAuthOauthClientsUserIdClientId
 *
 * Update an OAuth client
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /auth/oauth/clients/{userId}/{clientId}
 * @PathParam   {string}            userId          The id of the user to which the OAuth client is associated
 * @PathParam   {string}            clientId        The id of the OAuth client to update
 * @FormParam   {string}            displayName     The updated name for the OAuth client
 * @FormParam   {string}            secret          The updated secret for the OAuth client
 * @Return      {OAuthClient}                       The updated OAuth client
 * @HttpResponse                    200             client updated
 * @HttpResponse                    401             Unauthorized
 */
OAE.tenantRouter.on('post', '/api/auth/oauth/clients/:userId/:clientId', function(req, res) {
    OAuthAPI.Clients.updateClient(req.ctx, req.params.clientId, req.body.displayName, req.body.secret, function(err, client) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        res.send(200, client);
    });
});

/**
 * @REST deleteAuthOauthClientsUserIdClientId
 *
 * Delete an OAuth client
 *
 * @Server      tenant
 * @Method      DELETE
 * @Path        /auth/oauth/clients/{userId}/{clientId}
 * @PathParam   {string}            userId          The id of the user to which the OAuth client is associated
 * @PathParam   {string}            clientId        The id of the OAuth client to delete
 * @Return      {void}
 * @HttpResponse                    200             client deleted
 * @HttpResponse                    401             Unauthorized
 */
OAE.tenantRouter.on('delete', '/api/auth/oauth/clients/:userId/:clientId', function(req, res) {
    OAuthAPI.Clients.deleteClient(req.ctx, req.params.clientId, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        res.send(200);
    });
});
