"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/index.ts
var src_exports = {};
__export(src_exports, {
  FederatedAuthPlugin: () => FederatedAuthPlugin,
  FederatedGraphQLDataSource: () => FederatedGraphQLDataSource,
  FederatedToken: () => FederatedToken,
  GatewayAuthPlugin: () => GatewayAuthPlugin,
  PublicFederatedToken: () => PublicFederatedToken,
  TokenSigner: () => TokenSigner
});
module.exports = __toCommonJS(src_exports);

// src/datasource.ts
var import_gateway = require("@apollo/gateway");
var import_server = require("@apollo/server");

// src/jwt.ts
var import_crypto_js = __toESM(require("crypto-js"));
var jose = __toESM(require("jose"));

// src/token.ts
var import_isEqual = __toESM(require("lodash-es/isEqual"));
var FederatedToken = class {
  tokens;
  refreshTokens;
  values;
  _accessTokenModified;
  _refreshTokenModified;
  constructor() {
    this.tokens = {};
    this.refreshTokens = {};
    this.values = {};
    this._accessTokenModified = false;
    this._refreshTokenModified = false;
  }
  setAccessToken(name, token) {
    this.tokens[name] = token;
    this._accessTokenModified = true;
  }
  setRefreshToken(name, token) {
    this.refreshTokens[name] = token;
    this._refreshTokenModified = true;
  }
  isAccessTokenModified() {
    return this._accessTokenModified;
  }
  isRefreshTokenModified() {
    return this._refreshTokenModified;
  }
  loadAccessToken(at, trackModified = false) {
    const token = JSON.parse(Buffer.from(at, "base64").toString("ascii"));
    if (token.refreshTokens) {
      throw new Error("Refresh tokens are not allowed in the Access Token");
    }
    for (const k in token.tokens) {
      if (trackModified && !(0, import_isEqual.default)(this.tokens[k], token.tokens[k])) {
        this._accessTokenModified = true;
      }
      this.tokens[k] = token.tokens[k];
    }
    this.tokens = token.tokens;
    for (const k in token) {
      if (k !== "tokens") {
        this.values[k] = token[k];
      }
    }
  }
  dumpAccessToken() {
    const data = {
      tokens: this.tokens,
      ...this.values
    };
    if (!data) {
      return;
    }
    return Buffer.from(JSON.stringify(data)).toString("base64");
  }
  loadRefreshToken(value, trackModified = false) {
    const refreshTokens = JSON.parse(
      Buffer.from(value, "base64").toString("ascii")
    );
    for (const k in refreshTokens) {
      this.refreshTokens[k] = refreshTokens[k];
      if (trackModified && !(0, import_isEqual.default)(this.refreshTokens[k], refreshTokens[k])) {
        this._refreshTokenModified = true;
      }
    }
  }
  dumpRefreshToken() {
    if (!this.refreshTokens) {
      return;
    }
    return Buffer.from(JSON.stringify(this.refreshTokens)).toString("base64");
  }
};

// src/jwt.ts
var padUint8Array = (array, desiredLength) => {
  if (array.length >= desiredLength) {
    return array;
  }
  const paddedArray = new Uint8Array(desiredLength);
  paddedArray.set(array, desiredLength - array.length);
  return paddedArray;
};
var TokenSigner = class {
  constructor(config) {
    this.config = config;
    if (!config.encryptKey) {
      throw new Error("Missing encryptKey");
    }
    if (!config.signKey) {
      throw new Error("Missing signKey");
    }
    if (!config.audience) {
      throw new Error("Missing audience");
    }
    if (!config.issuer) {
      throw new Error("Missing issuer");
    }
    this._encryptKey = padUint8Array(
      jose.base64url.decode(config.encryptKey),
      32
    );
    if (this._encryptKey.length != 32) {
      throw new Error("Invalid encryptKey length");
    }
    this._signKey = jose.base64url.decode(config.signKey);
  }
  _encryptKey;
  _signKey;
  encryptString(value) {
    const key = jose.base64url.encode(this._encryptKey);
    return import_crypto_js.default.AES.encrypt(value, key).toString();
  }
  decryptString(value) {
    const key = jose.base64url.encode(this._encryptKey);
    return import_crypto_js.default.AES.decrypt(value, key).toString(import_crypto_js.default.enc.Utf8);
  }
  async signJWT(payload, exp) {
    return await new jose.SignJWT(payload).setExpirationTime(exp).setIssuedAt().setProtectedHeader({ alg: "HS256" }).setAudience(this.config.audience).setIssuer(this.config.issuer).sign(this._signKey);
  }
  async verifyJWT(value) {
    return await jose.jwtVerify(value, this._signKey, {
      algorithms: ["HS256"],
      audience: this.config.audience,
      issuer: this.config.issuer
    });
  }
  // For refresh token
  async encryptJWT(payload, exp) {
    const data = await new jose.EncryptJWT(payload).setProtectedHeader({ alg: "dir", enc: "A128CBC-HS256" }).setIssuedAt().setIssuer(this.config.issuer).setAudience(this.config.audience).setExpirationTime(exp).encrypt(this._encryptKey);
    return data;
  }
  async decryptJWT(jwt) {
    return await jose.jwtDecrypt(jwt, this._encryptKey, {
      audience: this.config.audience,
      issuer: this.config.issuer
    });
  }
};
var PublicFederatedToken = class extends FederatedToken {
  async createAccessJWT(signer) {
    const values = Object.values(this.tokens);
    const sorted = values.sort((a, b) => a.exp - b.exp);
    const exp = sorted[0].exp;
    const payload = {
      exp,
      state: this.encryptTokens(signer),
      ...this.values
    };
    return signer.signJWT(payload, exp);
  }
  async loadAccessJWT(signer, value) {
    const result = await signer.verifyJWT(value);
    if (!result) {
      throw new Error("Invalid JWT");
    }
    const payload = result.payload;
    this.tokens = this.decryptTokens(signer, payload.state);
    const knownKeys = ["state", "iat", "exp", "aud", "iss"];
    for (const k in payload) {
      if (!knownKeys.includes(k)) {
        this.values[k] = payload[k];
      }
    }
  }
  async createRefreshJWT(signer) {
    const payload = this.refreshTokens;
    const exp = Date.now() + 1e3 * 60 * 60 * 24 * 90;
    return signer.encryptJWT(payload, exp);
  }
  async loadRefreshJWT(signer, value) {
    const result = await signer.decryptJWT(value);
    if (!result) {
      throw new Error("Invalid JWT");
    }
    const payload = result.payload;
    const knownKeys = ["state", "iat", "exp", "aud", "iss"];
    for (const k in payload) {
      if (!knownKeys.includes(k)) {
        this.refreshTokens[k] = payload[k];
      }
    }
  }
  encryptTokens(signer) {
    const value = JSON.stringify(this.tokens);
    return signer.encryptString(value);
  }
  decryptTokens(signer, value) {
    const data = signer.decryptString(value);
    return JSON.parse(data);
  }
};

// src/datasource.ts
var FederatedGraphQLDataSource = class extends import_gateway.RemoteGraphQLDataSource {
  name;
  constructor({ name, url }) {
    super({ url });
    this.name = name;
  }
  willSendRequest(options) {
    const { request, context } = options;
    const headers = request.http?.headers ?? new import_server.HeaderMap();
    const token = context.federatedToken.dumpAccessToken();
    if (token) {
      headers.set("x-access-token", token);
    }
    const refreshToken = context.federatedToken.dumpRefreshToken();
    if (refreshToken) {
      headers.set("x-refresh-token", refreshToken);
    }
  }
  async didReceiveResponse({
    response,
    context
  }) {
    if (!response.http?.headers) {
      return response;
    }
    const headers = response.http?.headers;
    const token = headers.get("x-access-token");
    if (token) {
      if (!context.federatedToken) {
        context.federatedToken = new PublicFederatedToken();
      }
      context.federatedToken.loadAccessToken(token, true);
    }
    const refreshToken = headers.get("x-refresh-token");
    if (refreshToken) {
      if (!context.federatedToken) {
        context.federatedToken = new PublicFederatedToken();
      }
      context.federatedToken.loadRefreshToken(refreshToken);
    }
    return response;
  }
};

// src/gateway.ts
var import_graphql = require("graphql");
var GatewayAuthPlugin = class {
  constructor(signer) {
    this.signer = signer;
  }
  async requestDidStart(requestContext) {
    return this;
  }
  async didResolveOperation(requestContext) {
    const { contextValue, request } = requestContext;
    const authHeader = request.http?.headers.get("Authorization");
    const at = authHeader?.replace("Bearer ", "");
    if (at) {
      if (!contextValue.federatedToken) {
        contextValue.federatedToken = new PublicFederatedToken();
      }
      try {
        await contextValue.federatedToken.loadAccessJWT(this.signer, at);
      } catch (e) {
        throw new import_graphql.GraphQLError(
          "You are not authorized to perform this action.",
          {
            extensions: {
              code: "FORBIDDEN"
            }
          }
        );
      }
    }
    const rt = request.http?.headers.get("x-refresh-token");
    if (rt) {
      await contextValue.federatedToken?.loadRefreshJWT(this.signer, rt);
    }
  }
  async willSendResponse(requestContext) {
    const { contextValue, response } = requestContext;
    const token = contextValue?.federatedToken;
    if (token?.isAccessTokenModified()) {
      const accessToken = await token.createAccessJWT(this.signer);
      if (accessToken) {
        response.http.headers.set("X-Access-Token", accessToken);
      }
    }
    if (token?.isRefreshTokenModified()) {
      const refreshToken = await token.createRefreshJWT(this.signer);
      if (refreshToken) {
        response.http.headers.set("X-Refresh-Token", refreshToken);
      }
    }
  }
};

// src/plugin.ts
var FederatedAuthPlugin = class {
  async requestDidStart(requestContext) {
    const { contextValue, request } = requestContext;
    if (!contextValue.federatedToken) {
      contextValue.federatedToken = new FederatedToken();
    }
    const at = request.http?.headers.get("X-Access-Token");
    if (at) {
      contextValue.federatedToken.loadAccessToken(at);
    }
    const rt = request.http?.headers.get("X-Refresh-Token");
    if (rt) {
      contextValue.federatedToken.loadRefreshToken(rt);
    }
    return this;
  }
  async willSendResponse(requestContext) {
    const { contextValue, response } = requestContext;
    if (!contextValue.federatedToken) {
      return;
    }
    const t = contextValue.federatedToken;
    if (t.isAccessTokenModified()) {
      const val = t.dumpAccessToken();
      if (val) {
        response.http.headers.set("X-Access-Token", val);
      }
    }
    if (t.isRefreshTokenModified()) {
      const val = t.dumpRefreshToken();
      if (val) {
        response.http.headers.set("X-Refresh-Token", val);
      }
    }
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  FederatedAuthPlugin,
  FederatedGraphQLDataSource,
  FederatedToken,
  GatewayAuthPlugin,
  PublicFederatedToken,
  TokenSigner
});
//# sourceMappingURL=index.js.map