refactor: monorepo+pnpm (removed husky)
This commit is contained in:
285
packages/hoppscotch-app/helpers/fb/auth.ts
Normal file
285
packages/hoppscotch-app/helpers/fb/auth.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
import {
|
||||
User,
|
||||
getAuth,
|
||||
onAuthStateChanged,
|
||||
onIdTokenChanged,
|
||||
signInWithPopup,
|
||||
GoogleAuthProvider,
|
||||
GithubAuthProvider,
|
||||
signInWithEmailAndPassword as signInWithEmailAndPass,
|
||||
isSignInWithEmailLink as isSignInWithEmailLinkFB,
|
||||
fetchSignInMethodsForEmail,
|
||||
sendSignInLinkToEmail,
|
||||
signInWithEmailLink as signInWithEmailLinkFB,
|
||||
ActionCodeSettings,
|
||||
signOut,
|
||||
linkWithCredential,
|
||||
AuthCredential,
|
||||
UserCredential,
|
||||
} from "firebase/auth"
|
||||
import {
|
||||
onSnapshot,
|
||||
getFirestore,
|
||||
setDoc,
|
||||
doc,
|
||||
updateDoc,
|
||||
} from "firebase/firestore"
|
||||
import {
|
||||
BehaviorSubject,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
Subject,
|
||||
Subscription,
|
||||
} from "rxjs"
|
||||
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
|
||||
|
||||
export type HoppUser = User & {
|
||||
provider?: string
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
type AuthEvents =
|
||||
| { event: "login"; user: HoppUser }
|
||||
| { event: "logout" }
|
||||
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null }
|
||||
|
||||
/**
|
||||
* A BehaviorSubject emitting the currently logged in user (or null if not logged in)
|
||||
*/
|
||||
export const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
|
||||
/**
|
||||
* A BehaviorSubject emitting the current idToken
|
||||
*/
|
||||
export const authIdToken$ = new BehaviorSubject<string | null>(null)
|
||||
|
||||
/**
|
||||
* A subject that emits events related to authentication flows
|
||||
*/
|
||||
export const authEvents$ = new Subject<AuthEvents>()
|
||||
|
||||
/**
|
||||
* Initializes the firebase authentication related subjects
|
||||
*/
|
||||
export function initAuth() {
|
||||
const auth = getAuth()
|
||||
const firestore = getFirestore()
|
||||
|
||||
let extraSnapshotStop: (() => void) | null = null
|
||||
|
||||
onAuthStateChanged(auth, (user) => {
|
||||
/** Whether the user was logged in before */
|
||||
const wasLoggedIn = currentUser$.value !== null
|
||||
|
||||
if (!user && extraSnapshotStop) {
|
||||
extraSnapshotStop()
|
||||
extraSnapshotStop = null
|
||||
} else if (user) {
|
||||
// Merge all the user info from all the authenticated providers
|
||||
user.providerData.forEach((profile) => {
|
||||
if (!profile) return
|
||||
|
||||
const us = {
|
||||
updatedOn: new Date(),
|
||||
provider: profile.providerId,
|
||||
name: profile.displayName,
|
||||
email: profile.email,
|
||||
photoUrl: profile.photoURL,
|
||||
uid: profile.uid,
|
||||
}
|
||||
|
||||
setDoc(doc(firestore, "users", user.uid), us, { merge: true }).catch(
|
||||
(e) => console.error("error updating", us, e)
|
||||
)
|
||||
})
|
||||
|
||||
extraSnapshotStop = onSnapshot(
|
||||
doc(firestore, "users", user.uid),
|
||||
(doc) => {
|
||||
const data = doc.data()
|
||||
|
||||
const userUpdate: HoppUser = user
|
||||
|
||||
if (data) {
|
||||
// Write extra provider data
|
||||
userUpdate.provider = data.provider
|
||||
userUpdate.accessToken = data.accessToken
|
||||
}
|
||||
|
||||
currentUser$.next(userUpdate)
|
||||
}
|
||||
)
|
||||
}
|
||||
currentUser$.next(user)
|
||||
|
||||
// User wasn't found before, but now is there (login happened)
|
||||
if (!wasLoggedIn && user) {
|
||||
authEvents$.next({
|
||||
event: "login",
|
||||
user: currentUser$.value!!,
|
||||
})
|
||||
} else if (wasLoggedIn && !user) {
|
||||
// User was found before, but now is not there (logout happened)
|
||||
authEvents$.next({
|
||||
event: "logout",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onIdTokenChanged(auth, async (user) => {
|
||||
if (user) {
|
||||
authIdToken$.next(await user.getIdToken())
|
||||
|
||||
authEvents$.next({
|
||||
event: "authTokenUpdate",
|
||||
newToken: authIdToken$.value,
|
||||
user: currentUser$.value!!, // Force not-null because user is defined
|
||||
})
|
||||
} else {
|
||||
authIdToken$.next(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign user in with a popup using Google
|
||||
*/
|
||||
export async function signInUserWithGoogle() {
|
||||
return await signInWithPopup(getAuth(), new GoogleAuthProvider())
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign user in with a popup using Github
|
||||
*/
|
||||
export async function signInUserWithGithub() {
|
||||
return await signInWithPopup(
|
||||
getAuth(),
|
||||
new GithubAuthProvider().addScope("gist")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign user in with email and password
|
||||
*/
|
||||
export async function signInWithEmailAndPassword(
|
||||
email: string,
|
||||
password: string
|
||||
) {
|
||||
return await signInWithEmailAndPass(getAuth(), email, password)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sign in methods for a given email address
|
||||
*
|
||||
* @param email - Email to get the methods of
|
||||
*
|
||||
* @returns Promise for string array of the auth provider methods accessible
|
||||
*/
|
||||
export async function getSignInMethodsForEmail(email: string) {
|
||||
return await fetchSignInMethodsForEmail(getAuth(), email)
|
||||
}
|
||||
|
||||
export async function linkWithFBCredential(
|
||||
user: User,
|
||||
credential: AuthCredential
|
||||
) {
|
||||
return await linkWithCredential(user, credential)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email with the signin link to the user
|
||||
*
|
||||
* @param email - Email to send the email to
|
||||
* @param actionCodeSettings - The settings to apply to the link
|
||||
*/
|
||||
export async function signInWithEmail(
|
||||
email: string,
|
||||
actionCodeSettings: ActionCodeSettings
|
||||
) {
|
||||
return await sendSignInLinkToEmail(getAuth(), email, actionCodeSettings)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and returns whether the sign in link is an email link
|
||||
*
|
||||
* @param url - The URL to look in
|
||||
*/
|
||||
export function isSignInWithEmailLink(url: string) {
|
||||
return isSignInWithEmailLinkFB(getAuth(), url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email with sign in with email link
|
||||
*
|
||||
* @param email - Email to log in to
|
||||
* @param url - The action URL which is used to validate login
|
||||
*/
|
||||
export async function signInWithEmailLink(email: string, url: string) {
|
||||
return await signInWithEmailLinkFB(getAuth(), email, url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs out the user
|
||||
*/
|
||||
export async function signOutUser() {
|
||||
if (!currentUser$.value) throw new Error("No user has logged in")
|
||||
|
||||
await signOut(getAuth())
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider id and relevant provider auth token
|
||||
* as user metadata
|
||||
*
|
||||
* @param id - The provider ID
|
||||
* @param token - The relevant auth token for the given provider
|
||||
*/
|
||||
export async function setProviderInfo(id: string, token: string) {
|
||||
if (!currentUser$.value) throw new Error("No user has logged in")
|
||||
|
||||
const us = {
|
||||
updatedOn: new Date(),
|
||||
provider: id,
|
||||
accessToken: token,
|
||||
}
|
||||
|
||||
try {
|
||||
await updateDoc(
|
||||
doc(getFirestore(), "users", currentUser$.value.uid),
|
||||
us
|
||||
).catch((e) => console.error("error updating", us, e))
|
||||
} catch (e) {
|
||||
console.error("error updating", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export function getGithubCredentialFromResult(result: UserCredential) {
|
||||
return GithubAuthProvider.credentialFromResult(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue composable function that is called when the auth status
|
||||
* is being updated to being logged in (fired multiple times),
|
||||
* this is also called on component mount if the login
|
||||
* was already resolved before mount.
|
||||
*/
|
||||
export function onLoggedIn(exec: (user: HoppUser) => void) {
|
||||
let sub: Subscription | null = null
|
||||
|
||||
onMounted(() => {
|
||||
sub = currentUser$
|
||||
.pipe(
|
||||
map((user) => !!user), // Get a logged in status (true or false)
|
||||
distinctUntilChanged(), // Don't propagate unless the status updates
|
||||
filter((x) => x) // Don't propagate unless it is logged in
|
||||
)
|
||||
.subscribe(() => {
|
||||
exec(currentUser$.value!)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
sub?.unsubscribe()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user