Skip to content

Commit 052851e

Browse files
authored
feat(hubspot): add api_key_name support for Vault secret lookup by name (supabase#545)
* feat(hubspot): add api_key_name support for Vault secret lookup by name ## Summary Add `api_key_name` support to HubSpot FDW, enabling Vault secret lookup by name for environment-agnostic configuration. ### Infrastructure Changes (enables feature for all WASM FDWs) - Add `get-vault-secret-by-name` function to WIT interface (v1 and v2) - Implement host-side binding to call native `get_vault_secret_by_name` ### HubSpot FDW Changes - Add `api_key_name` option to HubSpot FDW authentication - Follow Stripe FDW implementation pattern - Update documentation with examples Fixes supabase#437 * docs(hubspot): clarify that api_key accepts Private App Access Token Add note explaining that HubSpot deprecated their legacy API Keys in November 2022 and that the api_key option accepts Private App Access Tokens, which is HubSpot's recommended authentication method. This helps address confusion reported in supabase#461. * fix: correct Rust type errors in api_key retrieval - Remove `.cloned()` call on Option<String> (already owned) - Pass &key_id and &key_name to get_vault_secret functions (expects &str)
1 parent 678b6a1 commit 052851e

5 files changed

Lines changed: 45 additions & 9 deletions

File tree

docs/catalog/hubspot.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ create foreign data wrapper wasm_wrapper
4242
validator wasm_fdw_validator;
4343
```
4444

45+
!!! note "About Authentication"
46+
HubSpot deprecated their legacy API Keys in November 2022. The `api_key` option in this wrapper accepts a **Private App Access Token**, which is HubSpot's recommended authentication method. See [HubSpot Private Apps](https://developers.hubspot.com/docs/guides/apps/private-apps/overview) for setup instructions.
47+
4548
### Store your credentials (optional)
4649

4750
By default, Postgres stores FDW credentials inside `pg_catalog.pg_foreign_server` in plain text. Anyone with access to this table will be able to view these credentials. Wrappers is designed to work with [Vault](https://supabase.com/docs/guides/database/vault), which provides an additional level of security for storing credentials. We recommend using Vault to store your credentials.
@@ -50,16 +53,18 @@ By default, Postgres stores FDW credentials inside `pg_catalog.pg_foreign_server
5053
-- Save your HubSpot private apps access token in Vault and retrieve the created `key_id`
5154
select vault.create_secret(
5255
'<HubSpot access token>', -- HubSpot private apps access token
53-
'hubspot',
56+
'hubspot', -- secret name for api_key_name lookup
5457
'HubSpot private apps access token for Wrappers'
5558
);
5659
```
5760

61+
The secret name (e.g., `'hubspot'`) can be used with `api_key_name` for environment-agnostic configuration.
62+
5863
### Connecting to HubSpot
5964

6065
We need to provide Postgres with the credentials to access HubSpot and any additional options. We can do this using the `create server` command:
6166

62-
=== "With Vault"
67+
=== "With Vault (by ID)"
6368

6469
```sql
6570
create server hubspot_server
@@ -74,6 +79,23 @@ We need to provide Postgres with the credentials to access HubSpot and any addit
7479
);
7580
```
7681

82+
=== "With Vault (by Name)"
83+
84+
```sql
85+
create server hubspot_server
86+
foreign data wrapper wasm_wrapper
87+
options (
88+
fdw_package_url 'https://github.com/supabase/wrappers/releases/download/wasm_hubspot_fdw_v0.1.0/hubspot_fdw.wasm',
89+
fdw_package_name 'supabase:hubspot-fdw',
90+
fdw_package_version '0.1.0',
91+
fdw_package_checksum '2cbf39e9e28aa732a225db09b2186a2342c44697d4fa047652d358e292ba5521',
92+
api_url 'https://api.hubapi.com/crm/v3', -- optional
93+
api_key_name 'hubspot' -- The secret name from above.
94+
);
95+
```
96+
97+
Using `api_key_name` enables environment-agnostic configuration - the same SQL works across dev, staging, and production as long as each environment has a Vault secret with the same name.
98+
7799
=== "Without Vault"
78100

79101
```sql

wasm-wrappers/fdw/hubspot_fdw/src/lib.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,17 @@ impl Guest for HubspotFdw {
221221
// get foreign server options
222222
let opts = ctx.get_options(&OptionsType::Server);
223223
this.base_url = opts.require_or("api_url", "https://api.hubapi.com/crm/v3");
224-
let api_key = match opts.get("api_key") {
225-
Some(key) => key,
226-
None => {
227-
let key_id = opts.require("api_key_id")?;
228-
utils::get_vault_secret(&key_id).unwrap_or_default()
229-
}
230-
};
224+
let api_key = opts
225+
.get("api_key")
226+
.or_else(|| {
227+
opts.get("api_key_id")
228+
.and_then(|key_id| utils::get_vault_secret(&key_id))
229+
})
230+
.or_else(|| {
231+
opts.get("api_key_name")
232+
.and_then(|key_name| utils::get_vault_secret_by_name(&key_name))
233+
})
234+
.ok_or("missing required option: 'api_key', 'api_key_id', or 'api_key_name'")?;
231235

232236
// HubSpot API authentication
233237
// ref: https://developers.hubspot.com/docs/guides/apps/authentication/intro-to-auth

wasm-wrappers/wit/v1/utils.wit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ interface utils {
88

99
cell-to-string: func(cell: option<cell>) -> string;
1010
get-vault-secret: func(secret-id: string) -> option<string>;
11+
get-vault-secret-by-name: func(secret-name: string) -> option<string>;
1112
}

wasm-wrappers/wit/v2/utils.wit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ interface utils {
88

99
cell-to-string: func(cell: option<cell>) -> string;
1010
get-vault-secret: func(secret-id: string) -> option<string>;
11+
get-vault-secret-by-name: func(secret-name: string) -> option<string>;
1112
}

wrappers/src/fdw/wasm_fdw/host/utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const _: () = {
3636
fn get_vault_secret(&mut self, secret_id: String) -> Option<String> {
3737
get_vault_secret(&secret_id)
3838
}
39+
40+
fn get_vault_secret_by_name(&mut self, secret_name: String) -> Option<String> {
41+
get_vault_secret_by_name(&secret_name)
42+
}
3943
}
4044
};
4145

@@ -71,5 +75,9 @@ const _: () = {
7175
fn get_vault_secret(&mut self, secret_id: String) -> Option<String> {
7276
get_vault_secret(&secret_id)
7377
}
78+
79+
fn get_vault_secret_by_name(&mut self, secret_name: String) -> Option<String> {
80+
get_vault_secret_by_name(&secret_name)
81+
}
7482
}
7583
};

0 commit comments

Comments
 (0)