Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Policy composable #419

Closed
kiaking opened this issue Dec 19, 2023 · 0 comments · Fixed by #491
Closed

Add Policy composable #419

kiaking opened this issue Dec 19, 2023 · 0 comments · Fixed by #491
Assignees
Labels
enhancement New feature or request

Comments

@kiaking
Copy link
Member

kiaking commented Dec 19, 2023

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()
  })
})
@kiaking kiaking added the enhancement New feature or request label Dec 19, 2023
@brc-dd brc-dd self-assigned this Mar 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants