@rytass/bpm-core-nestjs-module 0.1.8 → 0.1.10
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 +44 -0
- package/README.md +184 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
Releases are managed by [`nx release`](https://nx.dev/recipes/nx-release) with
|
|
10
10
|
Conventional Commits — see `nx.json` for the release config.
|
|
11
11
|
|
|
12
|
+
## 0.1.10 — 2026-05-28
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
- **Pattern A response status contract table** added — explicit success
|
|
17
|
+
/ auth-fail / validation-fail HTTP status codes for `/auth/login`,
|
|
18
|
+
`/auth/me`, `/auth/logout`. Also notes that `roles` and `permissions`
|
|
19
|
+
must be arrays (not `null`/`undefined`), and shows the
|
|
20
|
+
`projectMemberToApiMember` skeleton mapping `ApiMember.email: string`
|
|
21
|
+
(not `string | null`).
|
|
22
|
+
- **"Concrete wiring" snippet** for the recommended same-database
|
|
23
|
+
separate-schema option B: dual `TypeOrmModule.forRootAsync` with
|
|
24
|
+
`name: 'bpm'` so the host's existing connection and BPM's stay
|
|
25
|
+
isolated. Also shows handing `typeormConnectionName: 'bpm'` to
|
|
26
|
+
`BPMRootModule.forRootAsync`.
|
|
27
|
+
- **`createBPMMemberBaseResolverProvider<HostMember>` generic** added
|
|
28
|
+
to the README example with a callout about why omitting it causes
|
|
29
|
+
TS to infer `unknown`.
|
|
30
|
+
- "Current version" line refreshed to `0.1.10`.
|
|
31
|
+
|
|
32
|
+
### Why a patch
|
|
33
|
+
|
|
34
|
+
Documentation only.
|
|
35
|
+
|
|
36
|
+
## 0.1.9 — 2026-05-28
|
|
37
|
+
|
|
38
|
+
### Documentation
|
|
39
|
+
|
|
40
|
+
- **README "Machine-to-machine authentication" section** added under
|
|
41
|
+
Auth Context. Documents three patterns for server-side scripts (org
|
|
42
|
+
seeds, cron workers, integration tests) to authenticate against BPM:
|
|
43
|
+
(1) service member + login flow with cookie-jar fetch (recommended),
|
|
44
|
+
(2) service token header for hosts that issue long-lived JWTs,
|
|
45
|
+
(3) direct cookie injection for testing only.
|
|
46
|
+
- **README "Coexisting with a host's existing `<AuthProvider>`"** added
|
|
47
|
+
under Auth Context. Explains that BPM's React provider is a separate
|
|
48
|
+
context safe to nest under a host provider, with three rules of
|
|
49
|
+
thumb (scope to sub-tree, share cookie jar same-origin, don't
|
|
50
|
+
double-wrap `<AuthProvider>` directly).
|
|
51
|
+
|
|
52
|
+
### Why a patch
|
|
53
|
+
|
|
54
|
+
Documentation only.
|
|
55
|
+
|
|
12
56
|
## 0.1.8 — 2026-05-28
|
|
13
57
|
|
|
14
58
|
### Documentation
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ directory integration, storage adapters, and deployment.
|
|
|
14
14
|
|
|
15
15
|
## Package Status
|
|
16
16
|
|
|
17
|
-
Current version: `0.1.
|
|
17
|
+
Current version: `0.1.10`
|
|
18
18
|
|
|
19
19
|
The package is intended for NestJS backend hosts. It does not include the
|
|
20
20
|
Next.js backoffice UI and does not provide a production auth system by itself.
|
|
@@ -273,6 +273,31 @@ member shape to BPM's expectations, project Casbin roles into the BPM
|
|
|
273
273
|
role-literal strings (see "Mapping from a host RBAC system" below),
|
|
274
274
|
and compute `expiresAt` from the token TTL.
|
|
275
275
|
|
|
276
|
+
```ts
|
|
277
|
+
function projectMemberToApiMember(member: HostMember): ApiMember {
|
|
278
|
+
return {
|
|
279
|
+
memberId: member.id,
|
|
280
|
+
email: member.email ?? '', // ApiMember.email is `string`, not `string | null`
|
|
281
|
+
name: member.name,
|
|
282
|
+
roles: projectHostRolesToBPMRoles(member.roles), // [] if no admin/designer claim
|
|
283
|
+
permissions: [], // optional — return [] if not used
|
|
284
|
+
expiresAt: new Date(Date.now() + JWT_TTL_MS).toISOString(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Response status contract that BPM's React client expects:
|
|
290
|
+
|
|
291
|
+
| Endpoint | Success | Auth fail | Validation fail |
|
|
292
|
+
|---|---|---|---|
|
|
293
|
+
| `POST /auth/login` | `200` + `ApiMember` JSON, set HTTP-only cookie | `401` | `400` |
|
|
294
|
+
| `GET /auth/me` | `200` + `ApiMember` JSON | `401` (client treats as anonymous, **not error**) | n/a |
|
|
295
|
+
| `POST /auth/logout` | `204` (or `200` with empty body) | `401` ignored — client clears local state anyway | n/a |
|
|
296
|
+
|
|
297
|
+
`roles` and `permissions` MUST be arrays (use `[]` for no claim), not
|
|
298
|
+
`null` or `undefined` — the React client's `member.roles.includes(...)`
|
|
299
|
+
checks would crash otherwise.
|
|
300
|
+
|
|
276
301
|
#### Pattern B — Run BPM on a separate auth host
|
|
277
302
|
|
|
278
303
|
When BPM lives on its own subdomain (e.g. `bpm.example.com`) separate
|
|
@@ -296,6 +321,125 @@ the simplest path is to **not mount `<AuthProvider>` from
|
|
|
296
321
|
`useAuth()` shim that returns the host's session. This is rare — most
|
|
297
322
|
consumers find Pattern A simpler.
|
|
298
323
|
|
|
324
|
+
### Machine-to-machine authentication
|
|
325
|
+
|
|
326
|
+
Server-side scripts (org seeds, cron workers, integration tests) call
|
|
327
|
+
`configureBPMClient` to set the GraphQL base URL and optional default
|
|
328
|
+
headers, then must establish a session. Three patterns work:
|
|
329
|
+
|
|
330
|
+
#### Pattern 1 — Service member + login flow (recommended)
|
|
331
|
+
|
|
332
|
+
Create a dedicated BPM admin member (e.g. `bpm-sync@svc.example.com`),
|
|
333
|
+
store its credentials in Vault, and let the script call `loginApi`
|
|
334
|
+
during bootstrap. The login response sets an HTTP-only session cookie
|
|
335
|
+
that subsequent `requestGraphQl` calls automatically include.
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { configureBPMClient, loginApi } from '@rytass/bpm-core-client';
|
|
339
|
+
|
|
340
|
+
async function bootstrap(): Promise<void> {
|
|
341
|
+
configureBPMClient({
|
|
342
|
+
baseUrl: process.env.BPM_API_URL ?? 'http://localhost:17603',
|
|
343
|
+
fetch: globalThis.fetch,
|
|
344
|
+
});
|
|
345
|
+
await loginApi({
|
|
346
|
+
identifier: process.env.BPM_SYNC_IDENTIFIER!,
|
|
347
|
+
password: process.env.BPM_SYNC_PASSWORD!,
|
|
348
|
+
});
|
|
349
|
+
// Subsequent calls to readOrganizationDashboard / createOrgUnit / etc.
|
|
350
|
+
// automatically forward the session cookie.
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Caveats: `globalThis.fetch` on Node 20+ does **not** maintain a cookie
|
|
355
|
+
jar across calls by default — set up one of:
|
|
356
|
+
|
|
357
|
+
- `undici`'s `Agent` with `cookies` enabled, then `setGlobalDispatcher(agent)`
|
|
358
|
+
- the `tough-cookie` + `node-fetch-cookies` pairing
|
|
359
|
+
- BPM's `configureBPMClient({ fetch: cookieAwareFetch })` injection
|
|
360
|
+
|
|
361
|
+
Without a cookie jar, the login succeeds but no subsequent call carries
|
|
362
|
+
the session. Most production deployments already use `undici` with
|
|
363
|
+
cookie support; verify before shipping.
|
|
364
|
+
|
|
365
|
+
#### Pattern 2 — Service token header (when supported by host)
|
|
366
|
+
|
|
367
|
+
If your wrapper host issues a long-lived service token (e.g. a JWT
|
|
368
|
+
signed with a known shared secret), pass it through
|
|
369
|
+
`configureBPMClient`'s `headers`:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
configureBPMClient({
|
|
373
|
+
baseUrl: process.env.BPM_API_URL!,
|
|
374
|
+
headers: { Authorization: `Bearer ${process.env.BPM_SERVICE_TOKEN!}` },
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
This requires the wrapper host's auth middleware to honor `Authorization`
|
|
379
|
+
headers (member-base hosts can be configured to do so via
|
|
380
|
+
`authStrategy: 'jwt'` in addition to `cookieMode: true`).
|
|
381
|
+
|
|
382
|
+
#### Pattern 3 — Direct cookie injection (testing only)
|
|
383
|
+
|
|
384
|
+
For integration tests where you've already obtained a session cookie
|
|
385
|
+
out-of-band (e.g. from a Playwright fixture), pass it raw:
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
configureBPMClient({
|
|
389
|
+
baseUrl: 'http://localhost:17603',
|
|
390
|
+
headers: { Cookie: 'access_token=<jwt>; refresh_token=<jwt>' },
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Do not use this in production scripts — cookies expire and the script
|
|
395
|
+
must handle refresh, which means you actually want Pattern 1.
|
|
396
|
+
|
|
397
|
+
### Coexisting with a host's existing `<AuthProvider>`
|
|
398
|
+
|
|
399
|
+
Many host applications (Shuttle, custom admin panels) already mount
|
|
400
|
+
their own `<AuthProvider>` at the root of the Next.js tree. BPM's
|
|
401
|
+
`<AuthProvider>` (mounted indirectly via `<BPMNextProviders>`) is a
|
|
402
|
+
**separate context** that only tracks BPM session state. The two do
|
|
403
|
+
not conflict — they coexist as parallel React contexts:
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
// app/layout.tsx — host's root layout (untouched)
|
|
407
|
+
import { HostAuthProvider } from '@your-host/auth';
|
|
408
|
+
|
|
409
|
+
export default function RootLayout({ children }) {
|
|
410
|
+
return (
|
|
411
|
+
<html><body>
|
|
412
|
+
<HostAuthProvider>{children}</HostAuthProvider>
|
|
413
|
+
</body></html>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// app/operations/approval/layout.tsx — BPM sub-tree
|
|
418
|
+
'use client';
|
|
419
|
+
import { BPMNextProviders } from '@rytass/bpm-core-react/next';
|
|
420
|
+
|
|
421
|
+
export default function ApprovalLayout({ children }) {
|
|
422
|
+
return (
|
|
423
|
+
<BPMNextProviders loginPath="/login">
|
|
424
|
+
{children}
|
|
425
|
+
</BPMNextProviders>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Rules of thumb:
|
|
431
|
+
|
|
432
|
+
- **Scope `<BPMNextProviders>` to the BPM sub-tree** — never put it at
|
|
433
|
+
the root. Otherwise every host page goes through BPM's auth gate,
|
|
434
|
+
which redirects unauthenticated users to BPM's `/login`.
|
|
435
|
+
- **BPM auth and host auth share the cookie jar** when same-origin.
|
|
436
|
+
If `<BPMNextProviders loginPath="/login">` and the host's auth share
|
|
437
|
+
`/login`, the same login form can satisfy both — implement the host
|
|
438
|
+
login endpoint to also satisfy `/auth/login` from BPM's contract.
|
|
439
|
+
- **Do not import `<AuthProvider>` from `@rytass/bpm-core-react`
|
|
440
|
+
directly** if you've already mounted `<BPMNextProviders>` —
|
|
441
|
+
double-wrapping creates redundant `/auth/me` polls.
|
|
442
|
+
|
|
299
443
|
## Role and Permission Contract
|
|
300
444
|
|
|
301
445
|
BPM guards inspect `BPMAuthContext.roles` and `BPMAuthContext.permissions` with
|
|
@@ -412,7 +556,10 @@ export const hostMemberProviders: readonly Provider[] = [
|
|
|
412
556
|
provide: HOST_MEMBER_DIRECTORY,
|
|
413
557
|
useClass: HostMemberDirectory,
|
|
414
558
|
},
|
|
415
|
-
createBPMMemberBaseResolverProvider({
|
|
559
|
+
createBPMMemberBaseResolverProvider<HostMember>({
|
|
560
|
+
// ^^^^^^^^^^^^ explicit generic is recommended — without it,
|
|
561
|
+
// TS infers `unknown` for the adapter callbacks below and you get
|
|
562
|
+
// "Parameter 'member' implicitly has an 'any' type" errors.
|
|
416
563
|
adapterOptions: {
|
|
417
564
|
readEmail: (member): string => member.email,
|
|
418
565
|
readMemberId: (member): string => member.id,
|
|
@@ -544,7 +691,7 @@ interface HostOrgUnit {
|
|
|
544
691
|
readonly hostId: string; // e.g. ERP primary key — your host-FK
|
|
545
692
|
readonly code: string; // stable across sync runs
|
|
546
693
|
readonly name: string;
|
|
547
|
-
readonly type: OrgUnitType; // '
|
|
694
|
+
readonly type: OrgUnitType; // 'COMPANY' | 'DIVISION' | 'DEPARTMENT' | 'TEAM'
|
|
548
695
|
readonly parentCode: string | null;
|
|
549
696
|
}
|
|
550
697
|
|
|
@@ -791,6 +938,40 @@ BPM's `buildTypeOrmModuleOptions` create **two distinct connections**
|
|
|
791
938
|
to the same Postgres cluster with different `schema:` settings — that
|
|
792
939
|
is the cleanest separation Nest's TypeORM module supports.
|
|
793
940
|
|
|
941
|
+
Concrete wiring:
|
|
942
|
+
|
|
943
|
+
```ts
|
|
944
|
+
// apps/api/src/app/app.module.ts
|
|
945
|
+
@Module({
|
|
946
|
+
imports: [
|
|
947
|
+
// Host's existing TypeORM connection (default name, host's schema).
|
|
948
|
+
TypeOrmModule.forRootAsync({
|
|
949
|
+
imports: [HostVaultModule],
|
|
950
|
+
inject: [HostVaultService],
|
|
951
|
+
useFactory: (vault) => buildHostDataSourceOptions(vault), // schema: 'public'
|
|
952
|
+
}),
|
|
953
|
+
// BPM's own connection (named, separate schema, separate Vault path).
|
|
954
|
+
TypeOrmModule.forRootAsync({
|
|
955
|
+
name: 'bpm', // <-- distinct connection name
|
|
956
|
+
imports: [BPMVaultModule],
|
|
957
|
+
inject: [BPMVaultService],
|
|
958
|
+
useFactory: buildTypeOrmModuleOptions, // reads DB_SCHEMA=bpm_core from Vault
|
|
959
|
+
}),
|
|
960
|
+
// Hand BPM the connection name so its repositories bind to the right pool.
|
|
961
|
+
BPMRootModule.forRootAsync({
|
|
962
|
+
typeormConnectionName: 'bpm',
|
|
963
|
+
// ...
|
|
964
|
+
}),
|
|
965
|
+
],
|
|
966
|
+
})
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
If the host runs only one Vault path (e.g. `shuttle/develop`) and you
|
|
970
|
+
want to keep secrets there, expose `bpm_core/*` keys under the host
|
|
971
|
+
path with a prefix and provide a custom factory that reads them — BPM
|
|
972
|
+
doesn't require its own Vault path, only that the secrets are
|
|
973
|
+
available to `buildTypeOrmModuleOptions`.
|
|
974
|
+
|
|
794
975
|
Vault paths can be split independently: keep `shuttle/develop` for the
|
|
795
976
|
host and create `bpm_core/develop` for BPM secrets. BPM's helper reads
|
|
796
977
|
its own path; the host's auth module reads its own. They do not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rytass/bpm-core-nestjs-module",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Embeddable NestJS BPM approval workflow module: workflow engine, approval templates, forms, organization/member contracts, attachments, signatures, notifications, delegation, and TypeORM migrations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bpm",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"@nestjs/core": "^11.0.0",
|
|
142
142
|
"@nestjs/graphql": "^13.4.0",
|
|
143
143
|
"@nestjs/typeorm": "^11.0.1",
|
|
144
|
-
"@rytass/bpm-core-shared": "^0.1.
|
|
144
|
+
"@rytass/bpm-core-shared": "^0.1.10",
|
|
145
145
|
"@rytass/secret-adapter-vault-nestjs": "^0.4.5 || ^0.5.0",
|
|
146
146
|
"express": "^4.0.0 || ^5.0.0",
|
|
147
147
|
"graphql": "^16.0.0",
|