Skip to content

Conversation

@boikoa-gl
Copy link

Hey there! So you want to contribute to a Firebase SDK?
Before you file this pull request, please read these guidelines:

Discussion

  • Read the contribution guidelines (CONTRIBUTING.md).
  • If this has been discussed in an issue, make sure to link to the issue here.
    If not, go file an issue about this before creating a pull request to discuss.

Testing

  • Make sure all existing tests in the repository pass after your change.
  • If you fixed a bug or added a feature, add a new test to cover your code.

API Changes

  • At this time we cannot accept changes that affect the public API. If you'd like to help
    us make Firebase APIs better, please propose your change in an issue so that we
    can discuss it together.

@boikoa-gl boikoa-gl marked this pull request as draft January 13, 2026 16:49
@gemini-code-assist
Copy link

Summary of Changes

Hello @boikoa-gl, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates the Firebase Phone Number Verification (FPNV) feature into the SDK. It provides a new set of APIs and internal logic for verifying FPNV JWTs, allowing applications to securely authenticate users based on phone number verification. This enhancement streamlines the process of validating tokens issued by Firebase's phone authentication services, ensuring data integrity and user trust.

Highlights

  • New FPNV Service Introduction: Introduced the Firebase Phone Number Verification (FPNV) service, providing a dedicated entry point for verifying FPNV tokens within the SDK.
  • Core FPNV Classes: Added essential classes including FirebasePnv (service entry point), FirebasePnvToken (representation of a verified token), FirebasePnvException (custom exception handling), and FirebasePnvErrorCode (specific error codes for FPNV operations).
  • Robust Token Verification Logic: Implemented FirebasePnvTokenVerifier to handle the complex process of FPNV JWT validation, encompassing header checks (algorithm, key ID, type), signature verification, and claims validation (issuer, audience, expiration, subject) using the nimbusds library and fetching JWKS from a Google endpoint.
  • Comprehensive Unit Testing: Included extensive unit tests for all new FPNV components, ensuring the correctness of the service, token handling, error propagation, and the intricate token verification logic under various scenarios.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the Firebase Phone Number Verification (FPNV) feature, including the main service entry point, token and exception types, and the internal token verification logic. The overall structure is sound and follows existing patterns in the Firebase Admin SDK.

However, I've identified several critical issues, particularly concerning type safety in the FirebasePnvToken class that will lead to ClassCastException at runtime. There are also security-related gaps in the token validation logic, such as incomplete issuer verification. Additionally, there are inconsistencies in exception handling and some issues in the test suite that make it less robust.

My detailed comments below provide specific suggestions to address these points, which should be resolved before merging.

Comment on lines +63 to +65
public long getExpirationTime() {
return (long) claims.get("exp");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This method will throw a ClassCastException at runtime. The claims map contains a java.util.Date object for the 'exp' claim from the JWT library, which cannot be directly cast to a long. You should get the Date object and then convert it to seconds since the Unix epoch by calling getTime() / 1000.

Suggested change
public long getExpirationTime() {
return (long) claims.get("exp");
}
public long getExpirationTime() {
return ((java.util.Date) claims.get("exp")).getTime() / 1000L;
}

Comment on lines +70 to +72
public long getIssuedAt() {
return (long) claims.get("iat");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This method will throw a ClassCastException at runtime. The claims map contains a java.util.Date object for the 'iat' claim from the JWT library, which cannot be directly cast to a long. You should get the Date object and then convert it to seconds since the Unix epoch by calling getTime() / 1000.

Suggested change
public long getIssuedAt() {
return (long) claims.get("iat");
}
public long getIssuedAt() {
return ((java.util.Date) claims.get("iat")).getTime() / 1000L;
}

* Check the error code and message for more
* details.
*/
public class FirebasePnvException extends RuntimeException {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This exception extends RuntimeException, but it is declared in the throws clause of FirebasePnv.verifyToken(). This is misleading for users of the API and inconsistent with standard Java practices. To align with other exceptions in the Firebase Admin SDK (like FirebaseAuthException), this should be a checked exception. Please change it to extend Exception.

Suggested change
public class FirebasePnvException extends RuntimeException {
public class FirebasePnvException extends Exception {

Comment on lines +56 to +58
public List<String> getAudience() {
return (List<String>) claims.get("aud");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The 'aud' (audience) claim in a JWT can be either a single string or a list of strings. This implementation assumes it's always a List<String> and will throw a ClassCastException if the token contains a single string audience. The code should handle both cases gracefully.

Suggested change
public List<String> getAudience() {
return (List<String>) claims.get("aud");
}
public List<String> getAudience() {
Object audience = claims.get("aud");
if (audience instanceof String) {
return com.google.common.collect.ImmutableList.of((String) audience);
}
return (List<String>) audience;
}

Comment on lines +141 to +144
if (Strings.isNullOrEmpty(issuer)) {
throw new FirebasePnvException(FirebasePnvErrorCode.INVALID_ARGUMENT,
"Invalid issuer. Expected: " + issuer);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The issuer validation is incomplete. It only checks that the issuer claim is not empty. For security, you must verify that the issuer claim exactly matches the expected value for your service and project ID (e.g., https://fpnv.googleapis.com/projects/YOUR_PROJECT_ID). The current implementation would accept a token from any issuer.

Suggested change
if (Strings.isNullOrEmpty(issuer)) {
throw new FirebasePnvException(FirebasePnvErrorCode.INVALID_ARGUMENT,
"Invalid issuer. Expected: " + issuer);
}
String expectedIssuer = "https://fpnv.googleapis.com/projects/" + this.projectId;
if (!expectedIssuer.equals(issuer)) {
throw new FirebasePnvException(FirebasePnvErrorCode.INVALID_TOKEN,
String.format("Invalid issuer. Expected: %s but found: %s", expectedIssuer, issuer));
}

Comment on lines +59 to +60
private static final String PROJECT_ID = "test-project-123";
private static final String ISSUER = "https://fpnv.googleapis.com/projects/" + PROJECT_ID;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The PROJECT_ID is hardcoded to "test-project-123", but the FirebaseApp used to initialize the verifier is created with a service account for "mock-project-id". This inconsistency means the verifier instance will have a different project ID than what the tests are asserting against. This makes the issuer validation tests ineffective and they pass only because the implementation is also buggy. The test's project ID should be consistent with the one used by the verifier.

Suggested change
private static final String PROJECT_ID = "test-project-123";
private static final String ISSUER = "https://fpnv.googleapis.com/projects/" + PROJECT_ID;
private static final String PROJECT_ID = "mock-project-id";
private static final String ISSUER = "https://fpnv.googleapis.com/projects/" + PROJECT_ID;

Comment on lines +33 to +38
public FirebasePnvException(
FirebasePnvErrorCode authErrorCode,
String message
) {
super(message);
this.errorCode = authErrorCode;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter name authErrorCode is confusing in the context of FPNV (Firebase Phone Number Verification). A more descriptive name like errorCode or fpnvErrorCode would improve clarity.

Suggested change
public FirebasePnvException(
FirebasePnvErrorCode authErrorCode,
String message
) {
super(message);
this.errorCode = authErrorCode;
public FirebasePnvException(
FirebasePnvErrorCode errorCode,
String message
) {
super(message);
this.errorCode = errorCode;
}

Comment on lines +103 to +106
throw new FirebasePnvException(FirebasePnvErrorCode.SERVICE_ERROR,
"Chek your project: " + projectId + ". "
+ e.getMessage()
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a typo in the exception message. 'Chek' should be 'Check'.

Suggested change
throw new FirebasePnvException(FirebasePnvErrorCode.SERVICE_ERROR,
"Chek your project: " + projectId + ". "
+ e.getMessage()
);
throw new FirebasePnvException(FirebasePnvErrorCode.SERVICE_ERROR,
"Check your project: " + projectId + ". "
+ e.getMessage()
);

Comment on lines +165 to +172
// TODO: i guess this is redundant
// jwtProcessor.process did this already
// Verify Expiration
Date now = new Date();
Date exp = claims.getExpirationTime();
if (exp == null || now.after(exp)) {
throw new FirebasePnvException(FirebasePnvErrorCode.TOKEN_EXPIRED, "Token has expired.");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This manual expiration check is redundant. The DefaultJWTProcessor already validates the token's expiration time and will throw an ExpiredJWTException if it has expired. This exception is already being caught and handled in the verifyToken method. You can safely remove this block of code, as the TODO comment suggests.

Comment on lines +183 to +204
@Test
public void testVerifyToken_Claims_Expired() throws Exception {
Date past = new Date(System.currentTimeMillis() - 10000); // Expired

JWTClaimsSet expiredClaims = new JWTClaimsSet.Builder()
.issuer(ISSUER)
.audience(ISSUER)
.subject("+1555")
.expirationTime(past)
.build();

String tokenString = createToken(header, expiredClaims);

// Mock processor returning the expired claims
when(mockJwtProcessor.process(any(SignedJWT.class), any())).thenReturn(expiredClaims);

FirebasePnvException e = assertThrows(FirebasePnvException.class, () ->
verifier.verifyToken(tokenString)
);

assertEquals(FirebasePnvErrorCode.TOKEN_EXPIRED, e.getFpnvErrorCode());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test for token expiration is brittle because it relies on an implementation detail in FirebasePnvTokenVerifier (the redundant manual expiration check). Instead of mocking the processor to return expired claims, you should mock it to throw an ExpiredJWTException, which is the expected behavior from the underlying library. This would make the test more robust and independent of the verifier's internal implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant