Add `Policy` composable
Closed this issue · 0 comments
kiaking commented
Policy
is a composable to determine whether the user can perform certain actions. It provides streamlined and consistent authorization mechanism that can be integrated to other components.
Basic usage
// Define policy.
function useCanCreatePost(post: Post) {
const user = getLoggedInUser()
return usePolicy(({ allow, deny }) => {
if (post.author === user) {
return allow()
}
return deny()
})
}
// Use it.
const canCreatePost = useCanCreatePost(post)
canCreatePost.value.ok // boolean
API
type Policy<Code = any> = Ref<PolicyRaw<Code>>
interface PolicyRaw<Code = any> {
ok: boolean | null
code: Code
is(code: Code): boolean
}
interface PolicyResponse<Code = any> {
ok: boolean | null
code: Code
}
interface PolicyHelpers<Code = any> {
allow(code?: Code): PolicyResponse<Code>
deny(code?: Code): PolicyResponse<Code>
pending(code?: Code): PolicyResponse<Code>
}
function usePolicy<Code = any>(
fn: (helpers: PolicyHelpers) => PolicyResponse<Code>
): Policy<Code>
Using "code"
Policy can return response code that indicates "why" the user is not allowed to perform action. Using this, we may tell user the reason on UI.
<script setup lang="ts">
function useCanCreatePost(post: Post) {
const user = getLoggedInUser()
// Define code as generics.
return usePolicy<'ok' | 'not-author'>(({ allow, deny }) => {
// Set code on allow/deny.
return post.author === user
? allow('ok')
: deny('not-author')
})
}
const canCreatePost = useCanCreatePost(post)
</script>
<template>
<div>
<div v-if="canCreatePost.ok">
<!-- Show post -->
</div>
<div v-else>
<p v-if="canCreatePost.is('not-author')">
You are not the author of this post.
</p>
</div>
</div>
</template>
Pending state
Sometimes, we need to wait for api call to determine the final authorization. In this case, use pending
. The ok
becomes null
in this case.
function useCanCreatePost(post: MaybeRefOrGetter<Post>) {
const user = getLoggedInUser()
return usePolicy(({ allow, deny, pending }) => {
const p = toValue(post)
return p
? p.author === user ? allow() : deny()
: pending()
})
}
Real world app usage
In the real world, app, we should create a local usePolicy
composable that inherits this composable and inject currently logged in user instance for easier access.
interface PolicyHelpers<Code = any> extends SefirotPolicyHelpers<Code> {
user: User
}
function usePolicy<Code = any>(
fn: (helpers: PolicyHelpers<Code>) => PolicyResponse<Code>
): Policy<Code> {
const user = getLoggedInUser()
return useSefirotPolicy((helpers) => {
return fn({ ...helpers, user })
})
}
function canCreatePost(): Policy {
// Now the user object is available.
return usePolicy({ user, allow, deny } => {
return post.author === user
? allow()
: deny()
})
})