feat: add local app login
This commit is contained in:
parent
b17375eb16
commit
892f69817b
7 changed files with 119 additions and 7 deletions
|
|
@ -192,6 +192,7 @@
|
|||
"continue_with_github_enterprise": "Continue with GitHub Enterprise",
|
||||
"continue_with_google": "Continue with Google",
|
||||
"continue_with_microsoft": "Continue with Microsoft",
|
||||
"continue_with_username": "Continue with username",
|
||||
"email": "Email",
|
||||
"logged_out": "Logged out",
|
||||
"login": "Login",
|
||||
|
|
@ -200,7 +201,9 @@
|
|||
"logout": "Logout",
|
||||
"re_enter_email": "Re-enter email",
|
||||
"send_magic_link": "Send a magic link",
|
||||
"password": "Password",
|
||||
"sync": "Sync",
|
||||
"username": "Username",
|
||||
"we_sent_magic_link": "We sent you a magic link!",
|
||||
"we_sent_magic_link_description": "Check your inbox - we sent an email to {email}. It contains a magic link that will log you in."
|
||||
},
|
||||
|
|
|
|||
|
|
@ -60,6 +60,32 @@
|
|||
:label="`${t('auth.send_magic_link')}`"
|
||||
/>
|
||||
</form>
|
||||
<form
|
||||
v-if="mode === 'local'"
|
||||
class="flex flex-col space-y-2"
|
||||
@submit.prevent="signInWithUsernamePassword"
|
||||
>
|
||||
<HoppSmartInput
|
||||
v-model="form.username"
|
||||
type="text"
|
||||
placeholder=" "
|
||||
:label="t('auth.username')"
|
||||
input-styles="floating-input"
|
||||
/>
|
||||
<HoppSmartInput
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder=" "
|
||||
:label="t('auth.password')"
|
||||
input-styles="floating-input"
|
||||
/>
|
||||
|
||||
<HoppButtonPrimary
|
||||
:loading="signingInWithLocal"
|
||||
type="submit"
|
||||
:label="`${t('auth.login')}`"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div
|
||||
v-if="!allowedAuthProviders?.length && !additionalLoginItems.length"
|
||||
|
|
@ -116,7 +142,7 @@
|
|||
label="Privacy Policy"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mode === 'email'">
|
||||
<div v-if="mode === 'email' || mode === 'local'">
|
||||
<HoppButtonSecondary
|
||||
:label="t('auth.all_sign_in_options')"
|
||||
:icon="IconArrowLeft"
|
||||
|
|
@ -145,7 +171,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, onMounted, ref } from "vue"
|
||||
import { Component, Ref, onMounted, ref } from "vue"
|
||||
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useStreamSubscriber } from "@composables/stream"
|
||||
|
|
@ -159,6 +185,7 @@ import IconGoogle from "~icons/auth/google"
|
|||
import IconMicrosoft from "~icons/auth/microsoft"
|
||||
import IconArrowLeft from "~icons/lucide/arrow-left"
|
||||
import IconFileText from "~icons/lucide/file-text"
|
||||
import IconKeyRound from "~icons/lucide/key-round"
|
||||
|
||||
import { useService } from "dioc/vue"
|
||||
import { LoginItemDef } from "~/platform/auth"
|
||||
|
|
@ -178,6 +205,8 @@ const persistenceService = useService(PersistenceService)
|
|||
|
||||
const form = {
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
}
|
||||
|
||||
const isLoadingAllowedAuthProviders = ref(true)
|
||||
|
|
@ -186,6 +215,7 @@ const signingInWithGoogle = ref(false)
|
|||
const signingInWithGitHub = ref(false)
|
||||
const signingInWithMicrosoft = ref(false)
|
||||
const signingInWithEmail = ref(false)
|
||||
const signingInWithLocal = ref(false)
|
||||
const mode = ref("sign-in")
|
||||
|
||||
const tosLink = import.meta.env.VITE_APP_TOS_LINK
|
||||
|
|
@ -193,7 +223,7 @@ const privacyPolicyLink = import.meta.env.VITE_APP_PRIVACY_POLICY_LINK
|
|||
|
||||
type AuthProviderItem = {
|
||||
id: string
|
||||
icon: typeof IconGithub
|
||||
icon: Component
|
||||
label: string
|
||||
action: (...args: any[]) => any
|
||||
isLoading: Ref<boolean>
|
||||
|
|
@ -356,6 +386,24 @@ const signInWithEmail = async () => {
|
|||
})
|
||||
}
|
||||
|
||||
const signInWithUsernamePassword = async () => {
|
||||
signingInWithLocal.value = true
|
||||
|
||||
await platform.auth
|
||||
.signInWithUsernamePassword(form.username, form.password)
|
||||
.then(() => {
|
||||
showLoginSuccess()
|
||||
hideModal()
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
})
|
||||
.finally(() => {
|
||||
signingInWithLocal.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const authProvidersAvailable: AuthProviderItem[] = [
|
||||
{
|
||||
id: "GITHUB",
|
||||
|
|
@ -395,6 +443,15 @@ const authProvidersAvailable: AuthProviderItem[] = [
|
|||
},
|
||||
isLoading: signingInWithEmail,
|
||||
},
|
||||
{
|
||||
id: "LOCAL",
|
||||
icon: IconKeyRound,
|
||||
label: t("auth.continue_with_username"),
|
||||
action: () => {
|
||||
mode.value = "local"
|
||||
},
|
||||
isLoading: signingInWithLocal,
|
||||
},
|
||||
]
|
||||
|
||||
const hideModal = () => {
|
||||
|
|
|
|||
|
|
@ -186,6 +186,17 @@ export type AuthPlatformDef = {
|
|||
*/
|
||||
signInWithEmail: (email: string) => Promise<void>
|
||||
|
||||
/**
|
||||
* Called to sign in user with username and password.
|
||||
* @param username The username that is logging in.
|
||||
* @param password The password for the local account.
|
||||
* @returns An empty promise that is resolved when the operation is complete
|
||||
*/
|
||||
signInWithUsernamePassword: (
|
||||
username: string,
|
||||
password: string
|
||||
) => Promise<void>
|
||||
|
||||
/**
|
||||
* Check whether a given link is a valid sign in with email, magic link response url.
|
||||
* (i.e, a URL that COULD be from a magic link email)
|
||||
|
|
@ -261,7 +272,7 @@ export type AuthPlatformDef = {
|
|||
) => Promise<E.Either<GQLError<string>, undefined>>
|
||||
|
||||
/**
|
||||
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )
|
||||
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML, LOCAL )
|
||||
*/
|
||||
getAllowedAuthProviders: () => Promise<E.Either<string, string[]>>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "@app/api/generated/graphql"
|
||||
|
||||
const expectedAllowedProvidersSchema = z.object({
|
||||
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML"
|
||||
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML", "LOCAL"
|
||||
// keeping it as string to avoid backend accidentally breaking frontend when adding new providers
|
||||
providers: z.array(z.string()),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -429,6 +429,25 @@ export const def: AuthPlatformDef = {
|
|||
await sendMagicLink(email)
|
||||
},
|
||||
|
||||
async signInWithUsernamePassword(username: string, password: string) {
|
||||
const { response } = interceptorService.execute({
|
||||
id: Date.now(),
|
||||
url: `${import.meta.env.VITE_BACKEND_API_URL}/auth/local/signin`,
|
||||
version: "HTTP/1.1",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
content: content.json({ username, password }),
|
||||
})
|
||||
|
||||
const res = await response
|
||||
if (E.isLeft(res)) throw new Error("Failed to sign in")
|
||||
|
||||
await setAuthCookies(res.right.headers)
|
||||
await setInitialUser()
|
||||
},
|
||||
|
||||
async verifyEmailAddress() {
|
||||
return
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "@app/api/generated/graphql"
|
||||
|
||||
const expectedAllowedProvidersSchema = z.object({
|
||||
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML"
|
||||
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML", "LOCAL"
|
||||
// keeping it as string to avoid backend accidentally breaking frontend when adding new providers
|
||||
providers: z.array(z.string()),
|
||||
})
|
||||
|
|
@ -35,6 +35,19 @@ export const getAllowedAuthProviders = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const signInLocal = async (username: string, password: string) => {
|
||||
await axios.post(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/local/signin`,
|
||||
{
|
||||
username,
|
||||
password,
|
||||
},
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const updateUserDisplayName = (updatedDisplayName: string) =>
|
||||
runMutation<
|
||||
UpdateUserDisplayNameMutation,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ import {
|
|||
} from "@hoppscotch/common/platform/auth"
|
||||
import { PersistenceService } from "@hoppscotch/common/services/persistence"
|
||||
|
||||
import { getAllowedAuthProviders, updateUserDisplayName } from "./api"
|
||||
import {
|
||||
getAllowedAuthProviders,
|
||||
signInLocal,
|
||||
updateUserDisplayName,
|
||||
} from "./api"
|
||||
|
||||
export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>()
|
||||
const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
|
||||
|
|
@ -276,6 +280,11 @@ export const def: AuthPlatformDef = {
|
|||
await sendMagicLink(email)
|
||||
},
|
||||
|
||||
async signInWithUsernamePassword(username: string, password: string) {
|
||||
await signInLocal(username, password)
|
||||
await setInitialUser()
|
||||
},
|
||||
|
||||
isSignInWithEmailLink(url: string) {
|
||||
const urlObject = new URL(url)
|
||||
const searchParams = new URLSearchParams(urlObject.search)
|
||||
|
|
|
|||
Loading…
Reference in a new issue