fix: handle actions for logged-in users in case of token expiration (#5249)
Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: Nivedin <53208152+nivedin@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
ba700886b5
commit
637c380c07
19 changed files with 340 additions and 58 deletions
|
|
@ -216,4 +216,14 @@ export class AuthController {
|
|||
|
||||
return tokens.right;
|
||||
}
|
||||
|
||||
@Get('verify-token')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async verifyToken(@GqlUser() user: AuthUser) {
|
||||
return {
|
||||
isValid: true,
|
||||
uid: user.uid,
|
||||
message: 'Token is valid',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const addNewCollection = () => {
|
|||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
variables: [],
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ import { useService } from "dioc/vue"
|
|||
import { computed, ref } from "vue"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { removeGraphqlCollection } from "~/newstore/collections"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
|
|
@ -358,7 +359,9 @@ const toggleShowChildren = () => {
|
|||
showChildren.value = !showChildren.value
|
||||
}
|
||||
|
||||
const removeCollection = () => {
|
||||
const removeCollection = async () => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
// Cancel pick if picked collection is deleted
|
||||
if (
|
||||
props.picked?.pickedType === "gql-my-collection" &&
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { editGraphqlCollection } from "~/newstore/collections"
|
|||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
|
|
@ -62,12 +63,15 @@ watch(
|
|||
}
|
||||
)
|
||||
|
||||
const saveCollection = () => {
|
||||
const saveCollection = async () => {
|
||||
if (!editingName.value) {
|
||||
toast.error(`${t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
|
||||
// TODO: Better typechecking here ?
|
||||
const collectionUpdated = {
|
||||
...(props.editingCollection as any),
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { ref, watch } from "vue"
|
|||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { editGraphqlFolder } from "~/newstore/collections"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
|
@ -59,11 +60,13 @@ watch(
|
|||
}
|
||||
)
|
||||
|
||||
const editFolder = () => {
|
||||
const editFolder = async () => {
|
||||
if (!name.value) {
|
||||
toast.error(`${t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
editGraphqlFolder(props.folderPath, {
|
||||
...(props.folder as any),
|
||||
name: name.value,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ import { GQLTabService } from "~/services/tab/graphql"
|
|||
import IconSparkle from "~icons/lucide/sparkles"
|
||||
import IconThumbsUp from "~icons/lucide/thumbs-up"
|
||||
import IconThumbsDown from "~icons/lucide/thumbs-down"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
|
@ -155,12 +156,15 @@ watch(
|
|||
const submittedFeedback = ref(false)
|
||||
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
|
||||
|
||||
const saveRequest = () => {
|
||||
const saveRequest = async () => {
|
||||
if (!editingName.value) {
|
||||
toast.error(`${t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
|
||||
const requestUpdated = {
|
||||
...(props.request as any),
|
||||
name: editingName.value || (props.request as any).name,
|
||||
|
|
@ -173,7 +177,7 @@ const saveRequest = () => {
|
|||
folderPath: props.folderPath!,
|
||||
})
|
||||
|
||||
editGraphqlRequest(props.folderPath, props.requestIndex, requestUpdated)
|
||||
editGraphqlRequest(props.folderPath!, props.requestIndex!, requestUpdated)
|
||||
|
||||
if (possibleActiveTab) {
|
||||
possibleActiveTab.value.document.request.name = requestUpdated.name
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ import { useToast } from "@composables/toast"
|
|||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { useService } from "dioc/vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { removeGraphqlFolder } from "~/newstore/collections"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
|
@ -322,7 +323,9 @@ const toggleShowChildren = () => {
|
|||
showChildren.value = !showChildren.value
|
||||
}
|
||||
|
||||
const removeFolder = () => {
|
||||
const removeFolder = async () => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
// Cancel pick if the picked folder is deleted
|
||||
if (
|
||||
props.picked?.pickedType === "gql-my-folder" &&
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ import { useI18n } from "@composables/i18n"
|
|||
import { useToast } from "@composables/toast"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
|
|
@ -221,7 +222,9 @@ const dragStart = ({ dataTransfer }: any) => {
|
|||
dataTransfer.setData("requestIndex", props.requestIndex)
|
||||
}
|
||||
|
||||
const removeRequest = () => {
|
||||
const removeRequest = async () => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
// Cancel pick if the picked request is deleted
|
||||
if (
|
||||
props.picked &&
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
|||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||
import { EditingProperties } from "../Properties.vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
|
@ -333,7 +334,9 @@ const filteredCollections = computed(() => {
|
|||
return filteredCollections
|
||||
})
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
const displayModalAdd = async (shouldDisplay: boolean) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
showModalAdd.value = shouldDisplay
|
||||
}
|
||||
|
||||
|
|
@ -343,7 +346,9 @@ const displayModalEdit = (shouldDisplay: boolean) => {
|
|||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalImportExport = (shouldDisplay: boolean) => {
|
||||
const displayModalImportExport = async (shouldDisplay: boolean) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
showModalImportExport.value = shouldDisplay
|
||||
}
|
||||
|
||||
|
|
@ -386,15 +391,21 @@ const editCollection = (
|
|||
displayModalEdit(true)
|
||||
}
|
||||
|
||||
const duplicateCollection = ({
|
||||
const duplicateCollection = async ({
|
||||
path,
|
||||
collectionSyncID,
|
||||
}: {
|
||||
path: string
|
||||
collectionSyncID?: string
|
||||
}) => duplicateGraphQLCollection(path, collectionSyncID)
|
||||
}) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
duplicateGraphQLCollection(path, collectionSyncID)
|
||||
}
|
||||
|
||||
const onAddRequest = ({ name, path }: { name: string; path: string }) => {
|
||||
const onAddRequest = async ({ name, path }: { name: string; path: string }) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const newRequest = {
|
||||
...getDefaultGQLRequest(),
|
||||
name,
|
||||
|
|
@ -429,13 +440,15 @@ const addRequest = (payload: { path: string }) => {
|
|||
displayModalAddRequest(true)
|
||||
}
|
||||
|
||||
const onAddFolder = ({
|
||||
const onAddFolder = async ({
|
||||
name,
|
||||
path,
|
||||
}: {
|
||||
name: string
|
||||
path: string | undefined
|
||||
}) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
addGraphqlFolder(name, path ?? "0")
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
|
|
@ -489,13 +502,15 @@ const editRequest = (payload: {
|
|||
displayModalEditRequest(true)
|
||||
}
|
||||
|
||||
const duplicateRequest = ({
|
||||
const duplicateRequest = async ({
|
||||
folderPath,
|
||||
request,
|
||||
}: {
|
||||
folderPath: string
|
||||
request: HoppGQLRequest
|
||||
}) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
saveGraphqlRequestAs(folderPath, {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${t("action.duplicate")}`,
|
||||
|
|
@ -536,7 +551,7 @@ const selectRequest = ({
|
|||
})
|
||||
}
|
||||
|
||||
const dropRequest = ({
|
||||
const dropRequest = async ({
|
||||
folderPath,
|
||||
requestIndex,
|
||||
collectionIndex,
|
||||
|
|
@ -545,6 +560,9 @@ const dropRequest = ({
|
|||
requestIndex: number
|
||||
collectionIndex: number
|
||||
}) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
|
|
@ -607,11 +625,13 @@ const editProperties = ({
|
|||
displayModalEditProperties(true)
|
||||
}
|
||||
|
||||
const setCollectionProperties = (newCollection: {
|
||||
const setCollectionProperties = async (newCollection: {
|
||||
collection: Partial<HoppCollection> | null
|
||||
path: string
|
||||
isRootCollection: boolean
|
||||
}) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const { collection, path, isRootCollection } = newCollection
|
||||
if (!collection) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@
|
|||
@drop-request="dropRequest"
|
||||
@drop-collection="dropCollection"
|
||||
@display-modal-add="displayModalAdd(true)"
|
||||
@display-modal-import-export="displayModalImportExport(true)"
|
||||
@display-modal-import-export="
|
||||
displayModalImportExport(true, 'my-collections')
|
||||
"
|
||||
@duplicate-collection="duplicateCollection"
|
||||
@duplicate-request="duplicateRequest"
|
||||
@duplicate-response="duplicateResponse"
|
||||
|
|
@ -246,6 +248,7 @@ import {
|
|||
getCompleteCollectionTree,
|
||||
teamCollToHoppRESTColl,
|
||||
} from "~/helpers/backend/helpers"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
import {
|
||||
createChildCollection,
|
||||
createNewRootCollection,
|
||||
|
|
@ -381,6 +384,7 @@ const currentUser = useReadonlyStream(
|
|||
platform.auth.getCurrentUserStream(),
|
||||
platform.auth.getCurrentUser()
|
||||
)
|
||||
|
||||
const myCollections = useReadonlyStream(restCollections$, [], "deep")
|
||||
|
||||
// Dragging
|
||||
|
|
@ -755,7 +759,14 @@ const displayModalEditResponse = (show: boolean) => {
|
|||
if (!show) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalImportExport = (show: boolean) => {
|
||||
const displayModalImportExport = async (
|
||||
show: boolean,
|
||||
collectionType?: string
|
||||
) => {
|
||||
if (collectionType === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
}
|
||||
showModalImportExport.value = show
|
||||
|
||||
if (!show) resetSelectedData()
|
||||
|
|
@ -779,8 +790,14 @@ const displayTeamModalAdd = (show: boolean) => {
|
|||
teamListAdapter.fetchList()
|
||||
}
|
||||
|
||||
const addNewRootCollection = (name: string) => {
|
||||
const addNewRootCollection = async (name: string) => {
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
modalLoadingState.value = true
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) {
|
||||
modalLoadingState.value = false
|
||||
return
|
||||
}
|
||||
addRESTCollection(
|
||||
makeCollection({
|
||||
name,
|
||||
|
|
@ -802,6 +819,7 @@ const addNewRootCollection = (name: string) => {
|
|||
isRootCollection: true,
|
||||
})
|
||||
|
||||
modalLoadingState.value = false
|
||||
displayModalAdd(false)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
if (!collectionsType.value.selectedTeam) return
|
||||
|
|
@ -841,7 +859,7 @@ const addRequest = (payload: {
|
|||
displayModalAddRequest(true)
|
||||
}
|
||||
|
||||
const onAddRequest = (requestName: string) => {
|
||||
const onAddRequest = async (requestName: string) => {
|
||||
const newRequest = {
|
||||
...getDefaultRESTRequest(),
|
||||
name: requestName,
|
||||
|
|
@ -850,6 +868,9 @@ const onAddRequest = (requestName: string) => {
|
|||
const path = editingFolderPath.value
|
||||
if (!path) return
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
|
||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||
|
||||
tabs.createNewTab({
|
||||
|
|
@ -935,11 +956,13 @@ const addFolder = (payload: {
|
|||
displayModalAddFolder(true)
|
||||
}
|
||||
|
||||
const onAddFolder = (folderName: string) => {
|
||||
const onAddFolder = async (folderName: string) => {
|
||||
const path = editingFolderPath.value
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
if (!path) return
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
addRESTFolder(folderName, path)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
|
|
@ -1000,7 +1023,7 @@ const editCollection = (payload: {
|
|||
displayModalEditCollection(true)
|
||||
}
|
||||
|
||||
const updateEditingCollection = (newName: string) => {
|
||||
const updateEditingCollection = async (newName: string) => {
|
||||
if (!editingCollection.value) return
|
||||
|
||||
if (!newName) {
|
||||
|
|
@ -1009,6 +1032,8 @@ const updateEditingCollection = (newName: string) => {
|
|||
}
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const collectionIndex = editingCollectionIndex.value
|
||||
if (collectionIndex === null) return
|
||||
|
||||
|
|
@ -1058,10 +1083,12 @@ const editFolder = (payload: {
|
|||
displayModalEditFolder(true)
|
||||
}
|
||||
|
||||
const updateEditingFolder = (newName: string) => {
|
||||
const updateEditingFolder = async (newName: string) => {
|
||||
if (!editingFolder.value) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
if (!editingFolderPath.value) return
|
||||
|
||||
editRESTFolder(editingFolderPath.value, {
|
||||
|
|
@ -1104,6 +1131,8 @@ const duplicateCollection = async ({
|
|||
collectionSyncID?: string
|
||||
}) => {
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
duplicateRESTCollection(pathOrID, collectionSyncID)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
duplicateCollectionLoading.value = true
|
||||
|
|
@ -1141,7 +1170,7 @@ const editRequest = (payload: {
|
|||
displayModalEditRequest(true)
|
||||
}
|
||||
|
||||
const updateEditingRequest = (newName: string) => {
|
||||
const updateEditingRequest = async (newName: string) => {
|
||||
const request = editingRequest.value
|
||||
if (!request) return
|
||||
|
||||
|
|
@ -1150,6 +1179,9 @@ const updateEditingRequest = (newName: string) => {
|
|||
name: newName || request.name,
|
||||
}
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
|
||||
const folderPath = editingFolderPath.value
|
||||
const requestIndex = editingRequestIndex.value
|
||||
|
||||
|
|
@ -1295,12 +1327,15 @@ const updateEditingResponse = (newName: string) => {
|
|||
possibleExampleActiveTab.value.document.response.name = newName
|
||||
|
||||
nextTick(() => {
|
||||
possibleExampleActiveTab.value.document.isDirty = false
|
||||
possibleExampleActiveTab.value.document.saveContext = {
|
||||
originLocation: "user-collection",
|
||||
folderPath: folderPath,
|
||||
requestIndex: requestIndex,
|
||||
exampleID: editingResponseID.value!,
|
||||
const doc = possibleExampleActiveTab.value.document
|
||||
if (doc.type === "example-response") {
|
||||
doc.isDirty = false
|
||||
doc.saveContext = {
|
||||
originLocation: "user-collection",
|
||||
folderPath: folderPath,
|
||||
requestIndex: requestIndex,
|
||||
exampleID: editingResponseID.value!,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1361,11 +1396,14 @@ const updateEditingResponse = (newName: string) => {
|
|||
) {
|
||||
possibleActiveResponseTab.value.document.response.name = newName
|
||||
nextTick(() => {
|
||||
possibleActiveResponseTab.value.document.isDirty = false
|
||||
possibleActiveResponseTab.value.document.saveContext = {
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
exampleID: editingResponseID.value!,
|
||||
const doc = possibleActiveResponseTab.value.document
|
||||
if (doc.type === "example-response") {
|
||||
doc.isDirty = false
|
||||
doc.saveContext = {
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
exampleID: editingResponseID.value!,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1381,7 +1419,7 @@ const updateEditingResponse = (newName: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const duplicateRequest = (payload: {
|
||||
const duplicateRequest = async (payload: {
|
||||
folderPath: string
|
||||
request: HoppRESTRequest
|
||||
}) => {
|
||||
|
|
@ -1394,6 +1432,8 @@ const duplicateRequest = (payload: {
|
|||
}
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
saveRESTRequestAs(folderPath, newRequest)
|
||||
toast.success(t("request.duplicated"))
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
|
|
@ -1424,7 +1464,7 @@ const duplicateRequest = (payload: {
|
|||
}
|
||||
}
|
||||
|
||||
const duplicateResponse = (payload: ResponseConfigPayload) => {
|
||||
const duplicateResponse = async (payload: ResponseConfigPayload) => {
|
||||
const { folderPath, requestIndex, request, responseName } = payload
|
||||
|
||||
const response = request.responses[responseName]
|
||||
|
|
@ -1453,6 +1493,8 @@ const duplicateResponse = (payload: ResponseConfigPayload) => {
|
|||
}
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
editRESTRequest(folderPath, parseInt(requestIndex), updatedRequest)
|
||||
toast.success(t("response.duplicated"))
|
||||
|
||||
|
|
@ -1544,8 +1586,10 @@ const removeTeamCollectionOrFolder = async (collectionID: string) => {
|
|||
)()
|
||||
}
|
||||
|
||||
const onRemoveCollection = () => {
|
||||
const onRemoveCollection = async () => {
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const collectionIndex = editingCollectionIndex.value
|
||||
|
||||
const collectionToRemove =
|
||||
|
|
@ -1625,8 +1669,10 @@ const removeFolder = (id: string) => {
|
|||
displayConfirmModal(true)
|
||||
}
|
||||
|
||||
const onRemoveFolder = () => {
|
||||
const onRemoveFolder = async () => {
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const folderPath = editingFolderPath.value
|
||||
|
||||
if (!folderPath) return
|
||||
|
|
@ -1712,8 +1758,10 @@ const removeRequest = (payload: {
|
|||
displayConfirmModal(true)
|
||||
}
|
||||
|
||||
const onRemoveRequest = () => {
|
||||
const onRemoveRequest = async () => {
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const folderPath = editingFolderPath.value
|
||||
const requestIndex = editingRequestIndex.value
|
||||
|
||||
|
|
@ -1825,7 +1873,7 @@ const removeResponse = (payload: ResponseConfigPayload) => {
|
|||
displayConfirmModal(true)
|
||||
}
|
||||
|
||||
const onRemoveResponse = () => {
|
||||
const onRemoveResponse = async () => {
|
||||
const request = cloneDeep(editingRequest.value)
|
||||
|
||||
if (!request) return
|
||||
|
|
@ -1840,6 +1888,8 @@ const onRemoveResponse = () => {
|
|||
}
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const folderPath = editingFolderPath.value
|
||||
const requestIndex = editingRequestIndex.value
|
||||
|
||||
|
|
@ -2141,7 +2191,7 @@ const pathToLastIndex = (path: string) => {
|
|||
* This function is called when the user drops the request inside a collection
|
||||
* @param payload Object that contains the folder path, request index and the destination collection index
|
||||
*/
|
||||
const dropRequest = (payload: {
|
||||
const dropRequest = async (payload: {
|
||||
folderPath?: string | undefined
|
||||
requestIndex: string
|
||||
destinationCollectionIndex: string
|
||||
|
|
@ -2153,6 +2203,8 @@ const dropRequest = (payload: {
|
|||
let possibleTab = null
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
|
|
@ -2293,7 +2345,7 @@ const isMoveToSameLocation = (
|
|||
* to a different collection or folder
|
||||
* @param payload - object containing the collection index dragged and the destination collection index
|
||||
*/
|
||||
const dropCollection = (payload: {
|
||||
const dropCollection = async (payload: {
|
||||
collectionIndexDragged: string
|
||||
destinationCollectionIndex: string
|
||||
destinationParentPath?: string
|
||||
|
|
@ -2309,6 +2361,8 @@ const dropCollection = (payload: {
|
|||
if (collectionIndexDragged === destinationCollectionIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
if (
|
||||
checkIfCollectionIsAParentOfTheChildren(
|
||||
collectionIndexDragged,
|
||||
|
|
@ -2430,11 +2484,13 @@ const isAlreadyInRoot = (id: string) => {
|
|||
* to the root
|
||||
* @param payload - object containing the collection index dragged
|
||||
*/
|
||||
const dropToRoot = ({ dataTransfer }: DragEvent) => {
|
||||
const dropToRoot = async ({ dataTransfer }: DragEvent) => {
|
||||
if (dataTransfer) {
|
||||
const collectionIndexDragged = dataTransfer.getData("collectionIndex")
|
||||
if (!collectionIndexDragged) return
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
// check if the collection is already in the root
|
||||
if (isAlreadyInRoot(collectionIndexDragged)) {
|
||||
toast.error(`${t("collection.invalid_root_move")}`)
|
||||
|
|
@ -2545,7 +2601,7 @@ const isSameSameParent = (
|
|||
* @param payload - object containing the request index dragged and the destination request index
|
||||
* with the destination collection index
|
||||
*/
|
||||
const updateRequestOrder = (payload: {
|
||||
const updateRequestOrder = async (payload: {
|
||||
dragedRequestIndex: string
|
||||
destinationRequestIndex: string | null
|
||||
destinationCollectionIndex: string
|
||||
|
|
@ -2561,6 +2617,8 @@ const updateRequestOrder = (payload: {
|
|||
if (dragedRequestIndex === destinationRequestIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
if (
|
||||
!isSameSameParent(
|
||||
dragedRequestIndex,
|
||||
|
|
@ -2616,7 +2674,7 @@ const updateRequestOrder = (payload: {
|
|||
* This function is called when the user updates the collection or folder order
|
||||
* @param payload - object containing the collection index dragged and the destination collection index
|
||||
*/
|
||||
const updateCollectionOrder = (payload: {
|
||||
const updateCollectionOrder = async (payload: {
|
||||
dragedCollectionIndex: string
|
||||
destinationCollection: {
|
||||
destinationCollectionIndex: string | null
|
||||
|
|
@ -2630,6 +2688,8 @@ const updateCollectionOrder = (payload: {
|
|||
if (dragedCollectionIndex === destinationCollectionIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
if (
|
||||
!isSameSameParent(
|
||||
dragedCollectionIndex,
|
||||
|
|
@ -2781,7 +2841,7 @@ const getCurrentValue = (
|
|||
)?.currentValue
|
||||
}
|
||||
|
||||
const editProperties = (payload: {
|
||||
const editProperties = async (payload: {
|
||||
collectionIndex: string
|
||||
collection: HoppCollection | TeamCollection
|
||||
}) => {
|
||||
|
|
@ -2790,6 +2850,8 @@ const editProperties = (payload: {
|
|||
const collectionId = collection.id ?? collectionIndex.split("/").pop()
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
||||
|
||||
let inheritedProperties: HoppInheritedProperty = {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
:icon="IconEdit"
|
||||
:title="`${t('action.edit')}`"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="emit('edit-environment')"
|
||||
@click="emitEditEnvironment"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
|
|
@ -76,9 +76,9 @@
|
|||
:shortcut="['E']"
|
||||
:disabled="duplicateGlobalEnvironmentLoading"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-environment')
|
||||
hide()
|
||||
async () => {
|
||||
const ok = await emitEditEnvironment()
|
||||
if (ok) hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
@ -150,6 +150,7 @@ import {
|
|||
deleteEnvironment,
|
||||
duplicateEnvironment,
|
||||
} from "~/newstore/environments"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
|
|
@ -160,6 +161,13 @@ import { CurrentValueService } from "~/services/current-environment-value.servic
|
|||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const emitEditEnvironment = async (): Promise<boolean> => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return false
|
||||
emit("edit-environment")
|
||||
return true
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
environment: Environment
|
||||
|
|
@ -213,7 +221,9 @@ const duplicate = ref<typeof HoppSmartItem>()
|
|||
const exportAsJsonEl = ref<typeof HoppSmartItem>()
|
||||
const deleteAction = ref<typeof HoppSmartItem>()
|
||||
|
||||
const removeEnvironment = () => {
|
||||
const removeEnvironment = async () => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
if (props.environmentIndex === null) return
|
||||
if (!isGlobalEnvironment.value) {
|
||||
deleteEnvironment(props.environmentIndex as number, props.environment.id)
|
||||
|
|
@ -223,7 +233,9 @@ const removeEnvironment = () => {
|
|||
toast.success(`${t("state.deleted")}`)
|
||||
}
|
||||
|
||||
const duplicateEnvironments = () => {
|
||||
const duplicateEnvironments = async () => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
if (props.environmentIndex === null) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ import { defineActionHandler } from "~/helpers/actions"
|
|||
import { sortPersonalEnvironmentsAlphabetically } from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||
import { HandleEnvChangeProp } from "../index.vue"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
|
@ -159,17 +160,25 @@ const editingEnvironmentIndex = ref<number | null>(null)
|
|||
const editingVariableName = ref("")
|
||||
const secretOptionSelected = ref(false)
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
const displayModalAdd = async (shouldDisplay: boolean) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
action.value = "new"
|
||||
showModalDetails.value = shouldDisplay
|
||||
}
|
||||
const displayModalEdit = (shouldDisplay: boolean) => {
|
||||
action.value = "edit"
|
||||
const displayModalEdit = async (shouldDisplay: boolean) => {
|
||||
if (shouldDisplay) {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
action.value = "edit"
|
||||
}
|
||||
showModalDetails.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
const displayModalImportExport = (shouldDisplay: boolean) => {
|
||||
const displayModalImportExport = async (shouldDisplay: boolean) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
showModalImportExport.value = shouldDisplay
|
||||
}
|
||||
const selectEnvironment = (index: number, environment: Environment) => {
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ import { getEnvActionErrorMessage } from "~/helpers/error-messages"
|
|||
import { HandleEnvChangeProp } from "../index.vue"
|
||||
import { selectedEnvironmentIndex$ } from "~/newstore/environments"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import { handleTokenValidation } from "~/helpers/handleTokenValidation"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
|
|
@ -223,7 +224,9 @@ const selectedEnvironmentID = ref<string | null>(null)
|
|||
|
||||
const isTeamViewer = computed(() => props.team?.role === "VIEWER")
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
const displayModalAdd = async (shouldDisplay: boolean) => {
|
||||
const isValidToken = await handleTokenValidation()
|
||||
if (!isValidToken) return
|
||||
action.value = "new"
|
||||
showModalDetails.value = shouldDisplay
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { useToast } from "@composables/toast"
|
||||
import { isValidUser } from "~/helpers/isValidUser"
|
||||
|
||||
/**
|
||||
* High-level authentication validation handler with automatic error notifications.
|
||||
*
|
||||
* This wrapper around `isValidUser()` provides automatic toast error messages for invalid tokens.
|
||||
* Use this when you want standard error handling with user notifications.
|
||||
*
|
||||
* For silent validation or custom error handling, use `isValidUser()` directly.
|
||||
*
|
||||
* @returns {Promise<boolean>} True if user is valid, false otherwise (with toast error shown)
|
||||
*/
|
||||
export const handleTokenValidation = async (): Promise<boolean> => {
|
||||
const toast = useToast()
|
||||
const { valid, error } = await isValidUser()
|
||||
if (!valid) toast.error(error)
|
||||
return valid
|
||||
}
|
||||
44
packages/hoppscotch-common/src/helpers/isValidUser.ts
Normal file
44
packages/hoppscotch-common/src/helpers/isValidUser.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { platform } from "~/platform"
|
||||
|
||||
export type ValidUserResponse = {
|
||||
valid: boolean
|
||||
error: string
|
||||
}
|
||||
|
||||
export const SESSION_EXPIRED = "Session expired. Please log in again."
|
||||
|
||||
/**
|
||||
* Validates user authentication and token validity by making an API call.
|
||||
*
|
||||
* This function is kept separate from `handleTokenValidation()` to enable different use cases:
|
||||
* - Silent validation for conditional UI states (e.g., disabling components on token expiration)
|
||||
* - Background checks without triggering user notifications
|
||||
* - Custom error handling based on validation results
|
||||
*
|
||||
* Use `handleTokenValidation()` when you need automatic toast error notifications.
|
||||
* Use `isValidUser()` for silent validation or custom error handling scenarios.
|
||||
*
|
||||
* @returns {Promise<ValidUserResponse>} Authentication status with user existence and token validity
|
||||
*/
|
||||
export const isValidUser = async (): Promise<ValidUserResponse> => {
|
||||
const user = platform.auth.getCurrentUser()
|
||||
|
||||
if (user) {
|
||||
try {
|
||||
// If the platform provides a method to verify auth tokens, use it else assume tokens are valid (for central instance where firebase handles it)
|
||||
const hasValidTokens = platform.auth.verifyAuthTokens
|
||||
? await platform.auth.verifyAuthTokens()
|
||||
: true
|
||||
|
||||
return {
|
||||
valid: hasValidTokens,
|
||||
error: hasValidTokens ? "" : SESSION_EXPIRED,
|
||||
}
|
||||
} catch (error) {
|
||||
return { valid: false, error: SESSION_EXPIRED }
|
||||
}
|
||||
}
|
||||
|
||||
// allow user to perform actions without being logged in
|
||||
return { valid: true, error: "" }
|
||||
}
|
||||
|
|
@ -275,4 +275,10 @@ export type AuthPlatformDef = {
|
|||
* If a value is not given, then the value is assumed to be false.
|
||||
*/
|
||||
isEmailEditable?: boolean
|
||||
|
||||
/** Verifies if the current user's authentication tokens are valid
|
||||
* For self-hosted, this should verify the tokens with the backend
|
||||
* @returns True if tokens are valid, false otherwise
|
||||
*/
|
||||
verifyAuthTokens?: () => Promise<boolean>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -394,4 +394,31 @@ export const def: AuthPlatformDef = {
|
|||
event: "logout",
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the current user's authentication tokens are valid
|
||||
* @returns True if tokens are valid, false otherwise
|
||||
*/
|
||||
async verifyAuthTokens() {
|
||||
try {
|
||||
const BACKEND_API_URL = import.meta.env.VITE_BACKEND_API_URL
|
||||
|
||||
const client = await getClient()
|
||||
|
||||
const response = await client.get(
|
||||
`${BACKEND_API_URL}/auth/verify-token`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...this.getBackendHeaders(),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// axios automatically throws on error status codes, so if we reach here, it was successful
|
||||
return !!response.data.isValid
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -526,4 +526,30 @@ export const def: AuthPlatformDef = {
|
|||
event: "logout",
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the current user's authentication tokens are valid
|
||||
* @returns True if tokens are valid, false otherwise
|
||||
*/
|
||||
async verifyAuthTokens() {
|
||||
const BACKEND_API_URL = import.meta.env.VITE_BACKEND_API_URL
|
||||
|
||||
const { response } = interceptorService.execute({
|
||||
id: Date.now(),
|
||||
url: `${BACKEND_API_URL}/auth/verify-token`,
|
||||
method: "GET",
|
||||
version: "HTTP/1.1",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...this.getBackendHeaders(),
|
||||
},
|
||||
})
|
||||
|
||||
const res = await response
|
||||
if (E.isLeft(res)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return res.right.isValid
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -371,4 +371,27 @@ export const def: AuthPlatformDef = {
|
|||
}
|
||||
},
|
||||
getAllowedAuthProviders,
|
||||
|
||||
/**
|
||||
* Verifies if the current user's authentication tokens are valid
|
||||
* @returns True if tokens are valid, false otherwise
|
||||
*/
|
||||
async verifyAuthTokens() {
|
||||
try {
|
||||
const BACKEND_API_URL = import.meta.env.VITE_BACKEND_API_URL
|
||||
|
||||
const response = await axios.get(`${BACKEND_API_URL}/auth/verify-token`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...this.getBackendHeaders(),
|
||||
},
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
// axios automatically throws on error status codes, so if we reach here, it was successful
|
||||
return !!response.data.isValid
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue