I am using AWS transcription API on Node JS with following code
const tClient = new TranscribeClient({
region: "us-east-1",
credentials: {
accessKeyId: AWS_ID,
secretAccessKey: SECRET,
}
});
const params = {
TranscriptionJobName: "firstjob",
LanguageCode: "en-US", // For example, 'en-US'
MediaFormat: "m4a", // For example, 'wav'
Media: {
MediaFileUri: "https://transcribe-demo.s3-REGION.amazonaws.com/hello_world.m4a",
},
};
const run = async () => {
try {
const data = await tClient.send(
new StartTranscriptionJobCommand(params)
);
console.log("Success - put", data);
return data; // For unit tests.
} catch (err) {
console.log("Error", err);
}
};
run();
But I am getting following error, I have checked all the permissions and access keys are correct . I am unable to understand error reason.
AccessDeniedException: User: arn:aws:iam::494240200407:user/demno_system is not authorized to perform: transcribe:StartTranscriptionJob on resource: arn:aws:transcribe:us-east-1:494240200407:transcription-job/firstjob because no permissions boundary allows the transcribe:StartTranscriptionJob action
Any inputs are appreciated.
As stated in the error message, you have a permission boundary that doesn't allow this action. The fact that you added the required policy to the user has no effect, since the access is limited by the permission boundary. You need to edit the user's permission boundary to allow this.
Refer to the documentation.
Related
We operate bots by combining Firebase and Slack/bolt.
I currently use functions.config()to manage my Slack tokens and secrets, but would like to migrate to using Secret Manager.
https://firebase.google.com/docs/functions/config-env#secret-manager
boltapp.js
const functions = require("firebase-functions");
const { App, ExpressReceiver, subtype } = require("#slack/bolt");
const config = functions.config();
const expressReceiver = new ExpressReceiver({
signingSecret: process.env.SLACK_SECRET,
endpoints: "/events",
processBeforeResponse: true,
});
const app = new App({
receiver: expressReceiver,
token: process.env.SLACK_TOKEN
});
app.error(error => { console.error(error) });
app.use(async ({ client, payload, context, next }) => {
console.info('It\'s payload', JSON.stringify(payload))
if (!context?.retryNum) {
await next();
} else {
console.debug('app.use.context', context);
}
});
//**bot processing**//
// https://{your domain}.cloudfunctions.net/slack/events
module.exports = functions
.runWith({ secrets: ["SLACK_TOKEN","SLACK_SECRET"] })
.https.onRequest(expressReceiver.app);
But when I rewrote it for migration, I got the following error.
Is there a way to rewrite the code while avoiding this error?
Failed to load function definition from source: FirebaseError: Failed to load function definition from source: Failed to generate manifest from function source: Error: Apps used in a single workspace can be initialized with a token. Apps used in many workspaces should be initialized with oauth installer options or authorize.
Since you have not provided a token or authorize, you might be missing one or more required oauth installer options. See https://slack.dev/bolt-js/concepts#authenticating-oauth for these required fields.
I'm trying to connect IBM Watson and Google Assistant, but I keep receiving this error "TypeError: Cannot read property 'output' of undefined" and this "Function execution took 3323 ms, finished with status: 'crash'"
This is my code:
const {actionssdk} = require('actions-on-google');
const functions = require('firebase-functions');
const app = actionssdk({debug: true});
app.intent('actions.intent.MAIN', (conv) => {
conv.ask('Olá, como posso lhe ajudar?');
});
app.intent('actions.intent.TEXT', (conv, input) => {
var AssistantV1 = require('watson-developer-cloud/assistant/v1');
var assistant = new AssistantV1({
username: '###################################',
password: '###################################',
url: '###################################',
version: '2018-07-10'
});
conv.ask("eeeeeeeeeeeeeeeee");
return new Promise( (resolve, reject) => {
assistant.message(
{
workspace_id: '###################################',
input: { text: input },
headers: {'Content-Type':'application/json'}
},
function(err, response) {
conv.ask(response.output.text[0]);
resolve();
}
);
})
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
Rebeca, just to adding information, you are trying to add some outbound access, but you need to configure your account to do that.
"Billing account not configured. External network is not accessible
and quotas are severely limited. Configure billing account to remove
these restrictions"
If you wanted to call some API (IBM Watson, as verified) you'd need to enable billing.
For the other quotas, take a look here to see prices - as you can see there are limits to the number of invocations using free tier.
Your responseobject is null. Check it is not equal to null before to use it:
let speech;
if (response !== null) {
speech = response.output.text[0];
}
else{
speech = "I'm sorry, there was an error and I'm unable to answer";
}
conv.ask(speech);
I am attempting to verify my pact.json that has been generated by my consumer. However for verifying I need to include AWS4 credentials in order to be able to get a response from my provider. I am attempting to do this using customProviderHeaders. I am using the library AWS4(https://github.com/mhart/aws4) to generate the token. Below is my code:
const aws4 = require('aws4');
const path = require('path');
import { before, beforeEach, describe, it } from 'mocha';
const {
Verifier
} = require('../../../node_modules/#pact-foundation/pact');
function getToken() {
const opts: any = {
method: 'GET',
region: 'us-east-2',
service: 'execute-api',
path: '/qa/api/',
host: '123456789.execute-api.us-east-2.amazonaws.com',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
aws4.sign(opts, {accessKeyId: '$AWSACCESSKEY', secretAccessKey: '$AWSSECRETKEY'});
return opts.headers;
}
describe('Pact Verification', () => {
it('should validate the watchlist expectations', () => {
let headers = getToken();
let authToken = headers.Authorization;
let date = headers[`X-Amz-Date`];
let opts = {
provider: 'DealerBlock',
providerBaseUrl: 'https://3ua1cprd53.execute-api.us-east-2.amazonaws.com',
pactUrls: [path.resolve(process.cwd(), 'src/test/pact/path_to_my_json')],
customProviderHeaders: [`Authorization: ${authToken}`, `X-Amz-Date: ${date}`]
};
return new Verifier().verifyProvider(opts)
.then(output => {
console.log('STARTED');
console.log(opts.pactUrls);
console.log('Pact Verification Complete');
console.log(output);
});
});
});
The function getToken() generates a new token and I then grab the token and date and insert them into my request using the customer provider headers.
I see the following:
INFO: Replacing header 'Authorization: ' with 'Authorization: AWS4-HMAC-SHA256 Credential=AKIAJ5FTCODVMSUTEST/2018908/us-east-2/execute-api/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ceea9aac0303769da58357cb37cb849cb0bbfc13ff0a25cea977385368531349'
INFO: Replacing header 'X-Amz-Date: ' with 'X-Amz-Date: 20180528T184202Z'
However I get the following error:
Actual: {"message":"The request signature we calculated does not match the signature you provided.
Check your AWS Secret Access Key and signing method. Consult the service documentation for details."}
Am I using the customProviderHeaders in the correct manner? Or does anyone have any suggestions as to what I should do differently? I am able to send a request using the same credentials via Postman so not sure whats going on here.
Thanks!
It looks OK to my eyes.
Could it be that you're not interpolating the variables (that also appear not to be defined anywhere) in the following statement:
aws4.sign(opts, {accessKeyId: '$AWSACCESSKEY', secretAccessKey: '$AWSSECRETKEY'});
Was able to get this working when I passed in headers of: 'Content-Type': 'application/x-www-form-urlencoded' via customProviderHeaders.
Even though this header was listed in my consumer generated json contract, the pact provider did not seem to see it.
I'm trying to establish a federation among Amazon and Salesforce, in this way: if a user correctly authenticates through Salesforce it will see all S3 buckets in the given account.
Quite simple, I followed this blog post and changed something (i.e. I don't use a DyanamoDb table and the callback is for simplicity inside an S3 bucket). The flow that I'm trying to implement is called Enhanced (simplified) flow (details here):
I slightly modified the callback code compared to the article:
function onPageLoad() {
var url = window.location.href;
var match = url.match('id_token=([^&]*)');
var id_token = "";
if (match) {
id_token = match[1];
} else {
console.error("Impossibile recuperare il token");
}
AWS.config.region = "eu-west-1"
const cognitoParams = {
AccountId: "ACC_ID",
IdentityPoolId: "eu-west-1:XXX",
Logins: {
"login.salesforce.com": id_token
}
}
const identity = new AWS.CognitoIdentity()
identity.getId(cognitoParams, function (err, identityData) {
if (err) {
printMessage(err);
return;
}
const identityParams = {
IdentityId: identityData.IdentityId,
Logins: cognitoParams.Logins
}
identity.getCredentialsForIdentity(identityParams, function (err, data) {
if (err) {
printMessage(err);
} else {
var c = {
region: 'eu-west-1',
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretKey
};
var s3 = new AWS.S3(c);
// HERE IS THE ERRORE - data is empty and response contains the error
s3.listBuckets((response, data) => {
data.Buckets.forEach(function (value) { appendMessage(value.Name) })
});
}
});
});
// IRRELEVANT CODE
}
I can get the token from Salesforce, I can get the access and secret keys but when I try to list the buckets I get a laconic:
The AWS Access Key Id you provided does not exist in our records.
I found this error reasonable since I have no user at all and the keys are created on-the-fly. Where can I hit my head? The SDK is 2.103.0.
Could be due to eventual consistency of IAM, can you try to include a delay before calling the listbucket api or make the request to us-east-1 endpoint?
http://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_general.html#troubleshoot_general_access-denied-service2.
GetCredentialsForIdentity returns temporary credentials. So you should include AccessKeyId, SecretKey and SessionToken to make the request.
http://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
Hope this helps.
As far as I can tell, there is no way for an AWS Lambda function to look up a username of my Cognito users (I am using UserPools).
This seems extremely strange as I would have thought that applications everywhere depends almost always on manipulating the username.
I have been successful in getting the Cognito IdentityId but I can't see any way to relate the IdentityId to anything that looks up the Cognito User that the IdentityId relates to.
Is there any way of getting the username? What is the relationship between IdentityId and username?
I struggled to find an answer to this problem for a while because there just aren't any concise responses on any of these threads online.
It sounds like you're trying to come up with an effective Authorization strategy after the user has Authenticated their credentials against your Cognito User Pool using custom attributes.
I created a library that I use to export a few functions that allow me to capture the UserPoolId and the Username for the authenticated user so that I can capture the custom:<attribute> I need within my lambda so that the conditions I have implemented can then consume the API to the remaining AWS Services I need to provide authorization to for each user that is authenticated by my app.
Here is My library:
import AWS from "aws-sdk";
// ensure correct AWS region is set
AWS.config.update({
region: "us-east-2"
});
// function will parse the user pool id from a string
export function parseUserPoolId(str) {
let regex = /[^[/]+(?=,)/g;
let match = regex.exec(str)[0].toString();
console.log("Here is the user pool id: ", match);
return match.toString();
}
// function will parse the username from a string
export function parseUserName(str) {
let regex = /[a-z,A-Z,0-9,-]+(?![^:]*:)/g;
let match = regex.exec(str)[0].toString();
console.log("Here is the username: ", match);
return match.toString();
}
// function retries UserAttributes array from cognito
export function getCustomUserAttributes(upid, un) {
// instantiate the cognito IdP
const cognito = new AWS.CognitoIdentityServiceProvider({
apiVersion: "2016-04-18"
});
const params = {
UserPoolId: upid,
Username: un
};
console.log("UserPoolId....: ", params.UserPoolId);
console.log("Username....: ", params.Username);
try {
const getUser = cognito.adminGetUser(params).promise();
console.log("GET USER....: ", getUser);
// return all of the attributes from cognito
return getUser;
} catch (err) {
console.log("ERROR in getCustomUserAttributes....: ", err.message);
return err;
}
}
With this library implemented it can now be used by any lambda you need to create an authorization strategy for.
Inside of your lambda, you need to import the library above (I have left out the import statements below, you will need to add those so you can access the exported functions), and you can implement their use as such::
export async function main(event, context) {
const upId = parseUserPoolId(
event.requestContext.identity.cognitoAuthenticationProvider
);
// Step 2 --> Get the UserName from the requestContext
const usrnm = parseUserName(
event.requestContext.identity.cognitoAuthenticationProvider
);
// Request body is passed to a json encoded string in
// the 'event.body'
const data = JSON.parse(event.body);
try {
// TODO: Make separate lambda for AUTHORIZATION
let res = await getCustomUserAttributes(upId, usrnm);
console.log("THIS IS THE custom:primaryAccountId: ", res.UserAttributes[4].Value);
console.log("THIS IS THE custom:ROLE: ", res.UserAttributes[3].Value);
console.log("THIS IS THE custom:userName: ", res.UserAttributes[1].Value);
const primaryAccountId = res.UserAttributes[4].Value;
} catch (err) {
// eslint-disable-next-line
console.log("This call failed to getattributes");
return failure({
status: false
});
}
}
The response from Cognito will provide an array with the custom attributes you need. Console.log the response from Cognito with console.log("THIS IS THE Cognito response: ", res.UserAttributes); and check the index numbers for the attributes you want in your CloudWatch logs and adjust the index needed with:
res.UserAttributes[n]
Now you have an authorization mechanism that you can use with different conditions within your lambda to permit the user to POST to DynamoDB, or use any other AWS Services from your app with the correct authorization for each authenticated user.
In the response that you can see in res.UserAttributes[n] you will see the attribute for sub which is what you are looking for.
You can get the JWT token from the Authorization header and then decode it with some library for your language.
In the payload of the JWT is the username.
Or you can call listUsers on CognitoIdentityServiceProvider (http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#listUsers-property) with a filter of the sub that you get in {...}authorizer.claims.sub.
I got user details in lambda after adding Cognito Authorizer in Api gateway which gives decoded Authorization token passed in header in event.requestContext.authorizer.claims object.
elaborating on #doorstuck's answer, If you are using Lambda invoked by APIG with AWS_IAM Authorization. Then, you can get the username and other attributes as follows:
The event.requestContext.identity.cognitoAuthenticationProvider is a string that looks like
"cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxxx,cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxx:CognitoSignIn:SSSSSSSS"
The SSSSSSSS is the sub of the user in User Pool. You can easily decode the string to get the sub and use it in the filter of listUsers.
Example:
const provider =
event.requestContext.identity.cognitoAuthenticationProvider;
const sub=provider.split(':')[2];
const Params = {
UserPoolId: 'xxxxxxxxx', /* required */
Filter: "sub=\""+ sub + "\"",
Limit: 1
};
cognitoidentityserviceprovider.listUsers(Params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data.Users[0].Attributes);
});
The data includes useful information about the returned user where data.Users[0].Attributes has all your user attributes.
The result is
[ { Username: 'xxxxx',
Attributes: [Object],
UserCreateDate: 2017-09-12T04:52:50.589Z,
UserLastModifiedDate: 2017-10-24T01:50:00.109Z,
Enabled: true,
UserStatus: 'CONFIRMED' } ] }
data.Users[0].Attributes is
[ { Name: 'sub', Value: 'SSSSSSS' },
{ Name: 'address', Value: 'xxxxxxxxi' },
{ Name: 'email_verified', Value: 'true' },
..... ]
Note that you can also filter the returned attributes by using
AttributesToGet: [
'STRING_VALUE',
/* more items */
],
in Params.
If you front your Lambda function with API Gateway you can use the Cognito Authorizer to authenticate your User Pools tokens directly and pass in the username extracted from the token via $context.authorizer.claims.preferred_username
More details on this integration is here: http://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html
We can manipulate context in body mapping template also to get the sub (username) and it works fine for me. Try out this rather than splitting in you lamda function.
#set($sub = $context.identity.cognitoAuthenticationProvider.split(':')[2])
{
"tenantId": "$sub"
}