Zapier Cli: Not able to set authentication.connectionLabel dynamically - zapier

Following the developer docs, I have attempted numerous variations trying to achieve this.
I would like to extract the label from the token payload, if not I can build it from the /me response body.
This is my latest attempt:
var jwtDecode = require('jwt-decode');
const getTokenFields = (z, bundle) => {
var sub = jwtDecode(bundle.authData.token)['sub']
console.log(sub)
return sub;
};
const getLabelField = (z, bundle) => {
var sub = jwtDecode(bundle.authData.token)['sub']['label']
console.log(sub)
return sub;
};
const authentication = {
type: 'session',
test: {
url: '{{process.env.AP_HOST}}/me'
},
fields: [
{
key: 'token',
type: 'string',
required: true,
helpText: 'See settings to get your token'
}
],
sessionConfig: {
perform: getTokenFields
},
connectionLabel: {
perform: getLabelField
}
};
module.exports = authentication;
the only thing that I can get to work is:
connectionLabel: "{{bundle.authData.token}}", but it looks terrible!
connectionLabel: "{{bundle.authData.responsefield}}" feels like it should be something that should work but it doesn't either

David here, from the Zapier Platform team.
Instead of connectionLabel: {perform: getLabelField}, it should just be connectionLabel: getLabelField. You can also use a string that pulls from the bundle (like you're seeing) or any data that comes back from the designated test endpoint (see here).
There should be validation that prevents what you have (that'll error when you run zapier validate) but if that comes back clean then definitely let us know.

Related

How to get a value from SecretString returned from AwsCustomResource (service: 'SecretsManager', action: 'getSecretValue') as plain text?

I'm modifying the code from this workshop to do cross-account rather than cross-region.
https://cdk-advanced.workshop.aws/start.html
The first thing I did was install and configure cdk-assume-role-credential-plugin and bootstrapped.
In the workshop they use a AwsCustomResource to get around the cross-region limitation of StringParameter.valueFromLookup.
static valueFromLookup(scope, parameterName) Requires that the stack this scope is defined in will have explicit account/region information. Otherwise, it will fail during synthesis.Using an AwsCustomResource allows them to override the region from stack.
const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
onUpdate: { // will also be called for a CREATE event
service: 'SSM',
action: 'getParameter',
parameters: {
Name: props.targetKeyIdSsmParameterName
},
region: props.targetRegion,
physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
});
Unfortunately you cannot override account of stack in a similar fashion. Adding an assumedRole does not help either. It always looks for parameter in stack's account.
I tried to use SecretsManager & AwsCustomResource to get around that cross-account limitation:
const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
onUpdate: {
service: 'SecretsManager',
action: 'getSecretValue',
parameters: {
SecretId: props.targetKeyIdSsmParameterName
},
assumedRoleArn: 'arn:aws:iam::111111111111:role/MultiRegionS3CrrKmsCmkXacct',
region: props.targetRegion,
physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
});
const secretString = targetKeyLookupCR.getResponseField(dataPath);
The code above returns a string that looks like this.
"{"password":"/];asdf(5;{ASDF=L%UuVxasDFHg`(:l","MyKeyID":"arn:aws:kms:us-west-1: 111111111111:key/1111a22b-3c44-5ddd-66e7-f8f9999a1111"}"
I can see it in CloudWatch:
2021-08-14T04:34:15.641Z c5ae47f7-1e4a-43a3-a475-d0d16ea7e1be INFO Responding {"Status":"SUCCESS","Reason":"OK",...
"SecretString":"{"password":"/];asdf(5;{ASDF=L%UuVxasDFHg`(:l","MyKeyID":"arn:aws:kms:us-west-1: 111111111111:key/1111a22b-3c44-5ddd-66e7-f8f9999a1111}"...}
In the workshop they use the response directly:
role.addToPolicy(new iam.PolicyStatement({
resources: [targetKeyLookupCR.getResponseField('Parameter.Value')],
actions: ['kms:Encrypt'] }));
To get the Value of MyKeyID, I've tried...
JSON.parse and splitting the string. No matter what I try ends up with an error like 'Unexpected token $ in JSON at position 0' or 'TypeError: Cannot read property 'split' of undefined'
This led me to https://docs.aws.amazon.com/cdk/latest/guide/tokens.html#tokens_json, which I definitely thought was going to be the answer! It was not. More 'Unexpected token $ in JSON at position 0' errors.
const stack = cdk.Stack.of(this);
const secretString = targetKeyLookupCR.getResponseField('SecretString');
const jsonString = stack.toJsonString(secretString);
const jsonObj = JSON.parse(jsonString);
Update:
From this https://docs.aws.amazon.com/cdk/api/latest/docs/core-readme.html#cfnjson I got a different error that indicates it has the right string, but just can't parse it properly. Unexpected token p in JSON at position 3 at JSON.parse ().
const secretString = targetKeyLookupCR.getResponseField('SecretString');
const cfnJson = new CfnJson(this, 'cfn-json', {
value: secretString,
});
// #ts-ignore
const myKeyId = cfnJson.MyKeyID || '*';
This is because CfnJson adds quotes around string:
"Value": {
"Fn::Join": [
"",
[
"\"",
{
"Fn::GetAtt": [
"MySourceTargetKeyLookupACF65EAE",
"SecretString"
]
},
"\""
]
]
}
I'm not sure how to proceed. How would I get the ARN from secretString as plain text?
I guess I was looking for a more elegant solution, but this works...
const secretString = targetKeyLookupCR.getResponseField('SecretString');
const myKeyId = cdk.Fn.select(7,cdk.Fn.split('"', secretString));
It splits the secretString into an array using double quote as delimiter
cdk.Fn.split('"', secretString)
and selects the 7th item from array produced by split
cdk.Fn.select(7, <array>)

zapier performResume step not being waited for / run

I'm following the docs in zapier regarding the callbackUrl https://platform.zapier.com/cli_docs/docs#zgeneratecallbackurl however cannot seem to get the performResume step to be run. The zap I'm creating based on this integration also does not seem to wait for the callbackUrl to be hit.
const createScreenshot = (z, bundle) => {
const callbackUrl = z.generateCallbackUrl();
const promise = z.request({
url: 'https://myapi.com/v1/render',
method: 'POST',
params: {},
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: {
...bundle.inputData,
webhook_url: callbackUrl
},
removeMissingValuesFrom: {},
});
z.console.log("Returning from perform / createScreenshot");
return promise.then((response) => ({ ...response.data, waiting_for: "performResume" }));
const onScreenshotFinished = (z, bundle) => {
z.console.log("In performResume / onScreenshotFinished", bundle.cleanedRequest);
const responseBody = bundle.cleanedRequest;
let screenshotUrl;
if (responseBody.event === "render.succeeded") {
z.console.log("render was processed successfully", responseBody);
screenshotUrl = responseBody.result.renderUrl;
return { screenshotUrl, ...responseBody };
}
z.console.log("render was not processed", responseBody);
throw z.errors.Error("Screenshot was not successful");
}
module.exports = {
operation: {
perform: createScreenshot,
performResume: onScreenshotFinished,
...
}
}
We talked through this question (and its solution) on GitHub (zapier/zapier-platform#398), but to summarize for SO readers:
When setting up a resumable Zap, the editor uses the sample to populate the data in the callback. No actual waiting happens during the setup process. Once the zap is live, it works like normal.
So, to implement:
perform should return sample data that matches the data the "resume" webhook sends
performSubscribe can read that data and operate normally
See the GH issue for more info.

aws cdk lambda, appconfig typescript example please?

Can anyone provide or point at an AWS-CDK/typescript example provisioning an AWS AppConfig app/env/config consumed by a lambda please? Could not find anything meaningful online.
A little late to this party, If anyone is coming to this question, looking for a quick answer, here you go:
AppConfig Does Not Have Any Official L2 Constructs
This means that one has to work with L1 constructs currently vended here
This is ripe for someone authoring an L2/L3 construct (I am working on vending this custom construct soon - so keep an eye out on updates here).
This does not mean its hard to use AppConfig in CDK, its just that unlike L2/L3 constructs, we have to dig deeper into setting AppConfig up reading their documentation.
A Very Simple Example (YMMV)
Here is a custom construct I have to setup AppConfig:
import {
CfnApplication,
CfnConfigurationProfile,
CfnDeployment,
CfnDeploymentStrategy,
CfnEnvironment,
CfnHostedConfigurationVersion,
} from "aws-cdk-lib/aws-appconfig";
import { Construct } from "constructs";
// 1. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_appconfig-readme.html - there are no L2 constructs for AppConfig.
// 2. https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-working.html- this the CDK code from this console setup guide.
export class AppConfig extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
// create a new app config application.
const testAppConfigApp: CfnApplication = new CfnApplication(
this,
"TestAppConfigApp",
{
name: "TestAppConfigApp",
}
);
// you can customize this as per your needs.
const immediateDeploymentStrategy = new CfnDeploymentStrategy(
this,
"DeployStrategy",
{
name: "ImmediateDeployment",
deploymentDurationInMinutes: 0,
growthFactor: 100,
replicateTo: "NONE",
finalBakeTimeInMinutes: 0,
}
);
// setup an app config env
const appConfigEnv: CfnEnvironment = new CfnEnvironment(
this,
"AppConfigEnv",
{
applicationId: testAppConfigApp.ref,
// can be anything that makes sense for your use case.
name: "Production",
}
);
// setup config profile
const appConfigProfile: CfnConfigurationProfile = new CfnConfigurationProfile(
this,
"ConfigurationProfile",
{
name: "TestAppConfigProfile",
applicationId: testAppConfigApp.ref,
// we want AppConfig to manage the configuration profile, unless we need from SSM or S3.
locationUri: "hosted",
// This can also be "AWS.AppConfig.FeatureFlags"
type: "AWS.Freeform",
}
);
// Update AppConfig
const configVersion: CfnHostedConfigurationVersion = new CfnHostedConfigurationVersion(
this,
"HostedConfigurationVersion",
{
applicationId: testAppConfigApp.ref,
configurationProfileId: appConfigProfile.ref,
content: JSON.stringify({
someAppConfigResource: "SomeAppConfigResourceValue",
//... add more as needed.
}),
// https://www.rfc-editor.org/rfc/rfc9110.html#name-content-type
contentType: "application/json",
}
);
// Perform deployment.
new CfnDeployment(this, "Deployment", {
applicationId: testAppConfigApp.ref,
configurationProfileId: appConfigProfile.ref,
configurationVersion: configVersion.ref,
deploymentStrategyId: immediateDeploymentStrategy.ref,
environmentId: appConfigEnv.ref,
});
}
}
Here is what goes inside my lambda handler (please note that you should have lambda layers enabled for AppConfig extension, see more information here):
const http = require('http');
exports.handler = async (event) => {
const res = await new Promise((resolve, reject) => {
http.get(
"http://localhost:2772/applications/TestAppConfigApp/environments/Production/configurations/TestAppConfigProfile",
resolve
);
});
let configData = await new Promise((resolve, reject) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('error', err => reject(err));
res.on('end', () => resolve(data));
});
const parsedConfigData = JSON.parse(configData);
return parsedConfigData;
};
EDIT: As promised, I released a new npm library : https://www.npmjs.com/package/cdk-appconfig, as of this update, this is still pre-release, but time permitting I will release a 1.x soon. But this will allow people to check out/fork if need be. Please share feedback/or open PRs if you'd like to collaborate.
I have no experience with AWS AppConfig. But I would start here and here

Google Sign-In JavaScript client not working on PWA App

Since yesterday when I use the gapi.auth2 to do a Google Sign-in on an installed PWA app on Android, the App opens the browser window to select the user, but it remains blank.
The same page on the Chrome browser on Android open the user selection as usual. The code is the same, from the same server. The code was not modified in more than 15 days. I presume the problem is some change in the gapi JS client code from Google servers.
Inspecting the PWA Google Sign-in tab on chrome shows the following error:
Uncaught Failed to get parent origin from URL hash!
The origins on Google Developer Console are ok.
Anyone has any clue how to solve this?
Edit1: Code chunk
initGoogle() {
this.ngRedux.dispatch({ type: SN_INIT_GOOGLE });
Observable.create((observer: Observer<any>) => {
let head = document.getElementsByTagName('head');
(<any>window).__ongload = () => {
gapi.load('auth2', () => {
gapi.auth2.init({
client_id: `${AppConfig.google.clientID}`
}).then(() => {
this.auth2 = gapi.auth2.getAuthInstance();
this.googleInitiated();
observer.complete();
}, (err) => {
this.log.error(err);
observer.error(err);
});
});
};
let script: HTMLScriptElement = document.createElement('script');
script.src = 'https://apis.google.com/js/platform.js?onload=__ongload';
script.type = 'text/javascript';
head[ 0 ].appendChild(script);
}).pipe(
timeout(AppConfig.google.timeout),
retry(AppConfig.google.retries),
catchError(error => {
this.googleInitError();
return observableEmpty();
}),
take(1)
).subscribe();
}
async googleLogin(scope: string = 'profile email', rerequest: boolean = false, type: string = SN_GOOGLE_LOGIN): Promise<GoogleUser> {
let goopts = {
scope: this.ngRedux.getState().socialNetworks.getIn([ 'google', 'grantedScopes' ]),
prompt: rerequest ? 'consent' : undefined
};
try {
const user: GoogleUser = await this.auth2.signIn(<any>goopts);
...
return user;
} catch (error) {
...
return error;
}
}
Edit 2: Error screenshot
Screenshot
I had the similar issue as mentioned here. I had not registered my domain under Credential -> My OAuth Client ID -> Authorized JavaScript origins. By adding, it started working. Check the similar case for your app. It may help.
This bug should be fixed. Cannot reproduce it any more.

Apple Pay - onvalidatemerchant Payment Not Completed error

I am implementing apple pay onto our website. I do not have a macOS device and am using windows visual studios / mvcnet. I have done all the merchantID and certification stuff and configured it on the windows machine. I am using Apple Pay JS and on the step where the payment sheet is opened with session.begin(). I use an ajax call to retrieve a merchantSession, which I believe it does successfully because the object consumed by session.completeMerchantValidation(merchantSession) contains an epochnumber, expiration time and the site name. However, immediately after completeMerchantValidation, the oncancel event is fired, and I get a red alert saying "Payment not completed".
I need help with how to proceed from here, I read somewhere online that the domain on where I am testing needs to be a registered merchant domain. For example, when I test the functionality of the button, I need to be on www.mySite.com, where I have mySite.com registered as a domain. Can someone confirm if this is true.. because I am accessing the site from my iOS devices through local IP address. If that is not true, any help proceeding from where I am would be helpful.
function StartPaySession() {
var lineItems = [
{
label: 'Shipping',
amount: '0.00',
}
];
var shippingMethods = [
{
label: 'Free Shipping',
amount: '0.00',
identifier: 'free',
detail: 'Delivers in five business days',
},
{
label: 'Express Shipping',
amount: '5.00',
identifier: 'express',
detail: 'Delivers in two business days',
}
];
var total = {
label: 'Apple Pay Example',
amount: '8.99',
};
var paymentData = {
countryCode: 'US',
currencyCode: 'USD',
shippingMethods: shippingMethods,
lineItems: lineItems,
total: total,
supportedNetworks: ['amex', 'discover', 'masterCard', 'visa'],
merchantCapabilities: ['supports3DS'],
requiredShippingContactFields: ['postalAddress', 'email'],
};
var paySession = new ApplePaySession(2, paymentData);
paySession.onvalidatemerchant = function (event) {
var validationData = { ValidationUrl: event.validationURL };
$.ajax({
url: '/orders/cart/startapplepaysession',
method: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(validationData)
}).then(function (merchantSession) {
paySession.completeMerchantValidation(merchantSession);
alert("end = " + window.location.host);
}, function (error) {
alert("merchant validation unsuccessful: " + JSON.stringify(error));
paySession.abort();
});
};
paySession.onpaymentmethodselected = function (event) {
alert("1");
paySession.completePaymentMethodSelection(total, lineItems);
};
paySession.onshippingcontactselected = function (event) {
alert("2");
paySession.completeShippingContactSelection(ApplePaySession.STATUS_SUCCESS, shippingMethods, total, lineItems);
};
paySession.onshippingmethodselected = function (event) {
alert("3");
paySession.completeShippingMethodSelection(ApplePaySession.STATUS_SUCCESS, total, lineItems);
}
paySession.onpaymentauthorized = function (event) {
var token = event.payment.token;
alert("payment authorization | token = " + token);
paySession.completePayment(ApplePaySession.STATUS_SUCCESS);
}
paySession.oncancel = function (event) {
alert("payment cancel error " + event);
}
paySession.begin();
};
You are creating the Apple pay session at the wrong place.You need to create it from server side not on the client side.
These links might help requesting apple pay payment session, complete merchant validation
Steps are discussed here:on validate merchant
This is an old question, but thought I'd post my experience in case it's relevant. I was seeing the same behavior as described by original poster when testing on my local server, but was able to get payment sheet interactions to work.
What worked for me
Log in to AppleID as a Sandbox Test User
Specify "localhost" as the domain when performing merchant validation, e.g.:
{
"merchantIdentifier": "<your merchant ID>",
"displayName": "<your merchant display name>",
"initiative": "web",
"initiativeContext": "localhost"
}

Resources