Access user data in AWS lambda function with custom authorizer - node.js

I got a nodeJS lambda function which returns database data and I'd like to filter that data based on the user. I created a custom authorizer lambda function which gets the user for a JWT token, but I couldn't find a way to pass data from the authorizer function to the database function, except for principalId (user.id).
What possibilities do I have here? Do I need to setup cognito? Or is there another possibility?

While reading documentation I found out something different from what the accepted answer suggests. Maybe it's new, but now output can include not only a principalId, but also a "context", which is an object. Sample:
{
"principalId": "xxxxxxxx", // The principal user identification associated with the token send by the client.
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow|Deny",
"Resource": "arn:aws:execute-api:<regionId>:<accountId>:<appId>/<stage>/<httpVerb>/[<resource>/<httpVerb>/[...]]"
}
]
},
"context": {
"key": "value",
"numKey": 1,
"boolKey": true
}
}
More from official documentation here. Much better aproach. :)

It seems you have a couple of options.
1) You can place all the information about the user you need into the principal id that is set in the custom authorizer function. So maybe you could serialize the user as json or if you need just a couple of ids then concatenate them together with special character like: principalId: "userId|organizationId". I believe that there is some caching that API Gateway does around that principal id that is returned so I wouldn't make it anything that could be highly dynamic. You could also turn off caching for authorization as well, but that would slow down that endpoint as a result.
2) Just pass the user id and do the user lookup again to get all the information in the function that does the database call. If you're using DynamoDB it will be fast supposedly.
And Cognito seems nice but I don't think it will help you solve the particular problem that you're having now. If it was me though I would choose option 2.

One possible way is to encode the data object to base64 string from authorizer lambda function and decode it down the line.
var principalId = new Buffer(JSON.stringify({
id: 5,
name: "John"
})).toString('base64');
var policy = require('./policy.json');
var policyConfig = {
"principalId": principalId,
"policyDocument": policy
};
context.succeed(policyConfig);
Decoding can be done in two places, one place is the request template section. This can be done by writing a transformation in velocity scripts as shown below
{
"requestTemplate": {
"application/json": {
"principal": "$util.urlEncode($util.base64Decode($context.authorizer.principalId))"
}
}
}
Other option is to decode inside the endpoint lambda function with the nodejs Base64 decoding. Check the following link for more information.
stack overflow answer for base64 decode

Related

Microsoft Graph create share link for specific people

I would like to share document by link in sharepoint from microsoft graph code. Default behaviour is that every person who has link can see this file. I want to make this link working just for specific people.
So my code look like this:
Permission permission = await _graphClient.Sites[_options.SiteId]
.Drives[driveId]
.Items[itemId]
.CreateLink("view", "organization")
.Request()
.PostAsync();
This create share link for all people in organization. Now I would like to grant permissions (https://learn.microsoft.com/en-us/graph/api/permission-grant?view=graph-rest-1.0&tabs=csharp)
await graphClient.Shares["{sharedDriveItem-id}"].Permission
.Grant(roles,recipients)
.Request()
.PostAsync();
But I have no idea what should be in place "{sharedDriveItem-id}". When I put there itemId it doesn't work. Also if I put there permission.link.webUrl it also doesn't work.
What am I doing wrong?
From this documentation.
Once you create the shared link the response object returns an id, that's what you should use in place of the {sharedDriveItem-id}. See a similar response object below.
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "123ABC", // this is the sharedDriveItem-id
"roles": ["write"],
"link": {
"type": "view",
"scope": "anonymous",
"webUrl": "https://1drv.ms/A6913278E564460AA616C71B28AD6EB6",
"application": {
"id": "1234",
"displayName": "Sample Application"
},
},
"hasPassword": true
}
Okey, I found solution. There are few steps:
As sharedDriveItem-id I used encoded webUrl following by this instruction https://learn.microsoft.com/en-us/graph/api/shares-get?view=graph-rest-1.0&tabs=http
When I was creating link (https://learn.microsoft.com/en-us/graph/api/driveitem-createlink?view=graph-rest-1.0&tabs=http) in place scope i put "users"- there is no option like that in documentation but without that it doesn't work
I added Prefer in header https://learn.microsoft.com/en-us/graph/api/driveitem-createlink?view=graph-rest-1.0&tabs=http
I was using clientSecret/clientId authorization so I had to add azure app access to Sites.Manage.All and Sites.FullControl.All in Graph Api Permissions
Everything works If you using Microsoftg.Graph nuget in newest version (4.3 right now if I remember correctly)

How to do DynamoDB Fine-Grained Access Control?

First time posting a question so if I am not explaining properly please let me know. I am still very new to AWS and trying my best to learn.
MAIN QUESTION: What is the simplest way for me to test that the following setup is working as intended?
I was working with AWS DynamoDB trying to follow this idea:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html
Where each UserId will be their partition key and they will only be able to read, write and delete information on their specific row/items.
I first create a table using the same name GameScores
dynamodb table image
I also create a user pool called "gamers" with all default setting.
enter image description here
I create a policy using the policy they have on the documention and call it "dynmodbgametable" the only thing I changed was the "Resource" to match the ARN of the dynamoDB "GameScores" I just created.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccessToOnlyItemsMatchingUserID",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:Query",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:BatchWriteItem"
],
"Resource": [
"arn:aws:dynamodb:us-..rest of arn../GameScores"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${www.amazon.com:user_id}"
],
"dynamodb:Attributes": [
"UserId",
"GameTitle",
"Wins",
"Losses",
"TopScore",
"TopScoreDateTime"
]
},
"StringEqualsIfExists": {
"dynamodb:Select": "SPECIFIC_ATTRIBUTES"
}
}
}
]
}
I create a role. clicking Web identity for type of trusted entity and for the Choose a web identity provider I select Amazon Cognito and Identity Pool ID as the pool id from user pool "gamers" Pool id and then attach the policy I just created called "dynmodbgametable". I call the role "GameRole"
enter image description here
I go ahead and create two users in the "gamer" user pool.
At this point I don't know what I am suppose to do to test it to see if I have even followed the intructions propertly. I started setting up this Nodejs script to test and it works of putting stuff and getting stuff from the database, but I know it is using my default root creditials that are saved on my local machine. I think I am suppose to setup the "AWS.config.credentials" to something that would include the userpool and put in one of the usernames with their associated password. But I haven't had much luck figuring out how exactly I am suppose to do that. Was it nesscessary to to create a client app for the "gamers" user pool as well before this will work?
Here is the little script I was trying if that somehow helps.
var AWS = require("aws-sdk");
AWS.config.update({ region: "us-east-2" });
var ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
var params = {
TableName: "GameScores",
Item: {
UserId: { S: "user id" },
GameTitle: { S: "hobo" },
},
};
ddb.putItem(params, function (err, data) {
if (err) {
console.log("Error", err);
} else {
console.log("Success", data);
}
});
I don't really know how to obtain "${www.amazon.com:user_id}" and where or how to pass it to and from. Is there some endpoint on the database itself? Am I suppose to create some kind of endpoint to point to? I just know that this is the variable that is suppose to determine the partition key.
If I can figure out how to test that it is working, I feel some of this will click for me. Right now I feel like I am not quite understanding what is going on conceptually. All the YouTube videos, documents and other Stack overflow posts I have read online only seem to talk about this on a higher level or are not within the scope of what I am trying to do.
Thanks for any help that can be provided! I will be sure to edit this if something is missing.
EXTRA INFO PROBABLY NOT NEEDED: I currently have an AWS Amplify web application that has a working interface that has working authentication with a user pool. I would like to add this ability of fine grained access control so that when a user logs in, they would have access to edit their profile information (name, age etc) and not be able to view other profiles information. If I can get a working prototype of this fine grained access control stuff, I should be able to figure out how to get it working for my Amplify application.
For anyone that happens to stumble onto my post, I ended up going a slightly different route. It may not be useful for you but it is what solved my problem.
Because I was using AWS Amplify, I reached out to their discord (shout out to undef_obj for answering me!) he said the following:
looking at your link, you're attempting to leverage the IAM policy variables for Cognito Identity and craft your own access control matrix solution. While this is possible, it's going to be a lot of effort and testing with potential for security issues if something is implemented wrong. Assistance with that is outside the scope of the Amplify framework. However, if you're looking for fine grained authorization with Amplify this is built into the GraphQL Transformer #auth directive and I'd recommend looking at that. There are plenty of examples showing how to setup a React app to an Amplify GraphQL endpoint which uses AWS AppSync and DynamoDB as the backing store.
So I looked into this and found that using AWS AppSync worked for me!
I went to THIS LINK and followed some of the instructions there. Specifically:
Amplify add api
selected: GraphQl
authroization type: Amazon Cognito User Pool
(I already had user pool added to the project so it skipped the process of making a new user pool)
I kept choosing the defaults until "Choose a schema template"
I picked "Objects with fine-grained access control (e.g., a proj
ect management app with owner-based authorization)"
From there it setup a sample project I could start learning GraphQL from and how to implement the fine-grained access control. Using the code from the getPrivateNote resolver was probably the most useful thing. I also used this appsync starter application to figure out how to interact with GraphQL from my react client. This whole process took me HOURS AND HOURS to figure out, and currently I am still trying to fully understand how it all works, but so far this AppSync GraphQL seems to be the best for my scenario. The built in query system that AppSync has made it easier to test access control (i.e login with one user and see if I only had access to my own items)
Here is what my reactjs code ended up looking like for the client side:
import { API, graphqlOperation } from 'aws-amplify';
import QueryUserInfo from './graphql/QueryUserInfo';
...
getRequest = (evt) => {
return new Promise((resolve, reject) =>
{
API.graphql(graphqlOperation(QueryUserInfo))
.then((data) => {
if(data) {
console.log(data);
resolve(data);
} else {
console.log(data);
resolve(null);
}
})
.catch((err) => {
console.log(err);
resolve(null);
});
});
}
This is what the actual QueryUserInfo.js file looked like:
import gql from "graphql-tag";
export default gql(`
query QueryName {
getUser(id: "c35...rest of cognito user id...69") {
id
email
name
}
}`);
The resolver code is too long to post, but I just used the template code from Amplify and I think I only had to change #set( $allowedOwners0 = $util.defaultIfNull($ctx.result.owner, []) )
to #set( $allowedOwners0 = $util.defaultIfNull($ctx.result.id, []) )
since "id" was what I was using on my dynamoDB table, not "owner". Good luck to anyone reading this!

How to get the user id from Slack to bot service

I am creating a simple bot using Azure LUIS and this is my first one. I made some decent progress after doing some research and also now integrated with Slack as channel to test it.
The bot functionality is working fine, but I am looking to identify the user. So that I can personalize the bot conversation and also to pull the user specific information from his profile table.
Is there anyway, that I can get a UID or any reference ID of the slack user and so I can store that in my user table along with user profile?
So next time, when the user greets the bot, the bot can say "Hello, John." instead of justing say "Hello."
Thanks!
Yes. You can use the channelData object to get the ApiToken, and user values. For example, in C#, you could use turnContext.Activity.ChannelData to get those values in JSON:
{{
"SlackMessage": {
"token": "............",
"team_id": "<TEAM ID>",
"event": {
"type": "message",
"text": "thanks",
"user": "<USER WHO MESSAGED>",
"channel": "............",
"channel_type": "channel"
},
"type": "event_callback",
"event_id": ""............",
"event_time": 1553119134,
"authed_users": [
"............",
"<USER WHO MESSAGED>"
]
},
"ApiToken": "<ACTUAL TOKEN HERE>"
}}
Then, using those two pieces of information, you can then retrieve info from Slack.
https://slack.com/api/users.info?token=<ACTUAL TOKEN HERE>&user=<USER WHO MESSAGED>&pretty=1
And get a response that has the info you need:
{
"ok": true,
"user": {
"id": "<USER WHO MESSAGED>",
"team_id": "<TEAM ID>",
"real_name": "Dana V",
Ideally, you would would probably want to have bot user state setup and check that first, then if not there, then make the API call to Slack, then store in state. Therefore further requests don't need to go to Slack, but will just pull from the state store.
Basically, you could/should do this in the onTurn event. First, create your user state storage such as here.
Then you could check for that value and write to it if not populated. This example on simple prompts, might be helpful. You won't need to prompt for your user's name, as this example does, but does read/write username from state. You could still use dialogs, but you won't need them for the name prompting as you are doing that dynamically.
You can see here where username is being set and here where it is being retrieved. In this case, it is in the dialogs, but again; you would/could just do in the turn context (using logic to get and if not there, set).
I found the solution by priting the whole session object, which is having all the required informaiton. This could be same as mentioned by Dana above, but after debugging, this follwing made simple without making any changes.
var slackID = session.message.address.user.id
With above, I am able to identify the user.
Thanks.

AuthorizationError when confirming SNS subscription over HTTP

I'm writing a simple SNS client that is meant to subscribe itself to an SNS topic and then listen for notifications. I can successfully submit a sns.subscribe request, but when I pick up the SubscriptionConfirmation POST message from AWS and try and respond using sns.confirmSubscription I get an AuthorizationError returned:
[AuthorizationError: User: arn:aws:iam::xxx:user/mv-user is not authorized to perform: SNS:ConfirmSubscription on resource: arn:aws:sns:us-east-1:xxx:*]
If I use exactly the same Token and TopicArn in a GET query to the server the subscription confirmation works fine, with no authentication.
Any ideas why it's not working? My SNS topic is wide open with publish/subscribe permissions set to 'Everyone'.
For reference, my code is something like this:
var params = {
TopicArn: topicArn, // e.g. arn:aws:sns:us-east-1:xxx:yyy
Token: token // long token extracted from POST body
};
sns.confirmSubscription(params, function (err, data) {
if (err) {
// BOOOM - keep getting here with AuthorizationError
} else {
// Yay. Worked, but never seem to get here :(
}
});
However, if I navigate to the URL similar to this in a browser (i.e. completely unauthenticated), it works perfectly:
http://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&Token=<token>&TopicArn=arn%3Aaws%3Asns%3Aus-east-1%3Axxx%3Ayyy&Version=2010-03-31
The only differences seem to be the inclusion of 'Authorization' and 'Signature' headers in the programmatic version (checked using Wireshark).
Any ideas? Thanks in advance!
Update
In my code, if I just programatically do a simple GET request to the SubscribeURL in the SubscriptionConfirmation message this works fine. Just seems odd that the confirmSubscription API call doesn't work. Will probably stick to this workaround for now.
Update 2
Also get the same error when calling sns.unsubscribe although, again, calling the UnsubscribeURL in each notification works. Seems other people have run into that issue too but can't find any solutions.
I faced a similar issue while developing my application.
The way I ended up solving it is the following:
go to IAM and click on your user
go to the permissions tab and click on "Attach Policy"
use the filter to filter for "AmazonSNSFullAccess"
Attach the above policy to your user.
The above should take care of it.
If you wanna be fancy you can create a custom policy that is based on "AmazonSNSFullAccess" and apply it to you user instead.
The custom policy would be something similar to the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sns:ConfirmSubscription"
],
"Effect": "Allow",
"Resource": "YOUR_RESOURCE_ARN_SHOULD_BE_HERE"
}
]
}
The error says it all:
[AuthorizationError: User: arn:aws:iam::xxx:user/mv-user is not authorized to perform: SNS:ConfirmSubscription on resource: arn:aws:sns:us-east-1:xxx:*]
is basically telling you that the IAM user you're using to call ConfirmSubscription doesn't have the proper permissions to do so. Best bet is to update the permissions for that IAM user, specifically adding ConfirmSubscription permissions.
(Based on your comments, even though the documentation says otherwise, the error is pretty specific... might be worth following up directly with AWS about this issue, since either the error message or documentation is incorrect).

unable to configure a permission role in RavenDB

looking for some help or a blog post really regarding using the auth bundle with RavenDB..
using the HelloWorld example: http://ravendb.net/tutorials/hello-world
i'm trying to disable the user from querying for orders.. i've tried different auth roles approaches but i can't get the damn thing to work.
at present i've:
* created a authorization user
* created a authorization role
Id: Authorization/Roles/Orders
{
"Permissions": [
{
"Operation": "order/1",
"Tags": [
"Orders"
],
"Allow": false,
"Priority": 1
}
]
}
ID: Authorization/Users/ayende
{
"Name": "Ayende Rahien",
"Roles": [
"Authorization/Roles/Orders"
]
}
just can't get my head around how to filter out the orders from queries.
for example, querying orders/1 will return an order of 1 prior to applying the permission.
after using:
session.SecureFor("Authorization/Users/ayende", "orders/1");
I would expect orders to return no orders..
do i have this concept totally wrong or just configured my permission's wrong?
thanks
You can use the IsAllowed method to check whatever or not you can access a document or now, but also to check why you can / can't access a document.
Have you applied your permission to the document then invoked SaveChanges? Maybe if you post your code it would easier to tell what's happening.

Resources