├── .browserslistrc
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── jest.config.js
├── package.json
├── public
├── favicon.ico
└── index.html
├── publish.sh
├── scripts
└── release.js
├── src
├── App.vue
├── Authorized
│ ├── Authorized.vue
│ ├── index.ts
│ ├── reactive.ts
│ ├── type.ts
│ └── utils.ts
├── __tests__
│ └── index.spec.js
├── main.ts
└── shims-vue.d.ts
├── tsconfig.json
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | test:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v2
12 | with:
13 | persist-credentials: false
14 | - name: Install
15 | uses: sergioramos/yarn-actions/install@v6
16 | with:
17 | frozen-lockfile: true
18 | - name: Unit test
19 | run: |
20 | npm run test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 xiejiahe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
用于Vue3, 基于 ABAC 权限验证
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## 安装
14 | ```bash
15 | npm i vue-authorized -S
16 | # or
17 | yarn add vue-authorized
18 | ```
19 |
20 |
21 | ## 使用
22 | 一个典型的例子:
23 |
24 | `authority` 参数接收 `string`/`Array`/`number`/`boolean` , 如果是数组则有任意一个匹配则验证通过
25 | ```vue
26 |
27 |
30 | 有权限则显示
31 |
32 |
33 |
34 |
54 | ```
55 |
56 |
57 | 通过 API `Authorized.checked` 验证权限
58 | ```vue
59 |
60 | 有权限则显示
61 |
62 |
63 |
84 | ```
85 |
86 |
87 | 接收插槽 `no-match` 没有权限的情况下显示内容:
88 | ```vue
89 |
90 |
93 | 有权限则显示
94 |
95 |
96 | 无权限
97 |
98 |
99 |
100 | ```
101 |
102 |
103 | 无权限传入 `null` 或 空数组
104 | ```vue
105 |
59 |
60 |
70 |
--------------------------------------------------------------------------------
/src/Authorized/Authorized.vue:
--------------------------------------------------------------------------------
1 | // Copyright 2021 the vue-authorized authors. All rights reserved. MIT license.
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
60 |
--------------------------------------------------------------------------------
/src/Authorized/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 the vue-authorized authors. All rights reserved. MIT license.
2 |
3 | import Authorized from './Authorized.vue'
4 | import { handleSetPermissions, checkedPermission } from './utils'
5 |
6 | Authorized.setPermissions = handleSetPermissions
7 | Authorized.checked = checkedPermission
8 |
9 | export const setPermissions = handleSetPermissions
10 | export const checked = checkedPermission
11 |
12 | export default Authorized
--------------------------------------------------------------------------------
/src/Authorized/reactive.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 the vue-authorized authors. All rights reserved. MIT license.
2 |
3 | import { reactive } from 'vue'
4 | import { IReactive } from './type'
5 |
6 | export const permssions = reactive({
7 | value: [],
8 |
9 | // 优先验证 hasPermission,如果为 false 则无权限
10 | // 因为 value 可能会异步加载,初始化为空无法准确判断
11 | hasPermission: true
12 | })
13 |
14 | export const persMap = new Map()
15 |
--------------------------------------------------------------------------------
/src/Authorized/type.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 the vue-authorized authors. All rights reserved. MIT license.
2 |
3 | export type IReactive = {
4 | value: unknown[]
5 | hasPermission: boolean
6 | }
7 |
8 | export type IAuthority =
9 | | string
10 | | number
11 | | unknown[]
12 | | null
13 | | undefined
14 |
--------------------------------------------------------------------------------
/src/Authorized/utils.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 the vue-authorized authors. All rights reserved. MIT license.
2 |
3 | import { permssions, persMap } from './reactive'
4 | import { IAuthority } from './type'
5 |
6 | // 没有权限则传入 null
7 | export function handleSetPermissions(pers: unknown[] | null) {
8 | // No permissions
9 | if (
10 | pers === null ||
11 | (Array.isArray(pers) && pers.length === 0)
12 | ) {
13 | permssions.hasPermission = false
14 | persMap.clear()
15 | return
16 | }
17 |
18 | if (!Array.isArray(pers)) {
19 | return
20 | }
21 |
22 | persMap.clear()
23 | for (let i = 0; i < pers.length; i++) {
24 | persMap.set(pers[i], true)
25 | }
26 |
27 | if (pers.length > 0) {
28 | permssions.hasPermission = true
29 | }
30 |
31 | permssions.value = pers
32 | }
33 |
34 | // Check permissions
35 | // Return target
36 | export function checkedPermission(
37 | authority: IAuthority,
38 | target = true,
39 | ): any {
40 |
41 | if (target === undefined) {
42 | target = true
43 | }
44 |
45 | // falsy: No permissions specified
46 | if (!authority) {
47 | return target
48 | }
49 |
50 | if (
51 | typeof authority === 'string' ||
52 | typeof authority === 'boolean' ||
53 | typeof authority === 'number' ||
54 | // null|undefined
55 | authority == null
56 | ) {
57 | if (persMap.has(authority)) {
58 | return target
59 | }
60 |
61 | return false
62 | }
63 |
64 | if (Array.isArray(authority)) {
65 | for (let i = 0; i < authority.length; i++) {
66 | if (persMap.has(authority[i])) {
67 | return target
68 | }
69 | }
70 |
71 | return false
72 | }
73 |
74 |
75 | throw new Error('[vue-authorized]: unsupported parameters')
76 | }
77 |
--------------------------------------------------------------------------------
/src/__tests__/index.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils'
2 | import Authorized from '../Authorized'
3 | import { persMap } from '../Authorized/reactive'
4 |
5 | const mockPerm = [
6 | 'user:add',
7 | true,
8 | false,
9 | undefined,
10 | null,
11 | 123
12 | ]
13 |
14 | describe('', () => {
15 | beforeEach(() => {
16 | Authorized.setPermissions(mockPerm)
17 | })
18 |
19 |
20 | describe('Test ', () => {
21 | test('1(Have permission).test authority prop', () => {
22 | const wrapper = mount(Authorized, {
23 | props: {
24 | authority: 'user:add'
25 | },
26 | slots: {
27 | default: 'hello',
28 | },
29 | })
30 |
31 | expect(wrapper.text()).toBe('hello')
32 | })
33 |
34 | test('2(Have permission).test authority prop', () => {
35 | const wrapper = mount(Authorized, {
36 | props: {
37 | authority: ['user:add', 'abc']
38 | },
39 | slots: {
40 | default: 'hello',
41 | },
42 | })
43 |
44 | expect(wrapper.text()).toBe('hello')
45 | })
46 |
47 | test('3(No permission).test authority prop', () => {
48 | const wrapper = mount(Authorized, {
49 | props: {
50 | authority: ['abc']
51 | },
52 | slots: {
53 | default: 'hello',
54 | },
55 | })
56 |
57 | expect(wrapper.text()).toBe('')
58 | })
59 |
60 | test('4(No permission).test authority prop', () => {
61 | const wrapper = mount(Authorized, {
62 | props: {
63 | authority: ['abc']
64 | },
65 | slots: {
66 | default: 'hello',
67 | 'no-match': 'hello',
68 | },
69 | })
70 |
71 | expect(wrapper.html()).toBe('hello')
72 | })
73 |
74 | test('5(No permission).test no-match event', () => {
75 | const wrapper = mount(Authorized, {
76 | props: {
77 | authority: '3333333'
78 | }
79 | })
80 | expect(wrapper.emitted()).toHaveProperty('no-match')
81 | })
82 |
83 | test('6(No permission).test no-match event', () => {
84 | Authorized.setPermissions(null)
85 | const wrapper = mount(Authorized)
86 | expect(wrapper.emitted()).toHaveProperty('no-match')
87 | })
88 |
89 | test('7(No permission).test no-match event', async () => {
90 | Authorized.setPermissions(null)
91 | const wrapper = mount(Authorized, {
92 | props: {
93 | authority: 123
94 | }
95 | })
96 | expect(wrapper.emitted()).toHaveProperty('no-match')
97 | })
98 | })
99 |
100 | describe('Test checked', () => {
101 | test('1. Authorized.checked', () => {
102 | expect(Authorized.checked('user:add')).toBe(true)
103 | expect(Authorized.checked('user:add1')).toBe(false)
104 | expect(Authorized.checked('')).toBe(true)
105 | expect(Authorized.checked(null)).toBe(true)
106 | expect(Authorized.checked()).toBe(true)
107 | expect(Authorized.checked(false)).toBe(true)
108 | expect(Authorized.checked(false, null)).toBe(null)
109 |
110 | // 不支持 object
111 | try {
112 | Authorized.checked({})
113 | expect(1).toBe(2) // 如果这行能运行,则程序有问题
114 | } catch {}
115 | })
116 | })
117 |
118 | describe('Test setPermissions', () => {
119 | test('1. Authorized.setPermissions', () => {
120 | Authorized.setPermissions([1, 2, 3, 4, 5])
121 | expect(persMap.size).toBe(5)
122 |
123 | Authorized.setPermissions(null)
124 | expect(persMap.size).toBe(0)
125 | })
126 | })
127 | })
128 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 the vue-authorized authors. All rights reserved. MIT license.
2 |
3 | import { createApp } from 'vue'
4 | import App from './App.vue'
5 |
6 | createApp(App).mount('#app')
7 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ]
21 | },
22 | "lib": [
23 | "esnext",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "src/**/*.ts",
31 | "src/**/*.tsx",
32 | "src/**/*.vue",
33 | "tests/**/*.ts",
34 | "tests/**/*.tsx"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------