Problem with YouTube Data API to retrieve user's channel Id - javascript

I am trying to implement a React - Node js application that authenticates the user with Google and then retrieve its YouTube channel Id with google apis. I'm new to Google APIs, so I need some help to make this code works. The authentication with Google perfectly works, but I have a lot of difficulties in making the request to retrieve the channel id.
This is the code to focus in the React authentication component implemented with react-google-login:
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Log in with Google"
onSuccess={handleGoogleLoginSuccess}
onFailure={handleGoogleLoginFailure}
cookiePolicy={'single_host_origin'}
scope='https://www.googleapis.com/auth/youtube.readonly'
/>
const handleGoogleLoginSuccess = (googleData) => {
//The token id is in googleData.tokenId
console.log(googleData);
axios.post('auth/googleLogin', {
token: googleData.tokenId,
access_token: googleData.accessToken
}).then(response => {
//Login success
if(response.data.loginStatus === 'ok') {
setLoginMessage(''); //Reset message
const user = response.data.user;
console.log(user.email + " " + user.firstName + " " + user.lastName)
registerUser(user); //Register user in the context
//console.log(currentUser.email + " " + currentUser.firstName + " " + currentUser.lastName)
localStorage.setItem('user', JSON.stringify(user)); //Push user in the storage
history.push('/home'); //Redirect to home page
}else{ //Login fail
//Set error messages.
const message = response.data.message;
setLoginMessage(message);
}
});
}
const handleGoogleLoginFailure = () => {
setLoginMessage("Impossible to login with Google at the moment. Please retry later.")
}
While the end point in the express server is:
router.post('/googleLogin', async (req, res) => {
const { token, accessToken } = req.body;
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.CLIENT_ID
});
const {email, given_name, family_name} = ticket.getPayload();
const { OAuth2 } = google.auth;
const oauth2Client = new OAuth2();
oauth2Client.setCredentials({ access_token: accessToken });
var service = google.youtube({
version: 'v3',
auth: oauth2Client,
});
service.channels.list({
key: process.env.GOOGLE_API_KEY,
auth: client,
mine: true,
part: 'snippet',
}, (err, response) => {
if(err) {
console.log(err);
return;
}
var channels = response.data.items;
console.log(channels);
});
const [user, created] = await User.upsert({
email: email,
firstName: given_name,
lastName: family_name,
youtubeChannelId: 'TODO'
});
if(user) {
const accessToken = createTokens(user);
res.cookie("access-token", accessToken, {
maxAge: 60 * 60 * 24 * 1000, //one day
httpOnly: true
});
return res.json({
loginStatus: 'ok',
user: user
});
}else{
console.log("Error in login with Google");
}
});
I'm getting the error:
Error: No access, refresh token, API key or refresh handler callback is set.
Some ideas?

if you're using the Google OAuth 2.0 flow, I'm not sure why you're using the API key, since you're sending the user Access Token used to identify the user who completed the OAuth flow with your Client ID.
Also, I recommend using the global service auth, so you don't need to send auth credentials to each service call.
List my YouTube channels                                                                                
View in Fusebit
const { OAuth2 } = google.auth;
const oauth2Client = new OAuth2();
oauth2Client.setCredentials({ access_token: accessToken });
// Add global service auth
google.options({ auth: oauth2Client });
const youtube = googleClient.youtube('v3');
const channelsResponse = await youtube.channels.list({
part: 'id,statistics,snippet',
mine: true
});

Related

Is it possible to decrypt a password without having to use compare? (bcrypt)

I'm implementing a "forgot password" system in my authentication API in node. For this I created a "forgetPassword" controller where I send the user's email through req.body and with that an email with the recovery token is sent to the user, and that same token is also sent inside of the model user in the database in "tokenReset". The second controller is "resetPassword", where this recovery token is sent by params and a new password is sent in the req.body.
The biggest problem is, I think it's not safe for this token to be in the database without being encrypted, as someone inside the database can take advantage of this as a flaw, so I encrypted the token before sending it to the database. But the big question is, I have to fetch this token inside the database in "resetPassword" controller through the token sent in the params, but the token inside my database is encrypted and the one in the params is not. What would be the best way to resolve this?
forgotPassword controller
const jwt = require('jsonwebtoken');
const db = require('../../models/index');
const sendEmail = require('../../utils/mailer');
const bcrypt = require('bcryptjs');
exports.store = async (req, res) => {
const { email } = req.body;
const secret = process.env.JWT_SECRET;
try {
const user = await db.User.findOne({ where: { email } });
if (!user) {
res.status(400).json('User does not exist.');
}
const token = jwt.sign({ id: user.id }, secret, {
expiresIn: process.env.EXPIRES_FORGOTPASS,
});
const hashedToken = bcrypt.hashSync(token, 10);
await user.update({
tokenReset: hashedToken,
});
sendEmail({
from: 'noreply#email.com',
to: 'admin#gmail.com',
subject: 'Reset your password',
text: `Token sending email to reset password account from ${user.email}`,
html: `<h2>Token sending email to reset password account from ${user.email}</h2>
<h4>${token}</h4>
`,
});
return res.status(200).json('Check the verification link in your email!');
} catch (err) {
return console.log(err);
}
}
resetPassword controller
const jwt = require('jsonwebtoken');
const db = require('../../models/index');
const bcrypt = require('bcryptjs');
const sendEmail = require('../../utils/mailer');
exports.store = async (req, res) => {
const { token } = req.params;
const { password } = req.body;
const secret = process.env.JWT_SECRET;
try {
const userExists = await db.User.findOne({ where: { tokenReset: token } });
if (!userExists) {
return res.status(401).json('User not found in our database!');
}
try {
await jwt.verify(token, secret);
} catch (err) {
return res.status(401).json('Link expired, make a new password reset request!');
}
const hashedNewPass = bcrypt.hashSync(password, 10);
await userExists.update({
password: hashedNewPass,
tokenReset: "",
});
return res.status(200).json(`Password updated!`);
} catch (err) {
return console.log(err);
}
}

is there a way to know why cookie does not get sent to the server

please, I am working on a Nodejs REST api with sign-up and log-in functionality,
when a user logs in, they get an accessToken.
secondly, A cookie is saved containing refreshToken to enable the user make request to the /refresh endpoint for a new accessToken.
here is my problem; when a user logs in, I can see the cookie, but after subsequent requests to other routes, the cookie DISAPPEARS and is no longer sent along to the server, thus preventing me from making a request to the /refresh endpoint for a new accessToken.
// log-in controller
const User = require('../model/User');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const handleLogin = async (req, res) => {
const { user, pwd } = req.body;
if (!user || !pwd) return res.status(400).json({ 'message': 'Username and password are required.' });
const foundUser = await User.findOne({ username: user }).exec();
if (!foundUser) return res.sendStatus(401); //Unauthorized
// evaluate password
const match = await bcrypt.compare(pwd, foundUser.password);
if (match) {
const roles = Object.values(foundUser.roles).filter(Boolean);
// create JWTs
const accessToken = jwt.sign(
{
"UserInfo": {
"username": foundUser.username,
"roles": roles
}
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '10s' }
);
const refreshToken = jwt.sign(
{ "username": foundUser.username },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '1d' }
);
// Saving refreshToken with current user
foundUser.refreshToken = refreshToken;
const result = await foundUser.save();
console.log(result);
console.log(roles);
// Creates Secure Cookie with refresh token
res.cookie('jwt', refreshToken, { httpOnly: true, secure: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000 });
// Send authorization roles and access token to user
res.json({ roles, accessToken });
} else {
res.sendStatus(401);
}
}
module.exports = { handleLogin };
// refresh endpoint controller
const User = require('../model/User');
const jwt = require('jsonwebtoken');
const handleRefreshToken = async (req, res) => {
const cookies = req.cookies;
if (!cookies?.jwt) return res.sendStatus(401);
const refreshToken = cookies.jwt;
const foundUser = await User.findOne({ refreshToken }).exec();
if (!foundUser) return res.sendStatus(403); //Forbidden
// evaluate jwt
jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET,
(err, decoded) => {
if (err || foundUser.username !== decoded.username) return res.sendStatus(403);
const roles = Object.values(foundUser.roles);
const accessToken = jwt.sign(
{
"UserInfo": {
"username": decoded.username,
"roles": roles
}
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '10s' }
);
res.json({ roles, accessToken })
}
);
}
module.exports = { handleRefreshToken }
I ran into same issue as you had: thunder client could not send cookie back in sub sequential request. I fixed this issue after changing res.cookie in handleLogin. Give it a try:
res.cookie('jwt', refreshToken, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 });
Please check if you are using cookies parser middleware at right place in your server.js
The JWT refresh token is a one-off thing.
So, in your code after user signs in in the JWT server, you are getting accessToken which expires in 10 seconds, and refresh token which you are saving into the user's browser cookies. After accessToken expires, you are generating new accessToken using user's refreshToken. At this moment your current refreshToken becomes outdated, and when you will try to generate one more accessToken (in 10 seconds), it will fail.
So, in the handleRefreshToken() function you need to generate brand new refreshToken:
const refreshToken = jwt.sign(
{ "username": foundUser.username },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '1d' }
);
// Saving refreshToken with current user
foundUser.refreshToken = refreshToken;
const result = await foundUser.save();
console.log(result);
console.log(roles);
// Creates Secure Cookie with refresh token
res.cookie('jwt', refreshToken, { httpOnly: true, secure: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000 });

Login with BasicAuth React/Redux and Node JS

Let there be a landing page that enables the login. This landing page has:
Text field for user ID: LoginUserIDInput
Text field for password: LoginPasswordInput
Button to start the login process: LoginButton
React and Redux are supposed to do the following:
If the credentials are entered in the dialogue and the "Login
Execute" button is pressed the authentication is carried out via
basic authentication to the backend.
If authentication is successful, the token is stored in the Redux
store and redirected to the "private" page.
If the "Logout" button is pressed, the token in the Redux Store is
deleted and redirected to the start page.
The login process takes place via the http protocol. A separate BackEnd was created for the login process.
The login process is successful in that the basic auth is created from the credentials and a bearer token is sent back, but after the bearer token arrives I get an error message from the reducer.
How can a successful login succeed?
I will split the code into client and server side as I am not sure where the error is.
CLIENT SIDE (REACT/REDUX)
AuthenticationAction.js
//here I transform the userID and the password into a hash
//I get the Bearer Response. So the authentication process is
//succesful
function login(userID, password) {
const hash= Buffer.from(`${userID}:${password}`).toString('base64')
const requestOptions = {
method: 'POST',
headers: { 'Authorization': `Basic ${hash}` },
};
console.log(requestOptions)
return fetch('https://localhost:443/login', requestOptions)
.then(handleResponse)
.then(userSession => {
return userSession;
});}
function handleResponse(response){
//the problem starts here
//if everything is ok, I want to dispatch a success message to the
//reducer, if not an error message
const authorizationHeader = response.headers.get('Authorization');
return response.text().then(text => {
console.log('Receive result: '+authorizationHeader.split(" ")[1])
const base64Credentials = authorizationHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
/* console.log(credentials.split(':')[3].split(",")[0]) */
const [username, password] = credentials.split(':');
console.log(username)
console.log(password)
const data= text && JSON.parse(text);
var token = authorizationHeader.split(" ")[1];
let userSession = {
user: credentials.split(':')[3].split(",")[0],
accessToken: token
}
return userSession;
})}
SERVER SIDE (express/node js)
AuthenticationRoute.js
var express = require('express');
var router = express.Router();
var authenticationService = require('./AuthenticationService')
router.post('/', authenticationService.basicAuth);
module.exports = router;
Authentication Service
function basicAuth(req, res, next) {
if(!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
return res.status(400).json({
status: 'Fehler',
error: 'Nicht autorisiert',
});
}
const base64Credentials = req.headers.authorization.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
console.log("AuthenticationService " + username + " " + password);
userService.getByUserId(username, function(error, user) {
user.comparePassword(password.toString(), function(err, isMatch) {
if(err) {
console.log("Fehler")
throw err;
}
/*Passwort richtig*/
if(isMatch) {
console.log("Passwort richtig. Token wird erstellt.")
var issueAt = new Date().getTime();
var expirationTime = config.get('session.timeout')
var expiresAt = issueAt + (expirationTime * 1000);
var privateKey = config.get('session.tokenKey');
let token = jwt.sign({
"user": user.userID
}, privateKey, {
expiresIn: expiresAt,
algorithm: 'HS256'
});
console.log("Token erstellt: " + token);
res.header("Authorization", "Bearer " + token);
res.send("Token erfolgreich gesendet");
}
/*Passwort falsch*/
else {
console.log("Falsch.")
res.status(500).json({
error: "Passwort und userID stimmen nicht überein."
});
}
});
})
}
module.exports = {
basicAuth,
}

Jsonwebtoken not working as expected JWT Expired

Hello can some help with this, I can't seems to figure out why I always got en error TokenExpiredError: jwt expired I just created the token.
Here's what I wanted to do.
1.) when user logged in, response contains the accessToken and refreshToken
2.) call the auth/token every 30 seconds to generate new accessToken.
auth/token api will accept refresh_token as request data and the accessToken as Bearer Authorization, however in I always got jwt expired. It fails on the authentication middleware.
However when I used the accessToken I got from logged in api, I got jwt expired error.
JWT Service
sign(payload, expiresIn = '15m') {
return jwt.sign(payload, this.privateKey, {
algorithm: 'RS256',
expiresIn,
});
}
verify(token) {
return jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });
}
Controller.js
// login api
login(req, res, next) {
const { body: { email, password } } = req;
this.accountService.findByEmail(email)
.then(async (data) => {
const accessToken = this.jwtService.sign({ data}, '2m');
const refreshToken = this.jwtService.sign({ data}, '1h');
return res.send(new SuccessResponse(200, {accessToken, refreshToken}));
})
.catch((error) => {
next(error);
});
}
}
// auth/token api
authToken(req, res, next) {
const { body: { refresh_token } }= req;
const payload = { refresh_token };
const newAccessToken = this.jwtService.sign({ payload }, '1m', 'RS256');
}
authenticate.middleware.js
export const authenticate = (req, res, next) => {
const authorizationBearer = req.get('authorization');
const accessToken = authorizationBearer.replace('Bearer ', '');
const decodedData = jwtService.verify(accessToken);
if (decodedData) {
next();
}
}
Did I miss something or did I do anything wrong?
Library Used:
jsonwebtoken: 8.5.1
express: 4.17.1

I'm having trouble reading emails from Gmail using the Gmail API

I am listing all messages from the inbox, being able to view the IDs of each email, but I cannot send them by parameter to search for each email using the function: gmail.users.messages.get
Just return a message saying: undefined
Can anyone tell me why this is happening?
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Gmail API.
authorize(JSON.parse(content), listLabels);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
/**
* Lists the labels in the user's account.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listLabels(auth) {
const gmail = google.gmail({version: 'v1', auth});
gmail.users.labels.list({
userId: 'me',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const labels = res.data.labels;
if (labels.length) {
console.log('Labels:');
labels.forEach((label) => {
console.log(`- ${label.name}`);
});
listMessages(auth);
} else {
console.log('No labels found.');
}
});
}
/************************** My code is below *******************************/
function listMessages(auth){
var gmail = google.gmail('v1');
gmail.users.messages.list({
auth: auth,
userId: 'me',
q : 'has:attachment',
labelIds: 'INBOX',
}, (err, res) => {
if(err){
console.log('Erro ao listar os emails');
}
var message = res;
Object.keys(message).forEach(index => {
console.log(res[index]);
gmail.users.messages.get({
userId: 'me',
id: message[index].id,
format: 'full',
}, (err, res) => {
console.log(res);
})
})
})
}
You want to retrieve the messages with gmail.users.messages.get using the values retrieved from gmail.users.messages.list.
You want to achieve this using googleapis with Node.js.
You have already been able to get the values from Gmail using Gmail API.
Modification points:
The values from gmail.users.messages.list can be retrieved with res.data. In your script, how about modifying from var message = res is modified to var messages = res.data.messages?
When you use auth: auth with gmail.users.messages.list, please also use it at gmail.users.messages.get.
I think that these points might be the reason of your issue. When above points are reflected to your script, it becomes as follows.
Modified script:
From:
var message = res;
Object.keys(message).forEach(index => {
console.log(res[index]);
gmail.users.messages.get({
userId: 'me',
id: message[index].id,
format: 'full',
}, (err, res) => {
console.log(res);
})
})
To:
var messages = res.data.messages;
messages.forEach((message) => {
gmail.users.messages.get(
{
auth: auth,
userId: "me",
id: message.id,
format: "full",
},
(err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.data);
}
);
});
References:
googleapis for Node.js
Users.messages: get

Categories