Is it possible to access Google Cloud Datastore API via gapi javascript client? - google-api-js-client

Does anyone have any example code for accessing google cloud datastore API using gapi javascript client? https://developers.google.com/datastore/docs/overview

var clientId = '<YOUR_CLIENT_ID>';
var datasetId = '<YOUR_DATASET_ID>';
var scopes = 'https://www.googleapis.com/auth/datastore \
https://www.googleapis.com/auth/userinfo.email';
gapi.auth.authorize(
{client_id: clientId, scope: scopes, immediate: false},
function(authResult) {
if (authResult && !authResult.error) {
gapi.client.load('datastore', 'v1beta1', function() {
gapi.client.datastore.datasets.beginTransaction({
'datasetId': datasetId
}).execute(function(resp) {
console.log(resp);
});
});
}
});
Keep in mind that since Google Cloud Datastore has no ACL support, the javascript client will have full read/write access to the whole dataset.

Related

Google Cloud Search Query via Node.js Error: This project doesn't have Cloud Search's Query API Enabled

I am working on Google Cloud Search API to search documents stored in Google Drive. I have Google Workspace account and few documents stored in Google Drive. I am able to search using Google Cloud search console but facing issue using below node.js code for searching using API.
Issue: I am able to generate the access token but get below error for search query:
Error: This project doesn't have Cloud Search's Query API Enabled,
and/or the Cloud Search Platform license has not been assigned to the
user account calling the Query API
var {google} = require("googleapis");
var serviceAccount = require('C:/nodejstest/key/serviceAccountKey.json');
// Specify the required scope.
var scopes = [
"https://www.googleapis.com/auth/cloud_search",
"https://www.googleapis.com/auth/cloud_search.query"
];
var jwtClient = new google.auth.JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: scopes,
subject: 'sample#example.com'
});
// Use the JWT client to generate an access token.
jwtClient.authorize(function(error, tokens) {
if (error) {
console.log("Error making request to generate access token:", error);
} else if (tokens.access_token === null) {
console.log("Provided service account does not have permission to generate access tokens");
} else {
var accessToken = tokens.access_token;
console.log('accessToken= ' + accessToken)
// Include the access token in the Authorization header.
}
});
const service = google.cloudsearch({version: 'v1'});
service.query.search({
auth: jwtClient,
requestBody: {
requestOptions: {
searchApplicationId: 'searchapplications/default',
debugOptions:{enableDebugging: true}
},
query: 'My query'
}
}).then((res) => {
console.log(JSON.stringify({results:res.results.length}));
console.log(JSON.stringify({resultsInfo:res.results[0]}));
}).catch((err) => {
console.error('Unexpected error with cloud search API.');
console.error(err.toString());
});
In above code I am passing workspace admins email id as subject.
I followed steps mentioned at below link
Configure access to the Google Cloud Search REST API
Perform Google Workspace domain-wide delegation of authority
Go to the google console then select the your project.
Then search for Cloud Search API then enable it.
Also make sure that your service account have access on Cloud Search Indexing API

How to restrict API Key when calling the Reverse Geocoding API from Browser?

I am using Google Reverse Geocoding API from Browser.
The API works fine when using API Key with no restriction.
For example: https://maps.googleapis.com/maps/api/geocode/json?key=API_KEY_WITH_NO_RESTRICTION&latlng=41.8857156,-87.64823779999999 - OK
But as I am calling the API from the browser, I would like to restrict the API Key, preferably request originating from certain domains.
Now, as per the restriction guideline, HTTP Referer restrictions won't work for the Geocoding API (one of the Google Web Service API). It returns error "API keys with referer restrictions cannot be used with this API." in such case!
The other option is to use IP address restriction. But it seems to be more suited if the call was originating from the server. In that case server address could be added in the restriction.
How can I secure (restrict) the API Key if I want to continue to call the Geocoding API from the browser?
I figured out that I have to use Maps Javascript API in order to be able to call the Reverse Geocoding (Address Lookup) from browser (client) with HTTP Referer restrictions in place for the API Key.
In my initial implementation I used fetch(requestUrl) from the browser as it seemed very convenient and ended up with the above problem.
Example (using TypeScript):
Enable Maps Javascript API
Install required packages
npm install #googlemaps/js-api-loader
npm i -D #types/google.maps
reverseGeo.ts
import { Loader } from '#googlemaps/js-api-loader';
const loadScript = async (): Promise<void> => {
const loader = new Loader({
apiKey: API_KEY_WITH_REFERRER_RESTRICTION,
version: 'weekly',
});
await loader.load();
};
export async function reverseGeo(
lat: number, long: number
): Promise<string> {
await loadScript();
const geocoder = new google.maps.Geocoder();
const latlng = {
lat: lat,
lng: long,
};
try {
const { results } = await geocoder.geocode({ location: latlng });
if (results && results[0]) {
return results[0].formatted_address;
}
} catch (e) {
// handle exception
}
throw new TypeError('Zero result or reverse geo Failed'); // or handle other way
}
reverseGeo.spec.ts
import { reverseGeo} from './reverseGeo';
it('should test reverseGeo', async () => {
console.log(reverseGeo(22.5726, 88.3639));
});

AWS CDK passing API Gateway URL to static site in same Stack

I'm trying to deploy an S3 static website and API gateway/lambda in a single stack.
The javascript in the S3 static site calls the lambda to populate an HTML list but it needs to know the API Gateway URL for the lambda integration.
Currently, I generate a RestApi like so...
const handler = new lambda.Function(this, "TestHandler", {
runtime: lambda.Runtime.NODEJS_10_X,
code: lambda.Code.asset("build/test-service"),
handler: "index.handler",
environment: {
}
});
this.api = new apigateway.RestApi(this, "test-api", {
restApiName: "Test Service"
});
const getIntegration = new apigateway.LambdaIntegration(handler, {
requestTemplates: { "application/json": '{ "statusCode": "200" }' }
});
const apiUrl = this.api.url;
But on cdk deploy, apiUrl =
"https://${Token[TOKEN.39]}.execute-api.${Token[AWS::Region.4]}.${Token[AWS::URLSuffix.1]}/${Token[TOKEN.45]}/"
So the url is not parsed/generated until after the static site requires the value.
How can I calculate/find/fetch the API Gateway URL and update the javascript on cdk deploy?
Or is there a better way to do this? i.e. is there a graceful way for the static javascript to retrieve a lambda api gateway url?
Thanks.
You are creating a LambdaIntegration but it isn't connected to your API.
To add it to the root of the API do: this.api.root.addMethod(...) and use this to connect your LambdaIntegration and API.
This should give you an endpoint with a URL
If you are using the s3-deployment module to deploy your website as well, I was able to hack together a solution using what is available currently (pending a better solution at https://github.com/aws/aws-cdk/issues/12903). The following together allow for you to deploy a config.js to your bucket (containing attributes from your stack that will only be populated at deploy time) that you can then depend on elsewhere in your code at runtime.
In inline-source.ts:
// imports removed for brevity
export function inlineSource(path: string, content: string, options?: AssetOptions): ISource {
return {
bind: (scope: Construct, context?: DeploymentSourceContext): SourceConfig => {
if (!context) {
throw new Error('To use a inlineSource, context must be provided');
}
// Find available ID
let id = 1;
while (scope.node.tryFindChild(`InlineSource${id}`)) {
id++;
}
const bucket = new Bucket(scope, `InlineSource${id}StagingBucket`, {
removalPolicy: RemovalPolicy.DESTROY
});
const fn = new Function(scope, `InlineSource${id}Lambda`, {
runtime: Runtime.NODEJS_12_X,
handler: 'index.handler',
code: Code.fromAsset('./inline-lambda')
});
bucket.grantReadWrite(fn);
const myProvider = new Provider(scope, `InlineSource${id}Provider`, {
onEventHandler: fn,
logRetention: RetentionDays.ONE_DAY // default is INFINITE
});
const resource = new CustomResource(scope, `InlineSource${id}CustomResource`, { serviceToken: myProvider.serviceToken, properties: { bucket: bucket.bucketName, path, content } });
context.handlerRole.node.addDependency(resource); // Sets the s3 deployment to depend on the deployed file
bucket.grantRead(context.handlerRole);
return {
bucket: bucket,
zipObjectKey: 'index.zip'
};
},
};
}
In inline-lambda/index.js (also requires archiver installed into inline-lambda/node_modules):
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const fs = require('fs');
var archive = require('archiver')('zip');
exports.handler = async function(event, ctx) {
await new Promise(resolve => fs.unlink('/tmp/index.zip', resolve));
const output = fs.createWriteStream('/tmp/index.zip');
const closed = new Promise((resolve, reject) => {
output.on('close', resolve);
output.on('error', reject);
});
archive.pipe(output);
archive.append(event.ResourceProperties.content, { name: event.ResourceProperties.path });
archive.finalize();
await closed;
await s3.upload({Bucket: event.ResourceProperties.bucket, Key: 'index.zip', Body: fs.createReadStream('/tmp/index.zip')}).promise();
return;
}
In your construct, use inlineSource:
export class TestConstruct extends Construct {
constructor(scope: Construct, id: string, props: any) {
// set up other resources
const source = inlineSource('config.js', `exports.config = { apiEndpoint: '${ api.attrApiEndpoint }' }`);
// use in BucketDeployment
}
}
You can move inline-lambda elsewhere but it needs to be able to be bundled as an asset for the lambda.
This works by creating a custom resource that depends on your other resources in the stack (thereby allowing for the attributes to be resolved) that writes your file into a zip that is then stored into a bucket, which is then picked up and unzipped into your deployment/destination bucket. Pretty complicated but gets the job done with what is currently available.
The pattern I've used successfully is to put a CloudFront distribution or an API Gateway in front of the S3 bucket.
So requests to https://[api-gw]/**/* are proxied to https://[s3-bucket]/**/*.
Then I will create a new Proxy path in the same API gateway, for the route called /config which is a standard Lambda-backed API endpoint, where I can return all sorts of things like branding information or API keys to the frontend, whenever the frontend calls GET /config.
Also, this avoids issues like CORS, because both origins are the same (the API Gateway domain).
With CloudFront distribution instead of an API Gateway, it's pretty much the same, except you use the CloudFront distribution's "origin" configuration instead of paths and methods.

Sending message from Cognito triggers

I want to restrict user sign-ins from Cognito hosted UI. I can see there are triggers in which we can attach lambda, but whenever I change event object inside of lambda, instead of getting my custom message User exceeded limits, I get unrecognizable lambda output error.
Can anyone help me in this or is there any other way to achieve this functionality?
Now,I'm getting this
with this code :
exports.handler = (event, context, callback) => {
if (true) {
var error = new Error("Cannot signin because your signin count is 5");
// Return error to Amazon Cognito
callback(error, event);
}
// Return to Amazon Cognito
callback(null, event);
};
But,I don't want prefix PreAuthentication failed with error,I just want to display my message.
Any help is appreciated.
Currently, there is no way to stop Cognito from adding the prefix because the form is a hosted web UI.
If this is a hard requirement, the workaround is to create your own login form and use the aws-cognito-sdk
Once you make the call to cognitoUser.authenticateUser in the code below the Pre authentication trigger will fire the Lambda function and you will need to handle the error and parse it to remove the unwanted prefix.
Hope this Helps
aws Examples: Using the JavaScript SDK
var authenticationData = {
Username : 'username',
Password : 'password',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var accessToken = result.getAccessToken().getJwtToken();
/* Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer*/
var idToken = result.idToken.jwtToken;
},
//Your message from the Lambda will return here, you will need to parse the err to remove the unwanted prefix*
onFailure: function(err) {
alert(err);
},
});

Error: Client is unauthorized to retrieve access tokens (ServiceAccountCredentials) nodejs

I have been trying to create an app to list all gmail labels of given user by using service account.
Service account have domain-wide delegation and it's raise error when I ran this script "Client is unauthorized to retrieve access tokens".
var {google} = require('googleapis');
const SCOPES = [
'https://www.googleapis.com/auth/gmail.readonly'
];
var emailToLoginWith = 'useremail#anydomain.com';
var key = require('json_key_file_name.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
emailToLoginWith
);
jwtClient.authorize( function (err, tokens) {
if (err) {
console.log(err);
return;
}
console.log('tokens : ', tokens);
listLabels(jwtClient);
});
function listLabels(auth) {
var gmail = google.gmail({version: 'v1', auth: auth });
gmail.users.labels.list({
userId: 'user_id',
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
console.log('labels response', response);
var labels = response.labels;
if (labels.length == 0) {
console.log('No labels found.');
} else {
console.log('Labels:');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
console.log('- %s', label.name);
}
}
});
}
Client is unauthorized to retrieve access tokens.
This error normally happens when you have created the incorrect type of credentials on Google Developer console. You say that you are using a service account make sure that you have download the correct json file from. After that credentials and the code used to use them are diffrent. You cant use the credentials for a service account with code from browser you will get this error.
I am not a node developer but i found this
http://isd-soft.com/tech_blog/accessing-google-apis-using-service-account-node-js/
You may want to try to get the service account working with drive first. Then when that works move to gmail domain delegation with gmail and service accounts can be tricky sometimes its better to have an example you know works to build off of.

Resources