Role-based access control for Sapper. Works on both the server-, and, client-side.
Install as a dev dependency:
npm install --save-dev @beyonk/sapper-rbac
- For Sapper to work,
/client/.*
is automatically unrestricted.
import { Router } from '@beyonk/sapper-rbac'
const routes = new Router()
.unrestrict('/login.*')
.restrict('/admin/sales.*', [ 'admin', 'sales' ])
.restrict('/admin.*', ['admin'])
.restrict('.*', [ 'customer' ])
.build()
export default routes
import { guard } from '@beyonk/sapper-rbac'
import routes from './my-routes.js'
const app = polka()
.use(
sessionMiddleware,
(req, res, next) => {
const options = {
routes,
deny: () => {
res.writeHead(302, { Location: '/login' })
return res.end()
},
grant: () => {
return sapper.middleware({
session: () => (res.user ? { user: res.user } : {})
})(req, res, next)
}
}
return guard(req.path, res.user, options)
}
)
This middleware adds a user object at res.user
(or null if the request isn't authenticated). The only required attribute of this user is scope
which contains a list of authentication scopes that the user has:
function sessionMiddleware (req, res, next) {
res.user = {
scope: ['admin', 'other']
}
next()
}
For cases where the user is denied access, call this function.
The deny function receives two parameters:
deny (path, scope) {
// path: /some/path - the path the user attempted to access
// scope: {
// given: [ 'sales.view', 'booking.create' ] - the scopes the user has
// required: [ 'admin.view' ] - the scopes the user required
// }
}
For cases where the user is granted access, call this function.
On the client side, we integrate with the page store in the root _layout.svelte
:
import routes from './my-routes.js'
import { guard } from '@beyonk/sapper-rbac'
import { tick } from 'svelte'
import { stores, goto } from '@sapper/app'
const { page, session } = stores()
const options = {
routes,
deny: () => goto('/login')
// we don't specify grant here, since we don't need to do anything.
}
// Listen to the page store.
page.subscribe(async v => {
await tick() // let the previous routing finish first.
guard(v.path, $session.user, options)
})