Node AWS Lambda POST to Twitter with User context Auth - node.js

I am attempting to create a Node lambda on AWS which will authenticate with the Twitter API, via the twit npm module, in User Context. So that the lambda can then upload a GIF and then once uploaded, tweet on the user's timeline using the returned media_id.
I have created a twitter app etc. It works, however it is posting the tweet to the account associated with the app, instead of the user's timeline. Here is my entire lambda code:
const fs = require('fs');
const Twit = require('twit');
var T = new Twit({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
module.exports.oauth = (event, context, callback) => {
var b64content = fs.readFileSync('./testGIF.gif', { encoding: 'base64' })
// POST the media to Twitter
T.post('media/upload', { media_data: b64content }, function (err, data, response) {
// assign alt text to the media, for use by screen readers etc
var mediaIdStr = data.media_id_string
var altText = "Alt text"
var meta_params = { media_id: mediaIdStr, alt_text: { text: altText } }
T.post('media/metadata/create', meta_params, function (err, data, response) {
if (!err) {
// reference the media and post a tweet (media will attach to the tweet)
var params = { status: 'Testing, GIF Sharing', media_ids: [mediaIdStr] }
T.post('statuses/update', params, function (err, data, response) {
console.log(data)
})
}
})
})
const response = {
statusCode: 200,
};
callback(null, response);
};
Any help would be much appreciated!

You are currently using tokens for the user account associated with the app.
You'll need to obtain an OAuth token on the user's behalf and use that to make authorized calls to Twitter's APIs.
You need to first create a webpage for your twitter app, then do any of the following:
implement a twitter signin to directly capture an OAUTH token
implement a three legged authorization to directly capture an OAUTH token
use a PIN-based authorization
Once you have captured OAUTH token for the user, you can passed it on to your lambda to be used to initialize the Twit client.
https://dev.twitter.com/oauth/overview

Related

Oauth2 with Google docs API Node.js (trying to programmatically write a new google doc)

I have a typical web app with a client and a node.js server. When a user selects an option on the client, I want to export (create) a google doc in their drive.
I am half way there following this tutorial https://developers.google.com/identity/protocols/oauth2/web-server
With my current set up, after the user authenticates, the authentication token is sent to a web hook (server side), but I don't have any of the data for creating the google doc there.
If I did, I could create the doc from there. Otherwise, I need to send the token itself back to the client so I can create the doc with the necessary payload from there.
In that case, I don't know how to signal to the client that the user has been authenticated. Should I use a web socket?
Something tells me that my general set up might not be correct, and that I should be doing it a different way in my use case.
This is my client side code that brings the user to the google auth page after getting the auth url (not sure if this really needs to be done server side, but since I have some user credentials I thought it might be better).
export async function exportToGoogleDoc() {
const response = await POST(`${API_URL}export/gdoc`, {
'hello': 'world'
});
if (response.status == 200){
window.location.href = response.authUrl;
}
}
And then the endpoint (just returns the autheticationUrl)
api.post('/export/gdoc', express.raw({ type: 'application/json' }), async (req, res, next) => {
try {
const scopes = [
'https://www.googleapis.com/auth/drive'
];
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
const authorizationUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
include_granted_scopes: true
});
res.json({ 'status': 200, authUrl : authorizationUrl } );
} catch (err){
next(err);
}
});
But then after the user agrees and authenticates with their google account, the auth token is sent to this web hook. At the bottom I am able to write an empty google doc to the authenticated google drive, but I don't have the data I need to create the real doc.
api.get('/auth/google', express.raw({ type: 'application/json' }), async (req, res, next) => {
const q = url.parse(req.url, true).query;
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
if (q.error) {
console.log('Error:' + q.error);
} else {
const { tokens } = await oauth2Client.getToken(q.code.toString());
oauth2Client.setCredentials(tokens);
const drive = google.drive('v3');
const requestBody = {
'name': 'Dabble',
'mimeType': 'application/vnd.google-apps.document',
};
drive.files.create({
requestBody: requestBody,
fields: 'id',
auth: oauth2Client
}, function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id: ', file);
}
});
}
Somehow I either need to get the data for the google doc inside this web hook, or to listen for this web hook from the client.
OR I need an entirely different set up. I realize I also should be probably storing this token somewhere (local storage on client side?) and only making this call if they do not have a token.
Can anyone help me modify my set up? Thanks!

Twitter API: Multiple authenticated users return same home timeline

I want to display the tweets that users see in their Twitter home timeline. Concept here is
First, let the user login with "login with Twitter" functionality and get an OAuth token.
Then request Twitter API with those keys and get tweets.
So far I have achieved to get the timeline of my own developer account.
Here is the Nodejs code to get those tweets.
const {access_token, access_token_secret} = request.body;
const T = new Twit({
consumer_key: "xxxxxxxxxxxxxxxxxxxxx",
consumer_secret: "xxxxxxxxxxxxxxxxxx",
access_token,
access_token_secret,
});
const tweets = await this.T.get('statuses/home_timeline', { count: 40 });
Here is the API request from the frontend
export const loadPersonalTweets = (
access_token,
access_token_secret
) => () => {
axios
.post(`http://localhost:8000/tweet/homeTimeline`, {
access_token,
access_token_secret,
})
.then(
(res) => {
console.log(res) // supposed to get user's personal timeline
},
(err) => {
console.log(err);
}
);
};
I have retrieved consumer_key and consumer_secret from my developer account and I receive access_token and access_token_secret from the frontend app after the user authorizes it.
But it doesn't return what the other user sees in their timeline rather it returns the timeline of my developer account.
Can anyone redirect me to the correct way?
Edit: This is the token I get after users authorize the frontend app

Not able to watch Admin Users Directory using `google-admin-sdk`

I am trying to connect to the G-Suite's User directory using the google-admin-sdk. I am using an API Key for authorization and I am not able to reach a successful execution.
Here is the code snippet that I'm using:
import { google } from 'googleapis';
import uuid from 'uuid/v4';
const API_KEY = 'my api key goes here';
google.admin({
version: 'directory_v1',
auth: API_KEY
}).users.list({
customer: 'my_customer',
maxResults: 10,
orderBy: 'email',
}, (err, res: any) => {
if (err) { return console.error('The API returned an error:', err.message); }
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user: any) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
Output:
Login Required
Can someone tell me what I am doing wrong here?
Also, how do I proceed further for listening to the events emitted by the Google API?
---UPDATE---
Here is the snippet that works for me now:
import { JWT } from 'google-auth-library';
import { google } from 'googleapis';
// Importing the serivce account credentials
import { credentials } from './credentials';
const scopes = ['https://www.googleapis.com/auth/admin.directory.user'];
const adminEmail = 'admin_account_email_address_goes_here';
const myDomain = 'domain_name_goes_here';
async function main () {
const client = new JWT(
credentials.client_email,
undefined,
credentials.private_key,
scopes,
adminEmail
);
await client.authorize();
const service = google.admin('directory_v1');
const res = await service.users.list({
domain: myDomain,
auth: client
});
console.log(res);
}
main().catch(console.error);
--- Bonus Tip ---
If you face any Parse Errors while using other methods of the directory, remember to JSON.stringify the request body. For example, on the admin.users.watch method:
// Watch Request
const channelID = 'channel_id_goes_here';
const address = 'https://your-domain.goes/here/notifications';
const ttl = 3600; // Or any other TTL that you can think of
const domain = 'https://your-domain.goes';
const body = {
id: channelID,
type: 'web_hook',
address,
params: {
ttl,
},
};
// Remember to put this in an async function
const res = await service.users.watch({
domain,
customer: 'my_customer',
auth: client, // get the auth-client from above
event: 'add'
}, {
headers: {
'Content-Type': 'application/json'
},
// This is the important part
body: JSON.stringify(body),
});
As you can see in the official documentation, every request sent "to the Directory API must include an authorization token". In order to authorize your request, you have to use OAuth 2.0.
You are providing an API key instead, which is not appropriate for this process. API keys are usually used for accessing public data, not users' private data as in your current situation.
You should follow the steps provided in the Node.js Quickstart instead:
First, obtain client credentials from the Google API Console.
Second, authorize the client: obtain an access token after setting the user credentials and the appropriate scopes (a process accomplish in functions authorize and getNewToken in the Quickstart).
Finally, once the client is authorized, call the API (function listUsers).
Update:
If you want to use a Service Account for this, you will have to follow these steps:
Grant domain-wide delegation to the Service Account by following the steps specified here.
In the Cloud console, create a private key for the Service Account and download the corresponding JSON file. Copy it to your directory.
Use the Service Account to impersonate a user who has access to this resource (an Admin account). This is achieved by indicating the user's email address when creating the JWT auth client, as indicated in the sample below.
The code could be something along the following lines:
const {google} = require('googleapis');
const key = require('./credentials.json'); // The name of the JSON you downloaded
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/admin.directory.user'],
"admin#domain" // Please change this accordingly
);
// Create the Directory service.
const service = google.admin({version: 'directory_v1', auth: jwtClient});
service.users.list({
customer: 'my_customer',
maxResults: 10,
orderBy: 'email',
}, (err, res) => {
if (err) return console.error('The API returned an error:', err.message);
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
Reference:
Directory API: Authorize Requests
Directory API: Node.js Quickstart
Delegate domain-wide authority to your service account
Google Auth Library for Node.js
I hope this is of any help.

Twitter API Email not getting

I am using the following code to get email after twitter login
var requestTokenUrl = 'https://api.twitter.com/oauth/request_token';
var accessTokenUrl = 'https://api.twitter.com/oauth/access_token';
var profileUrl = 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true';
var accessTokenOauth = {
consumer_key: authConf.TWITTER_KEY,
consumer_secret: authConf.TWITTER_SECRET,
token: req.body.oauth_token,
verifier: req.body.oauth_verifier
};
// Step 3. Exchange oauth token and oauth verifier for access token.
request.post({ url: accessTokenUrl, oauth: accessTokenOauth }, function(err, response, accessToken) {
if (err) {
return res.status(500).json(err);
}
accessToken = qs.parse(accessToken);
var profileOauth = {
consumer_key: authConf.TWITTER_KEY,
consumer_secret: authConf.TWITTER_SECRET,
oauth_token: accessToken.oauth_token
};
// Step 4. Retrieve profile information about the current user.
request.get({
url: profileUrl,
oauth: profileOauth,
json: true
}, function(err, response, profile) {
if (err) {
console.log("..........." + err)
return res.status(500).json(err);
}
if (profile) {
//Succes : Do something
}
I am getting the access token. But in the step 4, I am getting an error as follows
{"errors":[{"message":"Your credentials do not allow access to this
resource","code":220}]}
I have tried refreshing the access tokens but of no use.
When you open your app setting # apps.twitter.com, under the Permisions tab, make sure that the Request email addresses from users checkbox is ticked as shown in the image, then update setting. You will need to regenerate access token for this new permission update to work.
Twitter Dev

Getting invalid_request for Youtube Analytics with nodejs library

I am trying to setup nodejs with youtube analytics api. I am currently using a refresh token to try and get access tokens. It works great when using postman but I can't seem to replicate the functionality in nodejs and get a 400: invalid_request with no additional information provided.
Here is my code
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oAuthClient = new OAuth2();
// Retrieve tokens via token exchange explained above or set them:
oAuthClient.setCredentials({
access_token: "",
refresh_token: process.env["YOUTUBE_ANALYTICS_REFRESHTOKEN"]
});
var youtubeAnalytics = google.youtubeAnalytics({
version: 'v1', auth: oAuthClient
});
var moduleExports = {
retrieveDailyBreakdownViews : function(){
var query = {
ids: 'channel==' + {channelID here},
'start-date': '2017-05-01',
'end-date': '2017-05-02',
metrics: 'views,estimatedMinutesWatched',
dimensions: 'insightPlaybackLocationType',
sort: 'views'
}
youtubeAnalytics.reports.query(query, (error, response) => {
console.log(error);
console.log(response);
});
}
}
module.exports = moduleExports;
Any ideas? If this doesn't work I can just try and build the query through HTTP/REST but I'd prefer to use the SDK.
In order to be able to refresh the access token, you will also need the client_id and client_secret. What's happening under the hood is the following request to refresh the token (referenced here):
POST https://accounts.google.com/o/oauth2/token
{
refresh_token: refresh_token,
client_id: this._clientId,
client_secret: this._clientSecret,
grant_type: 'refresh_token'
}
You'll need to initialize your Oauth2 client with :
var oAuthClient = new OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
You'll also need to provide a refresh token that was generated using the same client_id / client_secret if you hardcode the refresh token value
This is what I ended up doing to fix the issue
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(process.env["YOUTUBE_CLIENT_ID"],
process.env["YOUTUBE_CLIENT_SECRET"]);
oauth2Client.credentials.refresh_token =
process.env["YOUTUBE_ANALYTICS_REFRESHTOKEN"];
var youtubeAnalytics = google.youtubeAnalytics({
version: 'v1'
});
I then make my calls like this:
youtubeAnalytics.reports.query(query, (error, response) => {})

Resources