My web app allows signing up / signing in with a Google account. I am using the following code to obtain user info from Google:
var scopes = ['profile', 'email'];
var url = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: scopes });
router.route('/authorize').post((req, res) => {
code = req.body.code;
oauth2Client.getToken(code, (err, tokens) => {
if (err) return // error handler
oauth2Client.verifyIdToken(tokens.id_token, clientId, (err, login) => {
if (err) return // error handler
console.log(login.getPayload()); // this gives me the JSON object below
});
});
});
I've tried adding different scopes, but I always just get the same info, which doesn't include the user's real name:
{ azp: 'stuffblahblah',
aud: 'stuffblahblah',
sub: 'google-id-here',
email: 'email#address.com',
email_verified: true,
at_hash: 'some-hash',
iss: 'accounts.google.com',
iat: 1234567890,
exp: 1234567890 }
Despite some documentation suggesting it's possible to get info like real name in the id_token (see https://developers.google.com/identity/sign-in/android/backend-auth), I couldn't get it to return that info with the .getToken method. However, I was able to get it by requesting the info in a separate request via the access token:
let url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + access_token;
request(url, (err, response, body) => {
if (err) console.log('error');
console.log(body);
});
And the body looks like this:
{
"sub": "4319874317893142",
"name": "My Real name",
"given_name": "My First Name",
"family_name": "My Last Name",
"profile": "https://plus.google.com/link_to_my_profile",
"picture": "https://lh4.googleusercontent.com/link_to_my_pic.jpg",
"email": "email#address.com",
"email_verified": true,
"gender": "male",
"locale": "en"
}
Still wishing there was a way to grab the real name in my initial request, rather than having to make a separate one, but this works well enough.
Related
I am trying to use Mandrill to send an event-based email notification to the users of my web app. I am using Parse with Back4App.
In this tutorial (https://docs.back4app.com/docs/integrations/parse-server-mandrill/), the hosting providers suggest using the following method to call the Mandrill cloud code from an Android application:
public class Mandrill extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("your back4app app id”)
.clientKey(“your back4app client key ")
.server("https://parseapi.back4app.com/").build()
);
Map < String, String > params = new HashMap < > ();
params.put("text", "Sample mail body");
params.put("subject", "Test Parse Push");
params.put("fromEmail", "someone#example.com");
params.put("fromName", "Source User");
params.put("toEmail", "other#example.com");
params.put("toName", "Target user");
params.put("replyTo", "reply-to#example.com");
ParseCloud.callFunctionInBackground("sendMail", params, new FunctionCallback < Object > () {
#Override
public void done(Object response, ParseException exc) {
Log.e("cloud code example", "response: " + response);
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mandrill);
}
}
How can I implement this in JavaScript with the Parse JavaScript SDK?
This is what I've done so far but it won't send an email. I have Mandrill set up, as well as a verified email domain and valid DKIM and SPF.
// Run email Cloud code
Parse.Cloud.run("sendMail", {
text: "Email Test",
subject: "Email Test",
fromEmail: "no-reply#test.ca",
fromName: "TEST",
toEmail: "test#gmail.com",
toName: "test",
replyTo: "no-reply#test.ca"
}).then(function(result) {
// make sure to set the email sent flag on the object
console.log("result :" + JSON.stringify(result));
}, function(error) {
// error
});
I don't even get a result in the console, so I figure the cloud code is not even executing.
You have to add the Mandrill Email Adapter to the initialisation of your Parse Server, as described on their Github page. Also check the Parse Server Guide for how to initialise or use their example project.
Then set up Cloud Code by following the guide. You'll want to either call a Cloud Code function using your Android app or from any Javascript app, or use beforeSave or afterSave hooks of a Parse Object directly in Cloud Code, which allow you to send Welcome Emails when a user signs up. That could come in handy if you want to implement behaviour based emails based on object updates. Plus, because it is on the server and not the client, it is easier to maintain and scale.
To make the Cloud Code function actually send an email via Mandrill, you need to add some more code to your Cloud Code function. First, add a file with these contents:
var _apiUrl = 'mandrillapp.com/api/1.0';
var _apiKey = process.env.MANDRILL_API_KEY || '';
exports.initialize = function(apiKey) {
_apiKey = apiKey;
};
exports.sendTemplate = function(request, response) {
request.key = _apiKey;
return Parse.Cloud.httpRequest({
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
url: 'https://' + _apiUrl + '/messages/send-template.json',
body: request,
success: function(httpResponse) {
if (response) {
response.success(httpResponse);
}
return Parse.Promise.resolve(httpResponse);
},
error: function(httpResponse) {
if (response) {
response.error(httpResponse);
}
return Parse.Promise.reject(httpResponse);
}
});
};
Require that file in your Cloud Code file, and use it like any other Promise.
var Mandrill = require("./file");
Mandrill.sendTemplate({
template_name: "TEMPLATE_NAME",
template_content: [{}],
key: process.env.MANDRILL_API_KEY,
message: {
global_merge_vars: [{
name: "REPLACABLE_CONTENT_NAME",
content: "YOUR_CONTENT",
}],
subject: "SUBJECT",
from_email: "YOUR#EMAIL.COM",
from_name: "YOUR NAME",
to: [{
email: "RECIPIENT#EMAIL.COM",
name: "RECIPIENT NAME"
}],
important: true
},
async: false
})
.then(
function success() {
})
.catch(
function error(error) {
});
Make sure you create a template on Mailchimp, right click it and choose "Send to Mandrill", so that you can use that template's name when sending via the API.
It's a bit involved, but once set up, it works like a charm. Good luck!
The AWS documentation indicates that it is possible for an admin to create a user pool user in AWS Cognito using the API.
Here is the documentation I am referring to: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminCreateUser.html
However the documentation provides scant details and not even an example of how this is done. It makes no mention of what endpoint to call, what SDK function to use, or anything regarding authentication, etc.
Does anyone have experience creating new users directly from your code ?
It's actually quite easy if you follow the development documentation (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html), more specifically the "signUp" function.
From the Docs:
var params = {
ClientId: 'STRING_VALUE', /* required */
Password: 'STRING_VALUE', /* required */
Username: 'STRING_VALUE', /* required */
AnalyticsMetadata: {
AnalyticsEndpointId: 'STRING_VALUE'
},
SecretHash: 'STRING_VALUE',
UserAttributes: [
{
Name: 'STRING_VALUE', /* required */
Value: 'STRING_VALUE'
},
/* more items */
],
UserContextData: {
EncodedData: 'STRING_VALUE'
},
ValidationData: [
{
Name: 'STRING_VALUE', /* required */
Value: 'STRING_VALUE'
},
/* more items */
]
};
cognitoidentityserviceprovider.signUp(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
And using this, it's simple to create a user (example in Lambda, but can easily be modified as JS on its own):
'use strict'
var AWS = require('aws-sdk');
var resp200ok = { statusCode: 200, headers: {'Content-Type': 'application/json'}, body: {} };
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});
// ^ Hard to find that this is the way to import the library, but it was obvious in docs
exports.handler = function(event, context, callback){
var params = {
ClientId: 'the App Client you set up with your identity pool (usually 26 alphanum chars)',
Password: 'the password you want the user to have (keep in mind the password restrictions you set when creating pool)',
Username: 'the username you want the user to have',
UserAttributes:[ {
{
Name: 'name',
Value: 'Private'
},
{
Name: 'family_name',
Value: 'Not-Tellinglol'
},
}],
};
cognitoidentityserviceprovider.signUp(params, function(err, data) {
if (err){ console.log(err, err.stack); }
else{ resp200ok.body = JSON.stringify(data); callback(null, resp200ok); }
});
};
Anything you set to required in your Cognito pool setup has to be in the UserAttributes section (usually the email is defaulted to required, check if yours is). The list of things you can assign values to is found in (Cognito pool) General Settings -> App Clients -> Show Details -> Set Read/Write -> (list of things), here you can add custom attributes (like if you want to specify what city your user is from, or if you want to add whatever else (String/Number)).
When assigning a value to a custom field, your "Name" in the UserAttributes will be "custom:whatever", so if the custom field is "city" the Name is "custom:city".
Hopefully I wasn't stating too much of the obvious, but these are things it took me a while to figure out with the broken up SO info, and AWS docs, and I figured I'd plop it all together.
Here is an example using python/Flask
import traceback
import boto3
from flask import Flask, render_template, request
app = Flask(__name__)
def cognito_register_user(email):
print("sign up user: ", email)
try:
aws_client = boto3.client('cognito-idp', region_name = "us-west-2",)
response = aws_client.admin_create_user(UserPoolId="us-west-2_sdfgsdfgsdfg",Username=email,UserAttributes=[{"Name": "email","Value": email},{ "Name": "email_verified", "Value": "true" }],DesiredDeliveryMediums=['EMAIL'])
print("response=", response)
return response
except:
traceback.print_exc()
return None
#app.route('/')
def root():
return render_template('register_email.html', title='register mail')
#app.route('/register/email', methods=['POST'])
def sign_up():
if request.method == 'POST':
email = request.form['email']
print("email=", email)
cognito_register_user(email)
return render_template('register_email_complete.html', title='flask test', email=email)
if __name__ == "__main__":
app.run(debug=True)
I have the Google Directory API Javascript quickstart working. This part of the code lists the first 10 users in the directory:
gapi.client.directory.users.list({
'customer': 'my_customer',
'maxResults': 10,
'orderBy': 'email'
}).then(function(response) {
var users = response.result.users;
appendPre('Users:');
appendPre('test')
if (users && users.length > 0) {
for (i = 0; i < users.length; i++) {
var user = users[i];
appendPre('-' + user.primaryEmail + ' (' + user.name.fullName + ')');
}
} else {
appendPre('No users found.');
}
});
I want to add a user to the directory. It looks like this is done using users: insert. So after removing the 'readonly' part from the scope, I replace the above code snippet with this:
var user = {
"password": "Testpass123",
"primaryEmail": "albert.smith#mydomain.com",
"name": {
"givenName": "albert",
"familyName": "smith"
}
};
gapi.client.directory.users.insert(user);
Obviously this does not work, but I am unsure what I am missing. There is a "Try this API" tool on the users:insert reference page, and when I plug in the properties of 'user' in the "request body" field, it adds the user.
I'm not sure how to make a request body though, and I can't find a solution in the docs. The users:list method does not need a request body. I tried something like this, which also didn't work:
gapi.client.request({
'path': 'https://www.googleapis.com/admin/directory/v1/users',
'method': 'POST',
'body': user
});
Hoping someone can give me at least a general idea of what to do. I'm pretty new at this.
Try this dummy data derived from Apps Script's Admin SDK Add user and replace with your correct details:
sample request body:
var user = {
primaryEmail: 'liz#example.com',
name: {
givenName: 'Elizabeth',
familyName: 'Smith'
},
// Generate a random password string.
password: Math.random().toString(36)
};
Try wrapping the user object in a resource object like:
var user = {
resource: {
"password": "Testpass123",
"primaryEmail": "albert.smith#mydomain.com",
"name": {
"givenName": "albert",
"familyName": "smith"
}
}
}
I can't find a reference for this anymore so maybe someone else can post but it is working for me.
Based on https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/insert :
function execute() {
return gapi.client.directory.users.insert({
"resource": {
"name": {
"familyName": "Shmoger",
"givenName": "Joey"
},
"password": "ShmoeyJoey!",
"primaryEmail": "shmogerjoe#grower.com"
}
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error", err); });
}
I am using google's API for node.js
https://www.npmjs.com/package/googleapis
I am trying to get an array of all channels which belong to the person
who logged into my website with his google account.
I am using this scope for this matter:
''https://www.googleapis.com/auth/youtube.readonly'
Now here is part of my code:
app.get("/oauthcallback", function(req, res) {
//google redirected us back in here with random token
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) { //let's check if the query code is valid.
if (err) { //invalid query code.
console.log(err);
res.send(err);
return;
}
//google now verified that the login was correct.
googleAccountVerified(tokens, res); //now decide what to do with it
});
});
function googleAccountVerified(tokens, res){ //successfully verified.
//user was verified by google, continue.
oauth2Client.setCredentials(tokens); //save tokens to an object
//now ask google for the user's details
//with the verified tokens you got.
youtube.channels.list({
forUsername: true,
part: "snippet",
auth: oauth2Client
}, function (err, response) {
if(err) {
res.send("Something went wrong, can't get your google info");
return;
}
console.log(response.items[0].snippet);
res.send("test");
});
}
Now, in this console.log:
console.log(response.items[0].snippet);
I am getting the same info, no matter what account I am using to log into my website:
{ title: 'True',
description: '',
publishedAt: '2005-10-14T10:09:11.000Z',
thumbnails:
{ default: { url: 'https://i.ytimg.com/i/G9p-zLTq1mO1KAwzN2h0YQ/1.jpg?v=51448e08' },
medium: { url: 'https://i.ytimg.com/i/G9p-zLTq1mO1KAwzN2h0YQ/mq1.jpg?v=51448e08' },
high: { url: 'https://i.ytimg.com/i/G9p-zLTq1mO1KAwzN2h0YQ/hq1.jpg?v=51448e08' } },
localized: { title: 'True', description: '' } }
if I do console.log(response) which is the entire response
I get:
{ kind: 'youtube#channelListResponse',
etag: '"m2yskBQFythfE4irbTIeOgYYfBU/ch97FwhvtkdYcbQGBeya1XtFqyQ"',
pageInfo: { totalResults: 1, resultsPerPage: 5 },
items:
[ { kind: 'youtube#channel',
etag: '"m2yskBQFythfE4irbTIeOgYYfBU/bBTQeJyetWCB7vBdSCu-7VLgZug"',
id: 'UCG9p-zLTq1mO1KAwzN2h0YQ',
snippet: [Object] } ] }
So, two problems here:
1) How do I get an array of owned channels by the logged user,
inside the array I need objects which will represent each channel and basic info like channel name, profile pic.
2) why am I getting the info of some random youtube channel called "True"
Not sure about question one but for question two you get the information for the channel called true because you are asking for it. forUsername: true
I would hope that once you correct this the response may contain more than one channel if the username has more than one.
Just a follow up to the question about basic info.
You dont use Youtube API to get an account's profile information. Instead, try Retrieve Profile Information with G+:
To retrieve profile information for a user, use the people.get API method. To get profile information for the currently authorized user, use the userId value of me.
JavaScript example:
// This sample assumes a client object has been created.
// To learn more about creating a client, check out the starter:
// https://developers.google.com/+/quickstart/javascript
gapi.client.load('plus','v1', function(){
var request = gapi.client.plus.people.get({
'userId': 'me'
});
request.execute(function(resp) {
console.log('Retrieved profile for:' + resp.displayName);
});
});
Google Sign-in for Websites also enables Getting profile information:
After you have signed in a user with Google using the default scopes, you can access the user's Google ID, name, profile URL, and email address.
To retrieve profile information for a user, use the getBasicProfile() method. For example:
if (auth2.isSignedIn.get()) {
var profile = auth2.currentUser.get().getBasicProfile();
console.log('ID: ' + profile.getId());
console.log('Full Name: ' + profile.getName());
console.log('Given Name: ' + profile.getGivenName());
console.log('Family Name: ' + profile.getFamilyName());
console.log('Image URL: ' + profile.getImageUrl());
console.log('Email: ' + profile.getEmail());
}
I have the following object:
var user = {
firstName: req.body.first_name,
lastName: req.body.last_name,
email: req.body.email,
password: "",
id: "",
};
Now what I'm trying to do is post a request to the API and if the user is successfully saved in the database, it will return a user id as well as a password (Which can then be emailed to the person)..
request.post({
url: process.env.API_SIGNEDIN_ENDPOINT + "users/store",
form: user,
headers: {
Authorization: "Bearer " + req.session.authorization
}
}, function(error, response, body) {
// Check the response
var theResponse = JSON.parse(response.body);
if(theResponse.code == 400)
{
var error = [
{param: "email", msg: "This email address has already been taken!", value: ""}
];
res.render("create", {
errors: error,
});
}else{
var theUser = JSON.parse(body);
user.password = theUser.password;
user.id = theUser.id;
}
});
This is working fine, however, whenever I try to output user it's not updating the user object outside of this post. The user object is fine and it's working, the issue seems to be from when I try and access the user from this result callback. Any ideas?
EDIT:
Let's say I have "address1" (This is the persons main address) and I have "address2" (This is the persons second address).. The person might only have 1 address and therefore I only need to save one address. However using the logic that place everything in the .then() means I cannot do this because the user might not have 2 addresses but I still need to access the main address, for example:
mainAddress.save().then(function(addressData) {
var theAddressLicenceTick = req.body.address_licence;
if(theAddressLicenceTick)
{
var subAddress = models.addresses.build({
address_1: "asasfasf",
city: "asfafsaf",
postcode: "asfasf",
created_at: new Date(),
updated_at: new Date();
});
subAddress.save().then(function(subAddress) {
// continue integration
// would I have to do the functionality again?
});
}else{
// The user only has one address
}
});
Essentially, I have a customer table which can have multiple addresses through a link table. But I believe that there is an easier way instead of writing all of this code?