Skip to content

Commit 8bb517a

Browse files
committed
feat: Adds programmatic access control
Implements the `usePublicPaths` composable to provide programmatic access control. This composable allows developers to determine which paths are accessible to users, enabling dynamic navigation menus, API endpoint lists, and conditional UI elements based on user roles and permissions. Removes '/login' from default whitelist in documentation examples as this is already handled internally.
1 parent 947475e commit 8bb517a

File tree

3 files changed

+550
-5
lines changed

3 files changed

+550
-5
lines changed

docs/user-guide/authorization.md

Lines changed: 171 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default defineNuxtConfig({
3232
nuxtUsers: {
3333
// ... other options
3434
auth: {
35-
whitelist: ['/login', '/register', '/public'],
35+
whitelist: ['/register', '/public'],
3636
permissions: {
3737
admin: ['*'], // Admin can access everything
3838
user: ['/profile', '/settings', '/api/nuxt-users/me'],
@@ -101,7 +101,7 @@ permissions: {
101101
```ts
102102
nuxtUsers: {
103103
auth: {
104-
whitelist: ['/login', '/register', '/about'],
104+
whitelist: ['/register', '/about'],
105105
permissions: {
106106
admin: ['*'],
107107
user: ['/profile', '/dashboard'],
@@ -115,7 +115,7 @@ nuxtUsers: {
115115
```ts
116116
nuxtUsers: {
117117
auth: {
118-
whitelist: ['/login', '/register', '/products', '/categories'],
118+
whitelist: ['/register', '/products', '/categories'],
119119
permissions: {
120120
admin: ['*'],
121121
manager: ['/admin/*', '/api/admin/*', '/reports/*'],
@@ -130,7 +130,7 @@ nuxtUsers: {
130130
```ts
131131
nuxtUsers: {
132132
auth: {
133-
whitelist: ['/login', '/register', '/public/*'],
133+
whitelist: ['/register', '/public/*'],
134134
permissions: {
135135
super_admin: ['*'],
136136
admin: ['/admin/*', '/api/admin/*'],
@@ -212,4 +212,170 @@ The default role is `user` if not specified.
212212
**Wildcards not working as expected:**
213213
- Use `/*` for sub-path matching (e.g., `/admin/*` matches `/admin/users`)
214214
- Use `*` for full access (e.g., `admin: ['*']`)
215-
- Test complex patterns with the actual paths you're trying to protect
215+
- Test complex patterns with the actual paths you're trying to protect
216+
217+
## Programmatic Access Control
218+
219+
### usePublicPaths Composable
220+
221+
The module provides a `usePublicPaths` composable that allows you to programmatically determine which paths are accessible to users. This is useful for building dynamic navigation menus, API endpoint lists, or conditional UI elements.
222+
223+
```vue
224+
<script setup>
225+
import { usePublicPaths } from 'nuxt-users/composables'
226+
227+
const { getPublicPaths, getAccessiblePaths, isPublicPath, isAccessiblePath } = usePublicPaths()
228+
</script>
229+
```
230+
231+
#### Getting Public Paths
232+
233+
Get all paths that don't require authentication (accessible to everyone):
234+
235+
```js
236+
const publicPaths = getPublicPaths()
237+
console.log(publicPaths)
238+
/*
239+
{
240+
all: ['/login', '/reset-password', '/api/nuxt-users/session', '/about'],
241+
builtIn: {
242+
pages: ['/login', '/reset-password'],
243+
api: ['/api/nuxt-users/session', '/api/nuxt-users/password/forgot', '/api/nuxt-users/password/reset']
244+
},
245+
whitelist: ['/about', '/contact'], // From your nuxt.config.ts
246+
customPasswordResetPath: null,
247+
apiBasePath: '/api/nuxt-users'
248+
}
249+
*/
250+
```
251+
252+
#### Getting User-Accessible Paths
253+
254+
Get all paths accessible to the current authenticated user (includes public + role-based paths):
255+
256+
```js
257+
const accessiblePaths = getAccessiblePaths()
258+
console.log(accessiblePaths)
259+
/*
260+
For authenticated user with 'user' role:
261+
{
262+
all: ['/login', '/about', '/profile', '/dashboard', '/api/nuxt-users/me'],
263+
public: ['/login', '/reset-password', '/about'],
264+
roleBasedPaths: ['/profile', '/dashboard', '/api/nuxt-users/me'],
265+
userRole: 'user'
266+
}
267+
*/
268+
```
269+
270+
#### Checking Individual Paths
271+
272+
Check if specific paths are accessible:
273+
274+
```js
275+
// Check if a path is truly public (no auth required)
276+
console.log(isPublicPath('/login')) // true
277+
console.log(isPublicPath('/profile')) // false
278+
console.log(isPublicPath('/about')) // true (if whitelisted)
279+
280+
// Check if current user can access a path
281+
console.log(isAccessiblePath('/profile')) // true if user role allows
282+
console.log(isAccessiblePath('/admin')) // depends on user role
283+
console.log(isAccessiblePath('/api/users', 'POST')) // check specific HTTP method
284+
```
285+
286+
#### Practical Examples
287+
288+
**Building Dynamic Navigation:**
289+
290+
```vue
291+
<template>
292+
<nav>
293+
<NuxtLink
294+
v-for="item in visibleNavItems"
295+
:key="item.path"
296+
:to="item.path"
297+
>
298+
{{ item.label }}
299+
</NuxtLink>
300+
</nav>
301+
</template>
302+
303+
<script setup>
304+
const { isAccessiblePath } = usePublicPaths()
305+
306+
const allNavItems = [
307+
{ path: '/dashboard', label: 'Dashboard' },
308+
{ path: '/profile', label: 'Profile' },
309+
{ path: '/admin', label: 'Admin Panel' },
310+
{ path: '/about', label: 'About' }
311+
]
312+
313+
const visibleNavItems = computed(() =>
314+
allNavItems.filter(item => isAccessiblePath(item.path))
315+
)
316+
</script>
317+
```
318+
319+
**Conditional API Endpoints:**
320+
321+
```vue
322+
<script setup>
323+
const { isAccessiblePath } = usePublicPaths()
324+
325+
const availableActions = computed(() => {
326+
const actions = []
327+
328+
if (isAccessiblePath('/api/users', 'GET')) {
329+
actions.push({ label: 'View Users', endpoint: '/api/users', method: 'GET' })
330+
}
331+
332+
if (isAccessiblePath('/api/users', 'POST')) {
333+
actions.push({ label: 'Create User', endpoint: '/api/users', method: 'POST' })
334+
}
335+
336+
return actions
337+
})
338+
</script>
339+
```
340+
341+
**Role-Based UI Components:**
342+
343+
```vue
344+
<template>
345+
<div>
346+
<!-- Always visible for public paths -->
347+
<PublicContent v-if="isPublicPath($route.path)" />
348+
349+
<!-- Only visible if user can access admin routes -->
350+
<AdminPanel v-if="canAccessAdmin" />
351+
352+
<!-- Conditional buttons based on permissions -->
353+
<button v-if="canCreateUsers" @click="createUser">
354+
Create User
355+
</button>
356+
</div>
357+
</template>
358+
359+
<script setup>
360+
const { isPublicPath, isAccessiblePath } = usePublicPaths()
361+
362+
const canAccessAdmin = computed(() =>
363+
isAccessiblePath('/admin')
364+
)
365+
366+
const canCreateUsers = computed(() =>
367+
isAccessiblePath('/api/users', 'POST')
368+
)
369+
</script>
370+
```
371+
372+
### API Reference
373+
374+
| Method | Description | Returns |
375+
|--------|-------------|----------|
376+
| `getPublicPaths()` | Get all truly public paths (no auth required) | Object with categorized public paths |
377+
| `getAccessiblePaths()` | Get all paths accessible to current user | Object with public + role-based paths |
378+
| `isPublicPath(path)` | Check if a path is public | Boolean |
379+
| `isAccessiblePath(path, method?)` | Check if current user can access path | Boolean |
380+
381+
**Note:** Static assets (files with dots) and Nuxt internal routes (starting with `/_`) are always considered public and accessible.

0 commit comments

Comments
 (0)