Gmail API's intermittently failing with 400 "precondition check failed" - google-oauth

Our application creates and deletes Gmail filters on behalf of end users who authorize via OAuth. Approx. 5% of these requests fail with code 400 and message "precondition check failed".
We've ensured that the filters we are trying to create are valid via testing in our personal Gmail accounts. As this point we are pretty stumped. Is there any way to get additional information regarding the root cause of these failures? I've included our code for creating filters below.
await this.checkCredentialPreconditions(credential);
await this.initialize();
await this.throttle.take(5);
try {
await gapi.client.gmail.users.settings.filters.create({
access_token: credential.accessToken,
userId: request.userId,
key: this.apiKey,
resource: {
action: {
addLabelIds: request.addLabelIds,
removeLabelIds: request.removeLabelIds,
},
criteria: {
excludeChats: true,
query: request.query,
},
},
});
} catch (err: unknown) {
console.error(err);
throw err;
}

Related

How to redirect from GraphQL middleware resolver on authentication fail?

Introduction:
I am using GraphQL Mesh as a gateway between my app and an API. I use Apollo Client as the GraphQL client. When a user wants to visit the first screen after hitting the log-in button, I do a query to load data from a CMS. This query has to go through the gateway. In the gateway I do an auth check to see if the user has a valid JTW access token, if not, I want to redirect back to the sign-in page. If the user has a token, he is let through.
The gateway is-auth.ts resolver:
const header = context.headers.authorization;
if (typeof header === "undefined") {
return new Error("Unauthorized: no access token found.");
} else {
const token = header.split(" ")[1];
if (token) {
try {
const user = jwt.verify(token, process.env.JWT_SECRET as string);
} catch (error) {
return new Error("Unauthorized: " + error);
}
} else {
return new Error("Unauthorized: no access token found.");
}
}
return next(root, args, context, info);
},
Problem: Right now, I am returning Errors in the authentication resolver of the gateway, hoping that I could pick them up in the error object that is sent to Apollo Client and then redirect off of that. Unfortunately, I don't get that option, since the Errors are thrown immediately, resulting in an error screen for the user (not what I want). I was hoping this would work in order to redirect to the sign-in from the client-side, but it does not work:
const { data, error } = await apolloClient(accessToken).query({
query: gql`
query {
...where my query is.
}
`,
});
if (error) {
return {
redirect: {
permanent: false,
destination: `/sign-in`,
},
};
}
Does anyone perhaps have a solution to this problem?
This is the GraphQL Mesh documentation on the auth resolver, for anyone that wants to see it: https://www.graphql-mesh.com/docs/transforms/resolvers-composition. Unfortunately, it doesn't say anything about redirects.
Kind regards.

Office web add-in in JS and login with SSAL : interaction_in_progress after login

I'm trying to get an access token from Microsoft to connect to Graph API, by using a client that is a web add-in in Word 365 desktop (pure JS, not made with Angular or Node).
To authenticate, this is the code I'm using:
window.Office.onReady(() => {
initMsalInstance();
});
function initMsalInstance() {
myMSALObj = new msal.PublicClientApplication({
auth: {
clientId: "...",
authority: "...",
redirectUri: "",
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true
}
});
myMSALObj.handleRedirectPromise()
.then((response) => {
if (response) {
console.log(response);
} else {
console.log('noresp');
}
})
.catch((error) => {
console.log(error)
});
}
function signIn() {
myMSALObj.loginRedirect({
scopes: ['user.read', 'files.read.all']
});
}
I just have a button that calls the "signIn()" method, then it opens Chrome, I'm loggin in, and I'm redirected to the page I selected.
Unfortunately, in the add-in, nothing happens, my handleRedirectPromise() doesn't seem to get called, so I don't have the response and the token.
If I'm trying to click on "sign in" again, then this is the error I get:
interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
What can I do to complete the process and get my token into my Office 365 Word Web add-in?
You are getting this error because of this piece of code:
msalInstance.loginRedirect(loginRequest);
This code looks into the session storage for the key MSAL.[clientId].interaction.status and other temp values required for redirection process.
If such value exist and it's value equals the 'interaction_in_progress' then error will be thrown.
This is the known issue in MSAL.
Follow these steps to resolve this issue.
Account selection logic is app dependent and adjust as needed for different use cases. Set active account on page load.
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
msalInstance.addEventCallback((event) => {
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload.account) {
const account = event.payload.account;
msalInstance.setActiveAccount(account);
}
}, error=>{
console.log('error', error);
});
console.log('get active account', msalInstance.getActiveAccount());
// handle auth redirect/do all initial setup for MSAL
msalInstance.handleRedirectPromise().then(authResult=>{
// Check if user signed in
const account = msalInstance.getActiveAccount();
if(!account){
// redirect anonymous user to login page
msalInstance.loginRedirect();
}
}).catch(err=>{
// TODO: Handle errors
console.log(err);
});

graphql server email verify example

I'm starting to work on an express API using graphql with apollo-server-express and graphql-tools. My register user process steps are:
User submit user name, email and password.
Server send an email to user by Mailgun with unique link generated by uuid.
User follow the link to verify the registration.
But I'm in struggle at how to bind the mutation in the resolver. See snippets:
server.js
const buildOptions = async (req, res, done) => {
const user = await authenticate(req, mongo.Users)
return {
schema,
context: {
dataloaders: buildDataloaders(mongo),
mongo,
user
},
}
done()
}
// JWT setting
app.use('/graphAPI',
jwt({
secret: JWT_SECRET,
credentialsRequired: false,
}),
graphqlExpress(buildOptions),
res => data => res.send(JSON.stringify(data))
)
Mutation on resolver
signupUser: async (root, data, {mongo: { Users }}) => {
// Check existed accounts,
// if account is not exist, assign new account
const existed = await Users.findOne({email: data.email})
if (!existed) {
// create a token for sending email
const registrationToken = {
token: uuid.v4(),
created_at: new Date(),
expireAfterSeconds: 3600000 * 6 // half day
}
const newUser = {
name: data.name,
email: data.email,
password: await bcrypt.hash(data.password, 10),
created_at: new Date(),
verification_token: registrationToken,
is_verified: false,
}
const response = await Users.insert(newUser)
// send and email to user
await verifyEmail(newUser)
return Object.assign({id: response.insertedIds[0]}, newUser)
}
// Throw error when account existed
const error = new Error('Email existed')
error.status = 409
throw error
},
// VERIFY USER
// Set verify to true (after user click on the link)
// Add user to mailist
verifiedUser: async (root, data, {mongo: { Users }}) => {
await Users.updateOne(
{ email: data.email },
{
set: {is_verified: true},
unset: {verification_token: {token: ''}}
}
)
},
route config
routes.get('/verify?:token', (req, res, next) => {
res.render('verified', {title: 'Success'})
})
the route config is where I stuck, because the object is passed to all resolvers via the context inside graphqlExpress
Any one help me out or suggest for me any articles related. Thanks so much.
You will need 3 graphql endpoints and 1 apollo http endpoint for proper workflow.
Optionally you can combine 3 graphql endpoints in one, but then it will be a one big function with a lot of different responsibilities.
1# graphql endpoint: changepass-request
expects email param
check if user with such email found in db:
generate code
save it in the local account node
send code to the user email with http link to confirm code:
http://yoursite.com/auth/verify?code=1234
return redirect_uri: http://yoursite.com/auth/confirm-code
for UI page with prompt for confirmation code
2# graphql endpoint: changepass-confirm
expects code param:
if user with such code found in db, return redirect_uri to UI page with prompt for new pass with confirmation code in params: http://yoursite.com/auth/change-pass?code=1234
3# graphql endpoint: changepass-complete
expects code and new pass:
hash new password
search in db for local account with such code
3a. if not found:
return error with redirect_uri to login page:
http://yoursite.com/auth?success=false&message="Confirmation code is not correct, try again."
3b. if found:
change password for new, return success status with redirect_uri to login page:
http://yoursite.com/auth?success=true&message="ok"
4# apollo HTTP endpoint: http://yoursite.com/auth/verify?code=1234
if no code provided:
redirect to UI registration page with error message in params:
http://yoursite.com/auth?success=false&message="Confirmation code is not correct, try again."
if code provided: search in db for local account with such code
1a. if user not found:
redirect to reg ui with err mess in params:
http://yoursite.com/auth?success=false&message="Confirmation code is not correct, try again."
1.b if user found:
redirect to ui page with new password prompt and attach new code to params
I didn't put any code above, so you can use this workflow in other auth scenarios.
It seems like rather than utilizing the verifiedUser endpoint, it would be simpler to just keep that logic inside the controller for the /verify route. Something like:
routes.get('/verify?:token', (req, res) => {
Users.updateOne(
{ verification_token: { token } },
{
$set: {is_verified: true},
$unset: {verification_token: {token: ''}}
},
(err, data) => {
const status = err ? 'Failure' : 'Success'
res.render('verified', {title: status})
}
)
})

How can I get user email when logging in through Facebook in parse.com

I need to obtain a user email for business purposes in my parse.com application. Is it possible to fetch it during Facebook sign-up/login process?
For example I have the following login code
var signupLoginFB = function() {
Parse.FacebookUtils.logIn("email", {
success: function(user) {
//do something on successful login
},
error: function(user, error) {
//do something on error
}
});
}
I looked at the "user" object passed to the callback, and I didn't see any email there. Should I call some other FB or Parse API to fetch these data?
You'll want to make a call to FB.api() as follows:
var signupLoginFB = function() {
Parse.FacebookUtils.logIn("email", {
success: function(user) {
//do something on successful login
FB.api('/me', function(me) {
user.set("displayName", me.name);
user.set("email", me.email);
user.save();
console.log("/me response", me);
});
},
error: function(user, error) {
//do something on error
}
});
}
You'll see in the console all the fields returned. Note that they'll vary based on what they have decided to share, for example my Facebook profile doesn't include my email as public so it isn't returned in the "/me" call.
Consider also only doing the extra call if it is a new user, ie wrap that code in if (!user.existed()) {...}.

Google API client compatibility with Google Chrome extensions

I'm working inside of a Google Chrome extension and I would like to use Google's Client API, https://apis.google.com/js/client.js, to retrieve a user's Google+ ID.
I've supplied the following values in my manifest.json:
"oauth2": {
"client_id": "[CLIENT ID].apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/plus.me"
]
}
Providing these values allows me to successfully call chrome.identity.getAuthToken: http://developer.chrome.com/apps/identity.html
getAuthToken: function () {
chrome.identity.getAuthToken({
interactive: false
}, function (authToken) {
if (chrome.runtime.lastError) {
// User isn't signed into Google Chrome.
console.error(chrome.runtime.lastError.message);
}
});
}
Once I have an auth token, I'm able to issue an AJAX request and successfully get my info:
$.ajax({
url: 'https://www.googleapis.com/plus/v1/people/me',
headers: {
'Authorization': 'Bearer ' + authToken
},
success: function (response) {
console.log("Received user info", response);
},
error: function (error) {
console.error(error);
}
});
None of this uses the aforementioned Google Client API, though. It would be nice to leverage that instead, but I'm wondering if it is not meant for Google Chrome extensions. I'm able to at least get things sort of working like so:
GoogleAPI.auth.authorize({
client_id: '[CLIENT ID].apps.googleusercontent.com',
scope: 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me',
// Set immediate to false if authResult returns null
immediate: true
}, function(){
GoogleAPI.client.load('plus', 'v1', function () {
var request = GoogleAPI.client.plus.people.get({
'userId': 'me'
});
request.execute(function (response) {
console.log("Response:", response);
});
});
But I've already specified these values in my manifest -- so it seems a bit odd to re-refrence them. I could load my manifest and parse it, but I've already got something successfully working above.
Additionally, you have to call GoogleAPI.client.setApiKey to use Google's stuff. This works on a development environment because I am able to whitelist my machine's IP, but this would not work in a production environment as there will be many clients connecting.
So, should Google's Client API not be used within Google Chrome extensions?