how to update custom attributes of user in aws cognito - node.js

I'm integrating the stripe into the MERN app. On the stripe webhook call, I need to update the user custom attributes (Cognito). Is there any possible way to find the user using email in the pool and update its custom attributes?
I have tried (function:listUsers) this code to get the user but it doesn't provide the custom variables of the user.
const params = {
UserPoolId: AWS_POOL_ID,
AttributesToGet: ["email"],
};
new Promise((resolve, reject) => {
AWS.config.update({
region: AWS_REGION,
accessKeyId: AWS_ACCESS_PUBLIC,
secretAccessKey: AWS_ACCESS_SECRET,
});
const cognitoIdentityServiceProvider =
new AWS.CognitoIdentityServiceProvider();
cognitoIdentityServiceProvider.listUsers(params, (err, data) => {
if (err) {
console.log("cognito err::", err);
reject(err);
} else {
console.log("cognito data:", data);
resolve(data);
}
});
});
Response:
[
{
Username: '15a2-c98c-43a5-63c',
Attributes: [
{ Name: 'email', Value: 'abc#gmail.com' }
],
UserCreateDate: 2021-12-02T11:33:37.561Z,
UserLastModifiedDate: 2021-12-02T11:35:24.956Z,
Enabled: true,
UserStatus: 'CONFIRMED'
},
{
Username: '15ff633f-41faa9',
Attributes: [
{ Name: 'email', Value: 'abc#gmail.com' }
],,
UserCreateDate: 2021-09-17T14:37:12.943Z,
UserLastModifiedDate: 2021-09-18T04:08:25.443Z,
Enabled: true,
UserStatus: 'CONFIRMED'
}
]
but I need to get and update the custom variables
only email available for finding user

You are passing in email in the AttributesToGet property in the parameters passed to the call to list the user. Based on this documentation, if that array is not included, all attributes will be returned, including the custom attributes from Cognito, which should have the prefix of custom:.
You will also be able to update the user's custom attributes using this function.

Related

Google API Node.js Library - Grant Role to service account at project level

Goal
Assign a role dialogflow.admin to a service account I created for a project using the Node.js Client library for Google APIs.
Issue
When I try to update my service accounts IAM Policy and add a role to the service account. I get an error that the role is not supported for this resource type.
I am trying to give my service account the Dialogflow API Admin Role roles/dialogflow.admin
The method in the Node.js client library I am using is iam.projects.serviceAccounts.setIamPolicy.
I have already managed to create the service account with this Node.js client library with a function shown here.
async function createServiceAccount(projectID, serviceAccountID){
const authClient = await auth.getClient();
var request = {
name: "projects/"+projectID,
resource: {
"accountId": serviceAccountID,
"serviceAccount": {
"description" : "Service Account for project: "+projectID+" for DialogFlow authentication with VA",
"displayName": "VA Dialogflow Service Account "+projectID
}
},
auth: authClient,
};
await iam.projects.serviceAccounts.create(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
after this function runs, and I am sure the service account is created, I run my function that is meant to set the roles of this service account.
async function setServiceAccountRoles(projectID, serviceAccountID){
const authClient = await auth.getClient();
var request = {
resource_: "projects/"+projectID+"/serviceAccounts/"+serviceAccountID,
resource: {
policy: {
bindings: [
{
// role: "projects/"+projectID+"roles/dialogflow.admin",
role: "roles/dialogflow.admin",
"members": [
"serviceAccount:"+serviceAccountID
]
}
],
version: 1
}
},
auth: authClient,
};
await iam.projects.serviceAccounts.setIamPolicy(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
Error
When I run this function I am give this error:
code: 400,
errors: [
{
message: 'Role roles/dialogflow.admin is not supported for this resource.',
domain: 'global',
reason: 'badRequest'
}
]
I have used these following resources to get this far:
https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts/setIamPolicy
https://cloud.google.com/iam/docs/reference/rest/v1/Policy
https://cloud.google.com/iam/docs/granting-changing-revoking-access#granting_access_to_a_service_account_for_a_resource
Alternative methods.
I have tried changing the role to a project specific path like this:
async function setServiceAccountRoles(projectID, serviceAccountID){
const authClient = await auth.getClient();
var request = {
resource_: "projects/"+projectID+"/serviceAccounts/"+serviceAccountID,
resource: {
policy: {
bindings: [
{
role: "projects/"+projectID+"roles/dialogflow.admin",
"members": [
"serviceAccount:"+serviceAccountID
]
}
],
version: 1
}
},
auth: authClient,
};
await iam.projects.serviceAccounts.setIamPolicy(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
however, with this I get the error : message: "Role (projects/va-9986d601/roles/dialogflow.admin) does not exist in the resource's hierarchy.",
Is it possible that the only way to update my service account's roles and permission is through the console or gcloud commands? If so, is there any recommended ways of running said gcloud commands through the node.js client library or from a node application itself?
You are trying to set an IAM policy on the service account. That is used to grant other identities access to the service account itself.
You should modify the IAM binding for the project and not for the service account.
Use the getIamPolicy and setIamPolicy. Examples are included with the documentation.
WARNING: be very careful writing code that modifies a project's bindings. If you overwrite the bindings you can easily lock yourself out of your project. Then you would need to open a paid support ticket with Google Cloud Support. Practice with a throw away project.

disable verification code being sent on email verification on cognito

userPool.signUp(
userData.email,
userData.password,
attributeList,
[],
async (err, result) => {
if (err) {
failure(new HttpException(500, err.message));
}
let myCredentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: process.env.USER_POOL_ID!,
});
new AWS.Config({
credentials: myCredentials,
region: process.env.AWS_REGION,
});
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
cognitoIdentityServiceProvider.adminConfirmSignUp(
{
UserPoolId: process.env.USER_POOL_ID!,
Username: userData.email,
},
function (err, _data) {
if (err) {
failure(err);
}
cognitoIdentityServiceProvider.adminUpdateUserAttributes(
{
UserPoolId: process.env.USER_POOL_ID!,
Username: userData.email,
UserAttributes: [
{
Name: 'email_verified',
Value: 'true',
},
],
},
() => {
console.log('done');
}
);
}
);
if (result) {
const cognitoUser = result.user;
success({ user: cognitoUser, sub: result.userSub });
} else {
failure(
new HttpException(
500,
'Authentication server error, please try again later'
)
);
}
}
);
This is the code by which I am signing up a user and auto verify that user on cognito.
Problem being I have 2 different set of user roles, and one user i am auto confirming but for the other i want them to manually confirm, using the code that is sent by cognito.
Now for the one type of user for which the auto_confirmation is done the email and cofirm user is working perfectly, with one caveat.
The code for verification is being sent even if it's auto verified by admincognito.
How can i disable this on this particualr set of code, so that the other user role can confirm with the code that is being sent via email
Cognito isn't really configurable in this regard. Your escape hatch in this case is the lightly documented custom email sender lambda trigger. Perform whatever checks you want before sending the email through SES (or not).
I solved this issue by creating a pre-signup lambda trigger. And passing in the role to it as a user attribute and then auto verifying email and user based on the role.

How to edit event in google calendar created by service account

I am emailing an HTML link of a Google calendar event generated by Google API to users but they are unable to edit the event, they can only view it when they click the link. I am creating this event with a service account and sharing with other users.
How can I ensure these events are editable in the user's calendar?
This is a link to the code I am using:
const { google } = require('googleapis');
// Provide the required configuration
const CREDENTIALS = JSON.parse(process.env.CREDENTIALS);
const calendarId = process.env.CALENDAR_ID;
// Google calendar API settings
const SCOPES = ['https://www.googleapis.com/auth/calendar.events'];
const calendar = google.calendar({version : "v3"});
const auth = new google.auth.JWT(
CREDENTIALS.client_email,
null,
CREDENTIALS.private_key,
SCOPES,
'email_used_to_configure_the_service_account#gmail.com'
);
auth.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("Successfully connected!");
}
});
//fetching the even object from the db to get evnt.name and co
const saveEvent = {
summary: event.name,
location: event.room.host.location,
description: event.extra,
colorId: 3,
start: {
dateTime: event.startDate,
timeZone: 'Africa/Lagos',
},
end: {
dateTime: event.endDate,
timeZone: 'Africa/Lagos',
},
organizer: {
email: 'email_used_to_configure_the_service_account#gmail.com',
displayName: 'display name',
self: true
},
attendees: [{ email: 'email of recepient of event' }]
//visibility: 'public'
}
async function generateLink(){
try{
const val = await calendar.events.insert({ auth: auth, calendarId: calendarId, resource: saveEvent, sendNotifications: true });
if(val.status === 200 && val.statusText === 'OK'){
console.log('CREATED', val);
return val.data.htmlLink;
}
return console.log('NOT CREATED')
} catch(error){
console.log(`Error ${error}`);
return;
}
}
const link = await generateLink();
let mailData = {
name: user.name ? user.name : user.firstname,
token: `${config.get('platform.url')}/event-accepted/${invite.token}`,
coverImage: event.gallery.link,
eventName: event.name,
hostName: host.name? host.name : host.firstname,
venue: event.venue,
date: event.startDate,
time: event.startDate,
attendees: '',
ticketNo: '',
cost: event.amount,
action: link // htmlLink that takes you to the calendar where user can edit event.
}
mail.sendTemplate({
template: 'acceptEventEmail',
to: u.email.value,
context: mailData
});
Current Behaviour
Expected behaviour
P.S: Code has been added to the question
YOu created the event using a Service account, think of a service account as a dummy user. When it created the event it became the owner / organizer of the event and there for only the service account can make changes to it.
You either need the service account to update it and set someone else as organizer
"organizer": {
"email": "me#gmail.com",
"displayName": "L P",
"self": true
},
Service accounts cannot invite attendees without Domain-Wide Delegation of Authority
In enterprise applications you may want to programmatically access users data without any manual authorization on their part. In Google Workspace domains, the domain administrator can grant to third party applications domain-wide access to its users' data—this is referred as domain-wide delegation of authority. To delegate authority this way, domain administrators can use service accounts with OAuth 2.0.

Why can't Clarifai validate model output request with API key generated in the Clarifai Portal or with the Personal Access token?

Update
I'm able to get my original code, and the suggestions as well working when running it in isolation. However, what I need to do is call it from within a Firebase onRequest or onCall function. When this code gets wrapped by these, the malformed headers and request for authorization are still an issue. We use many other APIs this way so it's puzzling why the Clarifiai API is having these issues. Any suggestions on using it with Firebase?
Original
New to Clarifai and having some authentication issues while attempting to retrieve model outputs from the Food Model.
I've tried two different keys:
API key generated from an app I created in the Portal
API key - the Personal Access Token I generated for myself
In both cases I encounter an Empty or malformed authorization header response.
{
"status":{
"code":11102,
"description":"Invalid request",
"details":"Empty or malformed authorization header. Please provide an API key or session token.",
"req_id":"xyzreasdfasdfasdfasdfasf"
},
"outputs":[
]
}
I've following the following articles to piece together this code. This is running in a Node 10 environment.
Initialization
Food Model
Prediction
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key xyzKey");
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
resolve(JSON.stringify(response));
}
);
});
}
Update: There was an issue in versions prior to 7.0.2 where, if you had another library using #grpc/grpc-js with a different version, the grpc.Metadata object wasn't necessarily constructed from the library version that clarifai-grpc-nodejs was using.
To fix the issue, update the clarifai-grpc-nodejs library, and require the grpc object like this:
const {ClarifaiStub, grpc} = require("clarifai-nodejs-grpc");
Previously, the grpc object was imported directly from #grpc/grpc-js, which was the source of the problem.
There are two ways of authenticating to the Clarifai API:
with an API key, which is application-specific, meaning that an API key is attached to an application and can only do operations inside that application,
with a Personal Access Token (PAT), which is user-specific, which means you can assess / manipulate / do operations on all the applications the user owns / has access to (and also create/update/delete applications themselves).
When using a PAT, you have to specify, in your request data, which application you are targeting. With an API key this is not needed.
I've tested your example (using Node 12, though it should work in 10 as well) with a valid API key and it works fina (after putting it into an async function). Here's a full runnable example (replace YOUR_API_KEY with your valid API key).
function predict() {
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key YOUR_API_KEY");
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
resolve(JSON.stringify(response));
}
);
});
}
async function main() {
const response = await predict();
console.log(response);
}
main();
If you want to use a PAT in the above example, two things must change. Firstly, replace the API key with a PAT:
...
metadata.set("authorization", "Key YOUR_PAT");
...
To the method request object, add the application ID.
...
stub.PostModelOutputs(
{
user_app_id: {
user_id: "me", // The literal "me" resolves to your user ID.
app_id: "YOUR_APPLICATION_ID"
},
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
...
Make sure that you have respected the format to pass the key in your code as such:
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {YOUR_CLARIFAI_API_KEY}");
Make sure that "Key" is present.
Let me know.
EDIT: So looks like Firebase doesn't support custom headers. This is likely impacting the 'Authorization' header. At least this is my best guess. See the comments in the following ticket.
Firebase hosting custom headers not working
The following code works for me:
{
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {APP API KEY}");
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
console.log(JSON.stringify(response));
resolve(JSON.stringify(response));
}
);
});
}
There was a missing { although I'm not sure if that is what is reflected in the actual code you are running. I'm using in this case an APP API Key (when you create an App, there will be an API Key on the Application Details page.
It sounds like you might be using a Personal Access Token instead, which can be used like this:
{
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {Personal Access Token}"); // Sounds like you've made the personal access token correctly - go into settings, then authentication, then create one. Make sure it has proper permissions (I believe all by default).
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
user_app_id: {
user_id: "{USER ID}", // I used my actual ID, I did not put 'me'. You can find this under your profile.
app_id: "{APP NAME}" // This is the app ID found in the upper left corner of the app after it is created - not the API Key. This is generally what you named the app when you created it.
},
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
console.log(JSON.stringify(response));
resolve(JSON.stringify(response));
}
);
});
}
Make sure to fill out the: {Personal Access Token}, {USER ID} and {APP NAME}. I used my actual user id (found in the profile), and the app name is not the API Key for the app, but the name in the upper left corner when you're on the Application details page. This call worked for me.

MSAL - use two different clientid from different directory

I have an application that will be used by two different entities and each entity have their own Azure Active Directory.
Initially, the code I am using is:
var msalConfig = {
auth: {
clientId: '<client-id-1>'
authority: "https://login.microsoftonline.com/<tenant-id>"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
Now what I want to happen is, can I put two different client ID, and tenant ID?
I could use multiple tenant in the first AAD, but I want to limit it to only two tenants. What should be my approach here?
You could try using the Factory pattern and create a method that will instantiate the clientApplication for the proper client. For example:
const msalConfigFoo = {
auth: {
clientId: '<client-id-1>'
authority: "https://login.microsoftonline.com/<tenant-id>"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
var msalConfigBar = {
auth: {
clientId: '<client-id-2>'
authority: "https://login.microsoftonline.com/<tenant-id>"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
function getClientApplication(clientType) {
if (clientType == "foo") {
return new Msal.UserAgentApplication(msalConfigFoo);
} else {
return new Msal.UserAgentApplication(msalConfigBar);
}
}
Question: I could use multiple tenant in the first AAD, but I want to limit it to only two tenants. What should be my approach here?
Answer: if you develop a multiple-tenant AD application, you can validate "id_token" with its issuer after your users log in. For example :
var msalConfig = {
auth: {
clientId: 'b0114608-677e-4eca-ae22-60c32e1782d9', //This is your client ID
authority: "https://login.microsoftonline.com/common" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
var graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me"
};
// create a request object for login or token request calls
// In scenarios with incremental consent, the request object can be further customized
var requestObj = {
scopes: ["user.read"]
};
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
// Register Callbacks for redirect flow
// myMSALObj.handleRedirectCallbacks(acquireTokenRedirectCallBack, acquireTokenErrorRedirectCallBack);
myMSALObj.handleRedirectCallback(authRedirectCallBack);
// difine issuers
var issuers = new Array();
issuers[0]="https://login.microsoftonline.com/{TenantId}/v2.0";
issuers[1]="https://login.microsoftonline.com/{TenantId}/v2.0";
function signIn() {
myMSALObj.loginPopup(requestObj).then(idToken => {
var issuer =String(idToken.idToken["issuer"])
console.log(issuer)
if(issuers.indexOf(issuer) != -1){
//login successfully then your users can do otherthing
}else{
// your users use a wrong account
}
}).catch(function (error) {
//Please check the console for errors
console.log(error);
});
}
For more details, please refer to the document

Resources