firebase / firebase/firebase-functions
beforeEmailSent is triggered by Admin SDK generatePasswordResetLink despite returnOobLink
- Dominant language
- TypeScript
- Stars
- 1.1k
- Forks
- 232
- Avg merge
- 20h 46m
- Merged PRs (30d)
- 15
Description
### Description
`beforeEmailSent` is invoked for password reset links generated by the Firebase Admin SDK via `generatePasswordResetLink()`, even though no Firebase-managed email is sent.
This makes it impossible to block Firebase-managed password reset emails while still using Admin SDK-generated password reset links for a custom email delivery flow.
### Why this seems unexpected
The Admin SDK documentation recommends `generatePasswordResetLink()` for custom password reset emails:
https://firebase.google.com/docs/auth/admin/email-action-links#generate_password_reset_email_link
In the Node Admin SDK, `generatePasswordResetLink()` calls the Identity Toolkit `accounts:sendOobCode` endpoint with `returnOobLink: true`.
The Identity Platform REST docs describe `returnOobLink` as returning the OOB link instead of sending an email:
https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/sendOobCode
> Whether the confirmation link containing the OOB code should be returned in the response (no email is sent).
Because `beforeEmailSent` is documented and named as a trigger that runs before an email is sent, I would not expect it to block OOB link generation when `returnOobLink: true`.
### Minimal reproduction
1. Deploy a `beforeEmailSent` blocking function:
```js
import {
beforeEmailSent,
HttpsError,
} from "firebase-functions/v2/identity";
export const beforeAuthEmailSent = beforeEmailSent((event) => {
if (event.emailType === "PASSWORD_RESET") {
throw new HttpsError(
"permission-denied",
"Firebase-managed password reset email disabled.",
);
}
});
```
2. From a trusted backend using the Admin SDK, generate a custom password reset link:
```js
import { getAuth } from "firebase-admin/auth";
const link = await getAuth().generatePasswordResetLink("user@example.com", {
url: "https://example.com/reset",
handleCodeInApp: false,
});
```
3. The link generation fails because the `beforeEmailSent` function is invoked and blocks the operation.
### Expected behavior
Either:
1. `beforeEmailSent` should not be invoked when `sendOobCode` is called with `returnOobLink: true`, because no Firebase-managed email is being sent.
Or:
2. If this behavior is intentional, the `AuthBlockingEvent` exposed to `beforeEmailSent` should include enough context to distinguish a Firebase-managed email send from Admin/backend OOB link generation.
Useful context could include one or more of:
- whether `returnOobLink` was true
- whether the request was made with OAuth/service-account credentials
- whether the request came from an API-key client SDK flow vs a trusted backend/Admin SDK flow
- a request source/caller type
### Actual behavior
The function receives only `event.emailType === "PASSWORD_RESET"` for this case, so the handler cannot safely distinguish:
- a public client/API-key password reset email send, which I want to block
- a trusted backend/Admin SDK link generation request, which I want to allow and send through my own email provider
### Impact
For applications that need custom password reset email delivery and stronger anti-abuse controls, this creates a conflict:
- Allowing `PASSWORD_RESET` in `beforeEmailSent` leaves Firebase-managed password reset email sending available through the public Firebase Auth API.
- Blocking `PASSWORD_RESET` also breaks Admin SDK custom password reset link generation.
As a result, the only reliable workaround appears to be implementing a fully custom password reset token flow and using `admin.auth().updateUser(uid, { password })`, instead of using Firebase OOB password reset links.
### Notes
This may require an Identity Platform backend change, not only a change in this SDK. However, this repository is where the `beforeEmailSent` API and `AuthBlockingEvent` developer surface are exposed, so I am reporting it here first.
Contributor guide
Research direction
Start with the beforeEmailSent API and AuthBlockingEvent developer surface, then trace how Admin SDK generatePasswordResetLink uses the Identity Toolkit accounts:sendOobCode endpoint with returnOobLink: true. Compare that path with Firebase-managed password-reset email sending. Done means the behavior is intentionally defined and either link generation bypasses the trigger or the event exposes enough context to distinguish the two requests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- firebase, typescript
- Domain
- api, authentication, backend-api-design
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100