"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleOAuthAuthenticate = exports.handleOAuthToken = exports.InvalidTwoFactorError = exports.MissingTwoFactorError = void 0;
const tslib_1 = require("tslib");
const oauth2_server_1 = tslib_1.__importStar(require("@node-oauth/oauth2-server"));
const core_utils_1 = require("../../helpers/core-utils");
const otp_1 = require("../../helpers/otp");
const extra_utils_1 = require("../../../shared/extra-utils");
const constants_1 = require("../../initializers/constants");
const oauth_model_1 = require("./oauth-model");
class MissingTwoFactorError extends Error {
    constructor() {
        super(...arguments);
        this.code = 401;
        this.name = 'missing_two_factor';
    }
}
exports.MissingTwoFactorError = MissingTwoFactorError;
class InvalidTwoFactorError extends Error {
    constructor() {
        super(...arguments);
        this.code = 400;
        this.name = 'invalid_two_factor';
    }
}
exports.InvalidTwoFactorError = InvalidTwoFactorError;
const oAuthServer = new oauth2_server_1.default({
    accessTokenLifetime: constants_1.OAUTH_LIFETIME.ACCESS_TOKEN,
    refreshTokenLifetime: constants_1.OAUTH_LIFETIME.REFRESH_TOKEN,
    model: require('./oauth-model')
});
function handleOAuthToken(req, options) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const request = new oauth2_server_1.Request(req);
        const { refreshTokenAuthName, bypassLogin } = options;
        if (request.method !== 'POST') {
            throw new oauth2_server_1.InvalidRequestError('Invalid request: method must be POST');
        }
        if (!request.is(['application/x-www-form-urlencoded'])) {
            throw new oauth2_server_1.InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded');
        }
        const clientId = request.body.client_id;
        const clientSecret = request.body.client_secret;
        if (!clientId || !clientSecret) {
            throw new oauth2_server_1.InvalidClientError('Invalid client: cannot retrieve client credentials');
        }
        const client = yield (0, oauth_model_1.getClient)(clientId, clientSecret);
        if (!client) {
            throw new oauth2_server_1.InvalidClientError('Invalid client: client is invalid');
        }
        const grantType = request.body.grant_type;
        if (!grantType) {
            throw new oauth2_server_1.InvalidRequestError('Missing parameter: `grant_type`');
        }
        if (!['password', 'refresh_token'].includes(grantType)) {
            throw new oauth2_server_1.UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid');
        }
        if (!client.grants.includes(grantType)) {
            throw new oauth2_server_1.UnauthorizedClientError('Unauthorized client: `grant_type` is invalid');
        }
        if (grantType === 'password') {
            return handlePasswordGrant({
                request,
                client,
                bypassLogin
            });
        }
        return handleRefreshGrant({
            request,
            client,
            refreshTokenAuthName
        });
    });
}
exports.handleOAuthToken = handleOAuthToken;
function handleOAuthAuthenticate(req, res) {
    return oAuthServer.authenticate(new oauth2_server_1.Request(req), new oauth2_server_1.Response(res));
}
exports.handleOAuthAuthenticate = handleOAuthAuthenticate;
function handlePasswordGrant(options) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { request, client, bypassLogin } = options;
        if (!request.body.username) {
            throw new oauth2_server_1.InvalidRequestError('Missing parameter: `username`');
        }
        if (!bypassLogin && !request.body.password) {
            throw new oauth2_server_1.InvalidRequestError('Missing parameter: `password`');
        }
        const user = yield (0, oauth_model_1.getUser)(request.body.username, request.body.password, bypassLogin);
        if (!user)
            throw new oauth2_server_1.InvalidGrantError('Invalid grant: user credentials are invalid');
        if (user.otpSecret) {
            if (!request.headers[constants_1.OTP.HEADER_NAME]) {
                throw new MissingTwoFactorError('Missing two factor header');
            }
            if ((yield (0, otp_1.isOTPValid)({ encryptedSecret: user.otpSecret, token: request.headers[constants_1.OTP.HEADER_NAME] })) !== true) {
                throw new InvalidTwoFactorError('Invalid two factor header');
            }
        }
        const token = yield buildToken();
        return (0, oauth_model_1.saveToken)(token, client, user, { bypassLogin });
    });
}
function handleRefreshGrant(options) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { request, client, refreshTokenAuthName } = options;
        if (!request.body.refresh_token) {
            throw new oauth2_server_1.InvalidRequestError('Missing parameter: `refresh_token`');
        }
        const refreshToken = yield (0, oauth_model_1.getRefreshToken)(request.body.refresh_token);
        if (!refreshToken) {
            throw new oauth2_server_1.InvalidGrantError('Invalid grant: refresh token is invalid');
        }
        if (refreshToken.client.id !== client.id) {
            throw new oauth2_server_1.InvalidGrantError('Invalid grant: refresh token is invalid');
        }
        if (refreshToken.refreshTokenExpiresAt && refreshToken.refreshTokenExpiresAt < new Date()) {
            throw new oauth2_server_1.InvalidGrantError('Invalid grant: refresh token has expired');
        }
        yield (0, oauth_model_1.revokeToken)({ refreshToken: refreshToken.refreshToken });
        const token = yield buildToken();
        return (0, oauth_model_1.saveToken)(token, client, refreshToken.user, { refreshTokenAuthName });
    });
}
function generateRandomToken() {
    return (0, core_utils_1.randomBytesPromise)(256)
        .then(buffer => (0, extra_utils_1.sha1)(buffer));
}
function getTokenExpiresAt(type) {
    const lifetime = type === 'access'
        ? constants_1.OAUTH_LIFETIME.ACCESS_TOKEN
        : constants_1.OAUTH_LIFETIME.REFRESH_TOKEN;
    return new Date(Date.now() + lifetime * 1000);
}
function buildToken() {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const [accessToken, refreshToken] = yield Promise.all([generateRandomToken(), generateRandomToken()]);
        return {
            accessToken,
            refreshToken,
            accessTokenExpiresAt: getTokenExpiresAt('access'),
            refreshTokenExpiresAt: getTokenExpiresAt('refresh')
        };
    });
}
//# sourceMappingURL=oauth.js.map