@magek/mcp-server 0.0.8
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/README.md +42 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/prompts/cqrs-flow.d.ts +15 -0
- package/dist/prompts/cqrs-flow.js +252 -0
- package/dist/prompts/troubleshooting.d.ts +15 -0
- package/dist/prompts/troubleshooting.js +239 -0
- package/dist/resources/cli-reference.d.ts +13 -0
- package/dist/resources/cli-reference.js +193 -0
- package/dist/resources/documentation.d.ts +18 -0
- package/dist/resources/documentation.js +62 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +127 -0
- package/dist/utils/docs-loader.d.ts +19 -0
- package/dist/utils/docs-loader.js +111 -0
- package/docs/advanced/custom-templates.md +96 -0
- package/docs/advanced/data-migrations.md +181 -0
- package/docs/advanced/environment-configuration.md +74 -0
- package/docs/advanced/framework-packages.md +17 -0
- package/docs/advanced/health/sensor-health.md +389 -0
- package/docs/advanced/instrumentation.md +135 -0
- package/docs/advanced/register.md +119 -0
- package/docs/advanced/sensor.md +10 -0
- package/docs/advanced/testing.md +96 -0
- package/docs/advanced/touch-entities.md +45 -0
- package/docs/architecture/command.md +367 -0
- package/docs/architecture/entity.md +214 -0
- package/docs/architecture/event-driven.md +30 -0
- package/docs/architecture/event-handler.md +108 -0
- package/docs/architecture/event.md +145 -0
- package/docs/architecture/notifications.md +54 -0
- package/docs/architecture/queries.md +207 -0
- package/docs/architecture/read-model.md +507 -0
- package/docs/contributing.md +349 -0
- package/docs/docs-index.json +200 -0
- package/docs/features/error-handling.md +204 -0
- package/docs/features/event-stream.md +35 -0
- package/docs/features/logging.md +81 -0
- package/docs/features/schedule-actions.md +44 -0
- package/docs/getting-started/ai-coding-assistants.md +181 -0
- package/docs/getting-started/coding.md +543 -0
- package/docs/getting-started/installation.md +143 -0
- package/docs/graphql.md +1213 -0
- package/docs/index.md +62 -0
- package/docs/introduction.md +58 -0
- package/docs/magek-arch.png +0 -0
- package/docs/magek-cli.md +67 -0
- package/docs/magek-logo.svg +1 -0
- package/docs/security/authentication.md +189 -0
- package/docs/security/authorization.md +242 -0
- package/docs/security/security.md +16 -0
- package/package.json +46 -0
package/docs/index.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Documentation"
|
|
3
|
+
children:
|
|
4
|
+
- ./introduction.md
|
|
5
|
+
- ./getting-started/installation.md
|
|
6
|
+
- ./getting-started/coding.md
|
|
7
|
+
- ./architecture/event-driven.md
|
|
8
|
+
- ./architecture/command.md
|
|
9
|
+
- ./architecture/event.md
|
|
10
|
+
- ./architecture/event-handler.md
|
|
11
|
+
- ./architecture/entity.md
|
|
12
|
+
- ./architecture/read-model.md
|
|
13
|
+
- ./architecture/notifications.md
|
|
14
|
+
- ./architecture/queries.md
|
|
15
|
+
- ./features/event-stream.md
|
|
16
|
+
- ./features/schedule-actions.md
|
|
17
|
+
- ./features/logging.md
|
|
18
|
+
- ./features/error-handling.md
|
|
19
|
+
- ./security/security.md
|
|
20
|
+
- ./security/authentication.md
|
|
21
|
+
- ./security/authorization.md
|
|
22
|
+
- ./magek-cli.md
|
|
23
|
+
- ./graphql.md
|
|
24
|
+
- ./advanced/custom-templates.md
|
|
25
|
+
- ./advanced/data-migrations.md
|
|
26
|
+
- ./advanced/environment-configuration.md
|
|
27
|
+
- ./advanced/framework-packages.md
|
|
28
|
+
- ./advanced/instrumentation.md
|
|
29
|
+
- ./advanced/register.md
|
|
30
|
+
- ./advanced/sensor.md
|
|
31
|
+
- ./advanced/testing.md
|
|
32
|
+
- ./advanced/touch-entities.md
|
|
33
|
+
- ./advanced/health/sensor-health.md
|
|
34
|
+
- ./contributing.md
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Magek Framework Documentation
|
|
38
|
+
|
|
39
|
+
Welcome to the Magek documentation! Use the navigation on the left to browse guides and API reference.
|
|
40
|
+
|
|
41
|
+
## Quick Links
|
|
42
|
+
|
|
43
|
+
- **[Introduction](./introduction.md)** - Learn what Magek is
|
|
44
|
+
- **[Installation](./getting-started/installation.md)** - Get started quickly
|
|
45
|
+
- **[Coding Tutorial](./getting-started/coding.md)** - Build your first app
|
|
46
|
+
|
|
47
|
+
## Sections
|
|
48
|
+
|
|
49
|
+
### Getting Started
|
|
50
|
+
Set up your environment and build your first Magek application.
|
|
51
|
+
|
|
52
|
+
### Architecture
|
|
53
|
+
Understand Commands, Events, Entities, Read Models, and other core concepts.
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
Learn about Event Streams, Scheduled Actions, Logging, and Error Handling.
|
|
57
|
+
|
|
58
|
+
### Security
|
|
59
|
+
Implement Authentication and Authorization in your applications.
|
|
60
|
+
|
|
61
|
+
### Advanced Topics
|
|
62
|
+
Dive deeper into Providers, Migrations, Testing, and more.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Introduction"
|
|
3
|
+
group: "Guides"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Introduction
|
|
7
|
+
|
|
8
|
+
> _Progress isn't made by early risers. It's made by lazy men trying to find easier ways to do something._ — [Robert A. Heinlein](https://en.wikipedia.org/wiki/Robert_A._Heinlein)
|
|
9
|
+
|
|
10
|
+
## What is Magek?
|
|
11
|
+
|
|
12
|
+
Magek is the fastest way to create a scalable application. It is a new kind of framework to build scalable and reliable systems easier, reimagining the software development experience to maximize your team's speed and reduce friction on every level.
|
|
13
|
+
|
|
14
|
+
Magek follows an Event-Driven and a Domain-Driven Design approach in which you define your application in terms that are understandable by anyone in your company. From a bird’s eye view, your project is organized into:
|
|
15
|
+
|
|
16
|
+
- **Commands**: Define what a user can request from the system (i.e: Add an item to the cart)
|
|
17
|
+
- **Queries**: Define what a user can get from the system (i.e: Get cart items)
|
|
18
|
+
- **Events**: Simple records of facts (i.e: User X added item Y to the cart Z)
|
|
19
|
+
- **Entities**: Data about the things that the people in your company talk about (i.e: Orders, Customers, etc.)
|
|
20
|
+
- **Handlers**: Code that processes commands, reacts to events to trigger other actions, or update the entities after new events happen.
|
|
21
|
+
|
|
22
|
+
Events are the cornerstone of a Magek application, and that’s why we say that Magek is an event-driven framework. Events bring us many of the differentiating characteristics of Magek:
|
|
23
|
+
|
|
24
|
+
- **Real-time**: Events can trigger other actions when they’re created, and updates can be pushed to connected clients without extra requests.
|
|
25
|
+
- **High data resiliency**: Events are stored by default in an append-only database, so the data is never lost and it’s possible to recover any previous state of the system.
|
|
26
|
+
- **Scalable by nature**: Dependencies only happen at data level, so Magek apps can ingest more data without waiting for other operatons to complete. Low coupling also makes it easier to evolve the code without affecting other parts of the system.
|
|
27
|
+
- **Asynchronous**: Your users won't need to wait for your system to process the whole operation before continuing using it.
|
|
28
|
+
|
|
29
|
+
Before Magek, building an event-driven system with the mentioned characteristics required huge investments in hiring engineers with the needed expertise. Magek packs this expertise, acquired from real-case scenarios in high-scale companies, into a very simple tool that handles the hard parts for you!
|
|
30
|
+
|
|
31
|
+
We have redesigned the whole developer experience from scratch, taking advantage of the advanced TypeScript type system to go from project generation to a production-ready application which provides a real-time GraphQL API that can ingest thousands of concurrent users in a matter of minutes.
|
|
32
|
+
|
|
33
|
+
Magek's ultimate goal is making developer's lives easier, fulfilling the dream of writing code in a domain-driven way that eases communications for the whole team, without caring about how anything else is done at the infrastructure level!
|
|
34
|
+
|
|
35
|
+
## Magek Principles
|
|
36
|
+
|
|
37
|
+
Magek enhances developers' productivity by focusing only on business logic. Write your code, provide your credentials and let Magek do the rest. Magek takes a holistic and highly-opinionated approach at many levels:
|
|
38
|
+
|
|
39
|
+
- **Focus on business value**: The only code that makes sense is the code that makes your application different from any other.
|
|
40
|
+
- **Convention over configuration**: All the supporting code and configuration that is similar in all applications should be out of programmers’ sight.
|
|
41
|
+
- **Simple deployment**: Run your application as a standalone server or export handlers for serverless deployment.
|
|
42
|
+
- **Scale smoothly**: The code you write to handle your first 100 users will still work to handle your first million. You won't need to rewrite your application when it succeeds.
|
|
43
|
+
- **Event-source and CQRS**: Our world is event-driven, businesses are event-driven, and modern software maps better to reality when it’s event-driven.
|
|
44
|
+
- **Principle of Abstraction**: Building an application is hard enough to have to deal with recurring low-level details like SQL, API design, or authentication mechanisms, so we tend to build more semantic abstractions on top of them.
|
|
45
|
+
- **Real-time first**: Client applications must be able to react to events happening in the backend and notice data changes.
|
|
46
|
+
|
|
47
|
+
## Why use Magek
|
|
48
|
+
|
|
49
|
+
What does _Magek_ boost? Your team's productivity. Not just because it helps you work faster, but because it makes you worry about fewer buttons and switches. We aim to solve major productivity sinks for developers like designing the right infrastructure, writing APIs or dealing with ORMs.
|
|
50
|
+
|
|
51
|
+
Magek will fit like a glove in applications that are naturally event-driven like commerce applications (retail, e-commerce, omnichannel applications, warehouse management, etc.), business applications or communication systems, but it's a general-purpose framework that has several advantages over other solutions:
|
|
52
|
+
|
|
53
|
+
- **Faster time-to-market**: Magek can run your application from minute one, without complicated configurations. In addition to that, it features a set of code generators to help developers build the project scaffolding faster and focus on actual business code in a matter of seconds instead of dealing with complicated framework folklore.
|
|
54
|
+
- **Write less code**: Magek conventions and abstractions require less code to implement the same features. This not only speeds up development but combined with clear architecture guidelines also makes Magek projects easier to understand, iterate, and maintain.
|
|
55
|
+
- **Benefit from Typescript's advantages**: Typescript's type system provides an important security layer that helps developers make sure the code they write is the code they meant to write, making Magek apps more reliable and less error-prone.
|
|
56
|
+
- **All the advantages of Microservices, none of its cons**: Microservices are a great way to deal with code complexity, at least on paper. Services are isolated and can scale independently, and different teams can work independently, but that usually comes with a con: interfaces between services introduce huge challenges like delays, hard to solve cyclic dependencies, or deployment errors. In Magek, every handler function works independently, there are no direct dependencies between them, all communication happens asynchronously via events, and everything is compiled and type-checked atomically to avoid issues.
|
|
57
|
+
- **Event-sourcing by default**: Magek keeps all incremental data changes as events, indefinitely. This means that any previous state of the system can be recreated and replayed at any moment, enabling a whole world of possibilities for troubleshooting and auditing, syncing environments or performing tests and simulations.
|
|
58
|
+
- **Magek makes it easy to build enterprise-grade applications**: Implementing an event-sourcing system from scratch is a challenging exercise that usually requires highly specialized experts. There are some technical challenges like eventual consistency, message ordering, and snapshot building. Magek takes care of all of that and more for you, lowering the curve for people that are starting and making expert lives easier.
|
|
Binary file
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Magek CLI"
|
|
3
|
+
group: "Guides"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Magek CLI
|
|
7
|
+
|
|
8
|
+
The Magek CLI is a command line interface that helps you develop your Magek applications. It is built with Node.js and published to NPM through the package `@magek/cli`. It's automatically included as a dependency in every Magek project - no global installation required!
|
|
9
|
+
|
|
10
|
+
## No Installation Required
|
|
11
|
+
|
|
12
|
+
When you create a Magek project using `npm create magek@latest`, the CLI is automatically included as a dependency. Simply use `npx magek` to run any CLI command within your project directory.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Create a new project (CLI automatically included)
|
|
16
|
+
npm create magek@latest my-project
|
|
17
|
+
|
|
18
|
+
# Navigate to your project and start using the CLI
|
|
19
|
+
cd my-project
|
|
20
|
+
npx magek --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Once you're in your Magek project directory, you can use the `npx magek` command to see the help message.
|
|
26
|
+
|
|
27
|
+
> **Tip:** You can also run `npx magek --help` to get the same output.
|
|
28
|
+
|
|
29
|
+
## Command Overview
|
|
30
|
+
|
|
31
|
+
| Command | Description |
|
|
32
|
+
|---------|-------------|
|
|
33
|
+
| [`new:command`](/architecture/command#creating-a-command) | Creates a new command in the project |
|
|
34
|
+
| [`new:entity`](/architecture/entity#creating-an-entity) | Creates a new entity in the project |
|
|
35
|
+
| [`new:event`](/architecture/event#creating-an-event) | Creates a new event in the project |
|
|
36
|
+
| [`new:event-handler`](/architecture/event-handler#creating-an-event-handler) | Creates a new event handler in the project |
|
|
37
|
+
| [`new:read-model`](/architecture/read-model#creating-a-read-model) | Creates a new read model in the project |
|
|
38
|
+
| [`new:scheduled-command`](/features/schedule-actions#creating-a-scheduled-command) | Creates a new scheduled command in the project |
|
|
39
|
+
|
|
40
|
+
> **Tip:** To create a new Magek project, use the modern npm create pattern:
|
|
41
|
+
>
|
|
42
|
+
> ```bash
|
|
43
|
+
> npm create magek@latest my-project
|
|
44
|
+
> ```
|
|
45
|
+
>
|
|
46
|
+
> All CLI commands should be run with `npx magek` within your project directory.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm create magek@latest my-project
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Adapter CLI plugins
|
|
53
|
+
|
|
54
|
+
Magek CLI automatically discovers adapter packages named `@magek/adapter-*` that export a `magekCli` object. Any commands listed there are registered at startup and appear in `npx magek --help`.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
export const magekCli = {
|
|
58
|
+
commands: {
|
|
59
|
+
'migrate:status': MigrateStatusCommand,
|
|
60
|
+
'migrate:apply': MigrateApplyCommand,
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> **Note:** Plugin discovery executes code from installed adapter packages. Only install trusted adapters in your project.
|
|
66
|
+
|
|
67
|
+
See the [Getting Started guide](/getting-started/coding#1-create-the-project) for details.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 2892 715" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Artboard2" x="0" y="0" width="2891.53" height="714.98" style="fill:none;"/><clipPath id="_clip1"><rect x="0" y="0" width="2891.53" height="714.98"/></clipPath><g clip-path="url(#_clip1)"><g><path d="M1215.33,192.315c40.311,-0 72.818,12.388 97.518,37.166c24.701,24.777 37.051,59.366 37.051,103.767l0,193.264l-82.994,-0l-0,-181.965c-0,-25.769 -6.521,-45.491 -19.563,-59.168c-13.042,-13.678 -30.827,-20.516 -53.354,-20.516c-22.527,-0 -40.41,6.838 -53.65,20.516c-13.239,13.677 -19.859,33.399 -19.859,59.168l-0,181.965l-82.994,-0l-0,-181.965c-0,-25.769 -6.521,-45.491 -19.563,-59.168c-13.042,-13.678 -30.827,-20.516 -53.354,-20.516c-22.922,-0 -41.003,6.838 -54.243,20.516c-13.239,13.677 -19.859,33.399 -19.859,59.168l-0,181.965l-82.995,-0l0,-329.44l82.995,0l-0,39.842c10.67,-13.875 24.404,-24.777 41.201,-32.706c16.796,-7.929 35.272,-11.893 55.428,-11.893c25.689,-0 48.611,5.451 68.767,16.353c20.156,10.902 35.767,26.462 46.832,46.68c10.671,-19.029 26.183,-34.291 46.537,-45.788c20.353,-11.497 42.386,-17.245 66.099,-17.245Z" style="fill:#6545f5;fill-rule:nonzero;"/><path d="M1382.5,360.603c0,-33.301 6.62,-62.836 19.86,-88.604c13.239,-25.769 31.221,-45.591 53.946,-59.466c22.725,-13.875 48.117,-20.813 76.177,-20.813c24.503,0 45.943,4.956 64.321,14.867c18.377,9.91 33.099,22.398 44.165,37.463l-0,-46.978l83.587,0l-0,329.44l-83.587,-0l-0,-48.167c-10.671,15.461 -25.393,28.246 -44.165,38.355c-18.773,10.109 -40.411,15.164 -64.914,15.164c-27.665,-0 -52.859,-7.136 -75.584,-21.408c-22.725,-14.272 -40.707,-34.391 -53.946,-60.358c-13.24,-25.966 -19.86,-55.798 -19.86,-89.495Zm258.469,1.189c-0,-20.218 -3.952,-37.563 -11.857,-52.033c-7.904,-14.469 -18.575,-25.57 -32.012,-33.3c-13.437,-7.731 -27.862,-11.596 -43.275,-11.596c-15.414,-0 -29.641,3.766 -42.683,11.298c-13.042,7.533 -23.614,18.534 -31.716,33.004c-8.102,14.47 -12.153,31.616 -12.153,51.438c0,19.822 4.051,37.166 12.153,52.032c8.102,14.867 18.773,26.264 32.012,34.193c13.24,7.929 27.369,11.893 42.387,11.893c15.413,0 29.838,-3.865 43.275,-11.596c13.437,-7.73 24.108,-18.831 32.012,-33.301c7.905,-14.47 11.857,-31.814 11.857,-52.032Z" style="fill:#6545f5;fill-rule:nonzero;"/><path d="M1909.52,191.72c24.503,0 46.042,4.856 64.617,14.569c18.575,9.713 33.198,22.3 43.869,37.761l-0,-46.978l83.587,0l-0,331.819c-0,30.525 -6.126,57.78 -18.377,81.765c-12.252,23.984 -30.629,43.013 -55.132,57.087c-24.504,14.073 -54.144,21.11 -88.923,21.11c-46.635,0 -84.872,-10.902 -114.71,-32.706c-29.839,-21.804 -45.734,-48.537 -49.686,-86.198l82.401,-0c4.348,15.064 12.734,24.056 27.159,32.976c14.426,8.92 31.914,13.38 52.465,13.38c24.108,0 43.671,-7.235 58.689,-21.705c15.018,-14.47 22.527,-36.373 22.527,-65.709l-0,-51.141c-10.671,15.461 -25.393,28.345 -44.165,38.653c-18.773,10.307 -40.213,15.461 -64.321,15.461c-27.665,-0 -52.958,-7.136 -75.881,-21.408c-22.922,-14.272 -41.003,-34.391 -54.242,-60.358c-13.24,-25.966 -19.86,-55.798 -19.86,-89.495c0,-33.301 6.62,-62.836 19.86,-88.604c13.239,-25.769 31.221,-45.591 53.946,-59.466c22.725,-13.875 48.117,-20.813 76.177,-20.813Zm108.486,170.072c-0,-20.218 -3.952,-37.563 -11.857,-52.033c-7.904,-14.469 -18.575,-25.57 -32.012,-33.3c-13.437,-7.731 -27.862,-11.596 -43.276,-11.596c-15.413,-0 -29.64,3.766 -42.682,11.298c-13.042,7.533 -23.614,18.534 -31.716,33.004c-8.102,14.47 -12.153,31.616 -12.153,51.438c0,19.822 4.051,37.166 12.153,52.032c8.102,14.867 18.772,26.264 32.012,34.193c13.24,7.929 27.368,11.893 42.386,11.893c15.414,0 29.839,-3.865 43.276,-11.596c13.437,-7.73 24.108,-18.831 32.012,-33.301c7.905,-14.47 11.857,-31.814 11.857,-52.032Z" style="fill:#6545f5;fill-rule:nonzero;"/><path d="M2463.21,354.656c-0,11.893 -0.791,22.597 -2.372,32.112l-240.091,-0c1.976,23.786 10.276,42.418 24.899,55.897c14.622,13.479 32.605,20.219 53.946,20.219c30.826,-0 52.761,-13.281 65.803,-39.842l89.515,-0c-9.485,31.715 -27.665,57.78 -54.539,78.197c-26.874,20.417 -59.875,30.625 -99.001,30.625c-31.617,-0 -59.973,-7.037 -85.069,-21.111c-25.096,-14.073 -44.659,-33.994 -58.689,-59.763c-14.03,-25.768 -21.045,-55.501 -21.045,-89.198c0,-34.094 6.916,-64.025 20.749,-89.793c13.832,-25.769 33.198,-45.591 58.096,-59.466c24.898,-13.875 53.551,-20.813 85.958,-20.813c31.222,0 59.183,6.74 83.884,20.218c24.701,13.479 43.869,32.607 57.503,57.385c13.635,24.777 20.453,53.222 20.453,85.333Zm-85.959,-23.786c-0.395,-21.408 -8.102,-38.554 -23.12,-51.438c-15.018,-12.884 -33.395,-19.326 -55.132,-19.326c-20.551,-0 -37.841,6.244 -51.871,18.731c-14.03,12.488 -22.626,29.832 -25.788,52.033l155.911,-0Z" style="fill:#6545f5;fill-rule:nonzero;"/><path d="M2693.22,526.512l-111.45,-140.339l0,140.339l-82.994,-0l-0,-454.046l82.994,-0l0,264.35l110.264,-139.744l107.893,0l-144.648,165.315l145.834,164.125l-107.893,-0Z" style="fill:#6545f5;fill-rule:nonzero;"/></g><path d="M145.419,374.47l0,57.359c0,5.629 2.237,11.027 6.217,15.008c3.98,3.98 9.379,6.216 15.008,6.216l57.359,0l-57.359,0c-5.629,0 -11.028,2.236 -15.008,6.217c-3.98,3.98 -6.217,9.379 -6.217,15.008l0,57.359l0,-57.359c0,-5.629 -2.236,-11.028 -6.216,-15.008c-3.98,-3.981 -9.379,-6.217 -15.008,-6.217l-57.359,0l57.359,0c5.629,0 11.028,-2.236 15.008,-6.216c3.98,-3.981 6.216,-9.379 6.216,-15.008l0,-57.359Z" style="fill:url(#_Linear2);"/><path d="M496.134,490.373l-0,60.715c-0,5.958 2.367,11.673 6.58,15.886c4.213,4.213 9.928,6.58 15.886,6.58l60.715,0l-60.715,0c-5.958,0 -11.673,2.367 -15.886,6.581c-4.213,4.213 -6.58,9.927 -6.58,15.886l-0,60.715l-0,-60.715c-0,-5.959 -2.367,-11.673 -6.581,-15.886c-4.213,-4.214 -9.927,-6.581 -15.886,-6.581l-60.715,0l60.715,0c5.959,0 11.673,-2.367 15.886,-6.58c4.214,-4.213 6.581,-9.928 6.581,-15.886l-0,-60.715Z" style="fill:url(#_Linear3);"/><path d="M214.308,-20.291l0,110.579c0,10.852 4.311,21.259 11.984,28.933c7.674,7.673 18.082,11.984 28.934,11.984l110.578,0l-110.578,0c-10.852,0 -21.26,4.311 -28.934,11.985c-7.673,7.673 -11.984,18.081 -11.984,28.933l0,110.578l0,-110.578c0,-10.852 -4.311,-21.26 -11.984,-28.933c-7.674,-7.674 -18.082,-11.985 -28.934,-11.985l-110.578,0l110.578,0c10.852,0 21.26,-4.311 28.934,-11.984c7.673,-7.674 11.984,-18.081 11.984,-28.933l0,-110.579Z" style="fill:url(#_Linear4);"/><path d="M537.95,34.078l0,68.103c0,6.684 2.655,13.093 7.381,17.819c4.726,4.726 11.136,7.381 17.82,7.381l68.103,0l-68.103,0c-6.684,0 -13.094,2.656 -17.82,7.382c-4.726,4.726 -7.381,11.135 -7.381,17.819l0,68.103l0,-68.103c0,-6.684 -2.655,-13.093 -7.381,-17.819c-4.726,-4.726 -11.136,-7.382 -17.819,-7.382l-68.103,0l68.103,0c6.683,0 13.093,-2.655 17.819,-7.381c4.726,-4.726 7.381,-11.135 7.381,-17.819l0,-68.103Z" style="fill:url(#_Linear5);"/><path d="M605.044,177.724l-0,54.867c-0,5.385 2.139,10.549 5.946,14.356c3.808,3.808 8.972,5.947 14.356,5.947l54.868,-0l-54.868,-0c-5.384,-0 -10.548,2.139 -14.356,5.946c-3.807,3.808 -5.946,8.972 -5.946,14.357l-0,54.867l-0,-54.867c-0,-5.385 -2.139,-10.549 -5.947,-14.357c-3.807,-3.807 -8.972,-5.946 -14.356,-5.946l-54.867,-0l54.867,-0c5.384,-0 10.549,-2.139 14.356,-5.947c3.808,-3.807 5.947,-8.971 5.947,-14.356l-0,-54.867Z" style="fill:url(#_Linear6);"/><path d="M363.922,83.347c2.368,-4.668 7.158,-7.609 12.392,-7.609c5.234,-0 10.024,2.941 12.392,7.609l50.897,100.324c3.157,6.223 10.004,9.66 16.881,8.474l86.27,-14.883c4.45,-0.768 8.995,0.675 12.188,3.867c3.193,3.193 4.635,7.739 3.868,12.188l-14.883,86.271c-1.187,6.877 2.25,13.723 8.474,16.881l100.324,50.897c4.668,2.368 7.609,7.158 7.609,12.392c-0,5.234 -2.941,10.024 -7.609,12.392l-100.324,50.897c-6.224,3.157 -9.661,10.004 -8.474,16.881l14.883,86.27c0.767,4.45 -0.675,8.995 -3.868,12.188c-3.193,3.193 -7.738,4.636 -12.188,3.868l-86.27,-14.883c-6.877,-1.187 -13.724,2.25 -16.881,8.474l-50.897,100.324c-2.368,4.668 -7.158,7.609 -12.392,7.609c-5.234,-0 -10.024,-2.941 -12.392,-7.609l-50.897,-100.324c-3.158,-6.224 -10.005,-9.661 -16.882,-8.474l-86.27,14.883c-4.45,0.768 -8.995,-0.675 -12.188,-3.868c-3.193,-3.193 -4.635,-7.738 -3.867,-12.188l14.883,-86.27c1.186,-6.877 -2.251,-13.724 -8.474,-16.881l-100.325,-50.897c-4.667,-2.368 -7.608,-7.158 -7.608,-12.392c-0,-5.234 2.941,-10.024 7.608,-12.392l100.325,-50.897c6.223,-3.158 9.66,-10.004 8.474,-16.881l-14.883,-86.271c-0.768,-4.449 0.674,-8.995 3.867,-12.188c3.193,-3.192 7.738,-4.635 12.188,-3.867l86.27,14.883c6.877,1.186 13.724,-2.251 16.882,-8.474l50.897,-100.324Zm12.392,124.6c-83.787,0 -151.811,68.024 -151.811,151.811c0,83.786 68.024,151.81 151.811,151.81c83.786,0 151.81,-68.024 151.81,-151.81c0,-83.787 -68.024,-151.811 -151.81,-151.811Zm-0,17.745c73.993,-0 134.066,60.073 134.066,134.066c-0,73.993 -60.073,134.066 -134.066,134.066c-73.994,0 -134.067,-60.073 -134.067,-134.066c0,-73.993 60.073,-134.066 134.067,-134.066Zm-0,53.107c-44.683,0 -80.959,36.277 -80.959,80.959c0,44.682 36.276,80.958 80.959,80.958c44.682,0 80.958,-36.276 80.958,-80.958c0,-44.682 -36.276,-80.959 -80.958,-80.959Zm-0,40.48c22.341,-0 40.479,18.138 40.479,40.479c-0,22.341 -18.138,40.479 -40.479,40.479c-22.341,0 -40.48,-18.138 -40.48,-40.479c0,-22.341 18.139,-40.479 40.48,-40.479Z" style="fill:url(#_Linear7);"/></g><defs><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(235.239,454.298,-454.298,235.239,235.912,135.951)"><stop offset="0" style="stop-color:#fbd248;stop-opacity:1"/><stop offset="0.5" style="stop-color:#e95a85;stop-opacity:1"/><stop offset="0.72" style="stop-color:#b752b0;stop-opacity:1"/><stop offset="1" style="stop-color:#6545f5;stop-opacity:1"/></linearGradient><linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(235.239,454.298,-454.298,235.239,260.091,123.162)"><stop offset="0" style="stop-color:#fbd248;stop-opacity:1"/><stop offset="0.5" style="stop-color:#e95a85;stop-opacity:1"/><stop offset="0.72" style="stop-color:#b752b0;stop-opacity:1"/><stop offset="1" style="stop-color:#6545f5;stop-opacity:1"/></linearGradient><linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(286.237,539.459,-539.459,286.237,208.073,131.525)"><stop offset="0" style="stop-color:#fbd248;stop-opacity:1"/><stop offset="0.5" style="stop-color:#e95a85;stop-opacity:1"/><stop offset="0.72" style="stop-color:#b752b0;stop-opacity:1"/><stop offset="1" style="stop-color:#6545f5;stop-opacity:1"/></linearGradient><linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(235.239,454.298,-454.298,235.239,294.943,119.974)"><stop offset="0" style="stop-color:#fbd248;stop-opacity:1"/><stop offset="0.5" style="stop-color:#e95a85;stop-opacity:1"/><stop offset="0.72" style="stop-color:#b752b0;stop-opacity:1"/><stop offset="1" style="stop-color:#6545f5;stop-opacity:1"/></linearGradient><linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(235.239,454.298,-454.298,235.239,261.364,141.107)"><stop offset="0" style="stop-color:#fbd248;stop-opacity:1"/><stop offset="0.5" style="stop-color:#e95a85;stop-opacity:1"/><stop offset="0.72" style="stop-color:#b752b0;stop-opacity:1"/><stop offset="1" style="stop-color:#6545f5;stop-opacity:1"/></linearGradient><linearGradient id="_Linear7" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(235.239,454.298,-454.298,235.239,261.364,141.107)"><stop offset="0" style="stop-color:#fbd248;stop-opacity:1"/><stop offset="0.5" style="stop-color:#e95a85;stop-opacity:1"/><stop offset="0.72" style="stop-color:#b752b0;stop-opacity:1"/><stop offset="1" style="stop-color:#6545f5;stop-opacity:1"/></linearGradient></defs></svg>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authentication"
|
|
3
|
+
group: "Security"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authentication
|
|
7
|
+
|
|
8
|
+
Magek uses the OAuth 2.0 protocol to authenticate users. That means that it uses tokens to identify users and authorize them. These tokens are called _access tokens_ and are issued by an _authentication provider_. The most common authentication provider is [Auth0](https://auth0.com/), but you can use any other provider that supports OAuth 2.0.
|
|
9
|
+
|
|
10
|
+
## Configuring the authentication provider
|
|
11
|
+
|
|
12
|
+
The first step to configure authentication in Magek is to configure the authentication provider. The provider must support OAuth 2.0 and must be able to issue _access tokens_. In order to validate incoming tokens and make sure that user requests come from trustable origins, you need to provide one or more `TokenVerifier` instances at config time for each of your environments.
|
|
13
|
+
|
|
14
|
+
The `TokenVerifier` class is a simple interface that you can implement to define your own token verifiers. Magek provides a `JwksUriTokenVerifier` class that you can use to configure a JWT token verifier. The `JwksUriTokenVerifier` constructor accepts the following parameters:
|
|
15
|
+
|
|
16
|
+
- `issuer`: The issuer of the tokens. This is a mandatory parameter. This is commonly found in the token payload under the `iss` key.
|
|
17
|
+
- `jwksUri`: The URL of the JSON Web Key Set (JWKS) that contains the public keys used to verify the tokens. This is a mandatory parameter. You can find more information about JWKS [here](https://auth0.com/docs/jwks).
|
|
18
|
+
- `rolesClaim`: The name of the claim that contains the user roles. This is an optional parameter. If not provided, the `roles` claim will be used. This is commonly found in the token payload under the `roles` key.
|
|
19
|
+
|
|
20
|
+
Here is an example of how to configure a `JwksUriTokenVerifier`:
|
|
21
|
+
|
|
22
|
+
```typescript title="src/config/config.ts"
|
|
23
|
+
|
|
24
|
+
Magek.configure('production', (config: MagekConfig): void => {
|
|
25
|
+
config.appName = 'app-name'
|
|
26
|
+
config.runtime = ServerRuntime
|
|
27
|
+
config.eventStoreAdapter = eventStore
|
|
28
|
+
config.tokenVerifiers = [
|
|
29
|
+
new JwksUriTokenVerifier(
|
|
30
|
+
'https://my-auth0-tenant.auth0.com/', // Issuer
|
|
31
|
+
'https://my-auth0-tenant.auth0.com/.well-known/jwks.json', // JWKS URL
|
|
32
|
+
'role' // Roles claim
|
|
33
|
+
),
|
|
34
|
+
])
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
One common way to validate JWT tokens is by using a issuer-provided well-known URI on which you can find their [JSON Web Key](https://datatracker.ietf.org/doc/html/rfc7517) sets (JWKS). If you use this method, you only need to provide the issuer's name, the JWKS URI and, if you're using role-based authentication, an optional `rolesClaim` option that sets the claim from which Magek will read the role names.
|
|
39
|
+
|
|
40
|
+
### JWKS URI glossary
|
|
41
|
+
|
|
42
|
+
Here you can find a list of the most common authentication providers and their corresponding issuer, JWKS URI and roles claim:
|
|
43
|
+
|
|
44
|
+
The issuer and JWKS URI may change depending on the region you're using. Please check the provider's documentation to find the correct values for your use case.
|
|
45
|
+
|
|
46
|
+
The following list is not exhaustive and the information may be deprecated. If you want to add a new provider, or update an existing one, please open a PR to have this content up to date.
|
|
47
|
+
|
|
48
|
+
| Provider | Issuer | JWKS URI |
|
|
49
|
+
| ----------- | ----------------------------------------------- | ----------------------------------------------------------- |
|
|
50
|
+
| Auth0 | `https://<your-tenant>.auth0.com/` | `https://<your-tenant>.auth0.com/.well-known/jwks.json` |
|
|
51
|
+
| Okta | `https://<your-tenant>.okta.com/oauth2/default` | `https://<your-tenant>.okta.com/oauth2/default/v1/keys` |
|
|
52
|
+
| Google | `https://accounts.google.com` | `https://www.googleapis.com/oauth2/v3/certs` |
|
|
53
|
+
| Firebase | `https://accounts.google.com` | `https://www.googleapis.com/oauth2/v3/certs` |
|
|
54
|
+
|
|
55
|
+
## Public key based authentication
|
|
56
|
+
|
|
57
|
+
The `PublicKeyTokenVerifier` class uses the public key of the issuer to verify the token signature. This means that the issuer must provide a JWKS URI that can be used to verify the token signature. This is the most common way to verify tokens, but it's not the only one. If you want to use a different method, you can implement your own `TokenVerifier` class.
|
|
58
|
+
|
|
59
|
+
This is useful when the token issuer doesn't provide a JWKS URI, when you're implementing your own authentication mechanism or you're issuing self-signed tokens.
|
|
60
|
+
|
|
61
|
+
```typescript title="src/config/config.ts"
|
|
62
|
+
|
|
63
|
+
function publicKeyResolver(): Promise<string> {
|
|
64
|
+
// Your implementation here
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Magek.configure('production', (config: MagekConfig): void => {
|
|
68
|
+
config.appName = 'app-name'
|
|
69
|
+
config.runtime = ServerRuntime
|
|
70
|
+
config.eventStoreAdapter = eventStore
|
|
71
|
+
config.tokenVerifiers = [
|
|
72
|
+
new PublicKeyTokenVerifier(
|
|
73
|
+
'issuer-name', // Issuer name
|
|
74
|
+
publicKeyResolver(), // Promise that resolves to the public key string
|
|
75
|
+
'custom:roles' // Name of the claim to read the roles from (if you're using role-based authorization)
|
|
76
|
+
),
|
|
77
|
+
]
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Notice that the `publicKeyResolver` is a promise that resolves to a string, so it can be used to load the public key from a remote location too (i.e. get it from your KMS).
|
|
82
|
+
|
|
83
|
+
If you need to handle private keys in production, consider using a KMS [(Key Management System)](https://en.wikipedia.org/wiki/Key_management#Key_storage). These systems often provide API endpoints that let you encrypt/sign your JWT tokens without exposing the private keys. The public keys can be set in a `PublicKeyTokenVerifier` to automate verification.
|
|
84
|
+
|
|
85
|
+
## Custom authentication
|
|
86
|
+
|
|
87
|
+
If you want to implement your own authentication mechanism, you can implement your own `TokenVerifier` class. This class must implement the following interface:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
interface TokenVerifier {
|
|
91
|
+
/**
|
|
92
|
+
* Verify asd deserialize a stringified token with this token verifier.
|
|
93
|
+
* @param token The token to verify
|
|
94
|
+
*/
|
|
95
|
+
verify(token: string): Promise<DecodedToken>
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build a valid `UserEnvelope` from a decoded token.
|
|
99
|
+
* @param decodedToken The decoded token
|
|
100
|
+
*/
|
|
101
|
+
toUserEnvelope(decodedToken: DecodedToken): UserEnvelope
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Here is an example of how to implement a custom `TokenVerifier`:
|
|
106
|
+
|
|
107
|
+
```typescript title="src/config/config.ts"
|
|
108
|
+
|
|
109
|
+
class CustomTokenVerifier implements TokenVerifier {
|
|
110
|
+
public async verify(token: string): Promise<DecodedToken> {
|
|
111
|
+
// Your custom token verification logic here
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public toUserEnvelope(decodedToken: DecodedToken): UserEnvelope {
|
|
115
|
+
// Your custom logic to build a UserEnvelope from a decoded token here
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Magek.configure('production', (config: MagekConfig): void => {
|
|
120
|
+
config.appName = 'app-name'
|
|
121
|
+
config.runtime = ServerRuntime
|
|
122
|
+
config.eventStoreAdapter = eventStore
|
|
123
|
+
config.tokenVerifiers = [new CustomTokenVerifier()]
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Some use cases for this could be to check that the token was generated specifically for your service by inspecting the `aud` claim, or check that the token has not been blacklisted or invalidated by your business logic (i.e. a user logs out before the token's expiration date and is included in an invalidated tokens list to make sure that an attacker that finds the token later can't use it to impersonate the legitimate owner).
|
|
128
|
+
|
|
129
|
+
### Extend existing token verifiers
|
|
130
|
+
|
|
131
|
+
If you only need to perform extra validations on top of one of the existing `TokenVerifier`s, you can extend one of the default implementations:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
export class CustomValidator extends PrivateKeyValidator {
|
|
135
|
+
public async verify(token: string): Promise<UserEnvelope> {
|
|
136
|
+
// Call to the PrivateKeyValidator verify method to check the signature
|
|
137
|
+
const userEnvelope = await super.verify(token)
|
|
138
|
+
|
|
139
|
+
// Do my extra validations here. Throwing an error will reject the token
|
|
140
|
+
await myExtraValidations(userEnvelope.claims, token)
|
|
141
|
+
|
|
142
|
+
return userEnvelope
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Advanced authentication
|
|
148
|
+
|
|
149
|
+
If you need to do more advanced checks, you can implement the whole verification algorithm yourself. For example, if you're using non-standard or legacy tokens. Magek exposes for convenience many of the utility functions that it uses in the default `TokenVerifier` implementations:
|
|
150
|
+
|
|
151
|
+
| Function | Description |
|
|
152
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
153
|
+
| `getJwksClient` | Initializes a jwksRSA client that can be used to get the public key of a JWKS URI using the `getKeyWithClient` function. |
|
|
154
|
+
| `getKeyWithClient` | Initializes a function that can be used to get the public key from a JWKS URI with the signature required by the `verifyJWT` function. You can create a client using the `getJwksClient` function. |
|
|
155
|
+
| `verifyJWT` | Verifies a JWT token using a key or key resolver function and returns a Magek UserEnvelope. |
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
/**
|
|
159
|
+
* Initializes a jwksRSA client that can be used to get the public key of a JWKS URI using the
|
|
160
|
+
* `getKeyWithClient` function.
|
|
161
|
+
*/
|
|
162
|
+
export function getJwksClient(jwksUri: string) {
|
|
163
|
+
...
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Initializes a function that can be used to get the public key from a JWKS URI with the signature
|
|
168
|
+
* required by the `verifyJWT` function. You can create a client using the `getJwksClient` function.
|
|
169
|
+
*/
|
|
170
|
+
export function getKeyWithClient(
|
|
171
|
+
client: jwksRSA.JwksClient,
|
|
172
|
+
header: jwt.JwtHeader,
|
|
173
|
+
callback: jwt.SigningKeyCallback
|
|
174
|
+
): void {
|
|
175
|
+
...
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Verifies a JWT token using a key or key resolver function and returns a Magek UserEnvelope.
|
|
180
|
+
*/
|
|
181
|
+
export async function verifyJWT(
|
|
182
|
+
token: string,
|
|
183
|
+
issuer: string,
|
|
184
|
+
key: jwt.Secret | jwt.GetPublicKeyOrSecret,
|
|
185
|
+
rolesClaim?: string
|
|
186
|
+
): Promise<UserEnvelope> {
|
|
187
|
+
...
|
|
188
|
+
}
|
|
189
|
+
```
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authorization"
|
|
3
|
+
group: "Security"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authorization
|
|
7
|
+
|
|
8
|
+
Magek uses a whitelisting approach to authorize users to perform commands and read models. This means that you must explicitly specify which users are allowed to perform each action. In order to do that you must configure the `authorize` policy parameter on every Command or Read Model. This parameter accepts one of the following options:
|
|
9
|
+
|
|
10
|
+
- `'all'`: The command or read-model is explicitly public, any user can access it.
|
|
11
|
+
- `[Role1, Role2, ...]`: An array of authorized [Roles](#defining-roles), this means that only those authenticated users that have any of the roles listed there are authorized to execute the command.
|
|
12
|
+
- An authorizer function that matches the `CommandAuthorizer` interface for commands or the `ReadModelAuthorizer` interface for read models.
|
|
13
|
+
|
|
14
|
+
## Making commands and read models public
|
|
15
|
+
|
|
16
|
+
Setting the option `authorize: 'all'` in a command or read model will make it publicly accessible to anyone that has access to the GraphQL endpoint. For example, the following command can be executed by anyone, even if they don't provide a valid JWT token:
|
|
17
|
+
|
|
18
|
+
```typescript title="src/commands/create-comment.ts"
|
|
19
|
+
@Command({
|
|
20
|
+
authorize: 'all',
|
|
21
|
+
})
|
|
22
|
+
export class CreateComment {
|
|
23
|
+
...
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> **Danger:** Think twice if you really need fully open GraphQL endpoints in your application, this might be useful during development, but we recommend to **avoid exposing your endpoints in this way in production**. Even for public APIs, it might be useful to issue API keys to avoid abuse. Magek is designed to scale to any given demand, but scaling also increases infrastructure costs! (See [Denial of wallet attacks](https://www.sciencedirect.com/science/article/pii/S221421262100079X))
|
|
28
|
+
|
|
29
|
+
## Simple Role-based authorization
|
|
30
|
+
|
|
31
|
+
Magek provides a simple role-based authentication mechanism that will work in many standard scenarios. It is based on the concept of roles, which are just a set of permissions. For example, a `User` role might have the permission to `create` and `read` comments, while an `Admin` role might have the permission to `create`, `read`, and `delete` comments. You can define as many roles as you want, and then assign them to users.
|
|
32
|
+
|
|
33
|
+
### Defining @Roles
|
|
34
|
+
|
|
35
|
+
As many other Magek artifacts, Magek Roles are defined as simple decorated classes. We recommend them to be defined in the `src/config/roles.ts` file, but it is not limited to that file. To define a role, you only need to decorate an empty class with the `@Role` decorator as follows:
|
|
36
|
+
|
|
37
|
+
```typescript title="src/config/roles.ts"
|
|
38
|
+
@Role()
|
|
39
|
+
export class User {}
|
|
40
|
+
|
|
41
|
+
@Role()
|
|
42
|
+
export class Admin {}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Protecting commands and read models with roles
|
|
46
|
+
|
|
47
|
+
Once you have defined your roles, you can use them to protect your commands and read models. For example, the following command can only be executed by users that have the role `Admin`:
|
|
48
|
+
|
|
49
|
+
```typescript title="src/commands/create-comment.ts"
|
|
50
|
+
@Command({
|
|
51
|
+
authorize: [Admin],
|
|
52
|
+
})
|
|
53
|
+
export class CreateComment {
|
|
54
|
+
...
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This command will not be available to users with the role `User`.
|
|
59
|
+
|
|
60
|
+
### Associating users with roles
|
|
61
|
+
|
|
62
|
+
Magek will read the roles from the JWT token that you provide in the request. The token must include a claim with the key you specidied in the `rolesClaim` field. Magek will read such field and compare it with the declared ones in the `authorize` field of the protected command or read model.
|
|
63
|
+
|
|
64
|
+
For example, given the following setup:
|
|
65
|
+
|
|
66
|
+
## Magek Config
|
|
67
|
+
|
|
68
|
+
```typescript title="src/config/config.ts"
|
|
69
|
+
|
|
70
|
+
Magek.configure('production', (config: MagekConfig): void => {
|
|
71
|
+
config.appName = 'my-store'
|
|
72
|
+
config.runtime = ServerRuntime
|
|
73
|
+
config.eventStoreAdapter = eventStore
|
|
74
|
+
config.tokenVerifiers = [
|
|
75
|
+
new JwksUriTokenVerifier(
|
|
76
|
+
'https://my-auth0-tenant.auth0.com/', // Issuer
|
|
77
|
+
'https://my-auth0-tenant.auth0.com/.well-known/jwks.json', // JWKS URL
|
|
78
|
+
// highlight-next-line
|
|
79
|
+
'firebase:groups' // <- roles are read from 'firebase:groups' claim from the token
|
|
80
|
+
),
|
|
81
|
+
]
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Decoded Token
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
// highlight-next-line
|
|
90
|
+
"firebase:groups": "User", // <- roles are read from 'firebase:groups' claim
|
|
91
|
+
"iss": "https://securetoken.google.com/demoapp",
|
|
92
|
+
"aud": "demoapp",
|
|
93
|
+
"auth_time": 1604676721,
|
|
94
|
+
"user_id": "xJY5Y6fTbVggNtDjaNh7cNSBd7q1",
|
|
95
|
+
"sub": "xJY5Y6fTbVggNtDjaNh7cNSBd7q1",
|
|
96
|
+
"iat": 1604676721,
|
|
97
|
+
"exp": 1604680321,
|
|
98
|
+
"phone_number": "+999999999",
|
|
99
|
+
"firebase": {}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Magek Command
|
|
104
|
+
|
|
105
|
+
```typescript title="src/commands/create-comment.ts"
|
|
106
|
+
@Command({
|
|
107
|
+
authorize: [Admin],
|
|
108
|
+
})
|
|
109
|
+
export class CreateComment {
|
|
110
|
+
...
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Magek Roles
|
|
115
|
+
|
|
116
|
+
```typescript title="src/config/roles.ts"
|
|
117
|
+
@Role()
|
|
118
|
+
export class User {}
|
|
119
|
+
|
|
120
|
+
@Role()
|
|
121
|
+
export class Admin {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Magek will check that the token contains the `firebase:groups` claim and that it contains the `Admin` role.
|
|
125
|
+
Also, if the token doesn't contain the `Admin` role, the command will not be executed. As you can see, the decoded token
|
|
126
|
+
has `User` as value of the `firebase:groups` claim, so the command will not be executed.
|
|
127
|
+
|
|
128
|
+
## Custom authorization functions
|
|
129
|
+
|
|
130
|
+
Magek also allows you to implement your own authorization functions, in case the role-based authorization model doesn't work for your application. In order to
|
|
131
|
+
apply your own authorization functions, you need to provide them in the `authorize` field of the command or read model. As authorization functions are regular
|
|
132
|
+
JavaScript functions, you can easily reuse them in your project or even in other Magek projects as a library.
|
|
133
|
+
|
|
134
|
+
### Command Authorizers
|
|
135
|
+
|
|
136
|
+
As mentioned, the `authorize` parameter of the `@Command` can receive a function. However, this function must match the `CommandAuthorizer` type. This function will receive two parameters and return a `Promise` that will resolve if the user is authorized to execute the command or reject if not:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
export type CommandAuthorizer = (currentUser?: UserEnvelope, input?: CommandInput) => Promise<void>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Parameter | Type | Description |
|
|
143
|
+
| ----------- | -------------- | ----------------------------------------- |
|
|
144
|
+
| currentUser | `UserEnvelope` | User data decoded from the provided token |
|
|
145
|
+
| input | `CommandInput` | The input of the command |
|
|
146
|
+
|
|
147
|
+
For instance, if you want to restrict a command to users that have a permission named `Permission-To-Rock` in the `permissions` claim you can do this:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
|
|
151
|
+
const CustomCommandAuthorizer: CommandAuthorizer = async (currentUser) => {
|
|
152
|
+
if (!currentUser.claims['permissions'].includes('Permission-To-Rock')) {
|
|
153
|
+
throw new Error(`User ${currentUser.username} should not be rocking!`) // <- This will reject the access to the command
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@Command({
|
|
158
|
+
authorize: CustomCommandAuthorizer,
|
|
159
|
+
})
|
|
160
|
+
export class PerformIncredibleGuitarSolo {
|
|
161
|
+
...
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Read Model Authorizers
|
|
166
|
+
|
|
167
|
+
As with commands, the `authorize` parameter of the `@ReadModel` decorator can also receive a function. However, this function must match the `ReadModelAuthorizer` type. This function will receive two parameters and return a `Promise` that will resolve if the user is authorized to execute the command or reject if not:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
export type ReadModelAuthorizer<TReadModel extends ReadModelInterface> = (
|
|
171
|
+
currentUser?: UserEnvelope,
|
|
172
|
+
readModelRequestEnvelope?: ReadModelRequestEnvelope<TReadModel>
|
|
173
|
+
) => Promise<void>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Parameter | Type | Description |
|
|
177
|
+
| ----------- | -------------- | ----------------------------------------- |
|
|
178
|
+
| currentUser | `UserEnvelope` | User data decoded from the provided token |
|
|
179
|
+
| input | `CommandInput` | The input of the command |
|
|
180
|
+
|
|
181
|
+
For instance, you may want to restrict access to a specific resource only to people that has been granted read permission:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const CustomReadModelAuthorizer: ReadModelAuthorizer = async (currentUser, readModelRequestEnvelope) => {
|
|
185
|
+
const userPermissions = Magek.entity(UserPermissions, currentUser.username)
|
|
186
|
+
if (!userPermissions || !userPermissions.accessTo[readModelRequestEnvelope.className].includes(readModelRequestEnvelope.key.id)) {
|
|
187
|
+
throw new Error(`User ${currentUser.username} should not be looking here`)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@ReadModel({
|
|
192
|
+
authorize: CustomReadModelAuthorizer
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Event Stream Authorizers
|
|
197
|
+
|
|
198
|
+
You can restrict the access to the [Event Stream](/features/event-stream) of an `Entity` by providing an `authorizeReadEvents` function in the `@Entity` decorator. This function is called every time an event stream is requested. The function must match the `EventStreamAuthorizer` type receives the current user and the event search request as parameters. The function must return a `Promise<void>`. If the promise is rejected, the request will be denied. If the promise is resolved successfully, the request will be allowed.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
export type EventStreamAuthorizer = (
|
|
202
|
+
currentUser?: UserEnvelope,
|
|
203
|
+
eventSearchRequest?: EventSearchRequest
|
|
204
|
+
) => Promise<void>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
For instance, you can restrict access to entities that the current user own.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const CustomEventAuthorizer: EventStreamAuthorizer = async (currentUser, eventSearchRequest) => {
|
|
211
|
+
const { entityID } = eventSearchRequest.parameters
|
|
212
|
+
if (!entityID) {
|
|
213
|
+
throw new Error(`${currentUser.username} cannot list carts`)
|
|
214
|
+
}
|
|
215
|
+
const cart = Magek.entity(Cart, entityID)
|
|
216
|
+
if (cart.ownerUserName !== currentUser.userName) {
|
|
217
|
+
throw new Error(`${currentUser.username} cannot see events in cart ${entityID}`)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Entity({
|
|
222
|
+
authorizeReadEvents: CustomEventAuthorizer
|
|
223
|
+
})
|
|
224
|
+
export class Cart {
|
|
225
|
+
@field(type => UUID)
|
|
226
|
+
readonly id!: UUID
|
|
227
|
+
|
|
228
|
+
@field()
|
|
229
|
+
readonly ownerUserName!: string
|
|
230
|
+
|
|
231
|
+
@field()
|
|
232
|
+
readonly cartItems!: Array<CartItem>
|
|
233
|
+
|
|
234
|
+
@field()
|
|
235
|
+
public shippingAddress?: Address
|
|
236
|
+
|
|
237
|
+
@field()
|
|
238
|
+
public checks: number = 0
|
|
239
|
+
|
|
240
|
+
// ... reducers
|
|
241
|
+
}
|
|
242
|
+
```
|