"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
const roles_1 = require("@devlego/backend-core/roles");
const { PermissionTypes, PermissionLevels, doesHaveBasePermission, } = require("@devlego/backend-core/permissions");
const builderMiddleware = require("./builder");
const { isWebhookEndpoint } = require("./utils");
const { buildCsrfMiddleware } = require("@devlego/backend-core/auth");
const { getAppId } = require("@devlego/backend-core/context");
function hasResource(ctx) {
    return ctx.resourceId != null;
}
const csrf = buildCsrfMiddleware();
/**
 * Apply authorization to the requested resource:
 * - If this is a builder resource the user must be a builder.
 * - Builders can access all resources.
 * - Otherwise the user must have the required role.
 */
const checkAuthorized = (ctx, resourceRoles, permType, permLevel) => __awaiter(void 0, void 0, void 0, function* () {
    // check if this is a builder api and the user is not a builder
    const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global;
    const isBuilderApi = permType === PermissionTypes.BUILDER;
    if (isBuilderApi && !isBuilder) {
        return ctx.throw(403, "Not Authorized");
    }
    // check for resource authorization
    if (!isBuilder) {
        yield checkAuthorizedResource(ctx, resourceRoles, permType, permLevel);
    }
});
const checkAuthorizedResource = (ctx, resourceRoles, permType, permLevel) => __awaiter(void 0, void 0, void 0, function* () {
    // get the user's roles
    const roleId = ctx.roleId || roles_1.BUILTIN_ROLE_IDS.PUBLIC;
    const userRoles = yield (0, roles_1.getUserRoleHierarchy)(roleId, {
        idOnly: false,
    });
    const permError = "User does not have permission";
    // check if the user has the required role
    if (resourceRoles.length > 0) {
        // deny access if the user doesn't have the required resource role
        const found = userRoles.find((role) => resourceRoles.indexOf(role._id) !== -1);
        if (!found) {
            ctx.throw(403, permError);
        }
        // fallback to the base permissions when no resource roles are found
    }
    else if (!doesHaveBasePermission(permType, permLevel, userRoles)) {
        ctx.throw(403, permError);
    }
});
module.exports = (permType, permLevel = null, opts = { schema: false }) => (ctx, next) => __awaiter(void 0, void 0, void 0, function* () {
    // webhooks don't need authentication, each webhook unique
    // also internal requests (between services) don't need authorized
    if (isWebhookEndpoint(ctx) || ctx.internal) {
        return next();
    }
    if (!ctx.user) {
        return ctx.throw(403, "No user info found");
    }
    // check general builder stuff, this middleware is a good way
    // to find API endpoints which are builder focused
    yield builderMiddleware(ctx, permType);
    // get the resource roles
    let resourceRoles = [];
    let otherLevelRoles = [];
    const otherLevel = permLevel === PermissionLevels.READ
        ? PermissionLevels.WRITE
        : PermissionLevels.READ;
    const appId = getAppId();
    if (appId && hasResource(ctx)) {
        resourceRoles = yield (0, roles_1.getRequiredResourceRole)(permLevel, ctx);
        if (opts && opts.schema) {
            otherLevelRoles = yield (0, roles_1.getRequiredResourceRole)(otherLevel, ctx);
        }
    }
    // if the resource is public, proceed
    if (resourceRoles.includes(roles_1.BUILTIN_ROLE_IDS.PUBLIC) ||
        (otherLevelRoles && otherLevelRoles.includes(roles_1.BUILTIN_ROLE_IDS.PUBLIC))) {
        return next();
    }
    // check authenticated
    if (!ctx.isAuthenticated) {
        return ctx.throw(403, "Session not authenticated");
    }
    try {
        // check authorized
        yield checkAuthorized(ctx, resourceRoles, permType, permLevel);
    }
    catch (err) {
        // this is a schema, check if
        if (opts && opts.schema && permLevel) {
            yield checkAuthorized(ctx, otherLevelRoles, permType, otherLevel);
        }
        else {
            throw err;
        }
    }
    // csrf protection
    return csrf(ctx, next);
});
