pyios-firebase 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyios_firebase/__init__.py +26 -0
- pyios_firebase/analytics/__init__.py +434 -0
- pyios_firebase/analytics/names.py +251 -0
- pyios_firebase/app_check/__init__.py +483 -0
- pyios_firebase/auth/__init__.py +131 -0
- pyios_firebase/auth/_apple_name_cache.py +97 -0
- pyios_firebase/auth/_common.py +121 -0
- pyios_firebase/auth/_ui.py +21 -0
- pyios_firebase/auth/apple.py +326 -0
- pyios_firebase/auth/auth.py +592 -0
- pyios_firebase/auth/credentials.py +551 -0
- pyios_firebase/auth/errors.py +321 -0
- pyios_firebase/auth/facebook.py +240 -0
- pyios_firebase/auth/google.py +167 -0
- pyios_firebase/auth/multi_factor.py +394 -0
- pyios_firebase/auth/results.py +361 -0
- pyios_firebase/auth/user.py +457 -0
- pyios_firebase/core/__init__.py +32 -0
- pyios_firebase/crashlytics/__init__.py +463 -0
- pyios_firebase/database/__init__.py +62 -0
- pyios_firebase/database/database.py +144 -0
- pyios_firebase/database/errors.py +80 -0
- pyios_firebase/database/query.py +569 -0
- pyios_firebase/database/reference.py +428 -0
- pyios_firebase/database/snapshot.py +218 -0
- pyios_firebase/database/values.py +271 -0
- pyios_firebase/firestore/__init__.py +59 -0
- pyios_firebase/firestore/_limits.py +48 -0
- pyios_firebase/firestore/_query_state.py +228 -0
- pyios_firebase/firestore/_write.py +18 -0
- pyios_firebase/firestore/aggregate.py +137 -0
- pyios_firebase/firestore/collection_reference.py +100 -0
- pyios_firebase/firestore/document_change.py +63 -0
- pyios_firebase/firestore/document_reference.py +229 -0
- pyios_firebase/firestore/document_snapshot.py +102 -0
- pyios_firebase/firestore/enums.py +66 -0
- pyios_firebase/firestore/field_value.py +94 -0
- pyios_firebase/firestore/filter.py +319 -0
- pyios_firebase/firestore/firestore.py +184 -0
- pyios_firebase/firestore/listener_registration.py +55 -0
- pyios_firebase/firestore/query.py +679 -0
- pyios_firebase/firestore/query_snapshot.py +103 -0
- pyios_firebase/firestore/snapshot_metadata.py +36 -0
- pyios_firebase/firestore/transaction.py +130 -0
- pyios_firebase/firestore/write_batch.py +125 -0
- pyios_firebase/functions/__init__.py +329 -0
- pyios_firebase/messaging/__init__.py +37 -0
- pyios_firebase/messaging/messaging.py +435 -0
- pyios_firebase/messaging/notifications.py +292 -0
- pyios_firebase/performance/__init__.py +486 -0
- pyios_firebase/py.typed +0 -0
- pyios_firebase/remote_config/__init__.py +660 -0
- pyios_firebase/storage/__init__.py +41 -0
- pyios_firebase/storage/errors.py +103 -0
- pyios_firebase/storage/metadata.py +202 -0
- pyios_firebase/storage/reference.py +402 -0
- pyios_firebase/storage/storage.py +120 -0
- pyios_firebase/storage/task.py +198 -0
- pyios_firebase/types.py +93 -0
- pyios_firebase/util.py +413 -0
- pyios_firebase-0.2.0.dist-info/METADATA +1969 -0
- pyios_firebase-0.2.0.dist-info/RECORD +65 -0
- pyios_firebase-0.2.0.dist-info/WHEEL +5 -0
- pyios_firebase-0.2.0.dist-info/licenses/LICENSE +21 -0
- pyios_firebase-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Firebase Authentication errors.
|
|
2
|
+
|
|
3
|
+
Every auth callback's ``error`` is an ``AuthError``: a ``FirebaseError`` with
|
|
4
|
+
extra keys. Compare ``error.code`` with ``AuthErrorCode``::
|
|
5
|
+
|
|
6
|
+
def on_signed_in(result, error):
|
|
7
|
+
if error is not None:
|
|
8
|
+
if error.code == AuthErrorCode.WRONG_PASSWORD:
|
|
9
|
+
...
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
__all__ = ("AUTH_ERROR_DOMAIN", "AuthError", "AuthErrorCode", "auth_error_from_pointer")
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Final
|
|
17
|
+
|
|
18
|
+
from ..types import FirebaseError, ObjCObject
|
|
19
|
+
from ..util import nserror_to_dict, objc_instance
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from .credentials import AuthCredential
|
|
23
|
+
from .multi_factor import MultiFactorResolver
|
|
24
|
+
|
|
25
|
+
AUTH_ERROR_DOMAIN: Final = "FIRAuthErrorDomain"
|
|
26
|
+
"""The ``error.domain`` of Firebase Authentication errors."""
|
|
27
|
+
|
|
28
|
+
_NAME_KEY: Final = "FIRAuthErrorUserInfoNameKey"
|
|
29
|
+
_EMAIL_KEY: Final = "FIRAuthErrorUserInfoEmailKey"
|
|
30
|
+
_UPDATED_CREDENTIAL_KEY: Final = "FIRAuthErrorUserInfoUpdatedCredentialKey"
|
|
31
|
+
_RESOLVER_KEY: Final = "FIRAuthErrorUserInfoMultiFactorResolverKey"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AuthErrorCode:
|
|
35
|
+
"""The ``error.code`` values of Firebase Authentication errors.
|
|
36
|
+
|
|
37
|
+
Generated from ``FIRAuthErrorCode`` in the Firebase iOS SDK (13.1.1). The
|
|
38
|
+
numbers are fixed in the SDK; check them when you update it.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
INVALID_CUSTOM_TOKEN: Final = 17000
|
|
42
|
+
"""Indicates a validation error with the custom token."""
|
|
43
|
+
CUSTOM_TOKEN_MISMATCH: Final = 17002
|
|
44
|
+
"""Indicates the service account and the API key belong to different projects."""
|
|
45
|
+
INVALID_CREDENTIAL: Final = 17004
|
|
46
|
+
"""Indicates the IDP token or requestUri is invalid."""
|
|
47
|
+
USER_DISABLED: Final = 17005
|
|
48
|
+
"""Indicates the user's account is disabled on the server."""
|
|
49
|
+
OPERATION_NOT_ALLOWED: Final = 17006
|
|
50
|
+
"""Indicates the administrator disabled sign in with the specified identity provider."""
|
|
51
|
+
EMAIL_ALREADY_IN_USE: Final = 17007
|
|
52
|
+
"""Indicates the email used to attempt a sign up is already in use."""
|
|
53
|
+
INVALID_EMAIL: Final = 17008
|
|
54
|
+
"""Indicates the email is invalid."""
|
|
55
|
+
WRONG_PASSWORD: Final = 17009
|
|
56
|
+
"""Indicates the user attempted sign in with a wrong password."""
|
|
57
|
+
TOO_MANY_REQUESTS: Final = 17010
|
|
58
|
+
"""Indicates that too many requests were made to a server method."""
|
|
59
|
+
USER_NOT_FOUND: Final = 17011
|
|
60
|
+
"""Indicates the user account was not found."""
|
|
61
|
+
ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL: Final = 17012
|
|
62
|
+
"""Indicates account linking is required."""
|
|
63
|
+
REQUIRES_RECENT_LOGIN: Final = 17014
|
|
64
|
+
"""Indicates the user has attempted to change email or password more than 5 minutes after signing in."""
|
|
65
|
+
PROVIDER_ALREADY_LINKED: Final = 17015
|
|
66
|
+
"""Indicates an attempt to link a provider to which the account is already linked."""
|
|
67
|
+
NO_SUCH_PROVIDER: Final = 17016
|
|
68
|
+
"""Indicates an attempt to unlink a provider that is not linked."""
|
|
69
|
+
INVALID_USER_TOKEN: Final = 17017
|
|
70
|
+
"""Indicates user's saved auth credential is invalid the user needs to sign in again."""
|
|
71
|
+
NETWORK_ERROR: Final = 17020
|
|
72
|
+
"""Indicates a network error occurred (such as a timeout interrupted connection or unreachable host)."""
|
|
73
|
+
USER_TOKEN_EXPIRED: Final = 17021
|
|
74
|
+
"""Indicates the saved token has expired for example the user may have changed account password on another device."""
|
|
75
|
+
INVALID_API_KEY: Final = 17023
|
|
76
|
+
"""Indicates an invalid API key was supplied in the request."""
|
|
77
|
+
USER_MISMATCH: Final = 17024
|
|
78
|
+
"""Indicates that an attempt was made to reauthenticate with a user which is not the current user."""
|
|
79
|
+
CREDENTIAL_ALREADY_IN_USE: Final = 17025
|
|
80
|
+
"""Indicates an attempt to link with a credential that has already been linked with a different Firebase account."""
|
|
81
|
+
WEAK_PASSWORD: Final = 17026
|
|
82
|
+
"""Indicates an attempt to set a password that is considered too weak."""
|
|
83
|
+
APP_NOT_AUTHORIZED: Final = 17028
|
|
84
|
+
"""Indicates the App is not authorized to use Firebase Authentication with the provided API Key."""
|
|
85
|
+
EXPIRED_ACTION_CODE: Final = 17029
|
|
86
|
+
"""Indicates the OOB code is expired."""
|
|
87
|
+
INVALID_ACTION_CODE: Final = 17030
|
|
88
|
+
"""Indicates the OOB code is invalid."""
|
|
89
|
+
INVALID_MESSAGE_PAYLOAD: Final = 17031
|
|
90
|
+
"""Indicates that there are invalid parameters in the payload during a 'send password reset email' attempt."""
|
|
91
|
+
INVALID_SENDER: Final = 17032
|
|
92
|
+
"""Indicates that the sender email is invalid during a 'send password reset email' attempt."""
|
|
93
|
+
INVALID_RECIPIENT_EMAIL: Final = 17033
|
|
94
|
+
"""Indicates that the recipient email is invalid."""
|
|
95
|
+
MISSING_EMAIL: Final = 17034
|
|
96
|
+
"""Indicates that an email address was expected but one was not provided."""
|
|
97
|
+
MISSING_IOS_BUNDLE_ID: Final = 17036
|
|
98
|
+
"""Indicates that the iOS bundle ID is missing when a iOS App Store ID is provided."""
|
|
99
|
+
MISSING_ANDROID_PACKAGE_NAME: Final = 17037
|
|
100
|
+
"""Indicates that the android package name is missing when the `androidInstallApp` flag is set to `true`."""
|
|
101
|
+
UNAUTHORIZED_DOMAIN: Final = 17038
|
|
102
|
+
"""Indicates that the domain specified in the continue URL is not allowlisted in the Firebase console."""
|
|
103
|
+
INVALID_CONTINUE_URI: Final = 17039
|
|
104
|
+
"""Indicates that the domain specified in the continue URI is not valid."""
|
|
105
|
+
MISSING_CONTINUE_URI: Final = 17040
|
|
106
|
+
"""Indicates that a continue URI was not provided in a request to the backend which requires one."""
|
|
107
|
+
MISSING_PHONE_NUMBER: Final = 17041
|
|
108
|
+
"""Indicates that a phone number was not provided in a call to `verifyPhoneNumber:completion:`."""
|
|
109
|
+
INVALID_PHONE_NUMBER: Final = 17042
|
|
110
|
+
"""Indicates that an invalid phone number was provided in a call to `verifyPhoneNumber:completion:`."""
|
|
111
|
+
MISSING_VERIFICATION_CODE: Final = 17043
|
|
112
|
+
"""Indicates that the phone auth credential was created with an empty verification code."""
|
|
113
|
+
INVALID_VERIFICATION_CODE: Final = 17044
|
|
114
|
+
"""Indicates that an invalid verification code was used in the verifyPhoneNumber request."""
|
|
115
|
+
MISSING_VERIFICATION_ID: Final = 17045
|
|
116
|
+
"""Indicates that the phone auth credential was created with an empty verification ID."""
|
|
117
|
+
INVALID_VERIFICATION_ID: Final = 17046
|
|
118
|
+
"""Indicates that an invalid verification ID was used in the verifyPhoneNumber request."""
|
|
119
|
+
MISSING_APP_CREDENTIAL: Final = 17047
|
|
120
|
+
"""Indicates that the APNS device token is missing in the verifyClient request."""
|
|
121
|
+
INVALID_APP_CREDENTIAL: Final = 17048
|
|
122
|
+
"""Indicates that an invalid APNS device token was used in the verifyClient request."""
|
|
123
|
+
SESSION_EXPIRED: Final = 17051
|
|
124
|
+
"""Indicates that the SMS code has expired."""
|
|
125
|
+
QUOTA_EXCEEDED: Final = 17052
|
|
126
|
+
"""Indicates that the quota of SMS messages for a given project has been exceeded."""
|
|
127
|
+
MISSING_APP_TOKEN: Final = 17053
|
|
128
|
+
"""Indicates that the APNs device token could not be obtained."""
|
|
129
|
+
NOTIFICATION_NOT_FORWARDED: Final = 17054
|
|
130
|
+
"""Indicates that the app fails to forward remote notification to FIRAuth."""
|
|
131
|
+
APP_NOT_VERIFIED: Final = 17055
|
|
132
|
+
"""Indicates that the app could not be verified by Firebase during phone number authentication."""
|
|
133
|
+
CAPTCHA_CHECK_FAILED: Final = 17056
|
|
134
|
+
"""Indicates that the reCAPTCHA token is not valid."""
|
|
135
|
+
WEB_CONTEXT_ALREADY_PRESENTED: Final = 17057
|
|
136
|
+
"""Indicates that an attempt was made to present a new web context while one was already being presented."""
|
|
137
|
+
WEB_CONTEXT_CANCELLED: Final = 17058
|
|
138
|
+
"""Indicates that the URL presentation was cancelled prematurely by the user."""
|
|
139
|
+
APP_VERIFICATION_USER_INTERACTION_FAILURE: Final = 17059
|
|
140
|
+
"""Indicates a general failure during the app verification flow."""
|
|
141
|
+
INVALID_CLIENT_ID: Final = 17060
|
|
142
|
+
"""Indicates that the clientID used to invoke a web flow is invalid."""
|
|
143
|
+
WEB_NETWORK_REQUEST_FAILED: Final = 17061
|
|
144
|
+
"""Indicates that a network request within a SFSafariViewController or WKWebView failed."""
|
|
145
|
+
WEB_INTERNAL_ERROR: Final = 17062
|
|
146
|
+
"""Indicates that an internal error occurred within a SFSafariViewController or WKWebView."""
|
|
147
|
+
WEB_SIGN_IN_USER_INTERACTION_FAILURE: Final = 17063
|
|
148
|
+
"""Indicates a general failure during a web sign-in flow."""
|
|
149
|
+
LOCAL_PLAYER_NOT_AUTHENTICATED: Final = 17066
|
|
150
|
+
"""Indicates that the local player was not authenticated prior to attempting Game Center signin."""
|
|
151
|
+
NULL_USER: Final = 17067
|
|
152
|
+
"""Indicates that a non-null user was expected as an argument to the operation but a null user was provided."""
|
|
153
|
+
INVALID_PROVIDER_ID: Final = 17071
|
|
154
|
+
"""Represents the error code for when the given provider id for a web operation is invalid."""
|
|
155
|
+
TENANT_ID_MISMATCH: Final = 17072
|
|
156
|
+
"""Represents the error code for when an attempt is made to update the current user with a tenantId that differs from the current FirebaseAuth instance's tenantId."""
|
|
157
|
+
UNSUPPORTED_TENANT_OPERATION: Final = 17073
|
|
158
|
+
"""Represents the error code for when a request is made to the backend with an associated tenant ID for an operation that does not support multi-tenancy."""
|
|
159
|
+
INVALID_HOSTING_LINK_DOMAIN: Final = 17214
|
|
160
|
+
"""Indicates that the provided Firebase Hosting Link domain is not owned by the current project."""
|
|
161
|
+
REJECTED_CREDENTIAL: Final = 17075
|
|
162
|
+
"""Indicates that the credential is rejected because it's malformed or mismatching."""
|
|
163
|
+
GAME_KIT_NOT_LINKED: Final = 17076
|
|
164
|
+
"""Indicates that the GameKit framework is not linked prior to attempting Game Center signin."""
|
|
165
|
+
SECOND_FACTOR_REQUIRED: Final = 17078
|
|
166
|
+
"""Indicates that the second factor is required for signin."""
|
|
167
|
+
MISSING_MULTI_FACTOR_SESSION: Final = 17081
|
|
168
|
+
"""Indicates that the multi factor session is missing."""
|
|
169
|
+
MISSING_MULTI_FACTOR_INFO: Final = 17082
|
|
170
|
+
"""Indicates that the multi factor info is missing."""
|
|
171
|
+
INVALID_MULTI_FACTOR_SESSION: Final = 17083
|
|
172
|
+
"""Indicates that the multi factor session is invalid."""
|
|
173
|
+
MULTI_FACTOR_INFO_NOT_FOUND: Final = 17084
|
|
174
|
+
"""Indicates that the multi factor info is not found."""
|
|
175
|
+
ADMIN_RESTRICTED_OPERATION: Final = 17085
|
|
176
|
+
"""Indicates that the operation is admin restricted."""
|
|
177
|
+
UNVERIFIED_EMAIL: Final = 17086
|
|
178
|
+
"""Indicates that the email is required for verification."""
|
|
179
|
+
SECOND_FACTOR_ALREADY_ENROLLED: Final = 17087
|
|
180
|
+
"""Indicates that the second factor is already enrolled."""
|
|
181
|
+
MAXIMUM_SECOND_FACTOR_COUNT_EXCEEDED: Final = 17088
|
|
182
|
+
"""Indicates that the maximum second factor count is exceeded."""
|
|
183
|
+
UNSUPPORTED_FIRST_FACTOR: Final = 17089
|
|
184
|
+
"""Indicates that the first factor is not supported."""
|
|
185
|
+
EMAIL_CHANGE_NEEDS_VERIFICATION: Final = 17090
|
|
186
|
+
"""Indicates that the a verified email is required to changed to."""
|
|
187
|
+
MISSING_CLIENT_IDENTIFIER: Final = 17093
|
|
188
|
+
"""Indicates that the request does not contain a client identifier."""
|
|
189
|
+
MISSING_OR_INVALID_NONCE: Final = 17094
|
|
190
|
+
"""Indicates that the nonce is missing or invalid."""
|
|
191
|
+
BLOCKING_CLOUD_FUNCTION_ERROR: Final = 17105
|
|
192
|
+
"""Raised when a Cloud Function returns a blocking error."""
|
|
193
|
+
RECAPTCHA_NOT_ENABLED: Final = 17200
|
|
194
|
+
"""Indicates that reCAPTCHA Enterprise integration is not enabled for this project."""
|
|
195
|
+
MISSING_RECAPTCHA_TOKEN: Final = 17201
|
|
196
|
+
"""Indicates that the reCAPTCHA token is missing from the backend request."""
|
|
197
|
+
INVALID_RECAPTCHA_TOKEN: Final = 17202
|
|
198
|
+
"""Indicates that the reCAPTCHA token sent with the backend request is invalid."""
|
|
199
|
+
INVALID_RECAPTCHA_ACTION: Final = 17203
|
|
200
|
+
"""Indicates that the requested reCAPTCHA action is invalid."""
|
|
201
|
+
MISSING_CLIENT_TYPE: Final = 17204
|
|
202
|
+
"""Indicates that the client type is missing from the request."""
|
|
203
|
+
MISSING_RECAPTCHA_VERSION: Final = 17205
|
|
204
|
+
"""Indicates that the reCAPTCHA version is missing from the request."""
|
|
205
|
+
INVALID_RECAPTCHA_VERSION: Final = 17206
|
|
206
|
+
"""Indicates that the reCAPTCHA version sent to the backend is invalid."""
|
|
207
|
+
INVALID_REQ_TYPE: Final = 17207
|
|
208
|
+
"""Indicates that the request type sent to the backend is invalid."""
|
|
209
|
+
RECAPTCHA_SDK_NOT_LINKED: Final = 17208
|
|
210
|
+
"""Indicates that the reCAPTCHA SDK is not linked to the app."""
|
|
211
|
+
RECAPTCHA_SITE_KEY_MISSING: Final = 17209
|
|
212
|
+
"""Indicates that the reCAPTCHA SDK site key wasn't found."""
|
|
213
|
+
RECAPTCHA_ACTION_CREATION_FAILED: Final = 17210
|
|
214
|
+
"""Indicates that the reCAPTCHA SDK actions class failed to create."""
|
|
215
|
+
PASSWORD_DOES_NOT_MEET_REQUIREMENTS: Final = 17211
|
|
216
|
+
"""Indicates that the password does not meet the project's password policy."""
|
|
217
|
+
KEYCHAIN_ERROR: Final = 17995
|
|
218
|
+
"""Indicates an error occurred while attempting to access the keychain."""
|
|
219
|
+
INTERNAL_ERROR: Final = 17999
|
|
220
|
+
"""Indicates an internal error occurred."""
|
|
221
|
+
MALFORMED_JWT: Final = 18000
|
|
222
|
+
"""Raised when a JWT fails to parse correctly."""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
_CODE_NAMES: Final[dict[int, str]] = {
|
|
226
|
+
value: name for name, value in vars(AuthErrorCode).items() if isinstance(value, int)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class AuthError(FirebaseError):
|
|
231
|
+
"""An error from Firebase Authentication (or from a sign-in sheet)."""
|
|
232
|
+
|
|
233
|
+
name: str
|
|
234
|
+
"""The ``AuthErrorCode`` name of ``code`` (for example ``"WRONG_PASSWORD"``),
|
|
235
|
+
or ``""`` for errors from other domains."""
|
|
236
|
+
email: str | None
|
|
237
|
+
"""The email address the error is about, when Firebase gives one (for
|
|
238
|
+
example with ``ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL``)."""
|
|
239
|
+
updated_credential: AuthCredential | None
|
|
240
|
+
"""With ``ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL`` and
|
|
241
|
+
``CREDENTIAL_ALREADY_IN_USE``: a credential to sign in with instead."""
|
|
242
|
+
resolver: MultiFactorResolver | None
|
|
243
|
+
"""With ``SECOND_FACTOR_REQUIRED``: finishes the sign-in with a second
|
|
244
|
+
factor (see ``pyios_firebase.auth.multi_factor``)."""
|
|
245
|
+
cancelled: bool
|
|
246
|
+
"""True when the user closed a sign-in sheet (Google, Apple, web
|
|
247
|
+
providers): not a failure to report."""
|
|
248
|
+
message: str
|
|
249
|
+
"""A sentence to show to the user, when the library has a better one than
|
|
250
|
+
``description`` (Apple sign-in); otherwise ``""``."""
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def auth_error_from_nserror(error: ObjCObject) -> AuthError:
|
|
254
|
+
"""Turn an ``NSError`` from Firebase Auth into an ``AuthError``.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
error: the ``NSError``.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
An ``AuthError`` with the ``FirebaseError`` keys, plus ``name``,
|
|
261
|
+
``email``, ``updated_credential``, ``resolver``, ``cancelled``
|
|
262
|
+
(False) and ``message`` (``""``).
|
|
263
|
+
"""
|
|
264
|
+
from .credentials import AuthCredential
|
|
265
|
+
from .multi_factor import MultiFactorResolver
|
|
266
|
+
|
|
267
|
+
result = AuthError(nserror_to_dict(error) or {})
|
|
268
|
+
is_auth = result.domain == AUTH_ERROR_DOMAIN
|
|
269
|
+
result.name = _CODE_NAMES.get(result.code, "") if is_auth else ""
|
|
270
|
+
user_info = error.userInfo
|
|
271
|
+
email = user_info.objectForKey_(_EMAIL_KEY)
|
|
272
|
+
result.email = None if email is None else str(email)
|
|
273
|
+
credential = user_info.objectForKey_(_UPDATED_CREDENTIAL_KEY)
|
|
274
|
+
result.updated_credential = (
|
|
275
|
+
None if credential is None else AuthCredential(credential)
|
|
276
|
+
)
|
|
277
|
+
resolver = user_info.objectForKey_(_RESOLVER_KEY)
|
|
278
|
+
result.resolver = None if resolver is None else MultiFactorResolver(resolver)
|
|
279
|
+
result.cancelled = is_auth and result.code == AuthErrorCode.WEB_CONTEXT_CANCELLED
|
|
280
|
+
result.message = ""
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def auth_error_from_pointer(error_ptr: Any) -> AuthError | None:
|
|
285
|
+
"""``auth_error_from_nserror`` for a raw block argument.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
error_ptr: the ``NSError *`` a completion block received, or a null
|
|
289
|
+
pointer / ``None`` when there was no error.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
An ``AuthError``, or ``None`` when there was no error.
|
|
293
|
+
"""
|
|
294
|
+
return auth_error_from_nserror(objc_instance(error_ptr)) if error_ptr else None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def simple_auth_error(
|
|
298
|
+
description: str, code: int = 0, *, cancelled: bool = False
|
|
299
|
+
) -> AuthError:
|
|
300
|
+
"""An ``AuthError`` made in Python, for failures before Firebase is called.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
description: what went wrong.
|
|
304
|
+
code: the error code; 0 when there is none.
|
|
305
|
+
cancelled: True when the user cancelled.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
An ``AuthError`` with an empty ``domain``.
|
|
309
|
+
"""
|
|
310
|
+
return AuthError(
|
|
311
|
+
code=code,
|
|
312
|
+
description=description,
|
|
313
|
+
domain="",
|
|
314
|
+
underlying_error=None,
|
|
315
|
+
name="",
|
|
316
|
+
email=None,
|
|
317
|
+
updated_credential=None,
|
|
318
|
+
resolver=None,
|
|
319
|
+
cancelled=cancelled,
|
|
320
|
+
message="",
|
|
321
|
+
)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Facebook Login, then Firebase Auth.
|
|
2
|
+
|
|
3
|
+
from pyios_firebase.auth import facebook
|
|
4
|
+
|
|
5
|
+
facebook.initialize_facebook() # once, at app start
|
|
6
|
+
|
|
7
|
+
def on_signed_in(result, error):
|
|
8
|
+
if error is not None:
|
|
9
|
+
if not error.cancelled:
|
|
10
|
+
print("Sign-in failed:", error.description)
|
|
11
|
+
return
|
|
12
|
+
print("Signed in:", result.user.uid)
|
|
13
|
+
|
|
14
|
+
facebook.start_facebook_sign_in(on_signed_in)
|
|
15
|
+
|
|
16
|
+
Both of Facebook's login modes work:
|
|
17
|
+
|
|
18
|
+
- classic login gives an access token (also usable with the Graph API);
|
|
19
|
+
- Limited Login (``limited=True``, and what iOS falls back to when the user
|
|
20
|
+
has not allowed tracking) gives an OpenID Connect token, checked with a
|
|
21
|
+
nonce.
|
|
22
|
+
|
|
23
|
+
Needs the ``FacebookLogin`` framework (Swift package
|
|
24
|
+
``https://github.com/facebook/facebook-ios-sdk``, product ``FacebookLogin``),
|
|
25
|
+
the Facebook provider enabled in the Firebase console, and in ``Info.plist``:
|
|
26
|
+
``FacebookAppID``, ``FacebookClientToken``, ``FacebookDisplayName``, the URL
|
|
27
|
+
scheme ``fb<APP_ID>``, and ``LSApplicationQueriesSchemes`` with ``fbapi`` and
|
|
28
|
+
``fb-messenger-share-api``.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
__all__ = (
|
|
34
|
+
"FACEBOOK_LOGIN_TRACKING_ENABLED",
|
|
35
|
+
"FACEBOOK_LOGIN_TRACKING_LIMITED",
|
|
36
|
+
"get_facebook_credential",
|
|
37
|
+
"initialize_facebook",
|
|
38
|
+
"on_facebook_handle_url",
|
|
39
|
+
"on_facebook_handle_url_kivy",
|
|
40
|
+
"sign_out_of_facebook",
|
|
41
|
+
"start_facebook_sign_in",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
import hashlib
|
|
45
|
+
import secrets
|
|
46
|
+
from collections.abc import Callable, Sequence
|
|
47
|
+
from typing import Any, Final
|
|
48
|
+
|
|
49
|
+
from rubicon.objc import ns_from_py, objc_id
|
|
50
|
+
|
|
51
|
+
from ..types import ObjCObject
|
|
52
|
+
from ..util import objc_class, objc_instance, one_shot_block
|
|
53
|
+
from ._common import flag
|
|
54
|
+
from ._ui import root_view_controller
|
|
55
|
+
from .auth import Auth
|
|
56
|
+
from .credentials import AuthCredential, FacebookAuthProvider, OAuthProvider
|
|
57
|
+
from .errors import AuthError, auth_error_from_nserror, simple_auth_error
|
|
58
|
+
from .results import AuthDataResult
|
|
59
|
+
|
|
60
|
+
FACEBOOK_LOGIN_TRACKING_ENABLED: Final = 0
|
|
61
|
+
"""``FBSDKLoginTrackingEnabled``: classic login (an access token)."""
|
|
62
|
+
FACEBOOK_LOGIN_TRACKING_LIMITED: Final = 1
|
|
63
|
+
"""``FBSDKLoginTrackingLimited``: Limited Login (an OpenID Connect token)."""
|
|
64
|
+
|
|
65
|
+
DEFAULT_PERMISSIONS: Final = ("public_profile", "email")
|
|
66
|
+
|
|
67
|
+
CredentialCallback = Callable[[AuthCredential | None, AuthError | None], None]
|
|
68
|
+
DataResultCallback = Callable[[AuthDataResult | None, AuthError | None], None]
|
|
69
|
+
|
|
70
|
+
# each login keeps its LoginManager alive until Facebook answers
|
|
71
|
+
_pending: set[ObjCObject] = set()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _application() -> ObjCObject:
|
|
75
|
+
return objc_class("UIApplication").sharedApplication
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def initialize_facebook() -> bool:
|
|
79
|
+
"""Start the Facebook SDK. Call it once, at app start (Facebook's
|
|
80
|
+
``application(_:didFinishLaunchingWithOptions:)`` step).
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
What the Facebook SDK returns (True when it started).
|
|
84
|
+
"""
|
|
85
|
+
delegate = objc_class("FBSDKApplicationDelegate").sharedInstance
|
|
86
|
+
return bool(
|
|
87
|
+
delegate.application_didFinishLaunchingWithOptions_(_application(), None)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def on_facebook_handle_url(url_string: str) -> bool:
|
|
92
|
+
"""Give a URL the app was opened with to the Facebook SDK (the login
|
|
93
|
+
redirect back from the Facebook app or Safari).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
url_string: the URL.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
True if the Facebook SDK handled it.
|
|
100
|
+
"""
|
|
101
|
+
url = objc_class("NSURL").URLWithString_(url_string)
|
|
102
|
+
delegate = objc_class("FBSDKApplicationDelegate").sharedInstance
|
|
103
|
+
return bool(
|
|
104
|
+
delegate.application_openURL_options_(_application(), url, ns_from_py({}))
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def on_facebook_handle_url_kivy(
|
|
109
|
+
_window: Any, filename: str | bytes, *_args: Any
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Kivy handler: ``Window.bind(on_drop_file=on_facebook_handle_url_kivy)``.
|
|
112
|
+
|
|
113
|
+
Kivy (SDL) passes URLs the app was opened with as "dropped files"; this
|
|
114
|
+
forwards them to ``on_facebook_handle_url``.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
filename: the URL, as Kivy passes it (``bytes`` or ``str``). The
|
|
118
|
+
other arguments Kivy passes (the window, the position) are ignored.
|
|
119
|
+
"""
|
|
120
|
+
url_string = filename.decode("utf-8") if isinstance(filename, bytes) else filename
|
|
121
|
+
on_facebook_handle_url(url_string)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _credential_from_result(
|
|
125
|
+
result: ObjCObject, raw_nonce: str
|
|
126
|
+
) -> AuthCredential | AuthError:
|
|
127
|
+
if flag(result, "isCancelled"):
|
|
128
|
+
return simple_auth_error("Facebook login was cancelled", cancelled=True)
|
|
129
|
+
if result.token is not None:
|
|
130
|
+
return FacebookAuthProvider.credential(str(result.token.tokenString))
|
|
131
|
+
if result.authenticationToken is not None:
|
|
132
|
+
return OAuthProvider.credential(
|
|
133
|
+
FacebookAuthProvider.PROVIDER_ID,
|
|
134
|
+
id_token=str(result.authenticationToken.tokenString),
|
|
135
|
+
raw_nonce=raw_nonce,
|
|
136
|
+
)
|
|
137
|
+
return simple_auth_error("Facebook login gave no token")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_facebook_credential(
|
|
141
|
+
callback: CredentialCallback,
|
|
142
|
+
permissions: Sequence[str] = DEFAULT_PERMISSIONS,
|
|
143
|
+
limited: bool = False,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Show Facebook Login and give back a Firebase credential, without signing
|
|
146
|
+
in to Firebase (for example to link Facebook to the current user).
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
callback: gets ``(credential, None)``, or ``(None, error)``.
|
|
150
|
+
``error.cancelled`` is True when the user cancelled.
|
|
151
|
+
permissions: the Facebook permissions to ask for.
|
|
152
|
+
limited: True for Limited Login (no tracking). Facebook may also use
|
|
153
|
+
Limited Login on its own when the user has not allowed tracking;
|
|
154
|
+
both give a working credential.
|
|
155
|
+
"""
|
|
156
|
+
raw_nonce = secrets.token_urlsafe(32)
|
|
157
|
+
try:
|
|
158
|
+
# Firebase checks the token against SHA-256(raw nonce), as for Apple
|
|
159
|
+
configuration = (
|
|
160
|
+
objc_class("FBSDKLoginConfiguration")
|
|
161
|
+
.alloc()
|
|
162
|
+
.initWithPermissions_tracking_nonce_(
|
|
163
|
+
ns_from_py(list(permissions)),
|
|
164
|
+
FACEBOOK_LOGIN_TRACKING_LIMITED
|
|
165
|
+
if limited
|
|
166
|
+
else FACEBOOK_LOGIN_TRACKING_ENABLED,
|
|
167
|
+
hashlib.sha256(raw_nonce.encode()).hexdigest(),
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
if configuration is None:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f"Facebook rejected the login configuration (permissions {list(permissions)})"
|
|
173
|
+
)
|
|
174
|
+
manager = objc_class("FBSDKLoginManager").alloc().init()
|
|
175
|
+
presenter = root_view_controller()
|
|
176
|
+
except Exception as e: # noqa: BLE001 - must always answer the caller
|
|
177
|
+
callback(None, simple_auth_error(str(e)))
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
def done(result_ptr: Any, error_ptr: Any) -> None:
|
|
181
|
+
_pending.discard(manager)
|
|
182
|
+
if error_ptr:
|
|
183
|
+
callback(None, auth_error_from_nserror(objc_instance(error_ptr)))
|
|
184
|
+
return
|
|
185
|
+
if not result_ptr:
|
|
186
|
+
callback(None, simple_auth_error("Facebook login gave no result"))
|
|
187
|
+
return
|
|
188
|
+
try:
|
|
189
|
+
outcome = _credential_from_result(objc_instance(result_ptr), raw_nonce)
|
|
190
|
+
except Exception as e: # noqa: BLE001
|
|
191
|
+
callback(
|
|
192
|
+
None, simple_auth_error(f"could not read the Facebook result: {e!r}")
|
|
193
|
+
)
|
|
194
|
+
return
|
|
195
|
+
if isinstance(outcome, AuthCredential):
|
|
196
|
+
callback(outcome, None)
|
|
197
|
+
else:
|
|
198
|
+
callback(None, outcome)
|
|
199
|
+
|
|
200
|
+
_pending.add(manager)
|
|
201
|
+
manager.logInFromViewController_configuration_completion_(
|
|
202
|
+
presenter, configuration, one_shot_block(done, objc_id, objc_id)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def start_facebook_sign_in(
|
|
207
|
+
callback: DataResultCallback,
|
|
208
|
+
permissions: Sequence[str] = DEFAULT_PERMISSIONS,
|
|
209
|
+
limited: bool = False,
|
|
210
|
+
auth: Auth | None = None,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""Show Facebook Login, then sign in to Firebase.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
callback: gets ``(AuthDataResult, None)``, or ``(None, error)``.
|
|
216
|
+
``error.cancelled`` is True when the user cancelled: not a failure
|
|
217
|
+
to report. ``ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL`` means the
|
|
218
|
+
email already has an account with another provider.
|
|
219
|
+
permissions: the Facebook permissions to ask for.
|
|
220
|
+
limited: True for Limited Login (see ``get_facebook_credential``).
|
|
221
|
+
auth: the ``Auth`` to sign in to (default: ``Auth.auth()``).
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def got_credential(
|
|
225
|
+
credential: AuthCredential | None, error: AuthError | None
|
|
226
|
+
) -> None:
|
|
227
|
+
if credential is None:
|
|
228
|
+
callback(None, error)
|
|
229
|
+
return
|
|
230
|
+
(auth or Auth.auth()).sign_in_with_credential(credential, callback)
|
|
231
|
+
|
|
232
|
+
get_facebook_credential(got_credential, permissions, limited)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def sign_out_of_facebook() -> None:
|
|
236
|
+
"""Log out of Facebook Login (the next login asks again).
|
|
237
|
+
|
|
238
|
+
Call it with ``Auth.sign_out()``, which signs out of Firebase only.
|
|
239
|
+
"""
|
|
240
|
+
objc_class("FBSDKLoginManager").alloc().init().logOut()
|