intuit / oauth-jsclient

Intuit's NodeJS OAuth client provides a set of methods to make it easier to work with OAuth2.0 and Open ID
https://developer.intuit.com/
Apache License 2.0
119 stars 153 forks source link

Abandoned? #142

Open geoffcorey opened 11 months ago

geoffcorey commented 11 months ago

No security package upgrades, no updates in several years.

m1daz commented 11 months ago

They updated PHP out of all things and not node.js... the #1 developer rated backend framework..

WillsWebsites commented 11 months ago

Seriously

m1daz commented 11 months ago

After 18 hours of understanding their very weirdly designed documentation, I just ended up creating a custom class to just do what I want it to do. Sad considering I'm also using azure api to process invoices and read text from them and their documentation is 100x better and their packages are actually kept up to date

WillsWebsites commented 11 months ago

@m1daz Any chance I could snag that from you?

m1daz commented 11 months ago
import { GetPrisma } from "@/lib/database";

export class Quickbooks {
  private redirectUrl: string;
  private scope: string;
  private clientId: string;
  private clientSecret: string | undefined;

  private readonly bearerUrl =
    "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";

  constructor(
    redirectUrl: string,
    scope: string,
    clientId: string,
    clientSectet?: string
  ) {
    this.redirectUrl = redirectUrl;
    this.scope = scope;
    this.clientId = clientId;
    this.clientSecret = clientSectet;
  }
  get OAUTH_URL(): string {
    return `https://appcenter.intuit.com/app/connect/oauth2?client_id=${this.clientId}&scope=${this.scope}&redirect_uri=${this.redirectUrl}&response_type=code&state=PlaygroundAuth`;
  }

  private chunkBase64(b64: string): string {
    // Split b64 string with /r/n to avoid exceeding max line size (1000)
    // So for each 900 char for example, /r/n
    // dont add /r/n to last item
    const chunkSize = 72;
    const chunks = [];
    for (let i = 0; i < b64.length; i += chunkSize) {
      chunks.push(b64.slice(i, i + chunkSize));
    }
    return chunks.join("\r\n");
  }

  async createInvoice(
    realmId: string,
    token: string,
    customerId: string,
    loadInformation: {
      amount: number;
      number: number;
    }
  ): Promise<boolean> {
    // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/invoice#create-an-invoice
    // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities//attachable#upload-attachments
    const resp = await fetch(
      `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/invoice`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
          Line: [
            {
              DetailType: "SalesItemLineDetail",
              Amount: loadInformation.amount,
              SalesItemLineDetail: {
                ItemRef: {
                  name: "Services",
                  value: "1",
                },
              },
            },
          ],
          CustomerRef: {
            value: customerId,
          },
        }),
      }
    );
    const data = await resp.json();
    if (data && data.Invoice) {
      const invoiceId: string = data.Invoice.Id;
      return await this.uploadAttachment(
        realmId,
        token,
        invoiceId,
        loadInformation.number
      );
    } else {
      return false;
    }
  }

  async uploadAttachment(
    realmId: string,
    token: string,
    invoiceId: string,
    loadId: number
  ): Promise<boolean> {
    // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities//attachable#upload-attachments
    const clientHandle = await GetPrisma();
    if (!clientHandle.success) {
      return false;
    }
    const client = clientHandle.prismaHandle!;
    const invoiceFile = await client.invoice.findFirst({
      where: {
        load: {
          id: loadId,
        },
      },
    });
    if (!invoiceFile) {
      return false;
    }
    let pdfData = Buffer.from(invoiceFile.data ?? "", "base64");
    if (pdfData.toString().split(";base64,").length > 1) {
      pdfData = Buffer.from(pdfData.toString().split(";base64,")[1], "base64");
    }
    const body = `--dEneMo239
Content-Disposition: form-data; name="file_metadata_01"; filename="attachment.json"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
  "AttachableRef": [
  {"EntityRef": {
    "type": "Invoice", 
    "value": "${invoiceId}"
  }
}
],
"FileName": "invoice.pdf",
"ContentType": "application/pdf"
}
--dEneMo239
Content-Disposition: form-data; name="file_content_01"; filename="invoice.pdf"
Content-Type: application/pdf
Content-Transfer-Encoding: base64

${this.chunkBase64(pdfData.toString("base64"))}
--dEneMo239--`;
    // calculate body size in mb
    const bodySize = Buffer.byteLength(body, "utf8") / 1024 / 1024;
    const resp = await fetch(
      `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/upload`,
      {
        method: "POST",
        headers: {
          "Content-Type": "multipart/form-data; boundary=dEneMo239",
          Accept: "application/json",
          Authorization: `Bearer ${token}`,
        },
        body: body,
      }
    );
    const data = await resp.json();
    if (data && data.AttachableResponse) {
      return true;
    } else {
      return false;
    }
  }

  async createCustomer(
    realmId: string,
    token: string,
    customerInformation: {
      Name: string;
      EmailAddress?: string;
      Phone?: string;
      Address: {
        Street: string;
        City: string;
        Zip: string;
        State: string;
      };
    }
  ): Promise<string | undefined> {
    // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer#create-a-customer
    const resp = await fetch(
      `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/customer`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
          FullyQualifiedName: customerInformation.Name,
          PrimaryEmailAddr: {
            Address: customerInformation.EmailAddress ?? "",
          },
          DisplayName: customerInformation.Name,
          PrimaryPhone: {
            FreeFormNumber: customerInformation.Phone ?? "",
          },
          CompanyName: customerInformation.Name,
          BillAddr: {
            CountrySubDivisionCode: customerInformation.Address.State,
            City: customerInformation.Address.City,
            PostalCode: customerInformation.Address.Zip,
            Line1: customerInformation.Address.Street,
            Country: "USA",
          },
        }),
      }
    );
    const data = await resp.json();
    if (
      data.responseHeader === undefined ||
      data.responseHeader.status === 200
    ) {
      return data.Customer.Id;
    }
    return undefined;
  }

  async getAccessToken(code: string): Promise<{
    access_token: string;
    expires_in: number;
    refresh_token: string;
    x_refresh_token_expires_in: number;
    token_type: string;
  }> {
    // convert clientId & secret to base64 (nodejs)
    const bearer = Buffer.from(
      `${this.clientId}:${this.clientSecret}`
    ).toString("base64");
    const resp = await fetch(this.bearerUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json",
        Authorization: `Basic ${bearer}`,
      },
      body: `grant_type=authorization_code&code=${code}&redirect_uri=${this.redirectUrl}`,
    });
    const data: any = await resp.json();
    return data;
  }

  async refreshAccessToken(refreshToken: string): Promise<{
    access_token: string;
    expires_in: number;
    refresh_token: string;
    x_refresh_token_expires_in: number;
    token_type: string;
  }> {
    const bearer = Buffer.from(
      `${this.clientId}:${this.clientSecret}`
    ).toString("base64");
    const resp = await fetch(this.bearerUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json",
        Authorization: `Basic ${bearer}`,
      },
      body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
    });
    const data: any = await resp.json();
    return data;
  }
}

I made it work for me, you might need to modify it for you.

WillsWebsites commented 11 months ago

@m1daz Cool ty. I'm still trying to get everything authenticated but a few of those methods will be helpful for sure. Cheers

robert-mings commented 10 months ago

Hi @geoffcorey, @m1daz, @WillsWebsites - Robert with the Intuit team here. Thank you for your comments and contributions. You're absolutely right, we haven't done a great job of keeping this OAuth Client up to date. I'm happy to share, however, that this is changing. Over the next few months, we'll be conducting a full review (including actioning all open PRs + issues) alongside other Intuit SDKs/OAuth Clients. From there, expect to see regular updates across the board. Outside of this thread, if you have any additional suggestions, please send them our way!

m1daz commented 10 months ago

Hi @geoffcorey, @m1daz, @WillsWebsites - Robert with the Intuit team here. Thank you for your comments and contributions. You're absolutely right, we haven't done a great job of keeping this OAuth Client up to date. I'm happy to share, however, that this is changing. Over the next few months, we'll be conducting a full review (including actioning all open PRs + issues) alongside other Intuit SDKs/OAuth Clients. From there, expect to see regular updates across the board.

Outside of this thread, if you have any additional suggestions, please send them our way!

Thank you so much. I am very interested in working with intuit for our software, but I need to have the confidence in there being support.

WillsWebsites commented 10 months ago

@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:

Otherwise with intuit api's in general:

I can post these elsewhere if needed just let me know

robert-mings commented 10 months ago

Hi @geoffcorey, @m1daz, @WillsWebsites - Robert with the Intuit team here. Thank you for your comments and contributions. You're absolutely right, we haven't done a great job of keeping this OAuth Client up to date. I'm happy to share, however, that this is changing. Over the next few months, we'll be conducting a full review (including actioning all open PRs + issues) alongside other Intuit SDKs/OAuth Clients. From there, expect to see regular updates across the board. Outside of this thread, if you have any additional suggestions, please send them our way!

Thank you so much. I am very interested in working with intuit for our software, but I need to have the confidence in there being support.

@m1daz You bet! Check out the docs if you haven't already. Lots of great info there.

@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:

* Typescript with all defined types and responses

* Updating packages that have security flaws

Otherwise with intuit api's in general:

* Being able to get the payment invoice link as you're able to in the software itself (maybe it can be included in the invoice query response)

* Maybe the ability to not have to set up a full Oauth2 system to work with your companies own data if possible?

* An official API SDK instead of relying on the community one.

I can post these elsewhere if needed just let me know

@WillsWebsites Fantastic suggestions, thank you!

EthanDavis commented 9 months ago

@robert-mings any update on when security patches and types will be pushed out for this sdk?

abtonc commented 9 months ago

Would love to see some updates on security patches and/or typescript support.

rajeshgupta723 commented 9 months ago

Thank you all for your patience, much appreciated! Ensuring you that this SDK is still active and being maintained. We have plans to release the security patches asap, before we get too busy with the upcoming holiday season. Stay tuned!

rajeshgupta723 commented 9 months ago

Created a branch called 'hotfix-4.0.1' and ran npm audit for security fixes. Feel free to test the branch, or raise PR on it and provide any comment or suggestions you may have. Thanks

geoffcorey commented 7 months ago

I moved on and use @apigrate/quickbooks now. Years without security patches, being maintained by hacktoberfest and interns is not acceptable for a company dealing with finances.

m1daz commented 1 month ago

Hi @geoffcorey, @m1daz, @WillsWebsites - Robert with the Intuit team here. Thank you for your comments and contributions. You're absolutely right, we haven't done a great job of keeping this OAuth Client up to date. I'm happy to share, however, that this is changing. Over the next few months, we'll be conducting a full review (including actioning all open PRs + issues) alongside other Intuit SDKs/OAuth Clients. From there, expect to see regular updates across the board. Outside of this thread, if you have any additional suggestions, please send them our way!

Thank you so much. I am very interested in working with intuit for our software, but I need to have the confidence in there being support.

@m1daz You bet! Check out the docs if you haven't already. Lots of great info there.

@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:

* Typescript with all defined types and responses

* Updating packages that have security flaws

Otherwise with intuit api's in general:

* Being able to get the payment invoice link as you're able to in the software itself (maybe it can be included in the invoice query response)

* Maybe the ability to not have to set up a full Oauth2 system to work with your companies own data if possible?

* An official API SDK instead of relying on the community one.

I can post these elsewhere if needed just let me know

@WillsWebsites Fantastic suggestions, thank you!

Robert, today I had to modify some code for the quickbooks API and I decided to install this package instead of using my class that I made by reading the conversation. I was really happy seeing all the other changes, however, there's still one really big downfall for me right now. There's no TypeScript support whatsoever. This was promised previously, is it still coming?