329 lines
8.5 KiB
Vue
329 lines
8.5 KiB
Vue
<template>
|
|
<HoppSmartModal
|
|
v-if="show"
|
|
dialog
|
|
:title="`${t('auth.login_to_hoppscotch')}`"
|
|
styles="sm:max-w-md"
|
|
@close="hideModal"
|
|
>
|
|
<template #body>
|
|
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
|
|
<HoppSmartItem
|
|
v-for="provider in allowedAuthProviders"
|
|
:key="provider.id"
|
|
:loading="provider.isLoading.value"
|
|
:icon="provider.icon"
|
|
:label="provider.label"
|
|
@click="provider.action"
|
|
/>
|
|
|
|
<hr v-if="additonalLoginItems.length > 0" />
|
|
|
|
<HoppSmartItem
|
|
v-for="loginItem in additonalLoginItems"
|
|
:key="loginItem.id"
|
|
:icon="loginItem.icon"
|
|
:label="loginItem.text(t)"
|
|
@click="doAdditionalLoginItemClickAction(loginItem)"
|
|
/>
|
|
</div>
|
|
<form
|
|
v-if="mode === 'email'"
|
|
class="flex flex-col space-y-2"
|
|
@submit.prevent="signInWithEmail"
|
|
>
|
|
<HoppSmartInput
|
|
v-model="form.email"
|
|
type="email"
|
|
placeholder=" "
|
|
:label="t('auth.email')"
|
|
input-styles="floating-input"
|
|
/>
|
|
|
|
<HoppButtonPrimary
|
|
:loading="signingInWithEmail"
|
|
type="submit"
|
|
:label="`${t('auth.send_magic_link')}`"
|
|
/>
|
|
</form>
|
|
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
|
|
<div class="flex flex-col items-center justify-center max-w-md">
|
|
<icon-lucide-inbox class="w-6 h-6 text-accent" />
|
|
<h3 class="my-2 text-lg text-center">
|
|
{{ t("auth.we_sent_magic_link") }}
|
|
</h3>
|
|
<p class="text-center">
|
|
{{
|
|
t("auth.we_sent_magic_link_description", { email: form.email })
|
|
}}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div
|
|
v-if="mode === 'sign-in' && tosLink && privacyPolicyLink"
|
|
class="text-secondaryLight text-tiny"
|
|
>
|
|
By signing in, you are agreeing to our
|
|
<HoppSmartAnchor
|
|
class="link"
|
|
:to="tosLink"
|
|
blank
|
|
label="Terms of Service"
|
|
/>
|
|
and
|
|
<HoppSmartAnchor
|
|
class="link"
|
|
:to="privacyPolicyLink"
|
|
blank
|
|
label="Privacy Policy"
|
|
/>
|
|
</div>
|
|
<div v-if="mode === 'email'">
|
|
<HoppButtonSecondary
|
|
:label="t('auth.all_sign_in_options')"
|
|
:icon="IconArrowLeft"
|
|
class="!p-0"
|
|
@click="mode = 'sign-in'"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="mode === 'email-sent'"
|
|
class="flex justify-between flex-1 text-secondaryLight"
|
|
>
|
|
<HoppSmartAnchor
|
|
class="link"
|
|
:label="t('auth.re_enter_email')"
|
|
:icon="IconArrowLeft"
|
|
@click="mode = 'email'"
|
|
/>
|
|
<HoppSmartAnchor
|
|
class="link"
|
|
:label="`${t('action.dismiss')}`"
|
|
@click="hideModal"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</HoppSmartModal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { Ref, computed, onMounted, ref } from "vue"
|
|
|
|
import { useStreamSubscriber } from "@composables/stream"
|
|
import { useToast } from "@composables/toast"
|
|
import { useI18n } from "@composables/i18n"
|
|
|
|
import { platform } from "~/platform"
|
|
import { setLocalConfig } from "~/newstore/localpersistence"
|
|
|
|
import IconGithub from "~icons/auth/github"
|
|
import IconGoogle from "~icons/auth/google"
|
|
import IconEmail from "~icons/auth/email"
|
|
import IconMicrosoft from "~icons/auth/microsoft"
|
|
import IconArrowLeft from "~icons/lucide/arrow-left"
|
|
|
|
import { LoginItemDef } from "~/platform/auth"
|
|
|
|
defineProps<{
|
|
show: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: "hide-modal"): void
|
|
}>()
|
|
|
|
const { subscribeToStream } = useStreamSubscriber()
|
|
const t = useI18n()
|
|
const toast = useToast()
|
|
|
|
const form = {
|
|
email: "",
|
|
}
|
|
|
|
const signingInWithGoogle = ref(false)
|
|
const signingInWithGitHub = ref(false)
|
|
const signingInWithMicrosoft = ref(false)
|
|
const signingInWithEmail = ref(false)
|
|
const mode = ref("sign-in")
|
|
|
|
const tosLink = import.meta.env.VITE_APP_TOS_LINK
|
|
const privacyPolicyLink = import.meta.env.VITE_APP_PRIVACY_POLICY_LINK
|
|
|
|
type AuthProviderItem = {
|
|
id: string
|
|
icon: typeof IconGithub
|
|
label: string
|
|
action: (...args: any[]) => any
|
|
isLoading: Ref<boolean>
|
|
}
|
|
|
|
const additonalLoginItems = computed(
|
|
() => platform.auth.additionalLoginItems ?? []
|
|
)
|
|
|
|
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
|
|
await item.onClick()
|
|
emit("hide-modal")
|
|
}
|
|
|
|
onMounted(() => {
|
|
const currentUser$ = platform.auth.getCurrentUserStream()
|
|
|
|
subscribeToStream(currentUser$, (user) => {
|
|
if (user) hideModal()
|
|
})
|
|
})
|
|
|
|
const showLoginSuccess = () => {
|
|
toast.success(`${t("auth.login_success")}`)
|
|
}
|
|
|
|
const signInWithGoogle = async () => {
|
|
signingInWithGoogle.value = true
|
|
|
|
try {
|
|
await platform.auth.signInUserWithGoogle()
|
|
} catch (e) {
|
|
console.error(e)
|
|
/*
|
|
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
|
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
|
*/
|
|
toast.error(`${t("error.something_went_wrong")}`)
|
|
}
|
|
|
|
signingInWithGoogle.value = false
|
|
}
|
|
|
|
const signInWithGithub = async () => {
|
|
signingInWithGitHub.value = true
|
|
|
|
const result = await platform.auth.signInUserWithGithub()
|
|
|
|
if (!result) {
|
|
signingInWithGitHub.value = false
|
|
return
|
|
}
|
|
|
|
if (result.type === "success") {
|
|
// this.showLoginSuccess()
|
|
} else if (result.type === "account-exists-with-different-cred") {
|
|
toast.info(`${t("auth.account_exists")}`, {
|
|
duration: 0,
|
|
closeOnSwipe: false,
|
|
action: {
|
|
text: `${t("action.yes")}`,
|
|
onClick: async (_, toastObject) => {
|
|
await result.link()
|
|
showLoginSuccess()
|
|
|
|
toastObject.goAway(0)
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
console.log("error logging into github", result.err)
|
|
toast.error(`${t("error.something_went_wrong")}`)
|
|
}
|
|
|
|
signingInWithGitHub.value = false
|
|
}
|
|
|
|
const signInWithMicrosoft = async () => {
|
|
signingInWithMicrosoft.value = true
|
|
|
|
try {
|
|
await platform.auth.signInUserWithMicrosoft()
|
|
// this.showLoginSuccess()
|
|
} catch (e) {
|
|
console.error(e)
|
|
/*
|
|
A auth/account-exists-with-different-credential Firebase error wont happen between MS with Google or Github
|
|
If a Github account exists and user then logs in with MS email we get a "Something went wrong toast" and console errors and MS replaces GH as only provider.
|
|
The error messages are as follows:
|
|
FirebaseError: Firebase: Error (auth/popup-closed-by-user).
|
|
@firebase/auth: Auth (9.6.11): INTERNAL ASSERTION FAILED: Pending promise was never set
|
|
They may be related to https://github.com/firebase/firebaseui-web/issues/947
|
|
*/
|
|
toast.error(`${t("error.something_went_wrong")}`)
|
|
}
|
|
|
|
signingInWithMicrosoft.value = false
|
|
}
|
|
|
|
const signInWithEmail = async () => {
|
|
signingInWithEmail.value = true
|
|
|
|
await platform.auth
|
|
.signInWithEmail(form.email)
|
|
.then(() => {
|
|
mode.value = "email-sent"
|
|
setLocalConfig("emailForSignIn", form.email)
|
|
})
|
|
.catch((e) => {
|
|
console.error(e)
|
|
toast.error(e.message)
|
|
signingInWithEmail.value = false
|
|
})
|
|
.finally(() => {
|
|
signingInWithEmail.value = false
|
|
})
|
|
}
|
|
|
|
const hideModal = () => {
|
|
mode.value = "sign-in"
|
|
toast.clear()
|
|
|
|
emit("hide-modal")
|
|
}
|
|
|
|
const authProviders: AuthProviderItem[] = [
|
|
{
|
|
id: "GITHUB",
|
|
icon: IconGithub,
|
|
label: t("auth.continue_with_github"),
|
|
action: signInWithGithub,
|
|
isLoading: signingInWithGitHub,
|
|
},
|
|
{
|
|
id: "GOOGLE",
|
|
icon: IconGoogle,
|
|
label: t("auth.continue_with_google"),
|
|
action: signInWithGoogle,
|
|
isLoading: signingInWithGoogle,
|
|
},
|
|
{
|
|
id: "MICROSOFT",
|
|
icon: IconMicrosoft,
|
|
label: t("auth.continue_with_microsoft"),
|
|
action: signInWithMicrosoft,
|
|
isLoading: signingInWithMicrosoft,
|
|
},
|
|
{
|
|
id: "EMAIL",
|
|
icon: IconEmail,
|
|
label: t("auth.continue_with_email"),
|
|
action: () => {
|
|
mode.value = "email"
|
|
},
|
|
isLoading: signingInWithEmail,
|
|
},
|
|
]
|
|
|
|
const allowedAuthProvidersIDsString: string | undefined = import.meta.env
|
|
.VITE_ALLOWED_AUTH_PROVIDERS
|
|
|
|
const allowedAuthProvidersIDs = allowedAuthProvidersIDsString
|
|
? allowedAuthProvidersIDsString.split(",")
|
|
: []
|
|
|
|
const allowedAuthProviders =
|
|
allowedAuthProvidersIDs.length > 0
|
|
? authProviders.filter((provider) =>
|
|
allowedAuthProvidersIDs.includes(provider.id)
|
|
)
|
|
: authProviders
|
|
</script>
|