@application-kit/authorization 0.2.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +30 -2
  3. package/dist/cjs/nest/current-actor.decorator.d.ts +1 -0
  4. package/dist/cjs/nest/current-actor.decorator.js +7 -0
  5. package/dist/cjs/nest/index.d.ts +3 -0
  6. package/dist/cjs/nest/index.js +3 -0
  7. package/dist/cjs/nest/internal/index.d.ts +3 -0
  8. package/dist/cjs/nest/internal/index.js +19 -0
  9. package/dist/cjs/nest/internal/is-graphql-context.d.ts +2 -0
  10. package/dist/cjs/nest/internal/is-graphql-context.js +9 -0
  11. package/dist/cjs/nest/internal/is-public-handler.d.ts +3 -0
  12. package/dist/cjs/nest/internal/is-public-handler.js +9 -0
  13. package/dist/cjs/nest/internal/resolve-execution-actor.d.ts +3 -0
  14. package/dist/cjs/nest/internal/resolve-execution-actor.js +9 -0
  15. package/dist/cjs/nest/policies.guard.d.ts +0 -1
  16. package/dist/cjs/nest/policies.guard.js +7 -18
  17. package/dist/cjs/nest/resolve-execution-request.d.ts +2 -0
  18. package/dist/cjs/nest/resolve-execution-request.js +9 -0
  19. package/dist/cjs/nest/with-public-routes.d.ts +2 -0
  20. package/dist/cjs/nest/with-public-routes.js +36 -0
  21. package/dist/cjs/vue/index.d.ts +2 -0
  22. package/dist/cjs/vue/index.js +18 -0
  23. package/dist/cjs/vue/install-authorization.d.ts +11 -0
  24. package/dist/cjs/vue/install-authorization.js +27 -0
  25. package/dist/cjs/vue/route-ability.d.ts +14 -0
  26. package/dist/cjs/vue/route-ability.js +8 -0
  27. package/dist/esm/nest/current-actor.decorator.d.ts +1 -0
  28. package/dist/esm/nest/current-actor.decorator.js +4 -0
  29. package/dist/esm/nest/index.d.ts +3 -0
  30. package/dist/esm/nest/index.js +3 -0
  31. package/dist/esm/nest/internal/index.d.ts +3 -0
  32. package/dist/esm/nest/internal/index.js +3 -0
  33. package/dist/esm/nest/internal/is-graphql-context.d.ts +2 -0
  34. package/dist/esm/nest/internal/is-graphql-context.js +5 -0
  35. package/dist/esm/nest/internal/is-public-handler.d.ts +3 -0
  36. package/dist/esm/nest/internal/is-public-handler.js +5 -0
  37. package/dist/esm/nest/internal/resolve-execution-actor.d.ts +3 -0
  38. package/dist/esm/nest/internal/resolve-execution-actor.js +5 -0
  39. package/dist/esm/nest/policies.guard.d.ts +0 -1
  40. package/dist/esm/nest/policies.guard.js +7 -18
  41. package/dist/esm/nest/resolve-execution-request.d.ts +2 -0
  42. package/dist/esm/nest/resolve-execution-request.js +5 -0
  43. package/dist/esm/nest/with-public-routes.d.ts +2 -0
  44. package/dist/esm/nest/with-public-routes.js +32 -0
  45. package/dist/esm/vue/index.d.ts +2 -0
  46. package/dist/esm/vue/index.js +2 -0
  47. package/dist/esm/vue/install-authorization.d.ts +11 -0
  48. package/dist/esm/vue/install-authorization.js +23 -0
  49. package/dist/esm/vue/route-ability.d.ts +14 -0
  50. package/dist/esm/vue/route-ability.js +4 -0
  51. package/package.json +35 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
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
+
11
+ ## 0.3.0
12
+
13
+ ### Minor Changes
14
+
15
+ - ef902ec: Вход `/nest`: `CurrentActor()` — актор параметром обработчика, `withPublicRoutes(AuthGuard('jwt'))` — guard аутентификации с учётом `@Public()`, `resolveExecutionRequest` — чтение запроса без развилки HTTP/GraphQL. Новых зависимостей нет: `@nestjs/graphql` и `@nestjs/passport` пакету не нужны.
16
+
3
17
  ## 0.2.0
4
18
 
5
19
  ### 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,13 +48,39 @@ 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(...)` —
52
76
  проверяется конъюнкцией политик над ability актора из `request.user`. GraphQL-резолвер без обоих декораторов
53
77
  запрещён (fail-closed) с предупреждением в лог, REST-обработчик — пропускается: его авторизация на маршруте.
54
78
  - `CheckPolicies(...handlers)`, `PolicyHandler`, `permitAuthenticated` — политики обработчика.
55
- - `Public()`, `IS_PUBLIC_KEY` — открытый обработчик; ключ читает и guard аутентификации приложения.
79
+ - `Public()`, `IS_PUBLIC_KEY` — открытый обработчик.
80
+ - `withPublicRoutes(AuthGuard('jwt'))` — guard аутентификации приложения поверх его базы: открытый обработчик пропускает
81
+ мимо базы, а запрос ей отдаёт и на HTTP, и в GraphQL. `@nestjs/passport` пакету не нужен.
82
+ - `CurrentActor()` — параметр обработчика с `request.user`; на открытом обработчике актора может не быть.
83
+ - `resolveExecutionRequest(context)` — запрос без развилки транспорта. `@nestjs/graphql` пакету не нужен.
56
84
  - `AbilityFactory` — ability актора по карте грантов.
57
85
 
58
86
  Собирает механизм `AuthorizationModule.forRoot({ grants })`: фабрика ability, логгер и глобальный guard (`APP_GUARD`)
@@ -0,0 +1 @@
1
+ export declare const CurrentActor: (...dataOrPipes: unknown[]) => ParameterDecorator;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CurrentActor = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const resolve_execution_actor_js_1 = require("./internal/resolve-execution-actor.js");
6
+ const currentActorFactory = (_, context) => (0, resolve_execution_actor_js_1.resolveExecutionActor)(context);
7
+ exports.CurrentActor = (0, common_1.createParamDecorator)(currentActorFactory);
@@ -4,6 +4,9 @@ export * from './authorization-logger.interface.js';
4
4
  export * from './authorization.module.js';
5
5
  export * from './check-policies.decorator.js';
6
6
  export * from './constants.js';
7
+ export * from './current-actor.decorator.js';
7
8
  export * from './policies.guard.js';
8
9
  export * from './policy-handlers.js';
9
10
  export * from './public.decorator.js';
11
+ export * from './resolve-execution-request.js';
12
+ export * from './with-public-routes.js';
@@ -20,6 +20,9 @@ __exportStar(require("./authorization-logger.interface.js"), exports);
20
20
  __exportStar(require("./authorization.module.js"), exports);
21
21
  __exportStar(require("./check-policies.decorator.js"), exports);
22
22
  __exportStar(require("./constants.js"), exports);
23
+ __exportStar(require("./current-actor.decorator.js"), exports);
23
24
  __exportStar(require("./policies.guard.js"), exports);
24
25
  __exportStar(require("./policy-handlers.js"), exports);
25
26
  __exportStar(require("./public.decorator.js"), exports);
27
+ __exportStar(require("./resolve-execution-request.js"), exports);
28
+ __exportStar(require("./with-public-routes.js"), exports);
@@ -0,0 +1,3 @@
1
+ export * from './is-graphql-context.js';
2
+ export * from './is-public-handler.js';
3
+ export * from './resolve-execution-actor.js';
@@ -0,0 +1,19 @@
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("./is-graphql-context.js"), exports);
18
+ __exportStar(require("./is-public-handler.js"), exports);
19
+ __exportStar(require("./resolve-execution-actor.js"), exports);
@@ -0,0 +1,2 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ export declare const isGraphqlContext: (context: ExecutionContext) => boolean;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isGraphqlContext = void 0;
4
+ const GRAPHQL_CONTEXT_TYPE = 'graphql';
5
+ const isGraphqlContext = (context) => {
6
+ const transport = context.getType();
7
+ return transport === GRAPHQL_CONTEXT_TYPE;
8
+ };
9
+ exports.isGraphqlContext = isGraphqlContext;
@@ -0,0 +1,3 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ import type { Reflector } from '@nestjs/core';
3
+ export declare const isPublicHandler: (reflector: Reflector, context: ExecutionContext) => boolean;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPublicHandler = void 0;
4
+ const public_decorator_js_1 = require("../public.decorator.js");
5
+ const isPublicHandler = (reflector, context) => {
6
+ const targets = [context.getHandler(), context.getClass()];
7
+ return reflector.getAllAndOverride(public_decorator_js_1.IS_PUBLIC_KEY, targets) === true;
8
+ };
9
+ exports.isPublicHandler = isPublicHandler;
@@ -0,0 +1,3 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ import type { AbilityActorInterface } from '../../build-ability.js';
3
+ export declare const resolveExecutionActor: (context: ExecutionContext) => AbilityActorInterface | undefined;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveExecutionActor = void 0;
4
+ const resolve_execution_request_js_1 = require("../resolve-execution-request.js");
5
+ const resolveExecutionActor = (context) => {
6
+ const request = (0, resolve_execution_request_js_1.resolveExecutionRequest)(context);
7
+ return request?.user;
8
+ };
9
+ exports.resolveExecutionActor = resolveExecutionActor;
@@ -10,5 +10,4 @@ export declare class PoliciesGuard implements CanActivate {
10
10
  canActivate(context: ExecutionContext): boolean;
11
11
  private allowWithoutPolicies;
12
12
  private enforcePolicies;
13
- private extractActor;
14
13
  }
@@ -17,10 +17,9 @@ const common_1 = require("@nestjs/common");
17
17
  const core_1 = require("@nestjs/core");
18
18
  const check_policies_decorator_js_1 = require("./check-policies.decorator.js");
19
19
  const constants_js_1 = require("./constants.js");
20
- const public_decorator_js_1 = require("./public.decorator.js");
21
- const GRAPHQL_CONTEXT_TYPE = 'graphql';
22
- const GRAPHQL_CONTEXT_ARGUMENT_INDEX = 2;
23
- const isGraphql = (context) => context.getType() === GRAPHQL_CONTEXT_TYPE;
20
+ const is_graphql_context_js_1 = require("./internal/is-graphql-context.js");
21
+ const is_public_handler_js_1 = require("./internal/is-public-handler.js");
22
+ const resolve_execution_actor_js_1 = require("./internal/resolve-execution-actor.js");
24
23
  let PoliciesGuard = class PoliciesGuard {
25
24
  reflector;
26
25
  abilityFactory;
@@ -31,26 +30,22 @@ let PoliciesGuard = class PoliciesGuard {
31
30
  this.logger = logger;
32
31
  }
33
32
  canActivate(context) {
34
- const isPublic = this.reflector.getAllAndOverride(public_decorator_js_1.IS_PUBLIC_KEY, [
35
- context.getHandler(),
36
- context.getClass(),
37
- ]);
38
- if (isPublic === true) {
33
+ if ((0, is_public_handler_js_1.isPublicHandler)(this.reflector, context)) {
39
34
  return true;
40
35
  }
41
36
  const handlers = this.reflector.getAllAndOverride(check_policies_decorator_js_1.CHECK_POLICIES_KEY, [context.getHandler(), context.getClass()]) ?? [];
42
37
  return handlers.length === 0 ? this.allowWithoutPolicies(context) : this.enforcePolicies({ context, handlers });
43
38
  }
44
39
  allowWithoutPolicies(context) {
45
- if (isGraphql(context)) {
40
+ if ((0, is_graphql_context_js_1.isGraphqlContext)(context)) {
46
41
  this.logger.warn({ class: context.getClass().name, handler: context.getHandler().name }, 'PoliciesGuard deny-by-default — GraphQL resolver missing @Public/@CheckPolicies');
47
42
  throw new common_1.ForbiddenException('Forbidden by policy');
48
43
  }
49
44
  return true;
50
45
  }
51
46
  enforcePolicies(parameters) {
52
- const actor = this.extractActor(parameters.context);
53
- if (actor === null) {
47
+ const actor = (0, resolve_execution_actor_js_1.resolveExecutionActor)(parameters.context);
48
+ if (actor === undefined) {
54
49
  throw new common_1.ForbiddenException('No authenticated user');
55
50
  }
56
51
  const ability = this.abilityFactory.createForActor({
@@ -62,12 +57,6 @@ let PoliciesGuard = class PoliciesGuard {
62
57
  }
63
58
  return true;
64
59
  }
65
- extractActor(context) {
66
- const request = isGraphql(context)
67
- ? context.getArgByIndex(GRAPHQL_CONTEXT_ARGUMENT_INDEX).req
68
- : context.switchToHttp().getRequest();
69
- return request?.user ?? null;
70
- }
71
60
  };
72
61
  exports.PoliciesGuard = PoliciesGuard;
73
62
  exports.PoliciesGuard = PoliciesGuard = __decorate([
@@ -0,0 +1,2 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ export declare const resolveExecutionRequest: <TRequest>(context: ExecutionContext) => TRequest | undefined;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveExecutionRequest = void 0;
4
+ const is_graphql_context_js_1 = require("./internal/is-graphql-context.js");
5
+ const GRAPHQL_CONTEXT_ARGUMENT_INDEX = 2;
6
+ const resolveExecutionRequest = (context) => ((0, is_graphql_context_js_1.isGraphqlContext)(context)
7
+ ? context.getArgByIndex(GRAPHQL_CONTEXT_ARGUMENT_INDEX).req
8
+ : context.switchToHttp().getRequest());
9
+ exports.resolveExecutionRequest = resolveExecutionRequest;
@@ -0,0 +1,2 @@
1
+ import type { CanActivate, Type } from '@nestjs/common';
2
+ export declare const withPublicRoutes: <TGuard extends Type<CanActivate>>(base: TGuard) => TGuard;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.withPublicRoutes = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const core_1 = require("@nestjs/core");
15
+ const is_public_handler_js_1 = require("./internal/is-public-handler.js");
16
+ const resolve_execution_request_js_1 = require("./resolve-execution-request.js");
17
+ const withPublicRoutes = (base) => {
18
+ let PublicRoutesGuard = class PublicRoutesGuard extends base {
19
+ reflector;
20
+ canActivate(context) {
21
+ return (0, is_public_handler_js_1.isPublicHandler)(this.reflector, context) || super.canActivate(context);
22
+ }
23
+ getRequest(context) {
24
+ return (0, resolve_execution_request_js_1.resolveExecutionRequest)(context);
25
+ }
26
+ };
27
+ __decorate([
28
+ (0, common_1.Inject)(core_1.Reflector),
29
+ __metadata("design:type", core_1.Reflector)
30
+ ], PublicRoutesGuard.prototype, "reflector", void 0);
31
+ PublicRoutesGuard = __decorate([
32
+ (0, common_1.Injectable)()
33
+ ], PublicRoutesGuard);
34
+ return PublicRoutesGuard;
35
+ };
36
+ exports.withPublicRoutes = withPublicRoutes;
@@ -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 @@
1
+ export declare const CurrentActor: (...dataOrPipes: unknown[]) => ParameterDecorator;
@@ -0,0 +1,4 @@
1
+ import { createParamDecorator } from '@nestjs/common';
2
+ import { resolveExecutionActor } from './internal/resolve-execution-actor.js';
3
+ const currentActorFactory = (_, context) => resolveExecutionActor(context);
4
+ export const CurrentActor = createParamDecorator(currentActorFactory);
@@ -4,6 +4,9 @@ export * from './authorization-logger.interface.js';
4
4
  export * from './authorization.module.js';
5
5
  export * from './check-policies.decorator.js';
6
6
  export * from './constants.js';
7
+ export * from './current-actor.decorator.js';
7
8
  export * from './policies.guard.js';
8
9
  export * from './policy-handlers.js';
9
10
  export * from './public.decorator.js';
11
+ export * from './resolve-execution-request.js';
12
+ export * from './with-public-routes.js';
@@ -4,6 +4,9 @@ export * from './authorization-logger.interface.js';
4
4
  export * from './authorization.module.js';
5
5
  export * from './check-policies.decorator.js';
6
6
  export * from './constants.js';
7
+ export * from './current-actor.decorator.js';
7
8
  export * from './policies.guard.js';
8
9
  export * from './policy-handlers.js';
9
10
  export * from './public.decorator.js';
11
+ export * from './resolve-execution-request.js';
12
+ export * from './with-public-routes.js';
@@ -0,0 +1,3 @@
1
+ export * from './is-graphql-context.js';
2
+ export * from './is-public-handler.js';
3
+ export * from './resolve-execution-actor.js';
@@ -0,0 +1,3 @@
1
+ export * from './is-graphql-context.js';
2
+ export * from './is-public-handler.js';
3
+ export * from './resolve-execution-actor.js';
@@ -0,0 +1,2 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ export declare const isGraphqlContext: (context: ExecutionContext) => boolean;
@@ -0,0 +1,5 @@
1
+ const GRAPHQL_CONTEXT_TYPE = 'graphql';
2
+ export const isGraphqlContext = (context) => {
3
+ const transport = context.getType();
4
+ return transport === GRAPHQL_CONTEXT_TYPE;
5
+ };
@@ -0,0 +1,3 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ import type { Reflector } from '@nestjs/core';
3
+ export declare const isPublicHandler: (reflector: Reflector, context: ExecutionContext) => boolean;
@@ -0,0 +1,5 @@
1
+ import { IS_PUBLIC_KEY } from '../public.decorator.js';
2
+ export const isPublicHandler = (reflector, context) => {
3
+ const targets = [context.getHandler(), context.getClass()];
4
+ return reflector.getAllAndOverride(IS_PUBLIC_KEY, targets) === true;
5
+ };
@@ -0,0 +1,3 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ import type { AbilityActorInterface } from '../../build-ability.js';
3
+ export declare const resolveExecutionActor: (context: ExecutionContext) => AbilityActorInterface | undefined;
@@ -0,0 +1,5 @@
1
+ import { resolveExecutionRequest } from '../resolve-execution-request.js';
2
+ export const resolveExecutionActor = (context) => {
3
+ const request = resolveExecutionRequest(context);
4
+ return request?.user;
5
+ };
@@ -10,5 +10,4 @@ export declare class PoliciesGuard implements CanActivate {
10
10
  canActivate(context: ExecutionContext): boolean;
11
11
  private allowWithoutPolicies;
12
12
  private enforcePolicies;
13
- private extractActor;
14
13
  }
@@ -14,10 +14,9 @@ import { ForbiddenException, Inject, Injectable, } from '@nestjs/common';
14
14
  import { Reflector } from '@nestjs/core';
15
15
  import { CHECK_POLICIES_KEY } from './check-policies.decorator.js';
16
16
  import { ABILITY_FACTORY, AUTHORIZATION_LOGGER } from './constants.js';
17
- import { IS_PUBLIC_KEY } from './public.decorator.js';
18
- const GRAPHQL_CONTEXT_TYPE = 'graphql';
19
- const GRAPHQL_CONTEXT_ARGUMENT_INDEX = 2;
20
- const isGraphql = (context) => context.getType() === GRAPHQL_CONTEXT_TYPE;
17
+ import { isGraphqlContext } from './internal/is-graphql-context.js';
18
+ import { isPublicHandler } from './internal/is-public-handler.js';
19
+ import { resolveExecutionActor } from './internal/resolve-execution-actor.js';
21
20
  let PoliciesGuard = class PoliciesGuard {
22
21
  reflector;
23
22
  abilityFactory;
@@ -28,26 +27,22 @@ let PoliciesGuard = class PoliciesGuard {
28
27
  this.logger = logger;
29
28
  }
30
29
  canActivate(context) {
31
- const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
32
- context.getHandler(),
33
- context.getClass(),
34
- ]);
35
- if (isPublic === true) {
30
+ if (isPublicHandler(this.reflector, context)) {
36
31
  return true;
37
32
  }
38
33
  const handlers = this.reflector.getAllAndOverride(CHECK_POLICIES_KEY, [context.getHandler(), context.getClass()]) ?? [];
39
34
  return handlers.length === 0 ? this.allowWithoutPolicies(context) : this.enforcePolicies({ context, handlers });
40
35
  }
41
36
  allowWithoutPolicies(context) {
42
- if (isGraphql(context)) {
37
+ if (isGraphqlContext(context)) {
43
38
  this.logger.warn({ class: context.getClass().name, handler: context.getHandler().name }, 'PoliciesGuard deny-by-default — GraphQL resolver missing @Public/@CheckPolicies');
44
39
  throw new ForbiddenException('Forbidden by policy');
45
40
  }
46
41
  return true;
47
42
  }
48
43
  enforcePolicies(parameters) {
49
- const actor = this.extractActor(parameters.context);
50
- if (actor === null) {
44
+ const actor = resolveExecutionActor(parameters.context);
45
+ if (actor === undefined) {
51
46
  throw new ForbiddenException('No authenticated user');
52
47
  }
53
48
  const ability = this.abilityFactory.createForActor({
@@ -59,12 +54,6 @@ let PoliciesGuard = class PoliciesGuard {
59
54
  }
60
55
  return true;
61
56
  }
62
- extractActor(context) {
63
- const request = isGraphql(context)
64
- ? context.getArgByIndex(GRAPHQL_CONTEXT_ARGUMENT_INDEX).req
65
- : context.switchToHttp().getRequest();
66
- return request?.user ?? null;
67
- }
68
57
  };
69
58
  PoliciesGuard = __decorate([
70
59
  Injectable(),
@@ -0,0 +1,2 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ export declare const resolveExecutionRequest: <TRequest>(context: ExecutionContext) => TRequest | undefined;
@@ -0,0 +1,5 @@
1
+ import { isGraphqlContext } from './internal/is-graphql-context.js';
2
+ const GRAPHQL_CONTEXT_ARGUMENT_INDEX = 2;
3
+ export const resolveExecutionRequest = (context) => (isGraphqlContext(context)
4
+ ? context.getArgByIndex(GRAPHQL_CONTEXT_ARGUMENT_INDEX).req
5
+ : context.switchToHttp().getRequest());
@@ -0,0 +1,2 @@
1
+ import type { CanActivate, Type } from '@nestjs/common';
2
+ export declare const withPublicRoutes: <TGuard extends Type<CanActivate>>(base: TGuard) => TGuard;
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Inject, Injectable } from '@nestjs/common';
11
+ import { Reflector } from '@nestjs/core';
12
+ import { isPublicHandler } from './internal/is-public-handler.js';
13
+ import { resolveExecutionRequest } from './resolve-execution-request.js';
14
+ export const withPublicRoutes = (base) => {
15
+ let PublicRoutesGuard = class PublicRoutesGuard extends base {
16
+ reflector;
17
+ canActivate(context) {
18
+ return isPublicHandler(this.reflector, context) || super.canActivate(context);
19
+ }
20
+ getRequest(context) {
21
+ return resolveExecutionRequest(context);
22
+ }
23
+ };
24
+ __decorate([
25
+ Inject(Reflector),
26
+ __metadata("design:type", Reflector)
27
+ ], PublicRoutesGuard.prototype, "reflector", void 0);
28
+ PublicRoutesGuard = __decorate([
29
+ Injectable()
30
+ ], PublicRoutesGuard);
31
+ return PublicRoutesGuard;
32
+ };
@@ -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.2.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 && 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"