Skip to content

Commit

Permalink
refactor(provider): adds signing to google maps provider requests geo…
Browse files Browse the repository at this point in the history
…code and reverse

ref #FSMWO-1806
  • Loading branch information
dobrea-v committed Jul 2, 2024
1 parent 3a9cf75 commit d0ede2b
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 8 deletions.
11 changes: 8 additions & 3 deletions src/provider/google-maps/command/google-maps-geocode.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import { GeocodeCommand } from '../../../command';
import type { GeocodeQuery } from '../../../model';
import type { GoogleMapsGeocodeQueryInterface } from '../interface';
import { GoogleMapsLocationCommandMixin } from './mixin';
import { urlSign } from '../../../util/url-signing';

/**
* @link {https://developers.google.com/maps/documentation/geocoding/intro#GeocodingRequests}
*/
export class GoogleMapsGeocodeCommand extends GoogleMapsLocationCommandMixin(GeocodeCommand)<GoogleMapsGeocodeQueryInterface> {
constructor(httpClient: AxiosInstance, private readonly apiKey: string) {
super(httpClient, apiKey);
constructor(httpClient: AxiosInstance, private readonly apiKey: string, private readonly secret?: string) {
super(httpClient, apiKey, secret);
}

static getUrl(): string {
return 'https://maps.googleapis.com/maps/api/geocode/json';
return 'https://maps.googleapis.com/maps/api/geocode/json'
}

protected async buildQuery(query: GeocodeQuery): Promise<GoogleMapsGeocodeQueryInterface> {
Expand Down Expand Up @@ -46,6 +47,10 @@ export class GoogleMapsGeocodeCommand extends GoogleMapsLocationCommandMixin(Geo
providerQuery.region = `.${query.countryCode.toLowerCase()}`;
}

if (this.secret) {
providerQuery.signature = urlSign('/maps/api/geocode/json', providerQuery, this.secret);
}

return providerQuery;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { ReverseCommand } from '../../../command';
import type { ReverseQuery } from '../../../model';
import type { GoogleMapsReverseQueryInterface } from '../interface';
import { GoogleMapsLocationCommandMixin } from './mixin';
import { urlSign } from '../../../util/url-signing';

/**
* TODO implement result_type and location_type
* @link {https://developers.google.com/maps/documentation/geocoding/intro#ReverseGeocoding}
*/
export class GoogleMapsReverseCommand extends GoogleMapsLocationCommandMixin(ReverseCommand)<GoogleMapsReverseQueryInterface> {
constructor(httpClient: AxiosInstance, private readonly apiKey: string) {
super(httpClient, apiKey);
constructor(httpClient: AxiosInstance, private readonly apiKey: string, private readonly secret?: string) {
super(httpClient, apiKey, secret);
}

static getUrl(): string {
Expand All @@ -30,6 +31,10 @@ export class GoogleMapsReverseCommand extends GoogleMapsLocationCommandMixin(Rev
providerQuery.region = `.${query.countryCode.toLowerCase()}`;
}

if (this.secret) {
providerQuery.signature = urlSign('/maps/api/geocode/json', providerQuery, this.secret);
}

return providerQuery;
}
}
6 changes: 3 additions & 3 deletions src/provider/google-maps/google-maps.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {
} from './command';

export class GoogleMapsProvider extends AbstractHttpProvider {
constructor(httpClient: AxiosInstance, apiKey: string) {
constructor(httpClient: AxiosInstance, apiKey: string, secret?: string) {
super({
geocode: new GoogleMapsGeocodeCommand(httpClient, apiKey),
reverse: new GoogleMapsReverseCommand(httpClient, apiKey),
geocode: new GoogleMapsGeocodeCommand(httpClient, apiKey, secret),
reverse: new GoogleMapsReverseCommand(httpClient, apiKey, secret),
suggest: new GoogleMapsSuggestCommand(httpClient, apiKey),
placeDetails: new GoogleMapsPlaceDetailsCommand(httpClient, apiKey),
distance: new GoogleMapsDistanceCommand(httpClient, apiKey),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ export interface GoogleMapsQueryInterface {
*/
region?: string;
language: string;
/**
* Signature for signed request to Google Maps API to bypass the 25k requests/day limit
* https://developers.google.com/maps/documentation/maps-static/digital-signature#server-side-signing
*/
signature?: string;
}
1 change: 1 addition & 0 deletions src/util/url-signing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './url-signing';
62 changes: 62 additions & 0 deletions src/util/url-signing/url-signing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Example from https://googlemaps.github.io/url-signing/urlSigner.js
*/
import crypto from 'crypto';

/**
* Convert from 'web safe' base64 to true base64.
*
* @param {string} safeEncodedString The code you want to translate
* from a web safe form.
* @return {string}
*/
function removeWebSafe(safeEncodedString: string): string {
return safeEncodedString.replace(/-/g, '+').replace(/_/g, '/');
}

/**
* Convert from true base64 to 'web safe' base64
*
* @param {string} encodedString The code you want to translate to a
* web safe form.
* @return {string}
*/
function makeWebSafe(encodedString: string): string {
return encodedString.replace(/\+/g, '-').replace(/\//g, '_');
}

/**
* Takes a base64 code and decodes it.
*
* @param {string} code The encoded data.
* @return {Buffer}
*/
function decodeBase64Hash(code: string): Buffer {
// "new Buffer(...)" is deprecated. Use Buffer.from if it exists.
return Buffer.from ? Buffer.from(code, 'base64') : new Buffer(code, 'base64');
}

/**
* Takes a key and signs the data with it.
*
* @param {string} key Your unique secret key.
* @param {string} data The url to sign.
* @return {string}
*/
function encodeBase64Hash(key: string, data: string): string {
return crypto.createHmac('sha1', key).update(data).digest('base64');
}

/**
* Sign a URL using a secret key.
*
* @param {string} path The url you want to sign.
* @param {string} secret Your unique secret key.
* @param {Object} query Query object
* @return {string}
*/
export function urlSign(path: string, query: any, secret: string, ): string {
const queryString = new URLSearchParams(JSON.stringify(query)).toString();
const safeSecret = decodeBase64Hash(removeWebSafe(secret)).toString('base64');
return makeWebSafe(encodeBase64Hash(safeSecret, path + queryString));
}

0 comments on commit d0ede2b

Please sign in to comment.