"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrantAuthorization = void 0;
const dwn_error_js_1 = require("./dwn-error.js");
const message_js_1 = require("./message.js");
class GrantAuthorization {
    /**
     * Performs PermissionsGrant-based authorization against the given message
     * Does not validate grant `conditions` or `scope` beyond `interface` and `method`
     * @throws {Error} if authorization fails
     */
    static authorizeGenericMessage(tenant, incomingMessage, author, messageStore) {
        return __awaiter(this, void 0, void 0, function* () {
            const permissionsGrantId = incomingMessage.authorizationPayload.permissionsGrantId;
            // Fetch grant
            const permissionsGrantMessage = yield GrantAuthorization.fetchGrant(tenant, author, messageStore, permissionsGrantId);
            // verify that grant is active during incomingMessage's timestamp
            yield GrantAuthorization.verifyGrantActive(tenant, incomingMessage.message, permissionsGrantMessage, permissionsGrantId, messageStore);
            // Check grant scope for interface and method
            yield GrantAuthorization.verifyGrantScopeInterfaceAndMethod(incomingMessage.message, permissionsGrantMessage, permissionsGrantId);
        });
    }
    /**
     * Fetches PermissionsGrantMessage with CID `permissionsGrantId`, and validates that message author may use the grant
     * for this tenant.
     * @returns the PermissionsGrantMessage with CID `permissionsGrantId` if message exists
     * @throws {Error} if PermissionsGrantMessage with CID `permissionsGrantId` does not exist
     */
    static fetchGrant(tenant, author, messageStore, permissionsGrantId) {
        return __awaiter(this, void 0, void 0, function* () {
            const possibleGrantMessage = yield messageStore.get(tenant, permissionsGrantId);
            const dwnInterface = possibleGrantMessage === null || possibleGrantMessage === void 0 ? void 0 : possibleGrantMessage.descriptor.interface;
            const dwnMethod = possibleGrantMessage === null || possibleGrantMessage === void 0 ? void 0 : possibleGrantMessage.descriptor.method;
            if (possibleGrantMessage === undefined || dwnInterface !== message_js_1.DwnInterfaceName.Permissions || dwnMethod !== message_js_1.DwnMethodName.Grant) {
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationGrantMissing, `Could not find PermissionsGrant with CID ${permissionsGrantId}`);
            }
            const permissionsGrantMessage = possibleGrantMessage;
            // Validate `grantedTo`
            if (permissionsGrantMessage.descriptor.grantedTo !== author) {
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationNotGrantedToAuthor, `PermissionsGrant with CID ${permissionsGrantId} is not granted to ${author}`);
            }
            // Validate `grantedFor`
            if (permissionsGrantMessage.descriptor.grantedFor !== tenant) {
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationNotGrantedForTenant, `PermissionsGrant with CID ${permissionsGrantId} is not granted for DWN belonging to ${tenant}`);
            }
            return permissionsGrantMessage;
        });
    }
    /**
     * Verify that the incoming message is within the allowed time frame of the grant,
     * and the grant has not been revoked.
     * @throws {Error} if incomingMessage has timestamp for a time in which the grant is not active.
     */
    static verifyGrantActive(tenant, incomingMessage, permissionsGrantMessage, permissionsGrantId, messageStore) {
        return __awaiter(this, void 0, void 0, function* () {
            // Check that incomingMessage is within the grant's timeframe
            if (incomingMessage.descriptor.messageTimestamp < permissionsGrantMessage.descriptor.messageTimestamp) {
                // grant is not yet active
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationGrantNotYetActive, `The message has a timestamp before the associated PermissionsGrant becomes active`);
            }
            else if (incomingMessage.descriptor.messageTimestamp >= permissionsGrantMessage.descriptor.dateExpires) {
                // grant has expired
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationGrantExpired, `The message has timestamp after the expiry of the associated PermissionsGrant`);
            }
            // Check if grant has been revoked
            const query = {
                interface: message_js_1.DwnInterfaceName.Permissions,
                method: message_js_1.DwnMethodName.Revoke,
                permissionsGrantId,
            };
            const revokes = yield messageStore.query(tenant, query);
            const oldestExistingRevoke = yield message_js_1.Message.getOldestMessage(revokes);
            if (oldestExistingRevoke !== undefined && oldestExistingRevoke.descriptor.messageTimestamp <= incomingMessage.descriptor.messageTimestamp) {
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationGrantRevoked, `PermissionsGrant with CID ${permissionsGrantId} has been revoked`);
            }
        });
    }
    /**
     * Verify that the `interface` and `method` grant scopes match the incoming message
     * @throws {Error} if the `interface` and `method` of the incoming message do not match the scope of the PermissionsGrant
     */
    static verifyGrantScopeInterfaceAndMethod(incomingMessage, permissionsGrantMessage, permissionsGrantId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (incomingMessage.descriptor.interface !== permissionsGrantMessage.descriptor.scope.interface) {
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationInterfaceMismatch, `DWN Interface of incoming message is outside the scope of PermissionsGrant with CID ${permissionsGrantId}`);
            }
            else if (incomingMessage.descriptor.method !== permissionsGrantMessage.descriptor.scope.method) {
                throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.GrantAuthorizationMethodMismatch, `DWN Method of incoming message is outside the scope of PermissionsGrant with CID ${permissionsGrantId}`);
            }
        });
    }
}
exports.GrantAuthorization = GrantAuthorization;
//# sourceMappingURL=grant-authorization.js.map