Authress-Engineering / saml-login.js

SAML2.0 Application, Service Provider, and Identity Provider login adapter for Node.js

Home Page:https://authress.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Potentially handle logout differently

wparad opened this issue · comments

  public async generateLogoutRequest(user: Profile, options: LogoutOptions) : Promise<string> {
    const id = options.generateUniqueId();
    const instant = generateInstant();

    const request = {
      "samlp:LogoutRequest": {
        "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
        "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
        "@ID": id,
        "@Version": "2.0",
        "@IssueInstant": instant,
        "@Destination": options.logoutUrl,
        "saml:Issuer": {
          "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
          "#text": options.issuer,
        },
        "saml:NameID": {
          "@Format": user!.nameIDFormat,
          "#text": user!.nameID,
        },
      },
    } as LogoutRequestXML;

    if (user!.nameQualifier != null) {
      request["samlp:LogoutRequest"]["saml:NameID"]["@NameQualifier"] = user!.nameQualifier;
    }

    if (user!.spNameQualifier != null) {
      request["samlp:LogoutRequest"]["saml:NameID"]["@SPNameQualifier"] = user!.spNameQualifier;
    }

    if (user!.sessionIndex) {
      request["samlp:LogoutRequest"]["saml2p:SessionIndex"] = {
        "@xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol",
        "#text": user!.sessionIndex,
      };
    }

    await this.cacheProvider.save(id, instant);
    const request = buildXmlBuilderObject(request, false);
    await this._requestToUrl(request, null, "logout");
  }

  public async generateLogoutResponse(user: Profile, options: LogoutOptions) : Promise<string> {
    const id = options.generateUniqueId();
    const instant = generateInstant();

    const request = {
      "samlp:LogoutResponse": {
        "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
        "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
        "@ID": id,
        "@Version": "2.0",
        "@IssueInstant": instant,
        "@Destination": options.logoutUrl,
        "@InResponseTo": logoutRequest.ID,
        "saml:Issuer": {
          "#text": options.issuer,
        },
        "samlp:Status": {
          "samlp:StatusCode": {
            "@Value": "urn:oasis:names:tc:SAML:2.0:status:Success",
          },
        },
      },
    };

    return buildXmlBuilderObject(request, false);
  }

  private async _requestToUrl(
    request: string | null | undefined,
    response: string | null,
    operation: string,
    additionalParameters: querystring.ParsedUrlQuery
  ): Promise<string> {
    providerSingleSignOnUrl = assertRequired(options.providerSingleSignOnUrl, "providerSingleSignOnUrl is required");

    let buffer: Buffer;
    if (options.skipRequestCompression) {
      buffer = Buffer.from((request || response)!, "utf8");
    } else {
      buffer = await deflateRaw((request || response)!);
    }

    const base64 = buffer.toString("base64");
    let target = new URL(providerSingleSignOnUrl);

    if (operation === "logout") {
      if (options.logoutUrl) {
        target = new URL(options.logoutUrl);
      }
    } else if (operation !== "authorize") {
      throw new Error("Unknown operation: " + operation);
    }

    const samlMessage: querystring.ParsedUrlQuery = request
      ? {
          SAMLRequest: base64,
        }
      : {
          SAMLResponse: base64,
        };
    Object.keys(additionalParameters).forEach((k) => {
      samlMessage[k] = additionalParameters[k];
    });
    if (options.privateKey != null) {
      if (!providerSingleSignOnUrl) {
        throw new Error('"providerSingleSignOnUrl" config parameter is required for signed messages');
      }

      // sets .SigAlg and .Signature
      this.signRequest(samlMessage);
    }
    Object.keys(samlMessage).forEach((k) => {
      target.searchParams.set(k, samlMessage[k] as string);
    });

    return target.toString();
  }

  public async getLogoutResponseUrl(options: LogoutResponseOptions) : Promise<string> {
    const response = this._generateLogoutResponse(samlLogoutRequest);
    return await this._requestToUrl(null, response, 'logout');
  }