I have this code in which I have used the cognitoidentityserviceprovider Method known as listUsers which returns a list of all my cognito users. Below is the code.
createPatient = async () => {
var params = {
UserPoolId: 'xxxxxxxxxxxxxxxxxxxx',
AttributesToGet: [ "email" ],
Filter: "",
Limit: 10
};
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
cognitoidentityserviceprovider.listUsers(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
})
}
Now on one hand I would like to know how I can return the result in e.g a <div></div> like when one returns the values of a listquery when dealing with graphql queries.
On the other hand I would like to know how I could create form which takes in the data (UserpoolId, AttributesToGet, Filter, Limit) and maps the values to params so that e.g a user could fill them out himself.
Hope I have expressed myself well. Thanks a lot for any help.
Related
I am trying to list the matching users from cognito userpool using expressJs api to check if the username already exists or not.
Code:
listUserFromUserpool = async () => {
var params = {
UserPoolId: process.env.USER_POOL_ID /* required */,
AttributesToGet: ["username"],
Filter: 'username = "example"',
Limit: 5,
PaginationToken: "What to pass here initially ?",
};
try {
let data = await this.cognitoIdentity.listUsers(params).promise();
return data;
} catch (error) {
console.log(error);
return false;
}
};
But getting error in PaginationToken . What should i insert into the PaginationToken param initially as I get the PaginationToken in the next response?
Or, is there any way to get single user without using pagination?
I have the following user defined role in security with a predicate function on the create for a collection called formEntryData. Now I can create if I don't have the function which is below.
Under the Create function option
Lambda("values", Equals(Identity(), Select(["data"], Var("values"))))
Now I am creating a request with the following code, which works when the create is just set to all via checking the box, but if I use the function above it fails with permission denied. I must be doing somethign wrong
import { query as q } from "faunadb";
import { serverClient } from "../../utils/fauna-auth";
import { authClient } from "../../utils/fauna-auth";
import { getAuthCookie } from "../../utils/auth-cookies";
export default async (req, res) => {
// const { firstName, lastName, telephone, creditCardNumber } = req.body;
const token = getAuthCookie(req);
console.log(token);
const data = req.body.data;
var element = req.body.data;
element["FormID"] = req.body.id;
try {
await authClient(token).query(
q.Create(q.Collection("FormEntryData"), {
data: element,
})
);
res.status(200).end();
} catch (e) {
res.status(500).json({ error: e.message });
}
};
Any help would be greatly appreciated.
UPDATE: I have also added a index for the collection and given it read permissions in the Role
This was also asked on the Fauna community forums: https://forums.fauna.com/t/roles-membership-auth-token-permissions-denied/1681/4
It looks like two things were needed:
update the create predicate to match the data.user field: Lambda("values", Equals(CurrentIdentity(), Select(["data", "user"], Var("values")))), and
a user field needs to be provided in order to pass the provided predicate.
The answer in the forums used two requests: One to retrieve the calling user document (with CurrentIdentity()), and another to create the FormEntryData document. This can be (should be) done with a single request to limit cost (Every request to Fauna will take at least one Transactional Compute Op), and of course network time for two round trips. Consider the following:
await authClient(token).query(
Let(
{
userRef: q.CurrentIdentity(),
},
q.Create(q.Collection("FormEntryData"), {
data: {
...element,
user: q.Var("userRef")
}
})
)
);
I've tried googling tutorials/examples but I cannot find anything.
Currently, I can insert the JSON record into the first column so I know everything is plugged in, but the JSON is not being broken apart and being put into their respective columns.
This is what I have on my client making the call with a POST
async function apiAddRecordToTable(record, table_name) {
params = record;
let res = await axios.post('http://localhost:3000/tables/addrecord/' + table_name, params);
console.log('add record api called with selected tablename: ' + table_name);
console.log(res.data);
}
This is what I am calling it with (just to test)
apiAddRecordToTable({
"user_id" : "110",
"user_lon" : "64",
"user_lat" : "-111",
}, 'user');
As for the server, I have a
app.post('/tables/addrecord/user', db.addRecordUser);
and that calls into this final function:
function addRecordUser(request, response) {
console.log('into final add record user method');
console.log(request);
const rec = request.body;
pool.query('INSERT INTO user_table VALUES ($1)', [rec], (error, results) => {
if (error) {
throw error
}
console.log(results);
response.status(201).send('Row added')
})
}
The final result in the table looks like
USER ID | USER_LON | USER_LAT
{"user_id":"110","user_lon":"64","user_lat":"-111"}, <null>, <null>
I understand that it's most likely inserting the entire record because of the $1 argument but I tried so many variations and searched on how json gets inserted but could not get it to work beyond this. How can I make it look like
USER ID | USER_LON | USER_LAT
110, 64, -111
You need to parse the body to get the parse JSON with JSON.pare() method.
And then you can pass the values to the Parameterized query. using $1, $2, $3 place holders.
Example:
function addRecordUser(request, response) {
console.log('into final add record user method');
console.log(request);
const rec = JSON.parse(request.body);
let query = 'INSERT INTO user_table VALUES ($1, $2, $3)';
let values = [rec.user_id, rec.user_lon, rec.user_lat];
pool.query(query, values, (error, results) => {
if (error) {
throw error
}
console.log(results);
response.status(201).send('Row added')
})
}
I have a rest api end point (http://localhost:3000/api/skills) that retrieves a list of Skills i.e. coding, driving and etc.
User is user model schema; contains id and email
Skill is skill model schema; contains name
export async function getList(req, res, next) {
try {
const promise = await Promise.all([
User.findById(req.user._id),
Skill.list({ skip: req.query.skip, limit: req.query.limit }),
]);
const skills = promise[1].reduce((arr, skill) => {
arr.push({
...skill.toJSON(),
});
return arr;
}, []);
return res.status(HTTPStatus.OK).json(skills);
} catch (err) {
err.status = HTTPStatus.BAD_REQUEST;
return next(err);
}
}
When I make a call to this api, it returns the set of skills for all the users available in the database. i.e. if I have two users, it will return skills for both users.
How do I make it that it returns it only for that specific user I want?
Here is a gist for it
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"
}