@adobe/acc-js-sdk
Advanced tools
Comparing version 1.0.2 to 1.0.3
@@ -8,2 +8,7 @@ # Adobe Campaign Classic (ACC) SDK in JavaScript (node.js and browser) | ||
## Version 1.0.3 | ||
_2021/10/06_ | ||
* Added the `sdk.ip()` function to retreive the ouptbound IP to be whitelisted to access Campaign | ||
* New `ofSecurityToken` authentication method for the client-side SDK, which can be used to log on with a security token only. The session token will be passed automatically by the browser. | ||
## Version 1.0.2 | ||
@@ -10,0 +15,0 @@ _2021/09/17_ |
{ | ||
"name": "@adobe/acc-js-sdk", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "ACC Javascript SDK", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -161,3 +161,32 @@ # Adobe Campaign Classic (ACC) SDK in JavaScript (node.js and browser) | ||
## Logon with Security token | ||
If you want to use the SDK client-side in a web page returned by Campaign, you cannot use the previous authentication functions because you do not know the user and password, and because you cannot read the session token cookie. | ||
For this scenario, the `ofSecurityToken` function can be used. Pass it a security token (usually available as document.__securitytoken), and the SDK will let the browser handle the session token (cookie) for you. | ||
```html | ||
<script src="acc-sdk.js"></script> | ||
<script> | ||
(async () => { | ||
try { | ||
const sdk = document.accSDK; | ||
var securityToken = "@UyAN..."; | ||
const connectionParameters = sdk.ConnectionParameters.ofSecurityToken(url, securityToken); | ||
const client = await sdk.init(connectionParameters); | ||
await client.logon(); | ||
const option = await client.getOption("XtkDatabaseId"); | ||
console.log(option); | ||
} catch(ex) { | ||
console.error(ex); | ||
} | ||
})(); | ||
</script> | ||
</body> | ||
</html> | ||
``` | ||
Note: if the HTML page is served by the Campaign server (which is normally the case in this situation), you can pass an empty url to the `ofSecurityToken` API call. | ||
## LogOn / LogOff | ||
@@ -175,2 +204,20 @@ | ||
## IP Whitelisting | ||
Campaign includes an IP whitelisting component which prevents connections from unauthorized IP addresses. This is a common source of authentication errors. | ||
A node application using the SDK must be whitelisted to be able to access Campaign. The SDK `ip` function is a helper function that can help you find the IP or IPs which need to be whitelisted. | ||
This API is only meant for troubleshooting purposes and uses the `https://api.db-ip.com/v2/free/self` service. | ||
```js | ||
const ip = await sdk.ip(); | ||
``` | ||
Will return something like | ||
```json | ||
{ "ipAddress":"AAA.BBB.CCC.DDD","continentCode":"EU","continentName":"Europe","countryCode":"FR","countryName":"France","stateProv":"Centre-Val de Loire","city":"Bourges" } | ||
``` | ||
## Calling static SOAP methods | ||
@@ -177,0 +224,0 @@ |
@@ -32,2 +32,11 @@ /* | ||
await utils.sample({ | ||
title: "Display the outbound IP address (can be useful to troubleshoot IP whitelisting issues)", | ||
labels: [ "Basics", "IP", "Whitelisting", "403" ], | ||
code: async () => { | ||
const ip = await sdk.ip(); | ||
console.log(`>> ${JSON.stringify(ip)}`); | ||
} | ||
}); | ||
await utils.sample({ | ||
title: "Log on and log off", | ||
@@ -34,0 +43,0 @@ labels: [ "Basics", "connectionParameters", "ofUserAndPassword", "xtk:session", "logon", "logoff" ], |
@@ -178,3 +178,4 @@ "use strict"; | ||
constructor(type, sessionToken, securityToken) { | ||
if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" && type != "AnonymousUser") | ||
if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" && | ||
type != "AnonymousUser" && type != "SecurityToken") | ||
throw CampaignException.INVALID_CREDENTIALS_TYPE(type); | ||
@@ -300,2 +301,19 @@ this._type = type; | ||
/** | ||
* Creates connection parameters for a Campaign instance, using a security token. | ||
* Typically, called when embedding the SDK in Campaign: the session token will be | ||
* passed automatically as a cookie, so only the security token is actually needed | ||
* to logon | ||
* | ||
* @static | ||
* @param {string} endpoint The campaign endpoint (URL) | ||
* @param {string} securityToken The session token | ||
* @param {*} options connection options | ||
* @returns {ConnectionParameters} a ConnectionParameters object which can be used to create a Client | ||
*/ | ||
static ofSecurityToken(endpoint, securityToken, options) { | ||
const credentials = new Credentials("SecurityToken", "", securityToken); | ||
return new ConnectionParameters(endpoint, credentials, options); | ||
} | ||
/** | ||
* Creates connection parameters for a Campaign instance for an anonymous user | ||
@@ -563,11 +581,9 @@ * | ||
// with session token authentication, we do not expect a security token | ||
const isSessionTokenAuth = credentialsType == "SessionToken"; | ||
// with security token authentication, we do not expect a session token | ||
const needsSecurityToken = credentialsType != "SessionToken"; | ||
const hasSecurityToken = this._securityToken !== null && this._securityToken !== undefined && this._securityToken !== ""; | ||
const needsSessionToken = credentialsType != "SecurityToken"; | ||
const hasSessionToken = this._sessionToken !== null && this._sessionToken !== undefined && this._sessionToken !== ""; | ||
return this._sessionToken !== null && | ||
this._sessionToken !== undefined && | ||
this._sessionToken !== "" && | ||
(isSessionTokenAuth || ( | ||
this._securityToken !== null && | ||
this._securityToken !== undefined && | ||
this._securityToken !== "")); | ||
return (!needsSecurityToken || hasSecurityToken) && (!needsSessionToken || hasSessionToken); | ||
} | ||
@@ -635,9 +651,8 @@ | ||
this._securityToken = ""; | ||
const credentials = this._connectionParameters._credentials; | ||
// Clear session token cookie to ensure we're not inheriting an expired cookie. See NEO-26589 | ||
if (typeof document != "undefined") { | ||
if (credentials._type != "SecurityToken" && typeof document != "undefined") { | ||
document.cookie = '__sessiontoken=;path=/;' | ||
} | ||
const credentials = this._connectionParameters._credentials; | ||
if (credentials._type == "SessionToken" || credentials._type == "AnonymousUser") { | ||
@@ -650,2 +665,9 @@ that._sessionInfo = undefined; | ||
} | ||
else if (credentials._type == "SecurityToken") { | ||
that._sessionInfo = undefined; | ||
that._installedPackages = {}; | ||
that._sessionToken = ""; | ||
that._securityToken = credentials._securityToken; | ||
that.application = new Application(that); | ||
} | ||
else if (credentials._type == "UserPassword") { | ||
@@ -652,0 +674,0 @@ const user = credentials._getUser(); |
@@ -27,4 +27,30 @@ "use strict"; | ||
const { Client, Credentials, ConnectionParameters } = require('./client.js'); | ||
const request = require('./transport.js').request; | ||
/** | ||
* Get/Set the transport function (defaults to Axios). This function is used for testing / mocking the transport layer. | ||
* Called without arguments, it returns the current transport function | ||
* Called with an argument, it sets the current transport function and returns the previous one | ||
* | ||
* const t = jest.fn(); | ||
* const old = sdk._transport(t); | ||
* try { | ||
* t.mockReturnValueOnce(Promise.resolve(...); | ||
* ... call sdk function which uses the transport layer, for instance ip() | ||
* } finally { | ||
* sdk._transport(old); | ||
* } | ||
*/ | ||
var transport = request; | ||
function _transport(t) { | ||
if (t) { | ||
const old = transport; | ||
transport = t; | ||
return old; | ||
} | ||
return transport; | ||
} | ||
/** | ||
* @namespace Campaign | ||
@@ -66,2 +92,12 @@ */ | ||
/** | ||
* Get the outbound IP address (https://api.db-ip.com/v2/free/self) | ||
* Can be useful to troubleshoot IP whitelisting issues | ||
*/ | ||
async function ip() { | ||
const transport = _transport(); | ||
const ip = await transport({ url: "https://api.db-ip.com/v2/free/self" }); | ||
return ip; | ||
} | ||
/** | ||
@@ -118,2 +154,3 @@ * Escapes and quotes string contained in Xtk expressions. It's common to build xtk expressions such as "@name='Hello'". If 'Hello' is a variable, it's | ||
getSDKVersion: getSDKVersion, | ||
ip: ip, | ||
escapeXtk: escapeXtk, | ||
@@ -123,4 +160,5 @@ XtkCaster: XtkCaster, | ||
Credentials: Credentials, | ||
ConnectionParameters: ConnectionParameters | ||
ConnectionParameters: ConnectionParameters, | ||
_transport: _transport | ||
}; | ||
@@ -153,5 +153,7 @@ "use strict"; | ||
const cookieHeader = this._doc.createElement("Cookie"); | ||
cookieHeader.textContent = `__sessiontoken=${this._sessionToken}`; | ||
this._header.appendChild(cookieHeader); | ||
if (this._sessionToken) { | ||
const cookieHeader = this._doc.createElement("Cookie"); | ||
cookieHeader.textContent = `__sessiontoken=${this._sessionToken}`; | ||
this._header.appendChild(cookieHeader); | ||
} | ||
@@ -162,2 +164,6 @@ const securityTokenHeader = this._doc.createElement("X-Security-Token"); | ||
// Always write a sessiontoken element as the first parameter. Even when using SecurityToken authentication | ||
// and when the session token is actually passed implicitely as a cookie, one must write a sessiontoken | ||
// element. If not, authentication will fail because the first parameter is interpreted as the "authentication mode" | ||
// and eventually passed as the first parameter of CXtkLocalSessionPart::GetXtkSecurity | ||
this.writeString("sessiontoken", this._sessionToken); | ||
@@ -529,7 +535,8 @@ } | ||
'SoapAction': `${this.urn}#${this.methodName}`, | ||
'X-Security-Token': this._securityToken, | ||
'Cookie': '__sessiontoken=' + this._sessionToken | ||
'X-Security-Token': this._securityToken | ||
}, | ||
data: DomUtil.toXMLString(this._doc) | ||
}; | ||
if (this._sessionToken) | ||
options.headers['Cookie'] = '__sessiontoken=' + this._sessionToken; | ||
if (this._userAgentString) | ||
@@ -536,0 +543,0 @@ options.headers['User-Agent'] = this._userAgentString; |
@@ -55,3 +55,3 @@ /* | ||
const request = { | ||
method: options.method, | ||
method: options.method || "GET", | ||
url: options.url, | ||
@@ -58,0 +58,0 @@ headers: options.headers, |
@@ -63,2 +63,17 @@ /* | ||
describe("IP", () => { | ||
it("Should get IP address", async () => { | ||
const t = jest.fn(); | ||
const old = sdk._transport(t); | ||
try { | ||
t.mockReturnValueOnce(Promise.resolve({ "ipAddress":"AAA.BBB.CCC.DDD","continentCode":"EU","continentName":"Europe","countryCode":"FR","countryName":"France","stateProv":"Centre-Val de Loire","city":"Bourges" })); | ||
const ip = await sdk.ip(); | ||
expect(ip).toMatchObject({ "ipAddress":"AAA.BBB.CCC.DDD" }); | ||
} finally { | ||
sdk._transport(old); | ||
} | ||
}); | ||
}) | ||
}); |
@@ -153,10 +153,11 @@ /* | ||
assert.equal(request.headers["X-Security-Token"], ""); | ||
assert.equal(request.headers["Cookie"], "__sessiontoken="); | ||
expect(request.headers["Cookie"]).toBeUndefined(); | ||
const env = DomUtil.parse(request.data).documentElement; | ||
const header = hasChildElement(env, "SOAP-ENV:Header"); | ||
hasChildElement(header, "Cookie", "__sessiontoken="); | ||
expect(DomUtil.findElement(header, "Cookie")).toBeFalsy(); | ||
hasChildElement(header, "X-Security-Token"); | ||
const body = hasChildElement(env, "SOAP-ENV:Body"); | ||
const method = hasChildElement(body, "m:Empty", undefined, "xmlns:m", "urn:xtk:session", "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/"); | ||
hasChildElement(method, "sessiontoken", "", "xsi:type", "xsd:string"); | ||
// sessiontoken is always required as first parameter, even if not set | ||
expect(DomUtil.findElement(method, "sessiontoken")).toBeTruthy(); | ||
}); | ||
@@ -163,0 +164,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1035212
21241
1529