Skip to content

Commit c420bad

Browse files
committed
refactor: rename fpnv module to phone_number_verification for clarity
1 parent c5e2bd5 commit c420bad

File tree

2 files changed

+58
-53
lines changed

2 files changed

+58
-53
lines changed

firebase_admin/fpnv.py renamed to firebase_admin/phone_number_verification.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222

2323
import jwt
2424
from jwt import (
25-
PyJWKClient, InvalidTokenError, DecodeError, InvalidSignatureError,
25+
PyJWKClient, InvalidSignatureError,
2626
PyJWKClientError, InvalidAudienceError, InvalidIssuerError, ExpiredSignatureError
2727
)
2828

2929
from firebase_admin import App, _utils, exceptions
3030

31-
_FPNV_ATTRIBUTE = '_fpnv'
31+
_FPNV_ATTRIBUTE = '_phone_number_verification'
3232
_FPNV_JWKS_URL = 'https://fpnv.googleapis.com/v1beta/jwks'
3333
_FPNV_ISSUER = 'https://fpnv.googleapis.com/projects/'
3434
_ALGORITHM_ES256 = 'ES256'
@@ -37,25 +37,25 @@
3737
def _get_fpnv_service(app):
3838
return _utils.get_app_service(app, _FPNV_ATTRIBUTE, _FpnvService)
3939

40-
def verify_token(token: str, app: Optional[App] = None) -> FpnvToken:
40+
def verify_token(token: str, app: Optional[App] = None) -> PhoneNumberVerificationToken:
4141
"""Verifies a Firebase Phone Number Verification (FPNV) token.
4242
4343
Args:
4444
token: A string containing the FPNV JWT.
4545
app: An App instance (optional).
4646
4747
Returns:
48-
FpnvToken: The verified token claims.
48+
PhoneNumberVerificationToken: The verified token claims.
4949
5050
Raises:
5151
ValueError: If the token is not a string or is empty.
52-
InvalidFpnvTokenError: If the token is invalid or malformed.
53-
ExpiredFpnvTokenError: If the token has expired.
52+
InvalidTokenError: If the token is invalid or malformed.
53+
ExpiredTokenError: If the token has expired.
5454
"""
5555
return _get_fpnv_service(app).verify_token(token)
5656

5757

58-
class FpnvToken(dict):
58+
class PhoneNumberVerificationToken(dict):
5959
"""Represents a verified FPNV token.
6060
6161
This class behaves like a dictionary, allowing access to the decoded claims.
@@ -118,23 +118,23 @@ def __init__(self, app):
118118

119119
self._verifier = _FpnvTokenVerifier(self._project_id)
120120

121-
def verify_token(self, token) -> FpnvToken:
122-
"""Verifies the given FPNV token.
121+
def verify_token(self, token) -> PhoneNumberVerificationToken:
122+
"""Verifies a Firebase Phone Number Verification token.
123123
124124
Verifies the signature, expiration, and claims of the token.
125125
126126
Args:
127-
token: A string containing the FPNV JWT.
127+
token: A string containing the Firebase Phone Number Verification JWT.
128128
129129
Returns:
130-
FpnvToken: The verified token claims.
130+
PhoneNumberVerificationToken: The verified token claims.
131131
132132
Raises:
133133
ValueError: If the token is not a string or is empty.
134-
InvalidFpnvTokenError: If the token is invalid or malformed.
135-
ExpiredFpnvTokenError: If the token has expired.
134+
InvalidTokenError: If the token is invalid or malformed.
135+
ExpiredTokenError: If the token has expired.
136136
"""
137-
return FpnvToken(self._verifier.verify(token))
137+
return PhoneNumberVerificationToken(self._verifier.verify(token))
138138

139139

140140
class _FpnvTokenVerifier:
@@ -153,9 +153,9 @@ def verify(self, token) -> Dict[str, Any]:
153153
self._validate_headers(jwt.get_unverified_header(token))
154154
signing_key = self._jwks_client.get_signing_key_from_jwt(token)
155155
claims = self._decode_and_verify(token, signing_key.key)
156-
except (InvalidTokenError, DecodeError, PyJWKClientError) as exception:
157-
raise InvalidFpnvTokenError(
158-
'Verifying FPNV token failed.',
156+
except (jwt.InvalidTokenError, PyJWKClientError) as exception:
157+
raise InvalidTokenError(
158+
'Verifying phone number verification token failed.',
159159
cause=exception,
160160
http_response=getattr(exception, 'http_response', None)
161161
) from exception
@@ -165,18 +165,18 @@ def verify(self, token) -> Dict[str, Any]:
165165
def _validate_headers(self, headers: Any) -> None:
166166
"""Validates the headers."""
167167
if headers.get('kid') is None:
168-
raise InvalidFpnvTokenError("FPNV has no 'kid' claim.")
168+
raise InvalidTokenError("Token has no 'kid' claim.")
169169

170170
if headers.get('typ') != 'JWT':
171-
raise InvalidFpnvTokenError(
172-
'The provided FPNV token has an incorrect type header. ' \
171+
raise InvalidTokenError(
172+
'The provided token has an incorrect type header. ' \
173173
f"Expected 'JWT' but got {headers.get('typ')!r}."
174174
)
175175

176176
algorithm = headers.get('alg')
177177
if algorithm != _ALGORITHM_ES256:
178-
raise InvalidFpnvTokenError(
179-
'The provided FPNV token has an incorrect alg header. '
178+
raise InvalidTokenError(
179+
'The provided token has an incorrect alg header. '
180180
f'Expected {_ALGORITHM_ES256} but got {algorithm}.'
181181
)
182182

@@ -192,32 +192,32 @@ def _decode_and_verify(self, token, signing_key) -> Dict[str, Any]:
192192
issuer=expected_issuer
193193
)
194194
except InvalidSignatureError as exception:
195-
raise InvalidFpnvTokenError(
196-
'The provided FPNV token has an invalid signature.'
195+
raise InvalidTokenError(
196+
'The provided token has an invalid signature.'
197197
) from exception
198198
except InvalidAudienceError as exception:
199-
raise InvalidFpnvTokenError(
200-
'The provided FPNV token has an incorrect "aud" (audience) claim. '
199+
raise InvalidTokenError(
200+
'The provided token has an incorrect "aud" (audience) claim. '
201201
f'Expected {expected_issuer}.'
202202
) from exception
203203
except InvalidIssuerError as exception:
204-
raise InvalidFpnvTokenError(
205-
'The provided FPNV token has an incorrect "iss" (issuer) claim. '
204+
raise InvalidTokenError(
205+
'The provided token has an incorrect "iss" (issuer) claim. '
206206
f'Expected {expected_issuer}.'
207207
) from exception
208208
except ExpiredSignatureError as exception:
209-
raise ExpiredFpnvTokenError(
210-
'The provided FPNV token has expired.'
209+
raise ExpiredTokenError(
210+
'The provided token has expired.'
211211
) from exception
212-
except InvalidTokenError as exception:
213-
raise InvalidFpnvTokenError(
214-
f'Decoding FPNV token failed. Error: {exception}'
212+
except jwt.InvalidTokenError as exception:
213+
raise InvalidTokenError(
214+
f'Decoding token failed. Error: {exception}'
215215
) from exception
216216

217217
sub_claim = payload.get('sub')
218218
if not isinstance(sub_claim, str) or not sub_claim:
219-
raise InvalidFpnvTokenError(
220-
'The provided FPNV token has an incorrect "sub" (subject) claim. '
219+
raise InvalidTokenError(
220+
'The provided token has an incorrect "sub" (subject) claim. '
221221
'Expected a non-empty string.'
222222
)
223223

@@ -237,14 +237,14 @@ def check_string(cls, label: str, value: Any):
237237
raise ValueError(f'{label} must be a non-empty string.')
238238

239239
# Firebase Phone Number Verification (FPNV) Errors
240-
class InvalidFpnvTokenError(exceptions.InvalidArgumentError):
241-
"""Raised when an FPNV token is invalid."""
240+
class InvalidTokenError(exceptions.InvalidArgumentError):
241+
"""Raised when a Firebase Phone Number Verification token is invalid."""
242242

243243
def __init__(self, message, cause=None, http_response=None):
244244
exceptions.InvalidArgumentError.__init__(self, message, cause, http_response)
245245

246-
class ExpiredFpnvTokenError(InvalidFpnvTokenError):
247-
"""Raised when an FPNV token is expired."""
246+
class ExpiredTokenError(InvalidTokenError):
247+
"""Raised when a Firebase Phone Number Verification token is expired."""
248248

249249
def __init__(self, message, cause=None, http_response=None):
250-
InvalidFpnvTokenError.__init__(self, message, cause, http_response)
250+
InvalidTokenError.__init__(self, message, cause, http_response)
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from cryptography.hazmat.primitives.asymmetric import ec
2525

2626
import firebase_admin
27-
from firebase_admin import fpnv
27+
from firebase_admin import phone_number_verification as fpnv
2828
from tests import testutils
2929

3030
# Mock Data
@@ -62,7 +62,7 @@ def teardown_class(cls):
6262

6363
class TestFpnvToken:
6464
def test_properties(self):
65-
token = fpnv.FpnvToken(_MOCK_PAYLOAD)
65+
token = fpnv.PhoneNumberVerificationToken(_MOCK_PAYLOAD)
6666

6767
assert token.phone_number == _PHONE_NUMBER
6868
assert token.sub == _PHONE_NUMBER
@@ -140,7 +140,9 @@ def to_b64url(b_data):
140140

141141
def test_verify_token_module_level_delegation(self):
142142
"""Verifies module-level verify_token delegates correctly."""
143-
with patch('firebase_admin.fpnv._FpnvService.verify_token') as mock_verify:
143+
with patch(
144+
'firebase_admin.phone_number_verification._FpnvService.verify_token'
145+
) as mock_verify:
144146
mock_verify.return_value = 'mock-result'
145147
res = fpnv.verify_token('some-token')
146148
assert res == 'mock-result'
@@ -168,7 +170,7 @@ def test_verify_token_success(self, mock_header, mock_decode, mock_jwks_cls):
168170
token = fpnv.verify_token(token_str)
169171

170172
# Verify
171-
assert isinstance(token, fpnv.FpnvToken)
173+
assert isinstance(token, fpnv.PhoneNumberVerificationToken)
172174
assert token.phone_number == _PHONE_NUMBER
173175

174176
mock_header.assert_called_with(token_str)
@@ -192,19 +194,19 @@ def test_verify_token_no_name(self, mock_header):
192194
def test_verify_token_no_kid(self, mock_header):
193195
app = firebase_admin.get_app()
194196
mock_header.return_value = {'typ': 'JWT', 'alg': 'ES256'} # Missing kid
195-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="FPNV has no 'kid' claim."):
197+
with pytest.raises(fpnv.InvalidTokenError, match="Token has no 'kid' claim."):
196198
fpnv.verify_token('token', app=app)
197199

198200
@mock.patch('jwt.get_unverified_header')
199201
def test_verify_token_wrong_alg(self, mock_header):
200202
mock_header.return_value = {'kid': 'k', 'typ': 'JWT', 'alg': 'RS256'} # Wrong alg
201-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="incorrect alg"):
203+
with pytest.raises(fpnv.InvalidTokenError, match="incorrect alg"):
202204
fpnv.verify_token('token')
203205

204206
@mock.patch('jwt.get_unverified_header')
205207
def test_verify_token_wrong_typ(self, mock_header):
206208
mock_header.return_value = {'kid': 'k', 'typ': 'WRONG', 'alg': 'ES256'} # wrong typ
207-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="incorrect type header"):
209+
with pytest.raises(fpnv.InvalidTokenError, match="incorrect type header"):
208210
fpnv.verify_token('token')
209211

210212
def test_verify_token_jwk_error(self):
@@ -219,7 +221,10 @@ def test_verify_token_jwk_error(self):
219221
with mock.patch('jwt.get_unverified_header') as mock_header:
220222
mock_header.return_value = {'kid': 'k', 'typ': 'JWT', 'alg': 'ES256'}
221223

222-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="Verifying FPNV token failed"):
224+
with pytest.raises(
225+
fpnv.InvalidTokenError,
226+
match="Verifying phone number verification token failed"
227+
):
223228
fpnv.verify_token('token')
224229

225230
@mock.patch('jwt.PyJWKClient')
@@ -235,7 +240,7 @@ def test_verify_token_expired(self, mock_header, mock_decode, mock_jwks_cls):
235240
# Simulate ExpiredSignatureError
236241
mock_decode.side_effect = jwt.ExpiredSignatureError("Expired")
237242

238-
with pytest.raises(fpnv.ExpiredFpnvTokenError, match="token has expired"):
243+
with pytest.raises(fpnv.ExpiredTokenError, match="token has expired"):
239244
fpnv.verify_token('token')
240245

241246
@mock.patch('jwt.PyJWKClient')
@@ -251,7 +256,7 @@ def test_verify_token_invalid_signature(self, mock_header, mock_decode, mock_jwk
251256
# Simulate InvalidSignatureError
252257
mock_decode.side_effect = jwt.InvalidSignatureError("Wrong Signature")
253258

254-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="invalid signature"):
259+
with pytest.raises(fpnv.InvalidTokenError, match="invalid signature"):
255260
fpnv.verify_token('token')
256261

257262
@mock.patch('jwt.PyJWKClient')
@@ -267,7 +272,7 @@ def test_verify_token_invalid_audience(self, mock_header, mock_decode, mock_jwks
267272
# Simulate InvalidAudienceError
268273
mock_decode.side_effect = jwt.InvalidAudienceError("Wrong Aud")
269274

270-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="incorrect \"aud\""):
275+
with pytest.raises(fpnv.InvalidTokenError, match="incorrect \"aud\""):
271276
fpnv.verify_token('token')
272277

273278
@mock.patch('jwt.PyJWKClient')
@@ -283,7 +288,7 @@ def test_verify_token_invalid_issuer(self, mock_header, mock_decode, mock_jwks_c
283288
# Simulate InvalidIssuerError
284289
mock_decode.side_effect = jwt.InvalidIssuerError("Wrong Iss")
285290

286-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="incorrect \"iss\""):
291+
with pytest.raises(fpnv.InvalidTokenError, match="incorrect \"iss\""):
287292
fpnv.verify_token('token')
288293

289294
@mock.patch('jwt.PyJWKClient')
@@ -299,5 +304,5 @@ def test_verify_token_invalid_token(self, mock_header, mock_decode, mock_jwks_cl
299304
# Simulate InvalidTokenError
300305
mock_decode.side_effect = jwt.InvalidTokenError("Decoding FPNV token failed")
301306

302-
with pytest.raises(fpnv.InvalidFpnvTokenError, match="Decoding FPNV token failed"):
307+
with pytest.raises(fpnv.InvalidTokenError, match="Decoding FPNV token failed"):
303308
fpnv.verify_token('token')

0 commit comments

Comments
 (0)