963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
5.0 KiB
5.0 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Per-Route beforeEnter Guards Ignore Param/Query Changes | MEDIUM | Route-level beforeEnter guards don't fire when only params, query, or hash change, causing unexpected bypasses of validation logic | gotcha |
|
Per-Route beforeEnter Guards Ignore Param/Query Changes
Impact: MEDIUM - The beforeEnter guard defined in route configuration only triggers when entering a route from a DIFFERENT route. Changes to params, query strings, or hash within the same route do NOT trigger beforeEnter, potentially bypassing important validation logic.
Task Checklist
- Use in-component
onBeforeRouteUpdatefor param/query changes - Or use global
beforeEachwith route.params/query checks - Document which guards protect which scenarios
- Test navigation between same route with different params
The Problem
// router.js
const routes = [
{
path: '/orders/:id',
component: OrderDetail,
beforeEnter: async (to, from) => {
// This runs when entering from /products
// But NOT when navigating from /orders/1 to /orders/2!
const order = await checkOrderAccess(to.params.id);
if (!order.canView) {
return '/unauthorized';
}
},
},
];
Scenario:
- User navigates from
/productsto/orders/1- beforeEnter runs, access checked - User navigates from
/orders/1to/orders/2- beforeEnter DOES NOT run! - User might access order they don't have permission for!
What Triggers beforeEnter vs. What Doesn't
| Navigation | beforeEnter fires? |
|---|---|
/products → /orders/1 |
YES |
/orders/1 → /orders/2 |
NO |
/orders/1 → /orders/1?tab=details |
NO |
/orders/1#section → /orders/1#other |
NO |
/orders/1 → /products → /orders/2 |
YES (leaving and re-entering) |
Solution 1: Add In-Component Guard
<!-- OrderDetail.vue -->
<script setup>
import { onBeforeRouteUpdate } from 'vue-router';
// Handle param changes within the same route
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
const order = await checkOrderAccess(to.params.id);
if (!order.canView) {
return '/unauthorized';
}
}
});
</script>
Solution 2: Use Global beforeEach Instead
// router.js
router.beforeEach(async (to, from) => {
// Handle all order access checks globally
if (to.name === 'OrderDetail') {
// This runs on EVERY navigation to this route, including param changes
const order = await checkOrderAccess(to.params.id);
if (!order.canView) {
return '/unauthorized';
}
}
});
Solution 3: Combine Both Guards
// router.js - For entering from different route
const routes = [
{
path: '/orders/:id',
component: OrderDetail,
beforeEnter: (to) => validateOrderAccess(to.params.id),
},
];
// In component - For param changes within route
// OrderDetail.vue
onBeforeRouteUpdate((to) => validateOrderAccess(to.params.id));
// Shared validation function
async function validateOrderAccess(orderId) {
const order = await checkOrderAccess(orderId);
if (!order.canView) {
return '/unauthorized';
}
}
Solution 4: Use beforeEnter with Array of Guards
// guards/orderGuards.js
export const orderAccessGuard = async (to) => {
const order = await checkOrderAccess(to.params.id);
if (!order.canView) {
return '/unauthorized';
}
};
// router.js
const routes = [
{
path: '/orders/:id',
component: OrderDetail,
beforeEnter: [orderAccessGuard], // Can add multiple guards
},
];
// Still need in-component guard for param changes!
Full Navigation Guard Execution Order
Understanding when each guard type fires:
1. beforeRouteLeave (in-component, leaving component)
2. beforeEach (global)
3. beforeEnter (per-route, ONLY when entering from different route)
4. beforeRouteEnter (in-component, entering component)
5. beforeResolve (global)
6. afterEach (global, after navigation confirmed)
For param/query changes on same route:
1. beforeRouteUpdate (in-component) - ONLY this fires!
2. beforeEach (global)
3. beforeResolve (global)
4. afterEach (global)
Key Points
- beforeEnter is for route ENTRY only - Not for within-route changes
- Use onBeforeRouteUpdate for param changes - This is the in-component solution
- Global beforeEach always runs - Good for centralized validation
- Test param change scenarios - Easy to miss during development
- Consider security implications - Param-based access control needs both guards