@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.
- package/CHANGELOG.md +14 -0
- package/README.md +30 -2
- package/dist/cjs/nest/current-actor.decorator.d.ts +1 -0
- package/dist/cjs/nest/current-actor.decorator.js +7 -0
- package/dist/cjs/nest/index.d.ts +3 -0
- package/dist/cjs/nest/index.js +3 -0
- package/dist/cjs/nest/internal/index.d.ts +3 -0
- package/dist/cjs/nest/internal/index.js +19 -0
- package/dist/cjs/nest/internal/is-graphql-context.d.ts +2 -0
- package/dist/cjs/nest/internal/is-graphql-context.js +9 -0
- package/dist/cjs/nest/internal/is-public-handler.d.ts +3 -0
- package/dist/cjs/nest/internal/is-public-handler.js +9 -0
- package/dist/cjs/nest/internal/resolve-execution-actor.d.ts +3 -0
- package/dist/cjs/nest/internal/resolve-execution-actor.js +9 -0
- package/dist/cjs/nest/policies.guard.d.ts +0 -1
- package/dist/cjs/nest/policies.guard.js +7 -18
- package/dist/cjs/nest/resolve-execution-request.d.ts +2 -0
- package/dist/cjs/nest/resolve-execution-request.js +9 -0
- package/dist/cjs/nest/with-public-routes.d.ts +2 -0
- package/dist/cjs/nest/with-public-routes.js +36 -0
- package/dist/cjs/vue/index.d.ts +2 -0
- package/dist/cjs/vue/index.js +18 -0
- package/dist/cjs/vue/install-authorization.d.ts +11 -0
- package/dist/cjs/vue/install-authorization.js +27 -0
- package/dist/cjs/vue/route-ability.d.ts +14 -0
- package/dist/cjs/vue/route-ability.js +8 -0
- package/dist/esm/nest/current-actor.decorator.d.ts +1 -0
- package/dist/esm/nest/current-actor.decorator.js +4 -0
- package/dist/esm/nest/index.d.ts +3 -0
- package/dist/esm/nest/index.js +3 -0
- package/dist/esm/nest/internal/index.d.ts +3 -0
- package/dist/esm/nest/internal/index.js +3 -0
- package/dist/esm/nest/internal/is-graphql-context.d.ts +2 -0
- package/dist/esm/nest/internal/is-graphql-context.js +5 -0
- package/dist/esm/nest/internal/is-public-handler.d.ts +3 -0
- package/dist/esm/nest/internal/is-public-handler.js +5 -0
- package/dist/esm/nest/internal/resolve-execution-actor.d.ts +3 -0
- package/dist/esm/nest/internal/resolve-execution-actor.js +5 -0
- package/dist/esm/nest/policies.guard.d.ts +0 -1
- package/dist/esm/nest/policies.guard.js +7 -18
- package/dist/esm/nest/resolve-execution-request.d.ts +2 -0
- package/dist/esm/nest/resolve-execution-request.js +5 -0
- package/dist/esm/nest/with-public-routes.d.ts +2 -0
- package/dist/esm/nest/with-public-routes.js +32 -0
- package/dist/esm/vue/index.d.ts +2 -0
- package/dist/esm/vue/index.js +2 -0
- package/dist/esm/vue/install-authorization.d.ts +11 -0
- package/dist/esm/vue/install-authorization.js +23 -0
- package/dist/esm/vue/route-ability.d.ts +14 -0
- package/dist/esm/vue/route-ability.js +4 -0
- 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` — открытый
|
|
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);
|
package/dist/cjs/nest/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/cjs/nest/index.js
CHANGED
|
@@ -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,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,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,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,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;
|
|
@@ -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
|
|
21
|
-
const
|
|
22
|
-
const
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
53
|
-
if (actor ===
|
|
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,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,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,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);
|
package/dist/esm/nest/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/esm/nest/index.js
CHANGED
|
@@ -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';
|
|
@@ -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 {
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
50
|
-
if (actor ===
|
|
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,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,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,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;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@application-kit/authorization",
|
|
3
|
-
"version": "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"
|