Create user with custom attribute using AdminCreateUser in AWS Cognito - amazon-web-services

I am trying to create user in AWS Cognito with adminCreateUser API with the below code
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
UserPoolId: "us-east-1_302HlhnaC", /* required */
Username : "test#yopmail.com",
ForceAliasCreation: true,
TemporaryPassword: 'test#yopmail.com',
UserAttributes: [
{
Name: 'given_name', /* required */
Value: 'test'
},
{
Name: 'family_name', /* required */
Value: 'kumar'
},
{
Name: 'name', /* required */
Value: 'test'
},
{
Name: 'custom:dob', /* required */
Value: '1990-07-25'
},
{
Name: 'email', /* required */
Value: 'test#yopmail.com',
},
{
Name: 'email_verified', /* required */
Value: 'true',
}
/* more items */
],
};
cognitoidentityserviceprovider.adminCreateUser(params, function(error, data) {
console.log(error,data);
res.send("test");
});
It always throwing following exception :
InvalidParameterException: Attributes did not conform to the schema: custom:dob: Attribute does not exist in the schema.
Is am doing anything wrong,if yes please let me know the solution.
Thanks

You must add the custom attribute ahead of time. You can create custom attributes by visiting the User Pool and clicking the Attributes link.

Just adding my case here.
In my CloudFormation, I have:
Schema:
- AttributeDataType: String
Name: role
DeveloperOnlyAttribute: true
Mutable: true
Required: false
In the console, it translated into:
In the application adminCreateUser call, I had to provide it as dev:custom:role:
cognitoService.adminCreateUser({
UserPoolId: config.cognitoUserPoolId,
Username: email,
UserAttributes: [{
Name: 'dev:custom:role',
Value: role,
}]
}).promise()
Figured it out by trying. Wish I knew where the docs for this are.

Darcy's answer is correct. But I wanted to elaborate as that answer was focused on the AWS web console.
Also the other answer viz., prefixing "dev:" is probably an undocumented workaround (hence no documentation) and might stop working without warning.
First, the custom attributes has to be created when the Userpool is Created.
CreateUserPoolRequest request = new CreateUserPoolRequest
{
...
Schema = new List<SchemaAttributeType>
{
new SchemaAttributeType
{
Name = "email",
AttributeDataType = AttributeDataType.String,
Required = true,
Mutable = false
},
new SchemaAttributeType //custom attribute
{
Name = "blah",
AttributeDataType = AttributeDataType.String,
Mutable = false
},
...
};
And then when the user is created, it can be set.
var request = new AdminCreateUserRequest
{
...
UserAttributes = new List<AttributeType>
{
new AttributeType
{
Name = "email",
Value = "xyz#xyz.com"
},
new AttributeType //custom attribute
{
Name = $"custom:blah",
Value = "value for blah"
}
}
};
Now, just prefixing with "custom:" works.
Also note AWS continues with its tradition of having inconsistent api's by not having to prefix when creating the user pool and having the prefix when creating the user.

Related

DynamoDB JavaScript PutItemCommand is neither failing nor working

Please note: although this question mentions AWS SAM, it is 100% a DynamoDB JavaScript SDK question at heart and can be answered by anyone with experience writing JavaScript Lambdas (or any client-side apps) against DynamoDB using the AWS DynamoDB client/SDK.
So I used AWS SAM to provision a new DynamoDB table with the following attributes:
FeedbackDynamoDB:
Type: AWS::DynamoDB::Table
Properties:
TableName: commentary
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
StreamSpecification:
StreamViewType: NEW_IMAGE
This configuration successfully creates a DynamoDB table called commentary. However, when I view this table in the DynamoDB web console, I noticed a few things:
it has a partition key of id (type S)
it has no sort key
it has no (0) indexes
it has a read/write capacity mode of "5"
I'm not sure if this raises any red flags with anyone but I figured I would include those details, in case I've configured anything incorrectly.
Now then, I have a JavaScript (TypeScript) Lambda that instantiates a DynamoDB client (using the JavaScript SDK) and attempts to add a record/item to this table:
// this code is in a file named app.ts:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { User, allUsers } from './users';
import { Commentary } from './commentary';
import { PutItemCommand } from "#aws-sdk/client-dynamodb";
import { DynamoDBClient } from "#aws-sdk/client-dynamodb";
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const ddbClient = new DynamoDBClient({ region: "us-east-1" });
let status: number = 200;
let responseBody: string = "\"message\": \"hello world\"";
const { id, content, createdAt, providerId, receiverId } = JSON.parse(event.body);
const commentary = new Commentary(id, content, createdAt, providerId, receiverId);
console.log("deserialized this into commentary");
console.log("and the deserialized commentary has content of: " + commentary.getContent());
await provideCommentary(ddbClient, commentary);
responseBody = "\"message\": \"received commentary -- check dynamoDb!\"";
return {
statusCode: status,
body: responseBody
};
} catch (err) {
console.log(err);
return {
statusCode: 500,
body: JSON.stringify({
message: err.stack,
}),
};
}
};
const provideCommentary = async (ddbClient: DynamoDBClient, commentary: Commentary) => {
const params = {
TableName: "commentary",
Item: {
id: {
S: commentary.getId()
},
content: {
S: commentary.getContent()
},
createdAt: {
S: commentary.getCreatedAt()
},
providerId: {
N: commentary.getProviderId()
},
receiverId: {
N: commentary.getReceiverId()
}
}
};
console.log("about to try to insert commentary into dynamo...");
try {
console.log("wait for it...")
const rc = await ddbClient.send(new PutItemCommand(params));
console.log("DDB response:", rc);
} catch (err) {
console.log("hmmm something awry. something....in the mist");
console.log("Error", err.stack);
throw err;
}
};
Where commentary.ts is:
class Commentary {
private id: string;
private content: string;
private createdAt: Date;
private providerId: number;
private receiverId: number;
constructor(id: string, content: string, createdAt: Date, providerId: number, receiverId: number) {
this.id = id;
this.content = content;
this.createdAt = createdAt;
this.providerId = providerId;
this.receiverId = receiverId;
}
public getId(): string {
return this.id;
}
public getContent(): string {
return this.content;
}
public getCreatedAt(): Date {
return this.createdAt;
}
public getProviderId(): number {
return this.providerId;
}
public getReceiverId(): number {
return this.receiverId;
}
}
export { Commentary };
When I update the Lambda with this handler code, and hit the Lambda with the following curl (the Lambda is invoked by an API Gateway URL that I can hit via curl/http):
curl -i --request POST 'https://<my-api-gateway>.execute-api.us-east-1.amazonaws.com/Stage/feedback' \
--header 'Content-Type: application/json' -d '{"id":"123","content":"test feedback","createdAt":"2022-12-02T08:45:26.261-05:00","providerId":457,"receiverId":789}'
I get the following HTTP 500 response:
{"message":"SerializationException: NUMBER_VALUE cannot be converted to String\n
Am I passing it a bad request body (in the curl) or do I need to tweak something in app.ts and/or commentary.ts?
Interestingly the DynamoDB API expects numerical fields of items as strings. For example:
"N": "123.45"
The doc says;
Numbers are sent across the network to DynamoDB as strings, to maximize compatibility across languages and libraries. However, DynamoDB treats them as number type attributes for mathematical operations.
Have you tried sending your input with the numerical parameters as strings as shown below? (See providerId and receiverId)
{
"id":"123",
"content":"test feedback",
"createdAt":"2022-12-02T08:45:26.261-05:00",
"providerId":"457",
"receiverId":"789"
}
You can convert these IDs into string when you're populating your input Item:
providerId: {
N: String(commentary.getProviderId())
},
receiverId: {
N: String(commentary.getReceiverId())
}
You could also use .toString() but then you'd get errors if the field is not set (null or undefined).
Try using a promise to see the outcome:
client.send(command).then(
(data) => {
// process data.
},
(error) => {
// error handling.
}
);
Everything seems alright with your table setup, I believe it's Lambda async issue with the JS sdk. I'm guessing Lambda is not waiting on your code and exiting early. Can you include your full lambda code.

FederatedPrincipal with multiples Actions

I'm trying to create a FederatedPrincipal in aws-cdk with multiple Action as shown below:
Currently, I'm doing this (as shown below) in c#
new FederatedPrincipal("cognito-identity.amazonaws.com", new Dictionary<string, object>
{
{ "ForAnyValue:StringLike", new Dictionary<string,string> { ["cognito-identity.amazonaws.com:amr"] = "authenticated" } },
{ "StringEquals", new Dictionary<string,string> { ["cognito-identity.amazonaws.com:aud"] = cfn_identitypool.Ref } }
}, "sts:AssumeRoleWithWebIdentity");
How do I add the 2nd action - sts:TagSession?
This is currently not possible using high-level constructs. See this still open issue: https://github.com/aws/aws-cdk/issues/6699
TL;DR
The IPrincipal requires assumeRoleAction to be a string. But what you need is an array. It looks like it's been put on-hold because it means a BC-breaking change that the team does not want to introduce.
What I ended up with, is to use a low-level construct CfnRole. I use TypeScript but it should be straightforward to port it to C#.
const authenticatedRole = new iam.CfnRole(this, 'AuthenticatedRole', {
assumeRolePolicyDocument: {
'Statement': [{
'Effect': iam.Effect.ALLOW,
'Action': ['sts:AssumeRoleWithWebIdentity', 'sts:TagSession'],
'Condition': {
'StringEquals': {
'cognito-identity.amazonaws.com:aud': identityPool.getAtt('Ref')
},
'ForAnyValue:StringLike': {
'cognito-identity.amazonaws.com:amr': 'authenticated'
}
},
'Principal': {
'Federated': 'cognito-identity.amazonaws.com'
}
}]
}
});
const roleAttachment = new cognito.CfnIdentityPoolRoleAttachment(this, 'RoleAttachment', {
identityPoolId: identityPool.getAtt('Ref').toString(),
roles: {
'authenticated': authenticatedRole.getAtt('Arn'),
}
});
You can use the The withSessionTags method of the PrincipalBase class to address this issue, as described here and documented here

Invalid phone number format on Cognito signup

Im tryng to create a new user on my cognito user pool via nodeJS but i keep getting wrong phone number error...but i use the same format of number to send SMS via SNS services, i dont understand why this is happening
signup method:
module.exports.post = async (username,password,email,phoneNumber) => {
const environment = {
UserPoolId: xxxxxxx,
ClientId: xxxxxx,
}
return new Promise((reject,resolve) => {
const userPool = new AmazonCognitoIdentity.CognitoUserPool(environment);
const emailData = {
Name: 'Email',
Value: email
};
const userData = {
Name: 'Usuário',
Value: username
};
const phoneData = {
Name: 'Telefone',
Value: phoneNumber
};
const emailAttribute = new AmazonCognitoIdentity.CognitoUserAttribute(emailData);
const userAttribute = new AmazonCognitoIdentity.CognitoUserAttribute(userData);
const phoneAttribute = new AmazonCognitoIdentity.CognitoUserAttribute(phoneData);
userPool.signUp(username,password,[emailAttribute,userAttribute, phoneAttribute], null, (err,data) => {
if(err) console.log(err);
resolve(data);
});
});
}
the number format im passing:
+5521979724910
the error :
{ code: 'InvalidParameterException',
name: 'InvalidParameterException',
message: '1 validation error detected: Value \'phone number\' at \'userAttributes.2.member.name\' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\p{L}\\p{M}\\p{S}\\p{N}\\p{P}]+' }
Any ideas?
The Name attribute value should be phone_number instead of Telefone
const phoneData = {
Name : 'phone_number',
Value : '+15555555555'
};
use attribute name as 'phone_number'
Note: add the country code along with the phone number value. Otherwise it will throw another error

AWS Cognito custom attributes not being created [duplicate]

I am trying to setup a Cognito user using the AWS Cognito SDK and am having trouble adding custom attributes to a user. I have ensured that the variable names match up exactly and that the application allows read/write on all of the attributes. My code looks like this:
var attributeList = [];
var dataName = {
Name: 'name',
Value: name
};
var dataPhoneNumber = {
Name: 'phone_number',
Value: phone
};
var dataIsDriver = {
Name: 'custom:is_driver',
Value: 0
};
var attributeName = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataName);
var attributePhoneNumber = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataPhoneNumber);
var attributeIsDriver = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataIsDriver);
attributeList.push(attributeName);
attributeList.push(attributePhoneNumber);
attributeList.push(attributeIsDriver);
var username = generateUUID();
localStorage.setItem("username", username);
var userPool = getUserPool();
userPool.signUp(username, password, attributeList, null, function (err, result) {
if (err) {
alert(err);
return;
}
}
With this code, the name and phone_number attributes are being set correctly but there is no "is_driver." I've tried to use adminGetUser to get all of the user's attributes but is_driver still doesn't appear. Any guidance would be appreciated!
I think that might be happening because you are passing the attribute value as a number so 0. It's just a particularity of the service that attributes are treated as Strings for validation.
Can you try replacing that with the code below and see if it works.
var dataIsDriver = {
Name: 'custom:is_driver',
Value: '0'
};

Mongoose validation: required : false, validate : regex, issues with empty values

I get this message from Mongoose validation:
'Validator failed for path phone with value ``'
That shouldn't happen since phone is not required.
Here's my model schema:
var user = new Schema(
{
_id : { type: String, required: true },
name : { type: String, required: true},
phone : { type: String, required: false, validate: /^\d{10}$/ },
password : { type: String },
added : { type: Date, default: Date.now },
},
{collection : 'users'}
);
It seems that mongoose's validation fails when i use required: false and set validate property up.
If I change it to:
phone : { type: String, required: false},
Everything goes right, why is that?
What am I doing wrong?
You can simply check if the value entered exists (not null or undefined). If it exists, then test the regex:
var user = new Schema(
{
_id : { type: String, required: true },
name : { type: String, required: true},
phone : { type: String,/*not required by default**/
validate: {
validator: function(v) {
var re = /^\d{10}$/;
return (!v || !v.trim().length) || re.test(v)
},
message: 'Provided phone number is invalid.'
}
},
password : { type: String },
added : { type: Date, default: Date.now },
},
{collection : 'users'}
);
I think your regex is failing validation on empty string which should in this case be valid since this field is not required. Why don't you try this regex:
/^$|^\d{10}$/
This will match an empty string or 10 digits.
You may try with a custom validator as they are only triggered when there is a value on a given key because the key selection for custom validation is done via path() :
var user = new Schema({
// ...
phone : { type: String }, // using default - required:false
// ...
});
// Custom validation
user.path('phone').validate(function (value) {
// Your validation code here, should return bool
}, 'Some error message');
Have a look at this question: Why Mongoose doesn't validate empty document?
This will also effectively prevent the document to be persisted to the DB if validation fails, unless you handle the error accordingly.
BonusTip: Try to approach custom validations in a simplistic way, for example try to avoid loops when possible and avoid using libraries like lodash or underscore for in my experience I've seen that these may have a significant performance cost when working with lots of transactions.
use this function:
const valid= (id) =>{
return id.match(/^[0-9a-fA-F]{24}$/) ? true : false;
}