@empyria/moleculer 0.1.0 → 0.1.2
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/.oxlintrc.json +3 -0
- package/AGENTS.md +3 -3
- package/MetaGuard.middleware.js +2 -5
- package/README.md +10 -10
- package/mixins/Base.mixin.js +4 -4
- package/package.json +7 -7
- package/test/Base.mixin.test.js +3 -3
- package/test/Context.test.js +3 -3
- package/test/MetaGuard.test.js +4 -4
- package/util/Context.js +2 -2
package/.oxlintrc.json
ADDED
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
Moleculer helpers for **
|
|
4
|
-
`BaseMixin` (per-service
|
|
3
|
+
Moleculer helpers for **Empyria**, a nanoservice framework built primarily on Bun:
|
|
4
|
+
`BaseMixin` (per-service Empyria meta stamping), `MetaGuard` (a middleware guarding
|
|
5
5
|
actions/events against unwanted cross-federation access), and lower-level `Connector.js`/
|
|
6
6
|
`util/` helpers.
|
|
7
7
|
|
|
@@ -18,7 +18,7 @@ actions/events against unwanted cross-federation access), and lower-level `Conne
|
|
|
18
18
|
## Known incomplete module
|
|
19
19
|
|
|
20
20
|
`mixins/Connector.mixin.js` imports `@empyria/redis` and `@empyria/mongo`, which aren't
|
|
21
|
-
published as standalone packages yet (they still live inside the monolithic `
|
|
21
|
+
published as standalone packages yet (they still live inside the monolithic `empyria` repo's
|
|
22
22
|
`libs/`). It is **not** re-exported from [Mixins.js](./Mixins.js) — re-adding that export line
|
|
23
23
|
before those two packages exist would break every consumer's import of this whole package. Only
|
|
24
24
|
re-export it once both are published; the file itself has a comment marking this.
|
package/MetaGuard.middleware.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validateContext, getUser } from './util/Context.js'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EMPYRIA_FEDERATION_ID, BaseErrors } from '@empyria/common'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Builds the shared guard logic for `localAction`/`remoteAction` hooks: validates the
|
|
@@ -16,10 +16,7 @@ function guardAction(next, action, resolveUser, whitelistedActions) {
|
|
|
16
16
|
return async (ctx) => {
|
|
17
17
|
const { user } = validateContext(ctx, action)
|
|
18
18
|
|
|
19
|
-
if (
|
|
20
|
-
whitelistedActions.includes(action.name) &&
|
|
21
|
-
user.federation !== PRINCIPIA_FEDERATION_ID
|
|
22
|
-
) {
|
|
19
|
+
if (whitelistedActions.includes(action.name) && user.federation !== EMPYRIA_FEDERATION_ID) {
|
|
23
20
|
throw new BaseErrors.ServiceVoilation({
|
|
24
21
|
service: `Microservice ${ctx.action?.name || action.name} communication`,
|
|
25
22
|
})
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @empyria/moleculer
|
|
2
2
|
|
|
3
|
-
Moleculer helpers for **
|
|
3
|
+
Moleculer helpers for **Empyria**, a nanoservice framework built primarily on Bun:
|
|
4
4
|
a base service mixin, and a context-validating guard middleware against unwanted
|
|
5
5
|
(unauthenticated or cross-federation) access.
|
|
6
6
|
|
|
@@ -50,15 +50,15 @@ import { hashPassword, verifyPassword } from '@empyria/moleculer/util/Crypto.js'
|
|
|
50
50
|
|
|
51
51
|
## Modules
|
|
52
52
|
|
|
53
|
-
| Module | Purpose
|
|
54
|
-
| -------------------------------------------------------- |
|
|
55
|
-
| [Mixins.js](./Mixins.js) | Package entry point; re-exports `BaseMixin` and `MetaGuard`.
|
|
56
|
-
| [MetaGuard.middleware.js](./MetaGuard.middleware.js) | `MetaGuard` — a Moleculer middleware guarding actions/events against unwanted access, with optional caller resolution.
|
|
57
|
-
| [mixins/Base.mixin.js](./mixins/Base.mixin.js) | `BaseMixin` — stamps a
|
|
58
|
-
| [mixins/Connector.mixin.js](./mixins/Connector.mixin.js) | `ConnectorMixin` — adds optional Redis/Mongo setup. **Not yet exported from `Mixins.js`** — see below.
|
|
59
|
-
| [Connector.js](./Connector.js) | `connect` — starts a Moleculer broker and waits for required services.
|
|
60
|
-
| [util/Context.js](./util/Context.js) | Context helpers: `getParams`, `getUser`, `validateContext`, `actionStamped`, `timeStamped`, `checkDefined`.
|
|
61
|
-
| [util/Crypto.js](./util/Crypto.js) | Hashing, Ed25519 signing/verification, and password hashing (`hashPassword`/`verifyPassword`).
|
|
53
|
+
| Module | Purpose |
|
|
54
|
+
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
55
|
+
| [Mixins.js](./Mixins.js) | Package entry point; re-exports `BaseMixin` and `MetaGuard`. |
|
|
56
|
+
| [MetaGuard.middleware.js](./MetaGuard.middleware.js) | `MetaGuard` — a Moleculer middleware guarding actions/events against unwanted access, with optional caller resolution. |
|
|
57
|
+
| [mixins/Base.mixin.js](./mixins/Base.mixin.js) | `BaseMixin` — stamps a Empyria meta (actor/federation/flow/process IDs) on service start, and exposes the package version. |
|
|
58
|
+
| [mixins/Connector.mixin.js](./mixins/Connector.mixin.js) | `ConnectorMixin` — adds optional Redis/Mongo setup. **Not yet exported from `Mixins.js`** — see below. |
|
|
59
|
+
| [Connector.js](./Connector.js) | `connect` — starts a Moleculer broker and waits for required services. |
|
|
60
|
+
| [util/Context.js](./util/Context.js) | Context helpers: `getParams`, `getUser`, `validateContext`, `actionStamped`, `timeStamped`, `checkDefined`. |
|
|
61
|
+
| [util/Crypto.js](./util/Crypto.js) | Hashing, Ed25519 signing/verification, and password hashing (`hashPassword`/`verifyPassword`). |
|
|
62
62
|
|
|
63
63
|
### Known limitation: `ConnectorMixin`
|
|
64
64
|
|
package/mixins/Base.mixin.js
CHANGED
|
@@ -4,7 +4,7 @@ import { randomUUID } from 'crypto'
|
|
|
4
4
|
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { EMPYRIA_FEDERATION_ID, MOLECULER_SERVICE_ROLE, moleculerMeta } from '@empyria/common'
|
|
8
8
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url)
|
|
10
10
|
const __dirname = path.dirname(__filename)
|
|
@@ -14,8 +14,8 @@ const VERSION = JSON.parse(
|
|
|
14
14
|
).version
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Base Moleculer mixin every
|
|
18
|
-
*
|
|
17
|
+
* Base Moleculer mixin every Empyria service builds on. On `started`, stamps a
|
|
18
|
+
* Empyria-flavored `meta` (actor/federation/flow/process IDs) onto the service, and
|
|
19
19
|
* exposes the running package version as both a method and an action.
|
|
20
20
|
*/
|
|
21
21
|
export const BaseMixin = {
|
|
@@ -30,7 +30,7 @@ export const BaseMixin = {
|
|
|
30
30
|
|
|
31
31
|
this.meta = moleculerMeta({
|
|
32
32
|
actor: `${this.name}-service`,
|
|
33
|
-
federation:
|
|
33
|
+
federation: EMPYRIA_FEDERATION_ID,
|
|
34
34
|
flowID: randomUUID(),
|
|
35
35
|
processID: randomUUID(),
|
|
36
36
|
role: MOLECULER_SERVICE_ROLE,
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empyria/moleculer",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Moleculer helpers for
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Moleculer helpers for Empyria",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Imre Fazekas <imre.fazekas@icloud.com>",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/empyria-nano/guard-moleculer.git"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "./Mixins.js",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"test:watch": "bun test --watch"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@empyria/classification": "0.1.
|
|
30
|
-
"@empyria/common": "0.1.
|
|
29
|
+
"@empyria/classification": "0.1.2",
|
|
30
|
+
"@empyria/common": "0.1.2",
|
|
31
31
|
"moleculer": "0.15.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"oxfmt": "0.
|
|
35
|
-
"oxlint": "1.
|
|
34
|
+
"oxfmt": "0.64.0",
|
|
35
|
+
"oxlint": "1.79.0"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"bun": ">=1.4.0",
|
package/test/Base.mixin.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, afterEach } from 'bun:test'
|
|
2
2
|
import { ServiceBroker } from 'moleculer'
|
|
3
3
|
import { BaseMixin } from '../mixins/Base.mixin.js'
|
|
4
|
-
import {
|
|
4
|
+
import { EMPYRIA_FEDERATION_ID, MOLECULER_SERVICE_ROLE } from '@empyria/common'
|
|
5
5
|
|
|
6
6
|
let broker
|
|
7
7
|
|
|
@@ -10,14 +10,14 @@ afterEach(async () => {
|
|
|
10
10
|
})
|
|
11
11
|
|
|
12
12
|
describe('BaseMixin', () => {
|
|
13
|
-
test('stamps a
|
|
13
|
+
test('stamps a Empyria meta on start, and exposes the package version', async () => {
|
|
14
14
|
broker = new ServiceBroker({ logLevel: 'fatal', nodeID: 'base-mixin-test' })
|
|
15
15
|
broker.createService({ name: 'testsvc', mixins: [BaseMixin] })
|
|
16
16
|
await broker.start()
|
|
17
17
|
|
|
18
18
|
const svc = broker.getLocalService('testsvc')
|
|
19
19
|
expect(svc.meta.user.actor).toBe('testsvc-service')
|
|
20
|
-
expect(svc.meta.user.federation).toBe(
|
|
20
|
+
expect(svc.meta.user.federation).toBe(EMPYRIA_FEDERATION_ID)
|
|
21
21
|
expect(svc.meta.user.role).toBe(MOLECULER_SERVICE_ROLE)
|
|
22
22
|
expect(typeof svc.meta.user.flowID).toBe('string')
|
|
23
23
|
expect(typeof svc.meta.user.processID).toBe('string')
|
package/test/Context.test.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
timeStamped,
|
|
8
8
|
checkDefined,
|
|
9
9
|
} from '../util/Context.js'
|
|
10
|
-
import {
|
|
10
|
+
import { EmpyriaError } from '@empyria/common'
|
|
11
11
|
|
|
12
12
|
describe('getParams', () => {
|
|
13
13
|
test('unwraps a body.packet envelope', () => {
|
|
@@ -62,7 +62,7 @@ describe('validateContext', () => {
|
|
|
62
62
|
|
|
63
63
|
test('throws when the context has no authenticated user', () => {
|
|
64
64
|
const ctx = { meta: {} }
|
|
65
|
-
expect(() => validateContext(ctx, { name: 'v1.Test.action' })).toThrow(
|
|
65
|
+
expect(() => validateContext(ctx, { name: 'v1.Test.action' })).toThrow(EmpyriaError)
|
|
66
66
|
})
|
|
67
67
|
})
|
|
68
68
|
|
|
@@ -101,6 +101,6 @@ describe('checkDefined', () => {
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
test('throws when nothing is defined', () => {
|
|
104
|
-
expect(() => checkDefined({ a: undefined, b: null })).toThrow(
|
|
104
|
+
expect(() => checkDefined({ a: undefined, b: null })).toThrow(EmpyriaError)
|
|
105
105
|
})
|
|
106
106
|
})
|
package/test/MetaGuard.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test'
|
|
2
2
|
import { MetaGuard } from '../MetaGuard.middleware.js'
|
|
3
|
-
import {
|
|
3
|
+
import { EmpyriaError } from '@empyria/common'
|
|
4
4
|
|
|
5
5
|
const action = (name) => ({ name, service: { meta: {} } })
|
|
6
6
|
const ctxWithUser = (federation = 'my-fed', extra = {}) => ({
|
|
@@ -22,7 +22,7 @@ describe('MetaGuard: localAction / remoteAction', () => {
|
|
|
22
22
|
const guard = MetaGuard({ resolveUser: false })
|
|
23
23
|
await expect(
|
|
24
24
|
guard.localAction(async () => 'ok', action('v1.Test.action'))({ meta: {} }),
|
|
25
|
-
).rejects.toThrow(
|
|
25
|
+
).rejects.toThrow(EmpyriaError)
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
test('rejects a cross-federation call to a whitelisted action', async () => {
|
|
@@ -37,7 +37,7 @@ describe('MetaGuard: localAction / remoteAction', () => {
|
|
|
37
37
|
async () => 'ok',
|
|
38
38
|
action('v1.Restricted.action'),
|
|
39
39
|
)(ctxWithUser('other-federation')),
|
|
40
|
-
).rejects.toThrow(
|
|
40
|
+
).rejects.toThrow(EmpyriaError)
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
test('allows a same-federation call to a whitelisted action', async () => {
|
|
@@ -103,6 +103,6 @@ describe('MetaGuard: localEvent', () => {
|
|
|
103
103
|
const guard = MetaGuard({ resolveUser: false })
|
|
104
104
|
await expect(
|
|
105
105
|
guard.localEvent(async () => 'ok', action('some.event'))({ meta: {} }),
|
|
106
|
-
).rejects.toThrow(
|
|
106
|
+
).rejects.toThrow(EmpyriaError)
|
|
107
107
|
})
|
|
108
108
|
})
|
package/util/Context.js
CHANGED
|
@@ -34,7 +34,7 @@ export function getUser(ctx, action) {
|
|
|
34
34
|
* @param {Object} [action] - The action/event being guarded.
|
|
35
35
|
* @returns {{user: Object, tokenKey: string}|undefined} The context's user/tokenKey, or
|
|
36
36
|
* `undefined` for a `$`-prefixed internal action.
|
|
37
|
-
* @throws {
|
|
37
|
+
* @throws {EmpyriaError} `BaseErrors.ServiceVoilation` if `ctx.meta.user` is missing or incomplete.
|
|
38
38
|
*/
|
|
39
39
|
export function validateContext(ctx, action) {
|
|
40
40
|
const { user, tokenKey } = ctx.meta
|
|
@@ -82,7 +82,7 @@ export function timeStamped(object) {
|
|
|
82
82
|
* at least one to be present.
|
|
83
83
|
* @param {Object} object - Object to filter.
|
|
84
84
|
* @returns {Object} The defined-only copy.
|
|
85
|
-
* @throws {
|
|
85
|
+
* @throws {EmpyriaError} `BaseErrors.MissingData` if no properties are defined.
|
|
86
86
|
*/
|
|
87
87
|
export function checkDefined(object) {
|
|
88
88
|
const res = cloneDefined(object)
|