Skip to content

feat(client-search): removed node apis from worker build #1573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/algoliasearch/__tests__/algoliasearch.fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expect, test, vi } from 'vitest';
import { LogLevelEnum } from '../../client-common/src/types';
import { createConsoleLogger } from '../../logger-console/src/logger';
import { algoliasearch, apiClientVersion } from '../builds/fetch';
import { algoliasearch as node_algoliasearch } from '../builds/node';

test('sets the ua', () => {
const client = algoliasearch('APP_ID', 'API_KEY');
Expand All @@ -16,12 +17,24 @@ test('forwards node search helpers', () => {
const client = algoliasearch('APP_ID', 'API_KEY');
expect(client.generateSecuredApiKey).not.toBeUndefined();
expect(client.getSecuredApiKeyRemainingValidity).not.toBeUndefined();
expect(() => {
const resp = client.generateSecuredApiKey({ parentApiKey: 'foo', restrictions: { validUntil: 200 } });
expect(async () => {
const resp = await client.generateSecuredApiKey({ parentApiKey: 'foo', restrictions: { validUntil: 200 } });
client.getSecuredApiKeyRemainingValidity({ securedApiKey: resp });
}).not.toThrow();
});

test('web crypto implementation gives the same result as node crypto', async () => {
const client = algoliasearch('APP_ID', 'API_KEY');
const nodeClient = node_algoliasearch('APP_ID', 'API_KEY');
const resp = await client.generateSecuredApiKey({ parentApiKey: 'foo-bar', restrictions: { validUntil: 200 } });
const nodeResp = await nodeClient.generateSecuredApiKey({
parentApiKey: 'foo-bar',
restrictions: { validUntil: 200 },
});

expect(resp).toEqual(nodeResp);
});

test('with logger', () => {
vi.spyOn(console, 'debug');
vi.spyOn(console, 'info');
Expand Down
4 changes: 2 additions & 2 deletions packages/algoliasearch/__tests__/algoliasearch.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ test('forwards node search helpers', () => {
const client = algoliasearch('APP_ID', 'API_KEY');
expect(client.generateSecuredApiKey).not.toBeUndefined();
expect(client.getSecuredApiKeyRemainingValidity).not.toBeUndefined();
expect(() => {
const resp = client.generateSecuredApiKey({ parentApiKey: 'foo', restrictions: { validUntil: 200 } });
expect(async () => {
const resp = await client.generateSecuredApiKey({ parentApiKey: 'foo', restrictions: { validUntil: 200 } });
client.getSecuredApiKeyRemainingValidity({ securedApiKey: resp });
}).not.toThrow();
});
Expand Down
32 changes: 26 additions & 6 deletions packages/client-search/builds/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,26 @@ import type {
SearchClientNodeHelpers,
} from '../model';

import { createHmac } from 'node:crypto';
async function getCryptoKey(secret: string): Promise<CryptoKey> {
const secretBuf = new TextEncoder().encode(secret);
return await crypto.subtle.importKey('raw', secretBuf, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
}

async function generateHmacHex(cryptoKey: CryptoKey, queryParameters: string): Promise<string> {
const encoder = new TextEncoder();
const queryParametersUint8Array = encoder.encode(queryParameters);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, queryParametersUint8Array);
return Array.from(new Uint8Array(signature))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}

async function generateBase64Hmac(parentApiKey: string, queryParameters: string): Promise<string> {
const crypotKey = await getCryptoKey(parentApiKey);
const hmacHex = await generateHmacHex(crypotKey, queryParameters);
const combined = hmacHex + queryParameters;
return btoa(combined);
}

export function searchClient(appId: string, apiKey: string, options?: ClientOptions): SearchClient {
if (!appId || typeof appId !== 'string') {
Expand Down Expand Up @@ -55,7 +74,10 @@ export function searchClient(appId: string, apiKey: string, options?: ClientOpti
* @param generateSecuredApiKey.parentApiKey - The base API key from which to generate the new secured one.
* @param generateSecuredApiKey.restrictions - A set of properties defining the restrictions of the secured API key.
*/
generateSecuredApiKey: ({ parentApiKey, restrictions = {} }: GenerateSecuredApiKeyOptions): string => {
generateSecuredApiKey: async ({
parentApiKey,
restrictions = {},
}: GenerateSecuredApiKeyOptions): Promise<string> => {
let mergedRestrictions = restrictions;
if (restrictions.searchParams) {
// merge searchParams with the root restrictions
Expand All @@ -78,9 +100,7 @@ export function searchClient(appId: string, apiKey: string, options?: ClientOpti
);

const queryParameters = serializeQueryParameters(mergedRestrictions);
return Buffer.from(
createHmac('sha256', parentApiKey).update(queryParameters).digest('hex') + queryParameters,
).toString('base64');
return generateBase64Hmac(parentApiKey, queryParameters);
},

/**
Expand All @@ -91,7 +111,7 @@ export function searchClient(appId: string, apiKey: string, options?: ClientOpti
* @param getSecuredApiKeyRemainingValidity.securedApiKey - The secured API key generated with the `generateSecuredApiKey` method.
*/
getSecuredApiKeyRemainingValidity: ({ securedApiKey }: GetSecuredApiKeyRemainingValidityOptions): number => {
const decodedString = Buffer.from(securedApiKey, 'base64').toString('ascii');
const decodedString = atob(securedApiKey);
const regex = /validUntil=(\d+)/;
const match = decodedString.match(regex);

Expand Down
5 changes: 4 additions & 1 deletion packages/client-search/builds/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ export function searchClient(appId: string, apiKey: string, options?: ClientOpti
* @param generateSecuredApiKey.parentApiKey - The base API key from which to generate the new secured one.
* @param generateSecuredApiKey.restrictions - A set of properties defining the restrictions of the secured API key.
*/
generateSecuredApiKey: ({ parentApiKey, restrictions = {} }: GenerateSecuredApiKeyOptions): string => {
generateSecuredApiKey: async ({
parentApiKey,
restrictions = {},
}: GenerateSecuredApiKeyOptions): Promise<string> => {
let mergedRestrictions = restrictions;
if (restrictions.searchParams) {
// merge searchParams with the root restrictions
Expand Down
2 changes: 1 addition & 1 deletion packages/client-search/model/clientMethodProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ export type GetSecuredApiKeyRemainingValidityOptions = {
};

export type SearchClientNodeHelpers = {
generateSecuredApiKey: (opts: GenerateSecuredApiKeyOptions) => string;
generateSecuredApiKey: (opts: GenerateSecuredApiKeyOptions) => Promise<string>;
getSecuredApiKeyRemainingValidity: (opts: GetSecuredApiKeyRemainingValidityOptions) => number;
};

Expand Down