fix(firestore-send-email): keep SMTP_PASSWORD available so SendGrid works with AUTH_TYPE=OAuth2 - #3089
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the firestore-send-email kit to keep the SMTP_PASSWORD secret bound under OAuth2 authentication, ensuring that the SendGrid transport layer receives its API key. It also adds a new test suite for the transport layer and updates existing configuration tests. The reviewer noted that unconditionally binding SMTP_PASSWORD under OAuth2 would cause a usability regression for non-SendGrid OAuth2 users (e.g., Gmail OAuth2) by forcing them to provide a dummy password. To address this, the reviewer suggested conditionally binding and resolving SMTP_PASSWORD only when the SMTP connection URI points to SendGrid, moving the test cleanup hook to the top level, and adding corresponding test cases for both scenarios.
…orks with AUTH_TYPE=OAuth2 configFromEnv forced smtpPassword to undefined whenever AUTH_TYPE=OAuth2, but transportLayer picks the SendGrid transport purely on the SMTP URI host and passes smtpPassword as the SendGrid API key. With OAuth2 the transport got apiKey: undefined, sgMail.setApiKey was never called, and every send failed. The legacy extension reads SMTP_PASSWORD unconditionally (firestore-send-email/functions/src/config.ts, index.ts:86-90 on master), so SendGrid + OAuth2 worked there. The config now passes the SMTP password secret through for every auth type, and secretParamsForAuthType keeps SMTP_PASSWORD bound under OAuth2 so a future gated deploy binding cannot reintroduce the bug. Fixes #3009
5acfcdc to
85f5cc7
Compare
IzaakGough
left a comment
There was a problem hiding this comment.
The fix looks right. transportLayer checks isSendGrid before setSmtpCredentials, so the SendGrid branch is reachable under AUTH_TYPE=OAuth2 and now gets a real API key, and un-gating smtpPassword is safe for the OAuth2 non-SendGrid path because setSmtpCredentials returns setupOAuth2 before touching it. logs.ts still redacts it, and nothing else reads it. Verified locally at this head: 123 tests pass, typecheck and prettier clean, and restoring the old OAuth2 gate makes the new config test fail, so it does pin the behaviour.
One thing outside the diff that I think is worth including here: kits/firestore-send-email/README.md around line 156 still tells OAuth2 installs to create SMTP_PASSWORD with a placeholder value. Someone on SendGrid who follows that ends up at sgMail.setApiKey("<placeholder>") and every send fails with a 401, so the setup path the CHANGELOG entry describes as fixed still breaks. A short carve-out saying the real API key goes in SMTP_PASSWORD when the connection URI is smtp.sendgrid.net, whatever AUTH_TYPE is set to, would cover it.
The two inline notes are both about test durability rather than current behaviour, so neither needs to block.
@sendgrid/mail exports a MailService instance, so setApiKey and send are prototype methods. Under esModuleInterop the namespace import compiles to __importStar, which copies own properties only, so both were dropped and every SendGrid send threw, at init once an API key was present and at send time otherwise. The legacy extension has the same import line but no esModuleInterop, so it was unaffected. The mocks now expose the module as both the default and the named exports, matching its real shape. The new test runs against lib/ because vitest's transform does not reproduce the tsc emit that causes this.
|
Closed out the testing on a live SendGrid key, running the kit in the functions emulator with The config change does what it says. What is not proven: the send stopped at All of this was the emulator rather than a real deploy, so deploy-time secret binding onto the service is still untested. |
…SMTP_PASSWORD The OAuth2 setup step told every install to create SMTP_PASSWORD with a placeholder, which leaves SendGrid users failing every send with a 401.
…tLayer tests toHaveBeenCalledWith matches any recorded call, so without the reset the setApiKey assertion could be satisfied by an earlier test's call.
|
Took this to a live deploy on a test project to cover the one thing the emulator cannot reach, deploy-time secret binding. The deployed service binds all four secrets as env vars, One thing found along the way, separate from this PR's subject. Secret params resolve to two different resources depending on which layer of the deploy is asking.
So following the CLI's own suggestion creates a secret the deploy then ignores, and What does work today is For anyone coming from an extension none of this should be manual. This looks like a firebase-tools issue rather than anything to change in this PR, and the kit README wording is downstream of whatever it settles on. |
…m mock AUTH_TYPE, USER and HOST are all param names that exist as ambient shell vars, and AUTH_TYPE is a real key in the kit's own .env, so sourcing it failed the suite. The mock now reads a map the tests control.
…for function placement (#3102) Fixes #3069. The kit passed the raw DATABASE_REGION value as the function's region, and the param's select offers the Firestore multi-regions eur3/nam5/nam7, which are not Cloud Run regions, so multi-region deploys hard-failed. The issue proposed dropping the region option (the #3066 pattern). This PR deliberately deviates: by agreement with the firebase-tools team, DATABASE_REGION stays and is mapped instead, because the CLI's own inference is unreliable for param-declared databases (firebase/firebase-tools#11020) and the extension-to-kit migration exports DATABASE_REGION into the user's .env. Multi-regions map to a region inside them (nam5/nam7 to us-central1, eur3 to europe-west1, mirroring the CLI's FIRESTORE_DUAL_REGION_TO_REGION_MAPPING); regional locations pass through; unset or empty means no region option and the CLI fallback. #3101 is the sibling fix for firestore-bigquery-export. Tests pin all mapping cases plus the unset case; breaking the mapping fails them. Caveats: no live multi-region deploy was run for this kit, and the module-load process.env read needs firebase-tools >= 15.28.0 during discovery (older CLIs degrade to the no-region fallback). The approved #3089 also touches src/config.ts; this change edits only envDeployOptions at the end of the file, so overlap is limited to a trivial CHANGELOG conflict.
SendGrid sends fail in the kit when AUTH_TYPE=OAuth2. configFromEnv gates secrets on the auth type and forces smtpPassword to undefined under OAuth2, but transportLayer selects the SendGrid transport purely on the SMTP URI host (smtp.sendgrid.net) and passes smtpPassword as the SendGrid API key. The transport therefore got apiKey: undefined, sgMail.setApiKey was never called, and every send failed. The legacy extension reads SMTP_PASSWORD unconditionally (firestore-send-email/functions/src/config.ts and index.ts:86-90 on master), so SendGrid + OAuth2 worked there.
The fix passes the SMTP password secret through for every auth type, and secretParamsForAuthType now keeps SMTP_PASSWORD bound under OAuth2 so a future gated deploy binding cannot reintroduce the bug. New tests pin configFromEnv keeping the secret under OAuth2 and transportLayer handing the key to setApiKey; the changelog gets an entry. Rebased onto kits after #3057 merged, folding the new tests into the ported suites. Suite: 123 tests pass, tsc --noEmit clean. An adversarial review mutation-tested both halves of the fix (reverting config.ts fails the config tests, reverting the transport apiKey fails the transportLayer test) and found no regression in any consumer of smtpPassword; the obfuscated-config log path still redacts it. Caveat: no live SendGrid send was verified; the behavior is pinned at unit level against a mocked @sendgrid/mail.
Fixes #3009