chore: add proper error msg and disable email updation in SH (#5247)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
f52349e734
commit
cfa2caa1db
6 changed files with 152 additions and 80 deletions
|
|
@ -533,6 +533,7 @@
|
|||
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.",
|
||||
"delete_activity_log": "Failed to delete activity log",
|
||||
"delete_all_activity_logs": "Failed to delete all activity logs",
|
||||
"email_already_exists": "This email already exists with a different account. Please set a different email address.",
|
||||
"empty_email_address": "Email Address cannot be empty",
|
||||
"empty_profile_name": "Profile name cannot be empty",
|
||||
"empty_req_name": "Empty Request Name",
|
||||
|
|
@ -856,7 +857,8 @@
|
|||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
"viewer": "Viewer",
|
||||
"viewer_description": "Viewers can only view and use requests."
|
||||
"viewer_description": "Viewers can only view and use requests.",
|
||||
"verified_email_sent": "A verification email has been sent to your email address. Please refresh the page after verifying your email address. You will receive an email if this email is not associated with any other account."
|
||||
},
|
||||
"remove": {
|
||||
"star": "Remove star"
|
||||
|
|
|
|||
|
|
@ -117,9 +117,11 @@
|
|||
:autofocus="false"
|
||||
styles="flex mt-2 md:max-w-sm"
|
||||
:placeholder="`${t('settings.profile_email')}`"
|
||||
:disabled="!isEmailEditable"
|
||||
>
|
||||
<template #button>
|
||||
<HoppButtonSecondary
|
||||
v-if="isEmailEditable"
|
||||
filled
|
||||
outline
|
||||
:label="t('action.save')"
|
||||
|
|
@ -238,6 +240,10 @@ const probableUser = useReadonlyStream(
|
|||
platform.auth.getProbableUser()
|
||||
)
|
||||
|
||||
const isEmailEditable = computed(() => {
|
||||
return platform.auth.isEmailEditable ?? false
|
||||
})
|
||||
|
||||
const loadingCurrentUser = computed(() => {
|
||||
if (!probableUser.value) return false
|
||||
else if (!currentUser.value) return true
|
||||
|
|
@ -277,7 +283,7 @@ const emailAddress = ref(currentUser.value?.email || "")
|
|||
const updatingEmailAddress = ref(false)
|
||||
watchEffect(() => (emailAddress.value = currentUser.value?.email || ""))
|
||||
|
||||
const updateEmailAddress = () => {
|
||||
const updateEmailAddress = async () => {
|
||||
const inputEmailAddress = emailAddress.value.trim()
|
||||
if (!inputEmailAddress) {
|
||||
toast.error(`${t("error.empty_email_address")}`)
|
||||
|
|
@ -290,17 +296,25 @@ const updateEmailAddress = () => {
|
|||
}
|
||||
|
||||
updatingEmailAddress.value = true
|
||||
platform.auth
|
||||
.setEmailAddress(inputEmailAddress as string)
|
||||
.then(() => {
|
||||
toast.success(`${t("profile.updated")}`)
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
})
|
||||
.finally(() => {
|
||||
updatingEmailAddress.value = false
|
||||
})
|
||||
|
||||
const result = await platform.auth.setEmailAddress(inputEmailAddress)
|
||||
|
||||
if (!result) {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
updatingEmailAddress.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (result.type === "success") {
|
||||
toast.success(`${t("profile.verified_email_sent")}`)
|
||||
} else if (result.type === "email-already-in-use") {
|
||||
toast.error(`${t("error.email_already_exists")}`)
|
||||
} else if (result.type === "requires-recent-login") {
|
||||
await result.link()
|
||||
} else {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
}
|
||||
updatingEmailAddress.value = false
|
||||
}
|
||||
|
||||
const verifyingEmailAddress = ref(false)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ export type GithubSignInResult =
|
|||
| { type: "account-exists-with-different-cred"; link: () => Promise<void> } // We authenticated correctly, but the provider didn't match, so we give the user the opportunity to link to continue completing auth
|
||||
| { type: "error"; err: unknown } // Auth failed completely and we don't know why
|
||||
|
||||
export type SetEmailAddressResult =
|
||||
| { type: "success" } // The email address was set successfully
|
||||
| { type: "email-already-in-use" } // The email address is already in use by another account
|
||||
| { type: "requires-recent-login"; link: () => Promise<void> } // The user needs to re-authenticate to set the email address
|
||||
| { type: "no-user-logged-in" } // No user is currently logged in, so we can't set the email address
|
||||
| { type: "error"; err: unknown } // An error occurred while setting the email address
|
||||
|
||||
export type LoginItemDef = {
|
||||
id: string
|
||||
icon: Component
|
||||
|
|
@ -230,10 +237,18 @@ export type AuthPlatformDef = {
|
|||
|
||||
/**
|
||||
* Updates the email address of the user
|
||||
*
|
||||
* NOTES:
|
||||
* 1. This will return an error if the email is already in use by another account
|
||||
* 2. This will return an error if the user needs to re-authenticate
|
||||
* 3. This will return undefined if no user is logged in, so check for that before calling this
|
||||
*
|
||||
* @param email The new email to set this to.
|
||||
* @returns An empty promise that is resolved when the operation is complete
|
||||
* @returns A promise that resolves with the email update status when the operation is complete
|
||||
*/
|
||||
setEmailAddress: (email: string) => Promise<void>
|
||||
setEmailAddress: (
|
||||
email: string
|
||||
) => Promise<SetEmailAddressResult> | Promise<void>
|
||||
|
||||
/**
|
||||
* Updates the display name of the user
|
||||
|
|
@ -253,4 +268,11 @@ export type AuthPlatformDef = {
|
|||
* Defines the additional login items that should be shown in the login screen
|
||||
*/
|
||||
additionalLoginItems?: LoginItemDef[]
|
||||
|
||||
/**
|
||||
* Whether the email address is editable by the user or not.
|
||||
* This is used to determine whether the email address field should disabled in the user settings.
|
||||
* If a value is not given, then the value is assumed to be false.
|
||||
*/
|
||||
isEmailEditable?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@ import {
|
|||
AuthPlatformDef,
|
||||
HoppUser,
|
||||
} from "@hoppscotch/common/platform/auth"
|
||||
import {
|
||||
PersistenceService
|
||||
} from "@hoppscotch/common/services/persistence"
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { Body, getClient } from '@tauri-apps/api/http'
|
||||
import { open } from '@tauri-apps/api/shell'
|
||||
import { PersistenceService } from "@hoppscotch/common/services/persistence"
|
||||
import { listen } from "@tauri-apps/api/event"
|
||||
import { Body, getClient } from "@tauri-apps/api/http"
|
||||
import { open } from "@tauri-apps/api/shell"
|
||||
import { BehaviorSubject, Subject } from "rxjs"
|
||||
import { Store } from "tauri-plugin-store-api"
|
||||
import { Ref, ref, watch } from "vue"
|
||||
|
|
@ -23,7 +21,7 @@ const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat"
|
|||
const persistenceService = getService(PersistenceService)
|
||||
|
||||
async function logout() {
|
||||
let client = await getClient();
|
||||
let client = await getClient()
|
||||
await client.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/logout`)
|
||||
|
||||
const store = new Store(APP_DATA_PATH)
|
||||
|
|
@ -33,19 +31,27 @@ async function logout() {
|
|||
}
|
||||
|
||||
async function signInUserWithGithubFB() {
|
||||
await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/github?redirect_uri=desktop`);
|
||||
await open(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/github?redirect_uri=desktop`
|
||||
)
|
||||
}
|
||||
|
||||
async function signInUserWithGoogleFB() {
|
||||
await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/google?redirect_uri=desktop`);
|
||||
await open(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/google?redirect_uri=desktop`
|
||||
)
|
||||
}
|
||||
|
||||
async function signInUserWithMicrosoftFB() {
|
||||
await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/microsoft?redirect_uri=desktop`);
|
||||
await open(
|
||||
`${
|
||||
import.meta.env.VITE_BACKEND_API_URL
|
||||
}/auth/microsoft?redirect_uri=desktop`
|
||||
)
|
||||
}
|
||||
|
||||
async function getInitialUserDetails() {
|
||||
const store = new Store(APP_DATA_PATH);
|
||||
const store = new Store(APP_DATA_PATH)
|
||||
|
||||
try {
|
||||
const accessToken = await store.get("access_token")
|
||||
|
|
@ -60,20 +66,23 @@ async function getInitialUserDetails() {
|
|||
isAdmin
|
||||
createdOn
|
||||
}
|
||||
}`}
|
||||
|
||||
let res = await client.post(`${import.meta.env.VITE_BACKEND_GQL_URL}`,
|
||||
Body.json(body), {
|
||||
headers: {
|
||||
"Cookie": `access_token=${accessToken.value}`,
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
let res = await client.post(
|
||||
`${import.meta.env.VITE_BACKEND_GQL_URL}`,
|
||||
Body.json(body),
|
||||
{
|
||||
headers: {
|
||||
Cookie: `access_token=${accessToken.value}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
let res = {
|
||||
error: "auth/cookies_not_found"
|
||||
error: "auth/cookies_not_found",
|
||||
}
|
||||
|
||||
return res
|
||||
|
|
@ -149,14 +158,17 @@ async function setInitialUser() {
|
|||
}
|
||||
|
||||
async function refreshToken() {
|
||||
const store = new Store(APP_DATA_PATH);
|
||||
const store = new Store(APP_DATA_PATH)
|
||||
try {
|
||||
const refreshToken = await store.get("refresh_token")
|
||||
|
||||
let client = await getClient()
|
||||
let res = await client.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`, {
|
||||
headers: { "Cookie": `refresh_token=${refreshToken.value}` }
|
||||
})
|
||||
let res = await client.get(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`,
|
||||
{
|
||||
headers: { Cookie: `refresh_token=${refreshToken.value}` },
|
||||
}
|
||||
)
|
||||
|
||||
setAuthCookies(res.rawHeaders)
|
||||
|
||||
|
|
@ -175,13 +187,16 @@ async function refreshToken() {
|
|||
}
|
||||
|
||||
async function sendMagicLink(email: string) {
|
||||
const client = await getClient();
|
||||
let url = `${import.meta.env.VITE_BACKEND_API_URL}/auth/signin?origin=desktop`;
|
||||
const client = await getClient()
|
||||
let url = `${import.meta.env.VITE_BACKEND_API_URL}/auth/signin?origin=desktop`
|
||||
|
||||
const res = await client.post(url, Body.json({ email }));
|
||||
const res = await client.post(url, Body.json({ email }))
|
||||
|
||||
if (res.data && res.data.deviceIdentifier) {
|
||||
persistenceService.setLocalConfig("deviceIdentifier", res.data.deviceIdentifier)
|
||||
persistenceService.setLocalConfig(
|
||||
"deviceIdentifier",
|
||||
res.data.deviceIdentifier
|
||||
)
|
||||
} else {
|
||||
throw new Error("test: does not get device identifier")
|
||||
}
|
||||
|
|
@ -190,27 +205,26 @@ async function sendMagicLink(email: string) {
|
|||
}
|
||||
|
||||
async function setAuthCookies(rawHeaders: Array<String>) {
|
||||
let cookies = rawHeaders['set-cookie'].join("|")
|
||||
let cookies = rawHeaders["set-cookie"].join("|")
|
||||
|
||||
const accessTokenMatch = cookies.match(/access_token=([^;]+)/);
|
||||
const refreshTokenMatch = cookies.match(/refresh_token=([^;]+)/);
|
||||
const accessTokenMatch = cookies.match(/access_token=([^;]+)/)
|
||||
const refreshTokenMatch = cookies.match(/refresh_token=([^;]+)/)
|
||||
|
||||
const store = new Store(APP_DATA_PATH)
|
||||
|
||||
if (accessTokenMatch) {
|
||||
const accessToken = accessTokenMatch[1];
|
||||
const accessToken = accessTokenMatch[1]
|
||||
await store.set("access_token", { value: accessToken })
|
||||
}
|
||||
|
||||
if (refreshTokenMatch) {
|
||||
const refreshToken = refreshTokenMatch[1];
|
||||
const refreshToken = refreshTokenMatch[1]
|
||||
await store.set("refresh_token", { value: refreshToken })
|
||||
}
|
||||
|
||||
await store.save()
|
||||
}
|
||||
|
||||
|
||||
export const def: AuthPlatformDef = {
|
||||
getCurrentUserStream: () => currentUser$,
|
||||
getAuthEventsStream: () => authEvents$,
|
||||
|
|
@ -257,31 +271,36 @@ export const def: AuthPlatformDef = {
|
|||
return null
|
||||
},
|
||||
async performAuthInit() {
|
||||
const probableUser = JSON.parse(persistenceService.getLocalConfig("login_state") ?? "null")
|
||||
const probableUser = JSON.parse(
|
||||
persistenceService.getLocalConfig("login_state") ?? "null"
|
||||
)
|
||||
probableUser$.next(probableUser)
|
||||
await setInitialUser()
|
||||
|
||||
await listen('scheme-request-received', async (event: any) => {
|
||||
let deep_link = event.payload as string;
|
||||
await listen("scheme-request-received", async (event: any) => {
|
||||
let deep_link = event.payload as string
|
||||
|
||||
const params = new URLSearchParams(deep_link.split('?')[1]);
|
||||
const accessToken = params.get('access_token');
|
||||
const refreshToken = params.get('refresh_token');
|
||||
const token = params.get('token');
|
||||
const params = new URLSearchParams(deep_link.split("?")[1])
|
||||
const accessToken = params.get("access_token")
|
||||
const refreshToken = params.get("refresh_token")
|
||||
const token = params.get("token")
|
||||
|
||||
function isNotNullOrUndefined(x: any) {
|
||||
return x !== null && x !== undefined;
|
||||
return x !== null && x !== undefined
|
||||
}
|
||||
|
||||
if (isNotNullOrUndefined(accessToken) && isNotNullOrUndefined(refreshToken)) {
|
||||
if (
|
||||
isNotNullOrUndefined(accessToken) &&
|
||||
isNotNullOrUndefined(refreshToken)
|
||||
) {
|
||||
const store = new Store(APP_DATA_PATH)
|
||||
|
||||
await store.set("access_token", { value: accessToken });
|
||||
await store.set("refresh_token", { value: refreshToken } );
|
||||
await store.set("access_token", { value: accessToken })
|
||||
await store.set("refresh_token", { value: refreshToken })
|
||||
await store.save()
|
||||
|
||||
window.location.href = "/"
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (isNotNullOrUndefined(token)) {
|
||||
|
|
@ -289,7 +308,7 @@ export const def: AuthPlatformDef = {
|
|||
await this.signInWithEmailLink("", "")
|
||||
await setInitialUser()
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
waitProbableLoginToConfirm() {
|
||||
|
|
@ -327,7 +346,8 @@ export const def: AuthPlatformDef = {
|
|||
await signInUserWithMicrosoftFB()
|
||||
},
|
||||
async signInWithEmailLink(_email, _url) {
|
||||
const deviceIdentifier = persistenceService.getLocalConfig("deviceIdentifier")
|
||||
const deviceIdentifier =
|
||||
persistenceService.getLocalConfig("deviceIdentifier")
|
||||
|
||||
if (!deviceIdentifier) {
|
||||
throw new Error(
|
||||
|
|
@ -337,11 +357,14 @@ export const def: AuthPlatformDef = {
|
|||
|
||||
let verifyToken = persistenceService.getLocalConfig("verifyToken")
|
||||
|
||||
const client = await getClient();
|
||||
let res = await client.post(`${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`, Body.json({
|
||||
token: verifyToken,
|
||||
deviceIdentifier
|
||||
}));
|
||||
const client = await getClient()
|
||||
let res = await client.post(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`,
|
||||
Body.json({
|
||||
token: verifyToken,
|
||||
deviceIdentifier,
|
||||
})
|
||||
)
|
||||
|
||||
setAuthCookies(res.rawHeaders)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,19 +62,25 @@ async function logout() {
|
|||
|
||||
async function signInUserWithGithubFB() {
|
||||
await Io.openExternalLink({
|
||||
url: `${import.meta.env.VITE_BACKEND_API_URL}/auth/github?redirect_uri=desktop`,
|
||||
url: `${
|
||||
import.meta.env.VITE_BACKEND_API_URL
|
||||
}/auth/github?redirect_uri=desktop`,
|
||||
})
|
||||
}
|
||||
|
||||
async function signInUserWithGoogleFB() {
|
||||
await Io.openExternalLink({
|
||||
url: `${import.meta.env.VITE_BACKEND_API_URL}/auth/google?redirect_uri=desktop`,
|
||||
url: `${
|
||||
import.meta.env.VITE_BACKEND_API_URL
|
||||
}/auth/google?redirect_uri=desktop`,
|
||||
})
|
||||
}
|
||||
|
||||
async function signInUserWithMicrosoftFB() {
|
||||
await Io.openExternalLink({
|
||||
url: `${import.meta.env.VITE_BACKEND_API_URL}/auth/microsoft?redirect_uri=desktop`,
|
||||
url: `${
|
||||
import.meta.env.VITE_BACKEND_API_URL
|
||||
}/auth/microsoft?redirect_uri=desktop`,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -83,8 +89,9 @@ async function getInitialUserDetails(): Promise<
|
|||
> {
|
||||
try {
|
||||
const accessToken = await persistenceService.getLocalConfig("access_token")
|
||||
const refreshToken =
|
||||
await persistenceService.getLocalConfig("refresh_token")
|
||||
const refreshToken = await persistenceService.getLocalConfig(
|
||||
"refresh_token"
|
||||
)
|
||||
|
||||
if (!accessToken || !refreshToken) {
|
||||
return { error: "auth/cookies_not_found" }
|
||||
|
|
@ -217,8 +224,9 @@ export async function setInitialUser() {
|
|||
|
||||
async function refreshToken() {
|
||||
try {
|
||||
const refreshToken =
|
||||
await persistenceService.getLocalConfig("refresh_token")
|
||||
const refreshToken = await persistenceService.getLocalConfig(
|
||||
"refresh_token"
|
||||
)
|
||||
if (!refreshToken) return null
|
||||
|
||||
const { response } = interceptorService.execute({
|
||||
|
|
@ -447,8 +455,9 @@ export const def: AuthPlatformDef = {
|
|||
},
|
||||
|
||||
async signInWithEmailLink(_email: string, url: string) {
|
||||
const deviceIdentifier =
|
||||
await persistenceService.getLocalConfig("deviceIdentifier")
|
||||
const deviceIdentifier = await persistenceService.getLocalConfig(
|
||||
"deviceIdentifier"
|
||||
)
|
||||
|
||||
if (!deviceIdentifier) {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -302,8 +302,9 @@ export const def: AuthPlatformDef = {
|
|||
|
||||
const token = searchParams.get("token")
|
||||
|
||||
const deviceIdentifier =
|
||||
await persistenceService.getLocalConfig("deviceIdentifier")
|
||||
const deviceIdentifier = await persistenceService.getLocalConfig(
|
||||
"deviceIdentifier"
|
||||
)
|
||||
|
||||
await axios.post(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`,
|
||||
|
|
@ -355,8 +356,9 @@ export const def: AuthPlatformDef = {
|
|||
|
||||
async processMagicLink() {
|
||||
if (this.isSignInWithEmailLink(window.location.href)) {
|
||||
const deviceIdentifier =
|
||||
await persistenceService.getLocalConfig("deviceIdentifier")
|
||||
const deviceIdentifier = await persistenceService.getLocalConfig(
|
||||
"deviceIdentifier"
|
||||
)
|
||||
|
||||
if (!deviceIdentifier) {
|
||||
throw new Error(
|
||||
|
|
|
|||
Loading…
Reference in a new issue