How do you use Personal AWS S3 Resources with an Alexa-Hosted Skill, as well as Alexa-hosted S3 resources?

AWS-hosted Alexa skills offer 3 lambda functions, one S3 bucket and one DynamoDB database; but give you very little control over the databases and ultimately they are constrained in size.

I want to extend the S3 storage offered by Amazon for my Alexa Skill to additionally use my personal AWS S3 storage. Using this weblink I have managed to add my personal DynamoDB additionally to the single database offered by AWS hosting and it seems to work. I now have one DynamoDB database for users and the second for content. This involved setting up a Role in my personal AWS account, with a trust relationship to the ARN for my AWS-hosted lambda function, which allows it to assume the role with permissions to DynamoDB and S3. (I set both up at the same time and DynamoDB works.)

The problem I am encountering is with S3 pre-signed URLs, which I can’t make work. Pre-signed URLs use a required util file:

const AWS = require('aws-sdk');

const s3SigV4Client = new AWS.S3({
    signatureVersion: 'v4',
    region: process.env.S3_PERSISTENCE_REGION
});

module.exports.getS3PreSignedUrl = function getS3PreSignedUrl(s3ObjectKey) {

    const bucketName = process.env.S3_PERSISTENCE_BUCKET;
    const s3PreSignedUrl = s3SigV4Client.getSignedUrl('getObject', {
        Bucket: bucketName,
        Key: s3ObjectKey,
        Expires: 60*1 // the Expires is capped for 1 minute
    });
    console.log(`Util.s3PreSignedUrl: ${s3ObjectKey} URL ${s3PreSignedUrl}`);
    return s3PreSignedUrl;

}

As a minimum I need to change the bucket name (highlighted) from the S3_PERSISTENCE_BUCKET to the name of my new bucket in my personal account.; but I want to do this conditionally so that the bulk of my code can call the AWS-hosted S3 bucket, but occasionally I want to be access the additional content in the personally-hosted S3 bucket. As you can see from the code below, I’ve had a few attempts.

async handle(handlerInput) {
    const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
    
    // 1. Assume the AWS resource role using STS AssumeRole Action
    const STS = new AWS.STS({ apiVersion: '2011-06-15' });
    const credentials = await STS.assumeRole({
        RoleArn: 'arn:aws:iam::{{MY ROLE}}',
        RoleSessionName: 'AlexaHostedLambdaRole' 
    }, (err, res) => {
        if (err) {
            console.log('AssumeRole FAILED: ', err);
            throw new Error('Error while assuming role');
        }
        return res;
    }).promise();
    
    // 2. Make a new DynamoDB instance with the assumed role credentials
    //    and scan the DynamoDB table
    const dynamoDB = new AWS.DynamoDB({
        apiVersion: '2012-08-10',
        accessKeyId: credentials.Credentials.AccessKeyId,
        secretAccessKey: credentials.Credentials.SecretAccessKey,
        sessionToken: credentials.Credentials.SessionToken
    });

    var params = {
      TableName: "AlexaHostedTable",
      Key: {
        memory_id: { S: "00002" },
      }
    };

    const tableData = await dynamoDB.getItem(params, function (err, data) {
        if (err) {
            console.log('getItem FAILED', err);
            throw new Error('Error while getting Item from DynamoDB');
        } else {
        return data;}
    }).promise();

    // 3. Make a new S3 instance with the assumed role credentials

    const s3Two = new AWS.S3({
        apiVersion: '2012-08-10',
        accessKeyId: credentials.Credentials.AccessKeyId,
        secretAccessKey: credentials.Credentials.SecretAccessKey,
        sessionToken: credentials.Credentials.SessionToken
    });

    // ... Use table data as required ...
    var audioUrl =  Util.getS3PreSignedUrl({{..PREFIX..}}tableData.Item.file.S +".mp3").replace(/&/g,'&');            //Marker
 //        const Util2 = require('./util2.js'); // pre-signs URLs from personal S3 bucket
    var audioUrl2 = Util.getS3PreSignedUrl(tableData.Item.file.S +".mp3").replace(/&/g,'&');            //Marker
      
      speakOutput ='Introductory text blah blah blah'+`<audio src="${audioUrl}"/>`+`<audio src="${audioUrl2}"/>`+'Suffix text blah blah blah';                    // Marker
      console.log ("speakOutput: ", speakOutput);
         
    return handlerInput.responseBuilder
        .speak(speakOutput)
        .reprompt('add a reprompt if you want to keep the session open for the user to respond')
        .getResponse();
}
};

I have tried:

With the current version of the code, the references to the old bucket will work. Alexa appears to created pre-signed URLs to the bucket, but I think this is a red herring as they don't work.

Upvotes: 0

Views: 39

Answers (0)

Related Questions