Authenticating Google Pull Task Queue with NodeJS client - node.js

I have a Pull Task Queue running on App Engine. I am trying to access the queue externally from the NodeJS REST client: https://github.com/google/google-api-nodejs-client
I'm passing my Server API key in with the request:
var googleapis = require('googleapis'),
API_KEY = '...';
googleapis
.discover('taskqueue', 'v1beta2')
.execute(function(err, client) {
var req = client.taskqueue.tasks.insert({
project: 'my-project',
taskqueue: 'pull-queue',
key: API_KEY
});
req.execute(function(err, response) {
...
});
});
But I am getting back a 401 "Login Required" message. What am I missing?
If I need to use OAuth, how can I get an access token if my client is a NodeJS server instead of user/browser that can process the OAuth redirect URL?

The best way to do this is to take advantage of Service Accounts in GCE. This is a synthetic user account that is usable by anyone in the GCE project. Getting all of the auth lined up can be a little tricky. Here is an example on how to do this in python.
The general outline of what you need to do:
Start the GCE instance with the task queue OAuth scope.
Add the GCE service account to the task queue ACL in queue.yaml.
Acquire an access token. It looks like you can use the computeclient.js credential object to automate the HTTP call to http://metadata/computeMetadata/v1beta1/instance/service-accounts/default/token
Use this token in any API calls to the task queue API.
I'm not a Node expert, but searching around I saw found an example of how to connect to the Datastore API from Node using service accounts from GCE. It should be straightforward to adapt this to the task queue API.

Related

Google Cloud Tasks - Node.js client authentication failure

I'm trying to use #google-cloud/tasks, and have a piece of code modeled after the node.js documentation for creating an HTTP Target task. The Cloud Tasks API is enabled, and I've created a queue from the gcloud CLI.
The backend in my case is Cloud Run. I've followed the instructions to configure a service account for HTTP Target handler authentication. The service account has the following permissions:
Cloud Run Invoker
Cloud Tasks Enqueuer
Cloud Tasks Task Runner
Service Account User
Lastly regarding the bit about Using HTTP Target tasks with authentication tokens, I've added:
oidcToken: {
serviceAccountEmail,
},
to the task object. After all this, the response is still PERMISSION_DENIED: Request had insufficient authentication scopes.
Actually, even if I try something basic, like listing the queues:
process.env.GOOGLE_APPLICATION_CREDENTIALS = '/path-to-my-service-account.json';
const {CloudTasksClient} = require('#google-cloud/tasks');
const client = new CloudTasksClient();
const parent = client.queuePath('my-project', 'my-region', 'my-queue-name');
client.listQueues({parent})
I get the same authentication error. I'm running node inside a Docker container, so maybe that's why Application Default Credentials aren't working? Any idea how I can authenticate successfully?

GCP Consume a REST API after OAuth in Node.js

I am working to implement a Node.js webapp to be deployed on GCP App Engine.
Following the Node.js Bookshelf App sample, I did manage to implement a basic user authentication flow using the passport-google-oauth20 and retrieve basic profile information. I basically just got rid of what was not needed for my purposes
My custom code is available at: gist.github.com/vdenotaris/3a6dcd713e4c3ee3a973aa00cf0a45b0.
However, I would now like to consume a GCP Cloud Storage API to retrieve all the storage objects within a given buckets with the logged identity.
This should be possible by:
adding a proper scope for the request.
authenticating the REST requests using the user session token obtained via OAuth.
About the post-auth handler, the documentation says:
After you obtain credentials, you can store information about the
user. Passport.js automatically serializes the user to the session.
After the user’s information is in the session, you can make a couple
of middleware functions to make it easier to work with authentication.
// Middleware that requires the user to be logged in. If the user is not logged
// in, it will redirect the user to authorize the application and then return
// them to the original URL they requested.
function authRequired (req, res, next) {
if (!req.user) {
req.session.oauth2return = req.originalUrl;
return res.redirect('/auth/login');
}
next();
}
// Middleware that exposes the user's profile as well as login/logout URLs to
// any templates. These are available as `profile`, `login`, and `logout`.
function addTemplateVariables (req, res, next) {
res.locals.profile = req.user;
res.locals.login = `/auth/login?return=${encodeURIComponent(req.originalUrl)}`;
res.locals.logout = `/auth/logout?return=${encodeURIComponent(req.originalUrl)}`;
next();
}
But I do not see where the token is stored, how can I retrieve it and how to use it to consume a web-service (in my case, GCP storage).
I am not at all a node.js expert, so it would be nice having a bit more clarity on that: could someone explain me how to proceed in consuming a REST API using the logged user credentials (thus IAM/ACL privileges)?
If you want to access Cloud Storage through the use of a token obtained with OAuth, when the application requires user data, it will prompt a consent screen, asking for the user to authorize the app to get some of their data. If the user approves, an access token is generated, which can be attached to the user's request. This is better explained here.
If you plan to run your application in Google App Engine, there will be a service account prepared with the necessary authentication information, so no further setup is required. You may need to generate the service account credentials (generally in JSON format), that have to be added to the GOOGLE_APPLICATION_CREDENTIALS environment variable in gcloud.
Here is an example of how to authenticate and consume a REST API with the token that was obtained in the previous step. This, for example, would be a request to list objects stored in a bucket:
GET /storage/v1/b/example-bucket/o HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer [YOUR_TOKEN]

Authenticating a Google Cloud Function as a service account on other Google APIs

I have an HTTP-triggered function running on Google Cloud Functions, which uses require('googleapis').sheets('v4') to write data into a docs spreadsheet.
For local development I added an account via the Service Accounts section of their developer console. I downloaded the token file (dev-key.json below) and used it to authenticate my requests to the Sheets API as follows:
var API_ACCT = require("./dev-key.json");
let apiClient = new google.auth.JWT(
API_ACCT.client_email, null, API_ACCT.private_key,
['https://www.googleapis.com/auth/spreadsheets']
);
exports.myFunc = function (req, res) {
var newRows = extract_rows_from_my_client_app_request(req);
sheets.spreadsheets.values.append({
auth: apiClient,
// ...
resource: { values:newRows }
}, function (e) {
if (e) res.status(500).json({err:"Sheets API is unhappy"});
else res.status(201).json({ok:true})
});
};
After I shared my spreadsheet with my service account's "email address" e.g. local-devserver#foobar-bazbuzz-123456.iam.gserviceaccount.com — it worked!
However, as I go to deploy this to the Google Cloud Functions service, I'm wondering if there's a better way to handle credentials? Can my code authenticate itself automatically without needing to bundle a JWT key file with the deployment?
I noticed that there is a FUNCTION_IDENTITY=foobar-bazbuzz-123456#appspot.gserviceaccount.com environment variable set when my function runs, but I do not know how to use this in the auth value to my googleapis call. The code for google.auth.getApplicationDefault does not use that.
Is it considered okay practice to upload a private JWT token along with my GCF code? Or should I somehow be using the metadata server for that? Or is there a built-in way that Cloud Functions already can authenticate themselves to other Google APIs?
It's common to bundle credentials with a function deployment. Just don't check them into your source control. Cloud Functions for Firebase samples do this where needed. For example, creating a signed URL from Cloud Storage requires admin credentials, and this sample illustrates saving that credential to a file to be deployed with the functions.
I'm wondering if there's a better way to handle credentials? Can my
code authenticate itself automatically without needing to bundle a JWT
key file with the deployment?
Yes. You can use 'Application Default Credentials', instead of how you've done it, but you don't use the function getApplicationDefault() as it has been deprecated since this Q was posted.
The link above shows how to make a simple call using the google.auth.getClient API, providing the desired scope, and have it decide the credential type needed automatically. On cloud functions this will be a 'Compute' object, as defined in the google-auth-library.
These docs say it well here...
After you set up a service account, ADC can implicitly find your
credentials without any need to change your code, as described in the
section above.
Where ADC is Application Default Credentials.
Note that, for Cloud Functions, you use the App Engine service account:
YOUR_PROJECT_ID#appspot.gserviceaccount.com, as documented here. That is the one you found via the FUNCTION_IDENTITY env var - this rather tripped me up.
The final step is to make sure that the service account has the required access as you did with your spreadsheet.

How to get info of all workspaces that installed my slack app

How can I get Slack Workspace data while user installing my slack application?
My goal is to gather all Workspace ids that installed my application.
Main idea:
User have my application installed
Workspace ID(where application was installed) after authorization sends to some external url
Server handless request
Can I solve this issue via this process? https://api.slack.com/docs/oauth
Every Slack team needs to run through the OAuth process to install your Slack app to their workspace. This is usually done by calling a webpage of your app that has a "Add to Slack" button and moderates the Oauth process with the Slack server.
When the OAuth process is successful your app will receive the workspace specific access token along with info about team including team ID and team domain from the Slack server. Your app should store this information, so that you can retrieve and use it later as requested.
If you only stored the access token from each Slack team, you can call auth.test with that access token to retrieve more information about the connected Slack team including the team ID.
So in short, yes the OAuth process will help you gather that information.
If you didn't store them during the OAuth flow, then you cannot retrieve all existing workspaces that installed your Slack app.
As Erik said, you get the workspace information each time your app is installed, but it's your job to store that information.
If someone needs solution.
From slack-docs:
It’s time to setup a Redirect URL for your app. This is the endpoint
for Slack to send a unique temporary code to your server during a
user’s installation. Your server will then send back this code, along
with your Client ID and Client Secret, so that we know we can trust
you.
So if someone installing your app that redirects to redirect_url of your app settings. Than if user accepted permissions:
Slack API sent code to your server
You have to send code back. Example with node see below:
const request = require('request-promise');
let options = {
method: 'POST',
uri: 'https://slack.com/api/oauth.access',
formData: {
client_id: "client_id",
client_secret: "client_secret",
code: req.query.code
}, json: true
};
request.post(options).then(result => { res.json(result) });
In result variable you'll have all data for gathering.

Why do calls to the Service Management API work but calls to the Scheduler API fail?

I'm trying to make some calls to the new Azure Scheduler API. However, all my requests come back with this error:
<Error xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Code>AuthenticationFailed</Code>
<Message>The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.</Message>
</Error>
I'm pretty sure that I have everything setup correct because I can make calls using the same code and certificate to the Azure Service Management API.
The code I'm using to attach the certificate to the web request is from the MSDN Sample. The Scheduler API calls that I've tried to make are the Check Name Availability, Create Cloud Service, and Create Job Collection.
I've also verified that my subscription is Active for the preview of the Scheduler.
Here is an example of a request I've tried:
Create Cloud Service
Request A cloud service is created by submitting an HTTP PUT operation
to the CloudServices OData collection of the Service Management API
Tenant.Replace with your subscription ID and
with your cloud service ID.
So for this I create a web request pointing to:
https://management.core.windows.net/[MySubId]/cloudServices/[MyNewServiceName]
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUri);
// Define the requred headers to specify the API version and operation type.
request.Headers.Add("x-ms-version", "2012-03-01");
request.Method = "PUT";
request.ContentType = "application/xml";
Next I add the request body as specified in the documentation:
<CloudService xmlns:i='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://schemas.microsoft.com/windowsazure'>
<Label>[MyServiceName]</Label>
<Description>testing</Description>
<GeoRegion>uswest</GeoRegion>
</CloudService>
And finally I add the certificate that I use with my subscription to the account.
// Attach the certificate to the request.
request.ClientCertificates.Add(certificate);
I try to get the response and instead I get the error shown above.
BTW - I've also tried different regions thinking maybe it was a region issue since the scheduler isn't supported in all regions, but I still get the same response.
You need to register the scheduler in your application first by calling (PUT):
<subscription id>/services?service=scheduler.JobCollections&action=register
If you want to do this in .NET you can use the new Management libraries:
var schedulerServiceClient = new SchedulerManagementClient(credentials);
var result = schedulerServiceClient.RegisterResourceProvider();
Console.WriteLine(result.RequestId);
Console.WriteLine(result.StatusCode);
Console.ReadLine();
More detail: http://fabriccontroller.net/blog/posts/a-complete-overview-to-get-started-with-the-windows-azure-scheduler/

Resources