diff --git a/examples/international_address_autocomplete.mjs b/examples/international_address_autocomplete.mjs index 88ad611..2dbab98 100644 --- a/examples/international_address_autocomplete.mjs +++ b/examples/international_address_autocomplete.mjs @@ -3,20 +3,20 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.internationalAddressAutocomplete.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -const key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// const key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +const authId = process.env.SMARTY_AUTH_ID; +const authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. // https://www.smarty.com/docs/cloud/licensing -const clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["international-autocomplete-v2-cloud"]) - // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +const clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API const client = clientBuilder.buildInternationalAddressAutocompleteClient(); diff --git a/examples/international_postal_code.mjs b/examples/international_postal_code.mjs index f78f9c2..9589ec2 100644 --- a/examples/international_postal_code.mjs +++ b/examples/international_postal_code.mjs @@ -3,14 +3,14 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.internationalPostalCode.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. diff --git a/examples/international_street.mjs b/examples/international_street.mjs index 079eb76..1b37f6d 100644 --- a/examples/international_street.mjs +++ b/examples/international_street.mjs @@ -3,20 +3,20 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.internationalStreet.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. // https://www.smarty.com/docs/cloud/licensing -let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["international-global-plus-cloud"]); - // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +let clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API let client = clientBuilder.buildInternationalStreetClient(); diff --git a/examples/us_autocomplete_pro.mjs b/examples/us_autocomplete_pro.mjs index ff863ff..19bf61a 100644 --- a/examples/us_autocomplete_pro.mjs +++ b/examples/us_autocomplete_pro.mjs @@ -3,20 +3,20 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.usAutocompletePro.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. // https://www.smarty.com/docs/cloud/licensing -let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-autocomplete-pro-cloud"]); - // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +let clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API let client = clientBuilder.buildUsAutocompleteProClient(); diff --git a/examples/us_enrichment.mjs b/examples/us_enrichment.mjs index 044650b..df918f7 100644 --- a/examples/us_enrichment.mjs +++ b/examples/us_enrichment.mjs @@ -3,20 +3,20 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.usEnrichment.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. // https://www.smarty.com/docs/cloud/licensing -let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-property-data-principal-cloud"]); - // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +let clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API let client = clientBuilder.buildUsEnrichmentClient(); diff --git a/examples/us_extract.mjs b/examples/us_extract.mjs index 4542009..85f6405 100644 --- a/examples/us_extract.mjs +++ b/examples/us_extract.mjs @@ -3,14 +3,14 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.usExtract.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); let clientBuilder = new SmartyCore.ClientBuilder(credentials); // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API diff --git a/examples/us_reverse_geo.mjs b/examples/us_reverse_geo.mjs index 17d2d0a..8dcd519 100644 --- a/examples/us_reverse_geo.mjs +++ b/examples/us_reverse_geo.mjs @@ -3,20 +3,20 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.usReverseGeo.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. // https://www.smarty.com/docs/cloud/licensing -let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-reverse-geocoding-cloud"]); - // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +let clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API let client = clientBuilder.buildUsReverseGeoClient(); let lookup1 = new Lookup(40.27644, -111.65747); diff --git a/examples/us_street.mjs b/examples/us_street.mjs index 01fc28a..80ef993 100644 --- a/examples/us_street.mjs +++ b/examples/us_street.mjs @@ -3,21 +3,19 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.usStreet.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); // The appropriate license values to be used for your subscriptions // can be found on the Subscription page of the account dashboard. // https://www.smarty.com/docs/cloud/licensing -let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses([ - "us-rooftop-geocoding-cloud", -]); +let clientBuilder = new SmartyCore.ClientBuilder(credentials); // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API let client = clientBuilder.buildUsStreetApiClient(); diff --git a/examples/us_zipcode.mjs b/examples/us_zipcode.mjs index b34633c..46ce7b2 100644 --- a/examples/us_zipcode.mjs +++ b/examples/us_zipcode.mjs @@ -3,14 +3,14 @@ import SmartySDK from "smartystreets-javascript-sdk"; const SmartyCore = SmartySDK.core; const Lookup = SmartySDK.usZipcode.Lookup; -// for Server-to-server requests, use this code: -// let authId = process.env.SMARTY_AUTH_ID; -// let authToken = process.env.SMARTY_AUTH_TOKEN; -// const credentials = new SmartyCore.StaticCredentials(authId, authToken); - // for client-side requests (browser/mobile), use this code: -let key = process.env.SMARTY_EMBEDDED_KEY; -const credentials = new SmartyCore.SharedCredentials(key); +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); let clientBuilder = new SmartyCore.ClientBuilder(credentials); // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API diff --git a/index.mjs b/index.mjs index b45192c..c053a6c 100644 --- a/index.mjs +++ b/index.mjs @@ -7,6 +7,7 @@ import ClientBuilder from "./src/ClientBuilder.js"; import buildClient from "./src/util/buildClients.js"; import SharedCredentials from "./src/SharedCredentials.js"; import StaticCredentials from "./src/StaticCredentials.js"; +import BasicAuthCredentials from "./src/BasicAuthCredentials.js"; import Errors from "./src/Errors.js"; import LookupUSStreet from "./src/us_street/Lookup.js"; @@ -41,6 +42,7 @@ export const core = { buildClient, SharedCredentials, StaticCredentials, + BasicAuthCredentials, Errors, }; diff --git a/src/BasicAuthCredentials.ts b/src/BasicAuthCredentials.ts new file mode 100644 index 0000000..63294c9 --- /dev/null +++ b/src/BasicAuthCredentials.ts @@ -0,0 +1,19 @@ +import { Request } from "./types"; + +export default class BasicAuthCredentials { + private authId: string; + private authToken: string; + + constructor(authId: string, authToken: string) { + if (!authId || !authToken) { + throw new Error("credentials (auth id, auth token) required"); + } + this.authId = authId; + this.authToken = authToken; + } + + sign(request: Request): void { + const encoded = Buffer.from(`${this.authId}:${this.authToken}`).toString("base64"); + request.headers["Authorization"] = `Basic ${encoded}`; + } +} diff --git a/src/ClientBuilder.js b/src/ClientBuilder.js index 19a85b5..d4ae9d5 100644 --- a/src/ClientBuilder.js +++ b/src/ClientBuilder.js @@ -4,6 +4,7 @@ const BaseUrlSender = require("./BaseUrlSender"); const AgentSender = require("./AgentSender"); const StaticCredentials = require("./StaticCredentials"); const SharedCredentials = require("./SharedCredentials"); +const BasicAuthCredentials = require("./BasicAuthCredentials"); const CustomHeaderSender = require("./CustomHeaderSender"); const StatusCodeSender = require("./StatusCodeSender"); const LicenseSender = require("./LicenseSender"); @@ -54,7 +55,7 @@ class ClientBuilder { this.customQueries = new Map(); function credentialsProvided() { - return signer instanceof StaticCredentials || signer instanceof SharedCredentials; + return signer instanceof StaticCredentials || signer instanceof SharedCredentials || signer instanceof BasicAuthCredentials; } } diff --git a/tests/test_BasicAuthCredentials.ts b/tests/test_BasicAuthCredentials.ts new file mode 100644 index 0000000..9c10c38 --- /dev/null +++ b/tests/test_BasicAuthCredentials.ts @@ -0,0 +1,100 @@ +import { expect } from "chai"; +import BasicAuthCredentials from "../src/BasicAuthCredentials.js"; +import Request from "../src/Request.js"; + +describe("BasicAuthCredentials", function () { + it("creates credentials with valid authId and authToken", function () { + const credentials = new BasicAuthCredentials("testID", "testToken"); + + expect(credentials).to.not.be.null; + }); + + it("throws error when authId is empty", function () { + expect(() => new BasicAuthCredentials("", "testToken")).to.throw( + "credentials (auth id, auth token) required", + ); + }); + + it("throws error when authToken is empty", function () { + expect(() => new BasicAuthCredentials("testID", "")).to.throw( + "credentials (auth id, auth token) required", + ); + }); + + it("throws error when both authId and authToken are empty", function () { + expect(() => new BasicAuthCredentials("", "")).to.throw( + "credentials (auth id, auth token) required", + ); + }); + + it("creates credentials with special characters", function () { + const credentials = new BasicAuthCredentials("test@id#123", "token!@#$%^&*()"); + + expect(credentials).to.not.be.null; + }); + + it("signs request with Authorization header", function () { + const credentials = new BasicAuthCredentials("myID", "myToken"); + const request = new Request(); + + credentials.sign(request); + + expect("Authorization" in request.headers).to.equal(true); + const authHeader = request.headers["Authorization"] as string; + expect(authHeader.startsWith("Basic ")).to.equal(true); + + const encoded = authHeader.substring(6); + const decoded = Buffer.from(encoded, "base64").toString("utf-8"); + expect(decoded).to.equal("myID:myToken"); + }); + + it("signs request with password containing colon", function () { + const credentials = new BasicAuthCredentials("validUserID", "password:with:colons"); + const request = new Request(); + + credentials.sign(request); + + const authHeader = request.headers["Authorization"] as string; + const encoded = authHeader.substring(6); + const decoded = Buffer.from(encoded, "base64").toString("utf-8"); + expect(decoded).to.equal("validUserID:password:with:colons"); + }); + + it("signs request with special characters", function () { + const credentials = new BasicAuthCredentials("user@domain.com", "p@ssw0rd!"); + const request = new Request(); + + credentials.sign(request); + + const authHeader = request.headers["Authorization"] as string; + const encoded = authHeader.substring(6); + const decoded = Buffer.from(encoded, "base64").toString("utf-8"); + expect(decoded).to.equal("user@domain.com:p@ssw0rd!"); + }); + + it("signs request with unicode characters", function () { + const credentials = new BasicAuthCredentials("用户", "密码"); + const request = new Request(); + + credentials.sign(request); + + const authHeader = request.headers["Authorization"] as string; + const encoded = authHeader.substring(6); + const decoded = Buffer.from(encoded, "base64").toString("utf-8"); + expect(decoded).to.equal("用户:密码"); + }); + + it("overwrites existing Authorization header", function () { + const credentials = new BasicAuthCredentials("newID", "newToken"); + const request = new Request(); + request.headers["Authorization"] = "Bearer oldtoken"; + + credentials.sign(request); + + const authHeader = request.headers["Authorization"] as string; + expect(authHeader.startsWith("Basic ")).to.equal(true); + const encoded = authHeader.substring(6); + const decoded = Buffer.from(encoded, "base64").toString("utf-8"); + expect(decoded).to.equal("newID:newToken"); + }); +}); diff --git a/tests/test_SigningSender.ts b/tests/test_SigningSender.ts index e594207..3b888fd 100644 --- a/tests/test_SigningSender.ts +++ b/tests/test_SigningSender.ts @@ -4,6 +4,7 @@ import Response from "../src/Response.js"; import SigningSender from "../src/SigningSender.js"; import StaticCredentials from "../src/StaticCredentials.js"; import SharedCredentials from "../src/SharedCredentials.js"; +import BasicAuthCredentials from "../src/BasicAuthCredentials.js"; import { UnprocessableEntityError } from "../src/Errors"; describe("A signing sender", function () { @@ -55,4 +56,18 @@ describe("A signing sender", function () { expect(() => signingSender.send(request)).to.throw(UnprocessableEntityError); }); + + it("signs a request with basic auth credentials.", function () { + const basicAuthCredentials = new BasicAuthCredentials(mockAuthId, mockAuthToken); + const signingSender = new SigningSender(mockSender, basicAuthCredentials); + + signingSender.send(request); + + expect("Authorization" in request.headers).to.equal(true); + const authHeader = (request.headers as Record)["Authorization"]; + expect(authHeader.startsWith("Basic ")).to.equal(true); + const encoded = authHeader.substring(6); + const decoded = Buffer.from(encoded, "base64").toString("utf-8"); + expect(decoded).to.equal(`${mockAuthId}:${mockAuthToken}`); + }); });