Skip to content

Commit 6274cc0

Browse files
feat: sign record's public url (#358)
* feat: sign public url * refactor: compose url with URL() * refactor: clean up * chore: added test for publicUrl * fix: use apiPublicBaseUrl to generate public url * fix: improve test * refactor: clean up * refactor: improved test * refactor: clean up * fix: CI --------- Co-authored-by: Jan Buchar <Teyras@gmail.com>
1 parent f86f826 commit 6274cc0

File tree

7 files changed

+464
-164
lines changed

7 files changed

+464
-164
lines changed

Diff for: package-lock.json

+404-155
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"@typescript-eslint/eslint-plugin": "^7.0.0",
6868
"@typescript-eslint/parser": "^7.0.0",
6969
"commitlint": "^19.3.0",
70-
"crawlee": "^3.11.5",
70+
"crawlee": "^3.13.0",
7171
"eslint": "^8.57.0",
7272
"fs-extra": "^11.2.0",
7373
"gen-esm-wrapper": "^1.1.3",

Diff for: packages/apify/package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@
5858
"@apify/input_secrets": "^1.1.40",
5959
"@apify/log": "^2.4.3",
6060
"@apify/timeout": "^0.3.0",
61-
"@apify/utilities": "^2.9.3",
62-
"@crawlee/core": "^3.9.0",
63-
"@crawlee/types": "^3.9.0",
64-
"@crawlee/utils": "^3.9.0",
65-
"apify-client": "^2.12.0",
61+
"@apify/utilities": "^2.13.0",
62+
"@crawlee/core": "^3.13.0",
63+
"@crawlee/types": "^3.13.0",
64+
"@crawlee/utils": "^3.13.0",
65+
"apify-client": "^2.12.1",
6666
"fs-extra": "^11.2.0",
6767
"ow": "^0.28.2",
6868
"semver": "^7.5.4",
6969
"tslib": "^2.6.2",
7070
"ws": "^8.18.0"
7171
}
72-
}
72+
}

Diff for: packages/apify/src/configuration.ts

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export interface ConfigurationOptions extends CoreConfigurationOptions {
1616
actorRunId?: string;
1717
actorTaskId?: string;
1818
apiBaseUrl?: string;
19+
// apiBaseUrl is the internal API URL, accessible only within the platform(private network),
20+
// while apiPublicBaseUrl is the public API URL, available externally(through internet).
21+
apiPublicBaseUrl?: string;
1922
containerPort?: number;
2023
containerUrl?: string;
2124
proxyHostname?: string;
@@ -139,6 +142,7 @@ export class Configuration extends CoreConfiguration {
139142
APIFY_ACTOR_EVENTS_WS_URL: 'actorEventsWsUrl',
140143
APIFY_ACTOR_ID: 'actorId',
141144
APIFY_API_BASE_URL: 'apiBaseUrl',
145+
APIFY_API_PUBLIC_BASE_URL: 'apiPublicBaseUrl',
142146
APIFY_IS_AT_HOME: 'isAtHome',
143147
APIFY_ACTOR_RUN_ID: 'actorRunId',
144148
APIFY_ACTOR_TASK_ID: 'actorTaskId',
@@ -183,6 +187,7 @@ export class Configuration extends CoreConfiguration {
183187
defaultRequestQueueId: LOCAL_ACTOR_ENV_VARS[ACTOR_ENV_VARS.DEFAULT_REQUEST_QUEUE_ID],
184188
inputKey: 'INPUT',
185189
apiBaseUrl: 'https://api.apify.com',
190+
apiPublicBaseUrl: 'https://api.apify.com',
186191
proxyStatusUrl: 'http://proxy.apify.com',
187192
proxyHostname: LOCAL_APIFY_ENV_VARS[APIFY_ENV_VARS.PROXY_HOSTNAME],
188193
proxyPort: +LOCAL_APIFY_ENV_VARS[APIFY_ENV_VARS.PROXY_PORT],

Diff for: packages/apify/src/key_value_store.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createHmacSignature } from '@apify/utilities';
12
import type { StorageManagerOptions } from '@crawlee/core';
23
import { KeyValueStore as CoreKeyValueStore } from '@crawlee/core';
34

@@ -15,11 +16,18 @@ export class KeyValueStore extends CoreKeyValueStore {
1516
* access the value in the remote key-value store.
1617
*/
1718
override getPublicUrl(key: string): string {
18-
if (!(this.config as Configuration).get('isAtHome') && getPublicUrl) {
19+
const config = this.config as Configuration;
20+
if (!config.get('isAtHome') && getPublicUrl) {
1921
return getPublicUrl.call(this, key);
2022
}
2123

22-
return `https://api.apify.com/v2/key-value-stores/${this.id}/records/${key}`;
24+
const publicUrl = new URL(`${config.get('apiPublicBaseUrl')}/v2/key-value-stores/${this.id}/records/${key}`);
25+
26+
if (this.storageObject?.urlSigningSecretKey) {
27+
publicUrl.searchParams.append('signature', createHmacSignature(this.storageObject.urlSigningSecretKey as string, key));
28+
}
29+
30+
return publicUrl.toString();
2331
}
2432

2533
/**

Diff for: test/e2e/sdk/publicUrl/src/main.mjs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Actor, log } from 'apify';
2+
3+
await Actor.init();
4+
5+
const { data } = await Actor.getInput();
6+
7+
await Actor.setValue('public-record-key', JSON.stringify(data), { contentType: `application/json` });
8+
9+
const defaultKeyValueStore = await Actor.openKeyValueStore();
10+
const publicUrl = defaultKeyValueStore.getPublicUrl('public-record-key');
11+
12+
// Here we store the url itself
13+
await Actor.setValue('urlToPublicData', publicUrl);
14+
15+
log.info('Generated public url', { publicUrl });
16+
17+
await Actor.pushData({ publicUrl });
18+
19+
await Actor.exit();

Diff for: test/e2e/sdk/publicUrl/test.mjs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import assert from 'node:assert/strict';
2+
3+
import { ApifyClient } from 'apify';
4+
5+
const PUBLIC_DATA = { exposedData: 'test' };
6+
7+
const client = new ApifyClient({
8+
token: process.env.APIFY_TOKEN,
9+
});
10+
11+
const actor = client.actor(process.argv[2]);
12+
13+
const run = await actor.call({ data: PUBLIC_DATA }, { waitSecs: 15 });
14+
assert.equal(run.exitCode, 0);
15+
16+
const publicUrl = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('urlToPublicData');
17+
const data = await fetch(publicUrl.value).then((res) => res.json());
18+
19+
assert.deepEqual(data, PUBLIC_DATA);

0 commit comments

Comments
 (0)