@application-kit/authorization 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @application-kit/authorization
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 92d5b04: Вход `/vue`: `installAuthorization(app, { accessDenied, grants, resolveActor, router })` — ability на приложение, `<Can>`,
8
+ `useAbility()` и гвард маршрутов по `meta.can`; `canAccessRoute` и расширение `RouteMeta.can`. Гранты, источник актора и маршрут
9
+ отказа приходят параметрами. `@casl/vue`, `vue`, `vue-router` — опциональные peer, корневой вход их не тянет.
10
+
3
11
  ## 0.3.0
4
12
 
5
13
  ### Minor Changes
package/README.md CHANGED
@@ -10,7 +10,8 @@
10
10
  yarn add @application-kit/authorization
11
11
  ```
12
12
 
13
- Peer-зависимости: `@casl/ability`, `@nestjs/common`, `@nestjs/core`, `nestjs-pino`. Все обязательные.
13
+ Peer-зависимости: `@casl/ability`, `@nestjs/common`, `@nestjs/core`, `nestjs-pino` — обязательные; `@casl/vue`, `vue`,
14
+ `vue-router` — опциональные, нужны только входу `/vue`.
14
15
  `@nestjs/graphql` пакету не нужен: guard узнаёт GraphQL-запрос по типу контекста и работает и в приложении без GraphQL.
15
16
 
16
17
  ## Входы
@@ -19,6 +20,7 @@ Peer-зависимости: `@casl/ability`, `@nestjs/common`, `@nestjs/core`,
19
20
  |------|----------|-------|
20
21
  | `@application-kit/authorization` | сервер и браузер: реестры, `buildAbilityFor`, `composeAbilityGrants` | только `@casl/ability` |
21
22
  | `@application-kit/authorization/nest` | сервер: guard, декораторы, фабрика, логгер | `@nestjs/*`, `nestjs-pino` |
23
+ | `@application-kit/authorization/vue` | клиент и SSR: ability на приложение, `<Can>`, гвард маршрутов по `meta.can` | `vue`, `vue-router`, `@casl/vue` |
22
24
 
23
25
  Корневой вход не импортирует Nest — его можно собирать в браузерный бандл.
24
26
 
@@ -46,6 +48,28 @@ const grants = composeAbilityGrants(['EDITOR', 'VIEWER'], articleGrants, comment
46
48
  const ability = buildAbilityFor({ role: 'EDITOR' }, grants)
47
49
  ```
48
50
 
51
+ ## Vue
52
+
53
+ `installAuthorization(app, options)` ставит ability (свой экземпляр на приложение, поэтому и на запрос SSR), `<Can>`,
54
+ `useAbility()` и гвард: маршрут с `meta.can` без нужного права уводит на `accessDenied`. Решение только о показе
55
+ интерфейса: источник истины — бэкенд. `RouteMeta.can` объявляет сам вход.
56
+
57
+ ```ts
58
+ installAuthorization(app, {
59
+ accessDenied: { name: 'ACCESS_DENIED' }, // сам не несёт meta.can: отказ на нём зациклился бы
60
+ grants,
61
+ resolveActor: async () => meRepository.loadMe(), // null — гость; исполняется в контексте приложения
62
+ router,
63
+ })
64
+
65
+ const routes = [{ path: '/console', component: Console, meta: { can: { action: 'read', subject: 'Console' } } }]
66
+ ```
67
+
68
+ Актор запрашивается лениво, на первом маршруте с `meta.can`: маршруты без него запроса не ждут. Ошибка `resolveActor`
69
+ читается как гость, и следующий такой переход спросит снова — это и покрывает вход без перезагрузки страницы. Актор,
70
+ приехавший снапшотом SSR, `resolveActor` возвращает сам, без запроса. `canAccessRoute(ability, route)` — то же решение
71
+ отдельно от гварда.
72
+
49
73
  ## Nest
50
74
 
51
75
  - `PoliciesGuard` — глобальный guard (`APP_GUARD`): обработчик с `@Public()` пропускается, с `@CheckPolicies(...)` —
@@ -0,0 +1,2 @@
1
+ export * from './install-authorization.js';
2
+ export * from './route-ability.js';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./install-authorization.js"), exports);
18
+ __exportStar(require("./route-ability.js"), exports);
@@ -0,0 +1,11 @@
1
+ import type { App } from 'vue';
2
+ import type { RouteLocationRaw, Router } from 'vue-router';
3
+ import type { AbilityActorInterface } from '../build-ability.js';
4
+ import type { AbilityGrantMap } from '../types/ability-grant.js';
5
+ export interface InstallAuthorizationOptionsInterface {
6
+ accessDenied: RouteLocationRaw;
7
+ grants: AbilityGrantMap;
8
+ resolveActor(): Promise<AbilityActorInterface | null>;
9
+ router: Router;
10
+ }
11
+ export declare const installAuthorization: (app: App, options: InstallAuthorizationOptionsInterface) => void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installAuthorization = void 0;
4
+ const ability_1 = require("@casl/ability");
5
+ const vue_1 = require("@casl/vue");
6
+ const build_ability_js_1 = require("../build-ability.js");
7
+ const route_ability_js_1 = require("./route-ability.js");
8
+ const installAuthorization = (app, options) => {
9
+ const ability = (0, ability_1.createMongoAbility)();
10
+ app.use(vue_1.abilitiesPlugin, ability);
11
+ app.component('Can', vue_1.Can);
12
+ const hydrateAbility = async () => {
13
+ try {
14
+ const actor = await app.runWithContext(async () => options.resolveActor());
15
+ ability.update((0, build_ability_js_1.buildAbilityFor)(actor, options.grants).rules);
16
+ }
17
+ catch {
18
+ }
19
+ };
20
+ options.router.beforeEach(async (to) => {
21
+ if (to.meta.can !== undefined && ability.rules.length === 0) {
22
+ await hydrateAbility();
23
+ }
24
+ return (0, route_ability_js_1.canAccessRoute)(ability, to) || options.accessDenied;
25
+ });
26
+ };
27
+ exports.installAuthorization = installAuthorization;
@@ -0,0 +1,14 @@
1
+ import type { AnyAbility } from '@casl/ability';
2
+ import type { RouteLocationNormalized } from 'vue-router';
3
+ import type { Action } from '../types/actions.js';
4
+ import type { Subject } from '../types/subjects.js';
5
+ export interface RouteAbilityRequirementInterface {
6
+ action: Action;
7
+ subject: Subject;
8
+ }
9
+ declare module 'vue-router' {
10
+ interface RouteMeta {
11
+ can?: RouteAbilityRequirementInterface;
12
+ }
13
+ }
14
+ export declare const canAccessRoute: (ability: AnyAbility, route: Pick<RouteLocationNormalized, "meta">) => boolean;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canAccessRoute = void 0;
4
+ const canAccessRoute = (ability, route) => {
5
+ const requirement = route.meta.can;
6
+ return requirement === undefined || ability.can(requirement.action, requirement.subject);
7
+ };
8
+ exports.canAccessRoute = canAccessRoute;
@@ -0,0 +1,2 @@
1
+ export * from './install-authorization.js';
2
+ export * from './route-ability.js';
@@ -0,0 +1,2 @@
1
+ export * from './install-authorization.js';
2
+ export * from './route-ability.js';
@@ -0,0 +1,11 @@
1
+ import type { App } from 'vue';
2
+ import type { RouteLocationRaw, Router } from 'vue-router';
3
+ import type { AbilityActorInterface } from '../build-ability.js';
4
+ import type { AbilityGrantMap } from '../types/ability-grant.js';
5
+ export interface InstallAuthorizationOptionsInterface {
6
+ accessDenied: RouteLocationRaw;
7
+ grants: AbilityGrantMap;
8
+ resolveActor(): Promise<AbilityActorInterface | null>;
9
+ router: Router;
10
+ }
11
+ export declare const installAuthorization: (app: App, options: InstallAuthorizationOptionsInterface) => void;
@@ -0,0 +1,23 @@
1
+ import { createMongoAbility } from '@casl/ability';
2
+ import { abilitiesPlugin, Can } from '@casl/vue';
3
+ import { buildAbilityFor } from '../build-ability.js';
4
+ import { canAccessRoute } from './route-ability.js';
5
+ export const installAuthorization = (app, options) => {
6
+ const ability = createMongoAbility();
7
+ app.use(abilitiesPlugin, ability);
8
+ app.component('Can', Can);
9
+ const hydrateAbility = async () => {
10
+ try {
11
+ const actor = await app.runWithContext(async () => options.resolveActor());
12
+ ability.update(buildAbilityFor(actor, options.grants).rules);
13
+ }
14
+ catch {
15
+ }
16
+ };
17
+ options.router.beforeEach(async (to) => {
18
+ if (to.meta.can !== undefined && ability.rules.length === 0) {
19
+ await hydrateAbility();
20
+ }
21
+ return canAccessRoute(ability, to) || options.accessDenied;
22
+ });
23
+ };
@@ -0,0 +1,14 @@
1
+ import type { AnyAbility } from '@casl/ability';
2
+ import type { RouteLocationNormalized } from 'vue-router';
3
+ import type { Action } from '../types/actions.js';
4
+ import type { Subject } from '../types/subjects.js';
5
+ export interface RouteAbilityRequirementInterface {
6
+ action: Action;
7
+ subject: Subject;
8
+ }
9
+ declare module 'vue-router' {
10
+ interface RouteMeta {
11
+ can?: RouteAbilityRequirementInterface;
12
+ }
13
+ }
14
+ export declare const canAccessRoute: (ability: AnyAbility, route: Pick<RouteLocationNormalized, "meta">) => boolean;
@@ -0,0 +1,4 @@
1
+ export const canAccessRoute = (ability, route) => {
2
+ const requirement = route.meta.can;
3
+ return requirement === undefined || ability.can(requirement.action, requirement.subject);
4
+ };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@application-kit/authorization",
3
- "version": "0.3.0",
4
- "description": "Авторизация на CASL для NestJS и браузера: реестры subject'ов и ролей, ability из грантов модулей, глобальный guard политик",
3
+ "version": "0.4.0",
4
+ "description": "Авторизация на CASL для NestJS и браузера: реестры subject'ов и ролей, ability из грантов модулей, глобальный guard политик, гвард маршрутов Vue",
5
5
  "keywords": [
6
6
  "authorization",
7
7
  "casl",
8
8
  "nestjs",
9
- "policies"
9
+ "policies",
10
+ "vue"
10
11
  ],
11
12
  "homepage": "https://gitlab.com/application-kit/application-kit/-/tree/main/packages/authorization",
12
13
  "bugs": "https://gitlab.com/application-kit/application-kit/-/issues",
@@ -42,7 +43,17 @@
42
43
  "default": "./dist/cjs/nest/index.js"
43
44
  }
44
45
  },
45
- "./package.json": "./package.json"
46
+ "./package.json": "./package.json",
47
+ "./vue": {
48
+ "import": {
49
+ "types": "./dist/esm/vue/index.d.ts",
50
+ "default": "./dist/esm/vue/index.js"
51
+ },
52
+ "require": {
53
+ "types": "./dist/cjs/vue/index.d.ts",
54
+ "default": "./dist/cjs/vue/index.js"
55
+ }
56
+ }
46
57
  },
47
58
  "main": "./dist/cjs/index.js",
48
59
  "module": "./dist/esm/index.js",
@@ -51,13 +62,14 @@
51
62
  "dist"
52
63
  ],
53
64
  "scripts": {
54
- "build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src --entry-only=nest,internal && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
65
+ "build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src --entry-only=nest,internal,vue && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
55
66
  "lint:tsc": "tsc --noEmit --pretty --project .",
56
67
  "test:mutation": "stryker run",
57
68
  "test:unit": "vitest run"
58
69
  },
59
70
  "devDependencies": {
60
71
  "@casl/ability": "^7.0.1",
72
+ "@casl/vue": "^3.0.1",
61
73
  "@nestjs/common": "^11.2.7",
62
74
  "@nestjs/core": "^11.2.7",
63
75
  "@nestjs/testing": "^11.2.7",
@@ -70,13 +82,29 @@
70
82
  "reflect-metadata": "^0.2.2",
71
83
  "rxjs": "^7.8.2",
72
84
  "typescript": "^6.0.3",
73
- "vitest": "^4.1.11"
85
+ "vitest": "^4.1.11",
86
+ "vue": "^3.5.41",
87
+ "vue-router": "^5.2.0"
74
88
  },
75
89
  "peerDependencies": {
76
90
  "@casl/ability": "^7.0.0",
91
+ "@casl/vue": "^3.0.0",
77
92
  "@nestjs/common": "^11.0.0",
78
93
  "@nestjs/core": "^11.0.0",
79
- "nestjs-pino": "^4.6.1"
94
+ "nestjs-pino": "^4.6.1",
95
+ "vue": "^3.5.0",
96
+ "vue-router": "^5.0.0"
97
+ },
98
+ "peerDependenciesMeta": {
99
+ "@casl/vue": {
100
+ "optional": true
101
+ },
102
+ "vue": {
103
+ "optional": true
104
+ },
105
+ "vue-router": {
106
+ "optional": true
107
+ }
80
108
  },
81
109
  "engines": {
82
110
  "node": ">=24"