Skip to content

Commit d201aab

Browse files
authored
feat: add reCAPTCHA verdict to auth blocking functions (#166)
1 parent 80c0968 commit d201aab

File tree

3 files changed

+40
-8
lines changed

3 files changed

+40
-8
lines changed

src/firebase_functions/identity_fn.py

+11
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ class AdditionalUserInfo:
203203
is_new_user: bool
204204
"""A boolean indicating if the user is new or not."""
205205

206+
recaptcha_score: float | None
207+
"""The user's reCAPTCHA score, if available."""
208+
206209

207210
@_dataclasses.dataclass(frozen=True)
208211
class Credential:
@@ -282,6 +285,12 @@ class AuthBlockingEvent:
282285
The time the event was triggered."""
283286

284287

288+
RecaptchaActionOptions = _typing.Literal["ALLOW", "BLOCK"]
289+
"""
290+
The reCAPTCHA action options.
291+
"""
292+
293+
285294
class BeforeCreateResponse(_typing.TypedDict, total=False):
286295
"""
287296
The handler response type for 'before_user_created' blocking events.
@@ -302,6 +311,8 @@ class BeforeCreateResponse(_typing.TypedDict, total=False):
302311
custom_claims: dict[str, _typing.Any] | None
303312
"""The user's custom claims object if available."""
304313

314+
recaptcha_action_override: RecaptchaActionOptions | None
315+
305316

306317
class BeforeSignInResponse(BeforeCreateResponse, total=False):
307318
"""

src/firebase_functions/logger.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
class LogSeverity(str, _enum.Enum):
1313
"""
1414
`LogSeverity` indicates the detailed severity of the log entry. See
15-
`LogSeverity <https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity>`_.
15+
`LogSeverity <https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity>`.
1616
"""
1717

1818
DEBUG = "DEBUG"

src/firebase_functions/private/_identity_fn.py

+28-7
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def _additional_user_info_from_token_data(token_data: dict[str, _typing.Any]):
165165
profile=profile,
166166
username=username,
167167
is_new_user=is_new_user,
168+
recaptcha_score=token_data.get("recaptcha_score"),
168169
)
169170

170171

@@ -302,9 +303,35 @@ def _validate_auth_response(
302303
auth_response_dict["customClaims"] = auth_response["custom_claims"]
303304
if "session_claims" in auth_response_keys:
304305
auth_response_dict["sessionClaims"] = auth_response["session_claims"]
306+
if "recaptcha_action_override" in auth_response_keys:
307+
auth_response_dict["recaptchaActionOverride"] = auth_response[
308+
"recaptcha_action_override"]
305309
return auth_response_dict
306310

307311

312+
def _generate_response_payload(
313+
auth_response_dict: dict[str, _typing.Any] | None
314+
) -> dict[str, _typing.Any]:
315+
if not auth_response_dict:
316+
return {}
317+
318+
formatted_auth_response = auth_response_dict.copy()
319+
recaptcha_action_override = formatted_auth_response.pop(
320+
"recaptchaActionOverride", None)
321+
result = {}
322+
update_mask = ",".join(formatted_auth_response.keys())
323+
324+
if len(update_mask) != 0:
325+
result["userRecord"] = {
326+
**formatted_auth_response, "updateMask": update_mask
327+
}
328+
329+
if recaptcha_action_override is not None:
330+
result["recaptchaActionOverride"] = recaptcha_action_override
331+
332+
return result
333+
334+
308335
def before_operation_handler(
309336
func: _typing.Callable,
310337
event_type: str,
@@ -329,13 +356,7 @@ def before_operation_handler(
329356
if not auth_response:
330357
return _jsonify({})
331358
auth_response_dict = _validate_auth_response(event_type, auth_response)
332-
update_mask = ",".join(auth_response_dict.keys())
333-
result = {
334-
"userRecord": {
335-
**auth_response_dict,
336-
"updateMask": update_mask,
337-
}
338-
}
359+
result = _generate_response_payload(auth_response_dict)
339360
return _jsonify(result)
340361
# Disable broad exceptions lint since we want to handle all exceptions.
341362
# pylint: disable=broad-except

0 commit comments

Comments
 (0)