@chem-po/firebase-native 0.0.16 → 0.0.17
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.
- package/lib/commonjs/adapter/auth.js +221 -0
- package/lib/commonjs/adapter/auth.js.map +1 -0
- package/lib/commonjs/adapter/db.js +103 -0
- package/lib/commonjs/adapter/db.js.map +1 -0
- package/lib/commonjs/adapter/index.js +16 -0
- package/lib/commonjs/adapter/index.js.map +1 -0
- package/lib/commonjs/adapter/storage.js +52 -0
- package/lib/commonjs/adapter/storage.js.map +1 -0
- package/lib/commonjs/auth/functions.js +11 -0
- package/lib/commonjs/auth/functions.js.map +1 -0
- package/lib/commonjs/auth/index.js +17 -0
- package/lib/commonjs/auth/index.js.map +1 -0
- package/lib/commonjs/components/AuthenticatorVerify.js +90 -0
- package/lib/commonjs/components/AuthenticatorVerify.js.map +1 -0
- package/lib/commonjs/components/FirebaseSignIn.js +196 -0
- package/lib/commonjs/components/FirebaseSignIn.js.map +1 -0
- package/lib/commonjs/components/PhoneVerify.js +123 -0
- package/lib/commonjs/components/PhoneVerify.js.map +1 -0
- package/lib/commonjs/components/TwoFactorAuthModal.js +118 -0
- package/lib/commonjs/components/TwoFactorAuthModal.js.map +1 -0
- package/lib/commonjs/components/index.js +28 -0
- package/lib/commonjs/components/index.js.map +1 -0
- package/lib/commonjs/contexts/FirebaseContext.js +48 -0
- package/lib/commonjs/contexts/FirebaseContext.js.map +1 -0
- package/lib/commonjs/contexts/index.js +17 -0
- package/lib/commonjs/contexts/index.js.map +1 -0
- package/lib/commonjs/db/index.js +17 -0
- package/lib/commonjs/db/index.js.map +1 -0
- package/lib/commonjs/db/utils.js +120 -0
- package/lib/commonjs/db/utils.js.map +1 -0
- package/lib/commonjs/hooks/backend.js +12 -0
- package/lib/commonjs/hooks/backend.js.map +1 -0
- package/lib/commonjs/hooks/index.js +17 -0
- package/lib/commonjs/hooks/index.js.map +1 -0
- package/lib/commonjs/hooks/useAuthenticatorVerify.js +52 -0
- package/lib/commonjs/hooks/useAuthenticatorVerify.js.map +1 -0
- package/lib/commonjs/hooks/usePhoneVerify.js +83 -0
- package/lib/commonjs/hooks/usePhoneVerify.js.map +1 -0
- package/lib/commonjs/icons/Google.js +29 -0
- package/lib/commonjs/icons/Google.js.map +1 -0
- package/lib/commonjs/index.js +94 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/storage/index.js +17 -0
- package/lib/commonjs/storage/index.js.map +1 -0
- package/lib/commonjs/storage/utils.js +37 -0
- package/lib/commonjs/storage/utils.js.map +1 -0
- package/lib/commonjs/types/adapter.js +6 -0
- package/lib/commonjs/types/adapter.js.map +1 -0
- package/lib/commonjs/types/auth.js +6 -0
- package/lib/commonjs/types/auth.js.map +1 -0
- package/lib/commonjs/types/db.js +6 -0
- package/lib/commonjs/types/db.js.map +1 -0
- package/lib/commonjs/types/functions.js +6 -0
- package/lib/commonjs/types/functions.js.map +1 -0
- package/lib/commonjs/types/index.js +6 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/types/storage.js +6 -0
- package/lib/commonjs/types/storage.js.map +1 -0
- package/lib/module/adapter/auth.js +214 -0
- package/lib/module/adapter/auth.js.map +1 -0
- package/lib/module/adapter/db.js +96 -0
- package/lib/module/adapter/db.js.map +1 -0
- package/lib/module/adapter/index.js +9 -0
- package/lib/module/adapter/index.js.map +1 -0
- package/lib/module/adapter/storage.js +45 -0
- package/lib/module/adapter/storage.js.map +1 -0
- package/lib/module/auth/functions.js +3 -0
- package/lib/module/auth/functions.js.map +1 -0
- package/lib/module/auth/index.js +2 -0
- package/lib/module/auth/index.js.map +1 -0
- package/lib/module/components/AuthenticatorVerify.js +82 -0
- package/lib/module/components/AuthenticatorVerify.js.map +1 -0
- package/lib/module/components/FirebaseSignIn.js +187 -0
- package/lib/module/components/FirebaseSignIn.js.map +1 -0
- package/lib/module/components/PhoneVerify.js +116 -0
- package/lib/module/components/PhoneVerify.js.map +1 -0
- package/lib/module/components/TwoFactorAuthModal.js +110 -0
- package/lib/module/components/TwoFactorAuthModal.js.map +1 -0
- package/lib/module/components/index.js +3 -0
- package/lib/module/components/index.js.map +1 -0
- package/lib/module/contexts/FirebaseContext.js +39 -0
- package/lib/module/contexts/FirebaseContext.js.map +1 -0
- package/lib/module/contexts/index.js +2 -0
- package/lib/module/contexts/index.js.map +1 -0
- package/lib/module/db/index.js +2 -0
- package/lib/module/db/index.js.map +1 -0
- package/lib/module/db/utils.js +111 -0
- package/lib/module/db/utils.js.map +1 -0
- package/lib/module/hooks/backend.js +5 -0
- package/lib/module/hooks/backend.js.map +1 -0
- package/lib/module/hooks/index.js +2 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useAuthenticatorVerify.js +45 -0
- package/lib/module/hooks/useAuthenticatorVerify.js.map +1 -0
- package/lib/module/hooks/usePhoneVerify.js +76 -0
- package/lib/module/hooks/usePhoneVerify.js.map +1 -0
- package/lib/module/icons/Google.js +22 -0
- package/lib/module/icons/Google.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/storage/index.js +2 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/storage/utils.js +30 -0
- package/lib/module/storage/utils.js.map +1 -0
- package/lib/module/types/adapter.js +2 -0
- package/lib/module/types/adapter.js.map +1 -0
- package/lib/module/types/auth.js +2 -0
- package/lib/module/types/auth.js.map +1 -0
- package/lib/module/types/db.js +2 -0
- package/lib/module/types/db.js.map +1 -0
- package/lib/module/types/functions.js +2 -0
- package/lib/module/types/functions.js.map +1 -0
- package/lib/module/types/index.js +2 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/types/storage.js +2 -0
- package/lib/module/types/storage.js.map +1 -0
- package/lib/typescript/adapter/auth.d.ts +7 -0
- package/lib/typescript/adapter/auth.d.ts.map +1 -0
- package/lib/typescript/adapter/db.d.ts +5 -0
- package/lib/typescript/adapter/db.d.ts.map +1 -0
- package/lib/typescript/adapter/index.d.ts +9 -0
- package/lib/typescript/adapter/index.d.ts.map +1 -0
- package/lib/typescript/adapter/storage.d.ts +4 -0
- package/lib/typescript/adapter/storage.d.ts.map +1 -0
- package/lib/typescript/auth/functions.d.ts +4 -0
- package/lib/typescript/auth/functions.d.ts.map +1 -0
- package/lib/typescript/auth/index.d.ts +2 -0
- package/lib/typescript/auth/index.d.ts.map +1 -0
- package/lib/typescript/components/AuthenticatorVerify.d.ts +3 -0
- package/lib/typescript/components/AuthenticatorVerify.d.ts.map +1 -0
- package/lib/typescript/components/FirebaseSignIn.d.ts +6 -0
- package/lib/typescript/components/FirebaseSignIn.d.ts.map +1 -0
- package/lib/typescript/components/PhoneVerify.d.ts +6 -0
- package/lib/typescript/components/PhoneVerify.d.ts.map +1 -0
- package/lib/typescript/components/TwoFactorAuthModal.d.ts +3 -0
- package/lib/typescript/components/TwoFactorAuthModal.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +3 -0
- package/lib/typescript/components/index.d.ts.map +1 -0
- package/lib/typescript/contexts/FirebaseContext.d.ts +9 -0
- package/lib/typescript/contexts/FirebaseContext.d.ts.map +1 -0
- package/lib/typescript/contexts/index.d.ts +2 -0
- package/lib/typescript/contexts/index.d.ts.map +1 -0
- package/lib/typescript/db/index.d.ts +2 -0
- package/lib/typescript/db/index.d.ts.map +1 -0
- package/lib/typescript/db/utils.d.ts +6 -0
- package/lib/typescript/db/utils.d.ts.map +1 -0
- package/lib/typescript/hooks/backend.d.ts +2 -0
- package/lib/typescript/hooks/backend.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +2 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useAuthenticatorVerify.d.ts +8 -0
- package/lib/typescript/hooks/useAuthenticatorVerify.d.ts.map +1 -0
- package/lib/typescript/hooks/usePhoneVerify.d.ts +9 -0
- package/lib/typescript/hooks/usePhoneVerify.d.ts.map +1 -0
- package/lib/typescript/icons/Google.d.ts +5 -0
- package/lib/typescript/icons/Google.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +9 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/storage/index.d.ts +2 -0
- package/lib/typescript/storage/index.d.ts.map +1 -0
- package/lib/typescript/storage/utils.d.ts +4 -0
- package/lib/typescript/storage/utils.d.ts.map +1 -0
- package/lib/typescript/types/adapter.d.ts +6 -0
- package/lib/typescript/types/adapter.d.ts.map +1 -0
- package/lib/typescript/types/auth.d.ts +12 -0
- package/lib/typescript/types/auth.d.ts.map +1 -0
- package/lib/typescript/types/db.d.ts +8 -0
- package/lib/typescript/types/db.d.ts.map +1 -0
- package/lib/typescript/types/functions.d.ts +3 -0
- package/lib/typescript/types/functions.d.ts.map +1 -0
- package/lib/typescript/types/index.d.ts +24 -0
- package/lib/typescript/types/index.d.ts.map +1 -0
- package/lib/typescript/types/storage.d.ts +3 -0
- package/lib/typescript/types/storage.d.ts.map +1 -0
- package/package.json +29 -12
- package/src/adapter/auth.ts +281 -0
- package/src/adapter/db.ts +146 -0
- package/src/adapter/index.ts +30 -0
- package/src/adapter/storage.ts +58 -0
- package/src/auth/functions.ts +7 -0
- package/src/auth/index.ts +1 -0
- package/src/components/AuthenticatorVerify.tsx +75 -0
- package/src/components/FirebaseSignIn.tsx +187 -0
- package/src/components/PhoneVerify.tsx +102 -0
- package/src/components/TwoFactorAuthModal.tsx +133 -0
- package/src/components/index.ts +2 -0
- package/src/contexts/FirebaseContext.tsx +54 -0
- package/src/contexts/index.ts +1 -0
- package/src/db/index.ts +1 -0
- package/src/db/utils.ts +142 -0
- package/src/hooks/backend.ts +4 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAuthenticatorVerify.ts +45 -0
- package/src/hooks/usePhoneVerify.ts +76 -0
- package/src/icons/Google.tsx +24 -0
- package/src/index.ts +8 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/utils.ts +29 -0
- package/src/types/adapter.ts +13 -0
- package/src/types/auth.ts +13 -0
- package/src/types/db.ts +10 -0
- package/src/types/functions.ts +3 -0
- package/src/types/index.ts +26 -0
- package/src/types/storage.ts +3 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/types/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAE3D,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chem-po/firebase-native",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"author": "Elan Canfield",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"version": "0.0.17",
|
|
6
|
+
"main": "lib/commonjs/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"source": "src/index.ts",
|
|
6
9
|
"publishConfig": {
|
|
7
|
-
"access": "public"
|
|
10
|
+
"access": "public",
|
|
11
|
+
"react-native": "src/index.ts"
|
|
8
12
|
},
|
|
9
13
|
"sideEffects": false,
|
|
10
14
|
"keywords": [],
|
|
11
15
|
"files": [
|
|
12
|
-
"
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"cpp",
|
|
20
|
+
"*.podspec",
|
|
21
|
+
"!lib/typescript/example",
|
|
22
|
+
"!ios/build",
|
|
23
|
+
"!android/build",
|
|
24
|
+
"!android/gradle",
|
|
25
|
+
"!android/gradlew",
|
|
26
|
+
"!android/gradlew.bat",
|
|
27
|
+
"!android/local.properties",
|
|
28
|
+
"!**/__tests__",
|
|
29
|
+
"!**/__fixtures__",
|
|
30
|
+
"!**/__mocks__",
|
|
31
|
+
"!**/.*"
|
|
13
32
|
],
|
|
14
|
-
"author": "",
|
|
15
|
-
"license": "ISC",
|
|
16
33
|
"dependencies": {
|
|
17
34
|
"@react-native-google-signin/google-signin": "^14.0.1",
|
|
18
35
|
"@react-native-firebase/app": "^22.2.0",
|
|
@@ -26,9 +43,9 @@
|
|
|
26
43
|
"react-native-paper": "^5.14.3",
|
|
27
44
|
"react-native-svg": "15.11.2",
|
|
28
45
|
"zustand": "^4.3.3",
|
|
29
|
-
"@chem-po/
|
|
30
|
-
"@chem-po/
|
|
31
|
-
"@chem-po/react": "0.0.
|
|
46
|
+
"@chem-po/react": "0.0.17",
|
|
47
|
+
"@chem-po/core": "0.0.17",
|
|
48
|
+
"@chem-po/react-native": "0.0.17"
|
|
32
49
|
},
|
|
33
50
|
"devDependencies": {
|
|
34
51
|
"@babel/core": "^7.26.0",
|
|
@@ -69,7 +86,7 @@
|
|
|
69
86
|
"lint": "pnpm type-check && eslint . --fix",
|
|
70
87
|
"build": "bob build",
|
|
71
88
|
"prebuild": "pnpm lint",
|
|
72
|
-
"clean": "rm -rf
|
|
89
|
+
"clean": "rm -rf lib"
|
|
73
90
|
},
|
|
74
|
-
"module": "
|
|
91
|
+
"module": "lib/module/index.js"
|
|
75
92
|
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthAdapter,
|
|
3
|
+
BaseAuthProvider,
|
|
4
|
+
BaseUserData,
|
|
5
|
+
BaseUserRole,
|
|
6
|
+
EnrollmentFactor,
|
|
7
|
+
EnrollmentFactorsResult,
|
|
8
|
+
LoginResult,
|
|
9
|
+
MultiFactorVerification,
|
|
10
|
+
WithMultiFactorVerified,
|
|
11
|
+
} from '@chem-po/core'
|
|
12
|
+
import {
|
|
13
|
+
FirebaseAuthTypes,
|
|
14
|
+
getIdTokenResult,
|
|
15
|
+
getMultiFactorResolver,
|
|
16
|
+
GoogleAuthProvider,
|
|
17
|
+
onAuthStateChanged,
|
|
18
|
+
PhoneAuthProvider,
|
|
19
|
+
PhoneMultiFactorGenerator,
|
|
20
|
+
sendPasswordResetEmail,
|
|
21
|
+
signInWithCredential,
|
|
22
|
+
signInWithCustomToken,
|
|
23
|
+
signInWithEmailAndPassword,
|
|
24
|
+
signOut,
|
|
25
|
+
} from '@react-native-firebase/auth'
|
|
26
|
+
|
|
27
|
+
import { GoogleSignin } from '@react-native-google-signin/google-signin'
|
|
28
|
+
import { Auth, User } from '../types/auth'
|
|
29
|
+
|
|
30
|
+
const providerInitialized: Record<string, boolean> = {}
|
|
31
|
+
|
|
32
|
+
const getUserWithRole = async <UserData extends BaseUserData>(
|
|
33
|
+
user: FirebaseAuthTypes.User
|
|
34
|
+
): Promise<WithMultiFactorVerified<UserData>> => {
|
|
35
|
+
const { claims } = await getIdTokenResult(user)
|
|
36
|
+
return {
|
|
37
|
+
...(user as any),
|
|
38
|
+
role: claims.role ?? ('user' as BaseUserRole),
|
|
39
|
+
multiFactorVerified: !!user.multiFactor?.enrolledFactors.length
|
|
40
|
+
} as WithMultiFactorVerified<UserData>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const toEnrollmentFactor = (hint: FirebaseAuthTypes.MultiFactorInfo): EnrollmentFactor => {
|
|
44
|
+
if (hint.factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
|
|
45
|
+
return {
|
|
46
|
+
type: 'phone',
|
|
47
|
+
phoneNumber: (hint as FirebaseAuthTypes.PhoneMultiFactorInfo).phoneNumber,
|
|
48
|
+
enrollmentTime: hint.enrollmentTime,
|
|
49
|
+
displayName: (hint as FirebaseAuthTypes.PhoneMultiFactorInfo).displayName,
|
|
50
|
+
uid: hint.uid,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (hint.factorId === 'totp') {
|
|
54
|
+
return {
|
|
55
|
+
type: 'totp',
|
|
56
|
+
enrollmentTime: hint.enrollmentTime,
|
|
57
|
+
displayName: hint.displayName,
|
|
58
|
+
uid: hint.uid,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw new Error('Unsupported factor type: ' + hint.factorId)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const toFirebaseFactor = (factor: EnrollmentFactor): FirebaseAuthTypes.MultiFactorInfo => {
|
|
65
|
+
if (factor.type === 'phone') {
|
|
66
|
+
return {
|
|
67
|
+
factorId: PhoneMultiFactorGenerator.FACTOR_ID,
|
|
68
|
+
phoneNumber: factor.phoneNumber,
|
|
69
|
+
enrollmentTime: factor.enrollmentTime,
|
|
70
|
+
uid: factor.uid,
|
|
71
|
+
displayName: factor.displayName,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (factor.type === 'totp') {
|
|
75
|
+
return {
|
|
76
|
+
factorId: 'totp',
|
|
77
|
+
enrollmentTime: factor.enrollmentTime,
|
|
78
|
+
uid: factor.uid,
|
|
79
|
+
displayName: factor.displayName,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Unsupported factor type: ${(factor as FirebaseAuthTypes.MultiFactorInfo).factorId ?? 'Missing factor type'}`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sendMultiFactorCode = async (
|
|
88
|
+
auth: Auth,
|
|
89
|
+
factor: EnrollmentFactor,
|
|
90
|
+
resolver: FirebaseAuthTypes.MultiFactorResolver
|
|
91
|
+
): Promise<MultiFactorVerification> => {
|
|
92
|
+
const sessionId = resolver.session
|
|
93
|
+
// const verificationId = await new PhoneAuthProvider(auth).verifyPhoneNumber(phoneSignInFactor, sessionId)
|
|
94
|
+
if (factor.type === 'phone') {
|
|
95
|
+
const verificationId = await auth.verifyPhoneNumberWithMultiFactorInfo(
|
|
96
|
+
toFirebaseFactor(factor),
|
|
97
|
+
sessionId
|
|
98
|
+
)
|
|
99
|
+
return {
|
|
100
|
+
verificationId,
|
|
101
|
+
factor,
|
|
102
|
+
resolver,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Unsupported factor type: ${factor.type ?? 'Missing factor type'}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const getEnrolledFactors = async (auth: Auth, error: any): Promise<EnrollmentFactorsResult> => {
|
|
109
|
+
const resolver = getMultiFactorResolver(auth, error)
|
|
110
|
+
if (resolver.hints.length === 0) {
|
|
111
|
+
throw new Error('No multi-factor verification methods found, please enroll one on the website')
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
enrollmentFactors: resolver.hints.map(toEnrollmentFactor),
|
|
115
|
+
multiFactorResolver: resolver,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const verifyMultiFactor = async (
|
|
120
|
+
verification: MultiFactorVerification,
|
|
121
|
+
code: string
|
|
122
|
+
): Promise<LoginResult<User>> => {
|
|
123
|
+
const credential = await PhoneAuthProvider.credential(verification.verificationId, code)
|
|
124
|
+
const assertion = PhoneMultiFactorGenerator.assertion(credential)
|
|
125
|
+
const resolver = (verification as any).resolver
|
|
126
|
+
if (!resolver) throw new Error('Internal error signing in with two factor: resolver not found')
|
|
127
|
+
const userCredential = await resolver.resolveSignIn(assertion)
|
|
128
|
+
const user = userCredential.user
|
|
129
|
+
if (!user) throw new Error('No user found')
|
|
130
|
+
return { user: await getUserWithRole(user) }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const handleSignInError = async (error: any): Promise<LoginResult<User>> => {
|
|
134
|
+
if (error.code === 'auth/multi-factor-auth-required') {
|
|
135
|
+
return { requestArgs: error }
|
|
136
|
+
}
|
|
137
|
+
throw error
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const initializeProvider = async (provider: BaseAuthProvider) => {
|
|
141
|
+
if (providerInitialized[provider.name]) return
|
|
142
|
+
switch (provider.name) {
|
|
143
|
+
case 'google':
|
|
144
|
+
if (!(provider as GoogleAuthProvider).webClientId) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'Google web client ID is required when using Google Auth. Refer to react native firebase docs for more information.'
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true })
|
|
150
|
+
GoogleSignin.configure({
|
|
151
|
+
webClientId: (provider as any).webClientId,
|
|
152
|
+
})
|
|
153
|
+
break
|
|
154
|
+
case 'email':
|
|
155
|
+
// Native SDK doesn't need special initialization
|
|
156
|
+
break
|
|
157
|
+
default:
|
|
158
|
+
throw new Error(`Unsupported provider: ${provider.name}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
providerInitialized[provider.name] = true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const handleInitialLogin = async (
|
|
165
|
+
userCredential: FirebaseAuthTypes.UserCredential,
|
|
166
|
+
twoFactorRequired: boolean
|
|
167
|
+
): Promise<LoginResult<User>> => {
|
|
168
|
+
const user = userCredential.user
|
|
169
|
+
if (!user) throw new Error('No user found')
|
|
170
|
+
if (twoFactorRequired) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
'This app requires two factor authentication, please enroll a factor on the website and try again'
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
return { user: await getUserWithRole(user) }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const loginWithGoogle = async (
|
|
179
|
+
auth: Auth,
|
|
180
|
+
twoFactorRequired: boolean
|
|
181
|
+
): Promise<LoginResult<User>> => {
|
|
182
|
+
try {
|
|
183
|
+
const signInResult = await GoogleSignin.signIn()
|
|
184
|
+
const idToken = signInResult.data?.idToken
|
|
185
|
+
if (!idToken) {
|
|
186
|
+
throw new Error('No ID token found')
|
|
187
|
+
}
|
|
188
|
+
const googleCredential = GoogleAuthProvider.credential(idToken)
|
|
189
|
+
const userCredential = await signInWithCredential(auth, googleCredential)
|
|
190
|
+
return await handleInitialLogin(userCredential, twoFactorRequired)
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return await handleSignInError(error)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const getLoginWithPassword =
|
|
197
|
+
(auth: Auth, twoFactorRequired: boolean) =>
|
|
198
|
+
async (
|
|
199
|
+
provider: BaseAuthProvider,
|
|
200
|
+
{ email, password }: { email: string; password: string }
|
|
201
|
+
): Promise<LoginResult<User>> => {
|
|
202
|
+
await initializeProvider(provider)
|
|
203
|
+
try {
|
|
204
|
+
const userCredential = await signInWithEmailAndPassword(auth, email, password)
|
|
205
|
+
return await handleInitialLogin(userCredential, twoFactorRequired)
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return await handleSignInError(error)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const getLoginWithPopup =
|
|
212
|
+
(auth: Auth, twoFactorRequired: boolean) =>
|
|
213
|
+
async (provider: BaseAuthProvider): Promise<LoginResult<User>> => {
|
|
214
|
+
await initializeProvider(provider)
|
|
215
|
+
switch (provider.name) {
|
|
216
|
+
case 'google':
|
|
217
|
+
return loginWithGoogle(auth, twoFactorRequired)
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(`Unsupported provider: ${provider.name}`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const getLoginWithToken =
|
|
224
|
+
(auth: Auth, twoFactorRequired: boolean) =>
|
|
225
|
+
async (provider: BaseAuthProvider, token: string): Promise<LoginResult<User>> => {
|
|
226
|
+
await initializeProvider(provider)
|
|
227
|
+
try {
|
|
228
|
+
const userCredential = await signInWithCustomToken(auth, token)
|
|
229
|
+
return await handleInitialLogin(userCredential, twoFactorRequired)
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return await handleSignInError(error)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const resetPassword = async (
|
|
236
|
+
auth: Auth,
|
|
237
|
+
provider: BaseAuthProvider,
|
|
238
|
+
usernameOrEmail: string
|
|
239
|
+
): Promise<void> => {
|
|
240
|
+
await initializeProvider(provider)
|
|
241
|
+
await sendPasswordResetEmail(auth, usernameOrEmail)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const getFirebaseAuthAdapter = (
|
|
245
|
+
auth: Auth,
|
|
246
|
+
twoFactorRequired: boolean
|
|
247
|
+
): AuthAdapter<BaseAuthProvider, User, { email: string; password: string }> => ({
|
|
248
|
+
twoFactorRequired,
|
|
249
|
+
getCurrentUser: async () => {
|
|
250
|
+
const user = auth.currentUser
|
|
251
|
+
if (!user) return null
|
|
252
|
+
return getUserWithRole(user)
|
|
253
|
+
},
|
|
254
|
+
loginWithPassword: getLoginWithPassword(auth, twoFactorRequired),
|
|
255
|
+
loginWithPopup: getLoginWithPopup(auth, twoFactorRequired),
|
|
256
|
+
loginWithToken: getLoginWithToken(auth, twoFactorRequired),
|
|
257
|
+
resetPassword: (...args) => {
|
|
258
|
+
return resetPassword(auth, ...args)
|
|
259
|
+
},
|
|
260
|
+
verifyMultiFactor,
|
|
261
|
+
sendMultiFactorCode: (...args) => {
|
|
262
|
+
return sendMultiFactorCode(auth, ...args)
|
|
263
|
+
},
|
|
264
|
+
getEnrolledFactors: (...args) => {
|
|
265
|
+
return getEnrolledFactors(auth, ...args)
|
|
266
|
+
},
|
|
267
|
+
logout: async () => {
|
|
268
|
+
await signOut(auth)
|
|
269
|
+
},
|
|
270
|
+
subscribeToUser: (callback) => {
|
|
271
|
+
return onAuthStateChanged(auth, (user) => {
|
|
272
|
+
if (!user) {
|
|
273
|
+
callback(null)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
getUserWithRole(user).then((result) => {
|
|
277
|
+
callback(result)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
},
|
|
281
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addMetadata,
|
|
3
|
+
AnyObject,
|
|
4
|
+
BaseError,
|
|
5
|
+
DatabaseAdapter,
|
|
6
|
+
DBItem,
|
|
7
|
+
OnError,
|
|
8
|
+
OnItemData,
|
|
9
|
+
Unsubscribe,
|
|
10
|
+
WithMetadata,
|
|
11
|
+
} from '@chem-po/core'
|
|
12
|
+
import { useAuth } from '@chem-po/react'
|
|
13
|
+
import {
|
|
14
|
+
deleteDoc,
|
|
15
|
+
doc,
|
|
16
|
+
FirebaseFirestoreTypes,
|
|
17
|
+
getDoc,
|
|
18
|
+
getDocs,
|
|
19
|
+
onSnapshot,
|
|
20
|
+
setDoc,
|
|
21
|
+
} from '@react-native-firebase/firestore'
|
|
22
|
+
import { httpsCallable } from '@react-native-firebase/functions'
|
|
23
|
+
import { toCursorQuery, toFirestoreQuery } from '../db/utils'
|
|
24
|
+
import { Firestore, FirestoreBaseQuery, FirestoreCursor, FirestoreOnItemsData } from '../types/db'
|
|
25
|
+
import { Functions } from '../types/functions'
|
|
26
|
+
// import { BaseQuery, FirestoreCursor } from '../types/db'
|
|
27
|
+
|
|
28
|
+
const handleFirestoreError = (error: any): BaseError => ({
|
|
29
|
+
code: error?.code ?? 'unknown',
|
|
30
|
+
message: error?.name ?? 'Unknown Error',
|
|
31
|
+
description: error?.message ?? 'An unknown error occurred',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const toDBItem = <T extends AnyObject>(
|
|
35
|
+
doc: FirebaseFirestoreTypes.DocumentSnapshot<T>,
|
|
36
|
+
): DBItem<T> => ({
|
|
37
|
+
...(doc.data() as WithMetadata<T>),
|
|
38
|
+
id: doc.id,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const getSubscribeToQuery =
|
|
42
|
+
(db: Firestore) =>
|
|
43
|
+
<T extends AnyObject>(
|
|
44
|
+
baseQuery: FirestoreBaseQuery<T>,
|
|
45
|
+
onData: FirestoreOnItemsData<T>,
|
|
46
|
+
onError: OnError,
|
|
47
|
+
): Unsubscribe => {
|
|
48
|
+
const q = toFirestoreQuery<T>(db, baseQuery)
|
|
49
|
+
return onSnapshot(
|
|
50
|
+
q,
|
|
51
|
+
snapshot => {
|
|
52
|
+
const items = snapshot.docs.map(doc => toDBItem(doc))
|
|
53
|
+
onData(items, snapshot.docs[snapshot.docs.length - 1] as FirestoreCursor)
|
|
54
|
+
},
|
|
55
|
+
error => onError(handleFirestoreError(error)),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getFetchNextCursor =
|
|
60
|
+
(db: Firestore) =>
|
|
61
|
+
async <T extends AnyObject>(
|
|
62
|
+
baseQuery: FirestoreBaseQuery<T>,
|
|
63
|
+
): Promise<FirestoreCursor | null> => {
|
|
64
|
+
const { cursor } = baseQuery
|
|
65
|
+
const cursorDoc = cursor ? await getDoc(doc(db, baseQuery.collection, cursor.id)) : null
|
|
66
|
+
const q = toCursorQuery<T>(db, baseQuery, cursorDoc as FirestoreCursor | null)
|
|
67
|
+
const snapshot = await getDocs(q)
|
|
68
|
+
return (snapshot.docs[0] ?? null) as FirestoreCursor | null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const getFetchCount =
|
|
72
|
+
(functions: Functions) =>
|
|
73
|
+
async <T extends AnyObject>(baseQuery: FirestoreBaseQuery<T>): Promise<number> => {
|
|
74
|
+
const fetchCountFunc = httpsCallable(functions, 'getQueryCount')
|
|
75
|
+
const { data } = await fetchCountFunc(baseQuery)
|
|
76
|
+
return (data as { count: number }).count
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const getFetchItem =
|
|
80
|
+
(db: Firestore) =>
|
|
81
|
+
async <T extends AnyObject>(path: string): Promise<DBItem<T> | null> => {
|
|
82
|
+
const docRef = doc(db, path)
|
|
83
|
+
const docSnap = await getDoc(docRef)
|
|
84
|
+
const data = docSnap.data()
|
|
85
|
+
return data ? ({ ...data, id: docSnap.id } as DBItem<T>) : null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const getFetchItems =
|
|
89
|
+
(db: Firestore) =>
|
|
90
|
+
async <T extends AnyObject>(baseQuery: FirestoreBaseQuery<T>): Promise<DBItem<T>[]> => {
|
|
91
|
+
const q = toFirestoreQuery<T>(db, baseQuery)
|
|
92
|
+
const snapshot = await getDocs(q)
|
|
93
|
+
return snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }) as DBItem<T>)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const getCreateItem =
|
|
97
|
+
(db: Firestore) =>
|
|
98
|
+
async (path: string, item: AnyObject): Promise<string> => {
|
|
99
|
+
const docRef = doc(db, path)
|
|
100
|
+
const data = addMetadata(useAuth.getState().user, item, true)
|
|
101
|
+
await setDoc(docRef, data, { merge: true })
|
|
102
|
+
return docRef.id
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const getDeleteItem =
|
|
106
|
+
(db: Firestore) =>
|
|
107
|
+
async (path: string): Promise<void> => {
|
|
108
|
+
const docRef = doc(db, path)
|
|
109
|
+
await deleteDoc(docRef)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const getUpdateItem =
|
|
113
|
+
(db: Firestore) =>
|
|
114
|
+
async <T extends AnyObject>(path: string, item: Partial<T>): Promise<void> => {
|
|
115
|
+
const docRef = doc(db, path)
|
|
116
|
+
await setDoc(docRef, item, { merge: true })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const getSubscribeToItem =
|
|
120
|
+
(db: Firestore) =>
|
|
121
|
+
<T extends AnyObject>(path: string, onData: OnItemData<T>, onError: OnError): Unsubscribe => {
|
|
122
|
+
const docRef = doc(db, path)
|
|
123
|
+
return onSnapshot(
|
|
124
|
+
docRef,
|
|
125
|
+
snapshot => {
|
|
126
|
+
const data = snapshot.data()
|
|
127
|
+
onData(data ? ({ ...data, id: snapshot.id } as DBItem<T>) : null)
|
|
128
|
+
},
|
|
129
|
+
error => onError(handleFirestoreError(error)),
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const getFirebaseDatabaseAdapter = (
|
|
134
|
+
db: Firestore,
|
|
135
|
+
functions: Functions,
|
|
136
|
+
): DatabaseAdapter<FirestoreCursor> => ({
|
|
137
|
+
fetchNextCursor: getFetchNextCursor(db),
|
|
138
|
+
fetchCount: getFetchCount(functions),
|
|
139
|
+
fetchItem: getFetchItem(db),
|
|
140
|
+
fetchItems: getFetchItems(db),
|
|
141
|
+
createItem: getCreateItem(db),
|
|
142
|
+
deleteItem: getDeleteItem(db),
|
|
143
|
+
updateItem: getUpdateItem(db),
|
|
144
|
+
subscribeToItem: getSubscribeToItem(db),
|
|
145
|
+
subscribeToQuery: getSubscribeToQuery(db),
|
|
146
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BackendAdapter, BaseAuthProvider } from '@chem-po/core'
|
|
2
|
+
import { FirebaseStorageTypes } from '@react-native-firebase/storage'
|
|
3
|
+
import { Auth, EmailPasswordLogin, User } from '../types/auth'
|
|
4
|
+
import { Firestore, FirestoreCursor } from '../types/db'
|
|
5
|
+
import { Functions } from '../types/functions'
|
|
6
|
+
import { Storage } from '../types/storage'
|
|
7
|
+
import { getFirebaseAuthAdapter } from './auth'
|
|
8
|
+
import { getFirebaseDatabaseAdapter } from './db'
|
|
9
|
+
import { getFirebaseStorageAdapter } from './storage'
|
|
10
|
+
|
|
11
|
+
export type FirebaseAdapter<AuthProvider extends BaseAuthProvider> = BackendAdapter<
|
|
12
|
+
AuthProvider,
|
|
13
|
+
User,
|
|
14
|
+
EmailPasswordLogin,
|
|
15
|
+
FirestoreCursor,
|
|
16
|
+
Blob,
|
|
17
|
+
FirebaseStorageTypes.FullMetadata
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
export const getFirebaseAdapter = <AuthProvider extends BaseAuthProvider>(
|
|
21
|
+
auth: Auth,
|
|
22
|
+
db: Firestore,
|
|
23
|
+
storage: Storage,
|
|
24
|
+
functions: Functions,
|
|
25
|
+
twoFactorRequired: boolean,
|
|
26
|
+
): FirebaseAdapter<AuthProvider> => ({
|
|
27
|
+
auth: getFirebaseAuthAdapter(auth, twoFactorRequired),
|
|
28
|
+
db: getFirebaseDatabaseAdapter(db, functions),
|
|
29
|
+
storage: getFirebaseStorageAdapter(storage),
|
|
30
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { StorageAdapter, UploadedFileValue } from '@chem-po/core'
|
|
2
|
+
import {
|
|
3
|
+
deleteObject,
|
|
4
|
+
getDownloadURL,
|
|
5
|
+
getMetadata,
|
|
6
|
+
ref,
|
|
7
|
+
uploadBytesResumable,
|
|
8
|
+
} from '@react-native-firebase/storage'
|
|
9
|
+
import { Storage } from '../types/storage'
|
|
10
|
+
|
|
11
|
+
export const getFirebaseStorageAdapter = (storage: Storage): StorageAdapter<Blob, any> => ({
|
|
12
|
+
delete: async path => {
|
|
13
|
+
const storageRef = ref(storage, path)
|
|
14
|
+
await deleteObject(storageRef)
|
|
15
|
+
},
|
|
16
|
+
fetchObject: async path => {
|
|
17
|
+
const storageRef = ref(storage, path)
|
|
18
|
+
const url = await getDownloadURL(storageRef)
|
|
19
|
+
const response = await fetch(url)
|
|
20
|
+
return await response.blob()
|
|
21
|
+
},
|
|
22
|
+
fetchMetadata: async path => {
|
|
23
|
+
const storageRef = ref(storage, path)
|
|
24
|
+
return await getMetadata(storageRef)
|
|
25
|
+
},
|
|
26
|
+
getObjectUrl: async path => {
|
|
27
|
+
const storageRef = ref(storage, path)
|
|
28
|
+
return await getDownloadURL(storageRef)
|
|
29
|
+
},
|
|
30
|
+
upload: async (path, data, onUploadProgress) => {
|
|
31
|
+
const storageRef = ref(storage, path)
|
|
32
|
+
// Convert dataUrl to Blob
|
|
33
|
+
const blob = await fetch(data.dataUrl).then(res => res.blob())
|
|
34
|
+
const uploadTask = uploadBytesResumable(storageRef, blob)
|
|
35
|
+
return new Promise<UploadedFileValue>((resolve, reject) => {
|
|
36
|
+
uploadTask.on(
|
|
37
|
+
'state_changed',
|
|
38
|
+
snapshot => {
|
|
39
|
+
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
|
|
40
|
+
onUploadProgress({
|
|
41
|
+
loaded: snapshot.bytesTransferred,
|
|
42
|
+
total: snapshot.totalBytes,
|
|
43
|
+
percent: progress,
|
|
44
|
+
})
|
|
45
|
+
},
|
|
46
|
+
error => reject(error),
|
|
47
|
+
() => {
|
|
48
|
+
const fileValue: UploadedFileValue = {
|
|
49
|
+
filename: data.filename,
|
|
50
|
+
type: data.type,
|
|
51
|
+
storagePath: path,
|
|
52
|
+
}
|
|
53
|
+
resolve(fileValue)
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FirebaseFunctionsTypes } from '@react-native-firebase/functions'
|
|
2
|
+
|
|
3
|
+
export const getThirdPartyAuthUrl = (functions: FirebaseFunctionsTypes.Module) =>
|
|
4
|
+
functions.httpsCallable('getThirdPartyAuthUrl')
|
|
5
|
+
|
|
6
|
+
export const getThirdPartyAuthToken = (functions: FirebaseFunctionsTypes.Module) =>
|
|
7
|
+
functions.httpsCallable('getThirdPartyAuthToken')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './functions'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { LoadingButton, Txt } from '@chem-po/react-native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { StyleSheet, TextInput, View } from 'react-native'
|
|
4
|
+
import { useAuthenticatorVerify } from '../hooks/useAuthenticatorVerify'
|
|
5
|
+
|
|
6
|
+
export const AuthenticatorVerify = () => {
|
|
7
|
+
const { code, setCode, verifying, error, handleVerify } = useAuthenticatorVerify()
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<View style={styles.wrapper}>
|
|
11
|
+
<View style={styles.container}>
|
|
12
|
+
<Txt style={styles.text}>Enter the code on your authenticator app:</Txt>
|
|
13
|
+
<TextInput
|
|
14
|
+
style={styles.input}
|
|
15
|
+
value={code}
|
|
16
|
+
onChangeText={setCode}
|
|
17
|
+
placeholder="Verification Code"
|
|
18
|
+
keyboardType="number-pad"
|
|
19
|
+
maxLength={6}
|
|
20
|
+
autoFocus
|
|
21
|
+
/>
|
|
22
|
+
{error ? <Txt style={styles.errorText}>{error}</Txt> : null}
|
|
23
|
+
<LoadingButton style={styles.button} onPress={handleVerify} disabled={verifying}>
|
|
24
|
+
<Txt style={styles.buttonText}>{verifying ? 'Verifying...' : 'Verify'}</Txt>
|
|
25
|
+
</LoadingButton>
|
|
26
|
+
</View>
|
|
27
|
+
</View>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const styles = StyleSheet.create({
|
|
32
|
+
wrapper: {
|
|
33
|
+
flex: 1,
|
|
34
|
+
padding: 16,
|
|
35
|
+
},
|
|
36
|
+
container: {
|
|
37
|
+
flex: 1,
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
gap: 16,
|
|
41
|
+
},
|
|
42
|
+
text: {
|
|
43
|
+
fontSize: 16,
|
|
44
|
+
textAlign: 'center',
|
|
45
|
+
marginBottom: 8,
|
|
46
|
+
},
|
|
47
|
+
input: {
|
|
48
|
+
width: '100%',
|
|
49
|
+
height: 48,
|
|
50
|
+
borderWidth: 1,
|
|
51
|
+
borderColor: '#ccc',
|
|
52
|
+
borderRadius: 8,
|
|
53
|
+
paddingHorizontal: 16,
|
|
54
|
+
fontSize: 16,
|
|
55
|
+
marginBottom: 8,
|
|
56
|
+
},
|
|
57
|
+
button: {
|
|
58
|
+
backgroundColor: '#007AFF',
|
|
59
|
+
paddingHorizontal: 24,
|
|
60
|
+
paddingVertical: 12,
|
|
61
|
+
borderRadius: 8,
|
|
62
|
+
minWidth: 200,
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
},
|
|
65
|
+
buttonText: {
|
|
66
|
+
color: 'white',
|
|
67
|
+
fontSize: 16,
|
|
68
|
+
fontWeight: '600',
|
|
69
|
+
},
|
|
70
|
+
errorText: {
|
|
71
|
+
color: 'red',
|
|
72
|
+
fontSize: 14,
|
|
73
|
+
marginBottom: 8,
|
|
74
|
+
},
|
|
75
|
+
})
|