Skip to content

Commit c1c8d33

Browse files
committed
Validate url parameters
1 parent 0d93a71 commit c1c8d33

File tree

9 files changed

+120
-35
lines changed

9 files changed

+120
-35
lines changed

components/modules/proposal/VotesTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { DateTime } from "luxon"
44
55
/** Services */
6-
import { comma, space, validateCelestiaAddress } from "@/services/utils"
6+
import { comma, space, validateCelestiaAddress, validateCelestiaValidatorAddress } from "@/services/utils"
77
import { getVoteIcon, getVoteIconColor } from "@/services/utils/states"
88
99
/** UI */
@@ -124,7 +124,7 @@ watch(
124124
() => {
125125
if (!searchTerm.value) {
126126
emit("onFiltersReset", "address", true)
127-
} else if (!validateCelestiaAddress(searchTerm.value)) {
127+
} else if (!validateCelestiaAddress(searchTerm.value) && !validateCelestiaValidatorAddress(searchTerm.value)) {
128128
emit("updateFilters", "address", "", false)
129129
} else {
130130
emit("updateFilters", "address", searchTerm.value, true)

components/modules/validator/ValidatorOverview.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ const handleDelegate = () => {
431431
</Flex>
432432
433433
<Flex :class="$style.uptime_wrapper">
434-
<Tooltip v-for="t in uptime">
434+
<Tooltip v-for="t in uptime" @click="navigateTo(`/block/${t.height}`)">
435435
<Flex
436436
:class="$style.uptime"
437437
:style="{

pages/address/[hash].vue

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,32 @@ import AddressOverview from "@/components/modules/address/AddressOverview.vue"
44
import VotesTable from "@/components/modules/address/VotesTable.vue"
55
import AddressCharts from "@/components/modules/address/AddressCharts.vue"
66
7+
/** Services */
8+
import { isValidId } from "@/services/utils"
9+
710
/** API */
811
import { fetchAddressByHash } from "@/services/api/address"
912
1013
/** Store */
1114
import { useCacheStore } from "@/store/cache.store"
12-
1315
const cacheStore = useCacheStore()
1416
1517
const route = useRoute()
1618
const router = useRouter()
1719
1820
const address = ref()
19-
const { data: rawAddress } = await fetchAddressByHash(route.params.hash)
21+
const hash = route.params.hash.startsWith("0x") ? route.params.hash.slice(2) : route.params.hash
22+
if (isValidId(hash, "address")) {
23+
const { data: rawAddress } = await fetchAddressByHash(hash)
2024
21-
if (!rawAddress.value) {
22-
router.push("/")
25+
if (!rawAddress.value) {
26+
router.push("/")
27+
} else {
28+
address.value = rawAddress.value
29+
cacheStore.current.address = address.value
30+
}
2331
} else {
24-
address.value = rawAddress.value
25-
cacheStore.current.address = address.value
32+
router.push("/")
2633
}
2734
2835
defineOgImageComponent("AddressImage", {

pages/block/[height].vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import BlockOverview from "@/components/modules/block/BlockOverview.vue"
44
import BlobsTable from "@/components/modules/block/BlobsTable.vue"
55
66
/** Services */
7-
import { comma } from "@/services/utils"
7+
import { comma, isValidId } from "@/services/utils"
88
99
/** API */
1010
import { fetchBlockByHeight } from "@/services/api/block"
@@ -18,6 +18,11 @@ const cacheStore = useCacheStore()
1818
const route = useRoute()
1919
2020
const block = ref()
21+
22+
if (!isValidId(route.params.height, "block")) {
23+
navigateTo("/")
24+
}
25+
2126
const { data: rawBlock } = await fetchBlockByHeight(route.params.height)
2227
2328
const height = rawBlock.value ? rawBlock.value.height : Number(route.params.height)
@@ -28,7 +33,7 @@ const isUpcomingBlock = ref(!rawBlock.value)
2833
const isWaited = ref(isUpcomingBlock.value)
2934
3035
if (isUpcomingBlock.value && height > 1_000_000_000_000) {
31-
navigateTo(`/blocks`)
36+
navigateTo("/")
3237
}
3338
3439
watch(

pages/namespace/[id].vue

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import NamespaceOverview from "@/components/modules/namespace/NamespaceOverview.
44
import NamespaceCharts from "@/components/modules/namespace/NamespaceCharts.vue"
55
66
/** Services */
7-
import { getNamespaceID } from "@/services/utils"
7+
import { getNamespaceID, isValidId } from "@/services/utils"
88
99
/** API */
1010
import { fetchNamespaceByID } from "@/services/api/namespace"
@@ -17,13 +17,18 @@ const route = useRoute()
1717
const router = useRouter()
1818
1919
const namespace = ref()
20-
const { data: rawNamespace } = await fetchNamespaceByID(route.params.id)
2120
22-
if (!rawNamespace.value) {
23-
router.push("/")
21+
if (isValidId(route.params.id, "namespace")) {
22+
const { data: rawNamespace } = await fetchNamespaceByID(route.params.id)
23+
24+
if (!rawNamespace.value) {
25+
router.push("/")
26+
} else {
27+
namespace.value = rawNamespace.value[0]
28+
cacheStore.current.namespace = namespace.value
29+
}
2430
} else {
25-
namespace.value = rawNamespace.value[0]
26-
cacheStore.current.namespace = namespace.value
31+
router.push("/")
2732
}
2833
2934
defineOgImageComponent("NamespaceImage", {

pages/proposal/[id].vue

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import ProposalOverview from "@/components/modules/proposal/ProposalOverview.vue
44
import ProposalChanges from "@/components/modules/proposal/ProposalChanges.vue"
55
import ProposalDescription from "@/components/modules/proposal/ProposalDescription.vue"
66
7+
/** Services */
8+
import { isValidId } from "@/services/utils"
9+
710
/** API */
811
import { fetchProposalById } from "@/services/api/proposal"
912
@@ -14,20 +17,32 @@ const cacheStore = useCacheStore()
1417
const route = useRoute()
1518
1619
const proposal = ref()
17-
const { data: rawProposal } = await fetchProposalById({ id: route.params.id })
1820
19-
if (!rawProposal.value) {
20-
navigateTo({
21+
if (isValidId(route.params.id, "proposal")) {
22+
const { data: rawProposal } = await fetchProposalById({ id: route.params.id })
23+
24+
if (!rawProposal.value) {
25+
navigateTo({
26+
path: "/",
27+
query: {
28+
error: "not_found",
29+
target: "proposal",
30+
id: route.params.id,
31+
},
32+
})
33+
} else {
34+
proposal.value = rawProposal.value
35+
cacheStore.current.proposal = proposal.value
36+
}
37+
} else {
38+
navigateTo({
2139
path: "/",
2240
query: {
2341
error: "not_found",
2442
target: "proposal",
2543
id: route.params.id,
2644
},
2745
})
28-
} else {
29-
proposal.value = rawProposal.value
30-
cacheStore.current.proposal = proposal.value
3146
}
3247
3348
defineOgImageComponent("ProposalImage", {

pages/tx/[hash].vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import TxOverview from "@/components/modules/tx/TxOverview.vue"
44
import BlobsTable from "@/components/modules/block/BlobsTable.vue"
55
6+
/** Services */
7+
import { isValidId } from "@/services/utils"
8+
69
/** API */
710
import { fetchTxByHash } from "@/services/api/tx"
811
@@ -14,12 +17,17 @@ const route = useRoute()
1417
const router = useRouter()
1518
1619
const tx = ref()
17-
const { data: rawTx } = await fetchTxByHash(route.params.hash)
18-
if (!rawTx.value) {
19-
router.push("/")
20+
const hash = route.params.hash.startsWith("0x") ? route.params.hash.slice(2) : route.params.hash
21+
if (isValidId(hash, "tx")) {
22+
const { data: rawTx } = await fetchTxByHash(hash)
23+
if (!rawTx.value) {
24+
router.push("/")
25+
} else {
26+
tx.value = rawTx.value
27+
cacheStore.current.transaction = tx.value
28+
}
2029
} else {
21-
tx.value = rawTx.value
22-
cacheStore.current.transaction = tx.value
30+
router.push("/")
2331
}
2432
2533
defineOgImageComponent("TxImage", {

pages/validator/[id].vue

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
/** Components: Modules */
33
import ValidatorOverview from "@/components/modules/validator/ValidatorOverview.vue"
44
5+
/** Services */
6+
import { isValidId } from "@/services/utils"
7+
58
/** API */
69
import { fetchValidatorByID } from "@/services/api/validator"
710
@@ -13,19 +16,23 @@ const route = useRoute()
1316
const router = useRouter()
1417
1518
const validator = ref()
16-
const { data: rawValidator } = await fetchValidatorByID(route.params.id)
19+
if (isValidId(route.params.id, "validator")) {
20+
const { data: rawValidator } = await fetchValidatorByID(route.params.id)
1721
18-
if (!rawValidator.value) {
19-
router.push("/")
22+
if (!rawValidator.value) {
23+
router.push("/")
24+
} else {
25+
validator.value = rawValidator.value
26+
cacheStore.current.validator = validator.value
27+
}
2028
} else {
21-
validator.value = rawValidator.value
22-
cacheStore.current.validator = validator.value
29+
router.push("/")
2330
}
2431
2532
defineOgImageComponent("ValidatorImage", {
2633
title: "Validator",
2734
validator: validator.value,
28-
cacheKey: `${validator.value?.moniker || validator.value.address.hash}`,
35+
cacheKey: `${validator.value?.moniker || validator.value?.address?.hash}`,
2936
})
3037
3138
useHead({

services/utils/general.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,23 @@ export const splitAddress = (address, format = "string") => {
9393
export function validateCelestiaAddress(address) {
9494
if (!address) return false
9595

96-
const prefixes = ["celestiavaloper", "celestiavalcons", "celestia"] // order is important
96+
const prefix = "celestia1"
97+
98+
if (!address.startsWith(prefix)) return false
99+
100+
const hashPart = address.slice(prefix.length)
101+
if (hashPart.length !== 38) return false
102+
103+
const validChars = /^[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+$/
104+
if (!validChars.test(hashPart)) return false
105+
106+
return true
107+
}
108+
109+
export function validateCelestiaValidatorAddress(address) {
110+
if (!address) return false
111+
112+
const prefixes = ["celestiavaloper", "celestiavalcons"]
97113
const prefix = prefixes.find(p => address.startsWith(p))
98114
if (!prefix) return false
99115

@@ -103,6 +119,7 @@ export function validateCelestiaAddress(address) {
103119
if (hashPart.length !== 38) return false
104120

105121
const validChars = /^[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+$/
122+
106123
if (!validChars.test(hashPart)) return false
107124

108125
return true
@@ -257,3 +274,24 @@ export function hexToRgba(hex, alpha = 255) {
257274

258275
return `rgba(${r}, ${g}, ${b}, ${a})`
259276
}
277+
278+
export function isValidId(id, type) {
279+
switch (type) {
280+
case "block":
281+
case "proposal":
282+
case "validator":
283+
return /^[0-9]+$/.test(id);
284+
285+
case "tx":
286+
return /^[A-Fa-f0-9]{64}$/.test(id);
287+
288+
case "namespace":
289+
return /^[0-9a-f]{56}$/.test(id);
290+
291+
case "address":
292+
return validateCelestiaAddress(id);
293+
294+
default:
295+
return false;
296+
}
297+
}

0 commit comments

Comments
 (0)