@appxdigital/appx-core 0.1.120 → 0.1.121-beta.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 +123 -0
- package/LICENSE +21 -21
- package/PRERELEASES.md +142 -0
- package/README.md +627 -533
- package/RELEASING.md +168 -0
- package/dist/common/config/permissionsConfigTypes.d.ts +8 -0
- package/dist/common/config/permissionsConfigTypes.d.ts.map +1 -1
- package/dist/common/config/permissionsConfigTypes.js +13 -1
- package/dist/common/decorators/permission.decorator.d.ts +14 -0
- package/dist/common/decorators/permission.decorator.d.ts.map +1 -1
- package/dist/common/decorators/permission.decorator.js +16 -1
- package/dist/common/guards/rbac.guard.d.ts.map +1 -1
- package/dist/common/guards/rbac.guard.js +8 -1
- package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
- package/dist/common/interceptors/prisma.interceptor.js +24 -8
- package/dist/common/setup/core-config.d.ts +66 -0
- package/dist/common/setup/core-config.d.ts.map +1 -0
- package/dist/common/setup/core-config.js +67 -0
- package/dist/common/setup/setup-core-security.d.ts +62 -0
- package/dist/common/setup/setup-core-security.d.ts.map +1 -0
- package/dist/common/setup/setup-core-security.js +40 -0
- package/dist/config/generate-all.js +4 -0
- package/dist/config/generate-controllers.js +19 -2
- package/dist/config/generate-dtos.d.ts +2 -0
- package/dist/config/generate-dtos.d.ts.map +1 -0
- package/dist/config/generate-dtos.js +285 -0
- package/dist/config/utils.d.ts +14 -0
- package/dist/config/utils.d.ts.map +1 -1
- package/dist/config/utils.js +35 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/modules/auth/auth.controller.d.ts.map +1 -1
- package/dist/modules/auth/auth.controller.js +6 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/core/core.controller.d.ts.map +1 -1
- package/dist/modules/core/core.controller.js +12 -5
- package/dist/modules/user/user.service.d.ts.map +1 -1
- package/dist/modules/user/user.service.js +7 -3
- package/dist/prisma/prisma.service.d.ts +94 -2
- package/dist/prisma/prisma.service.d.ts.map +1 -1
- package/dist/prisma/prisma.service.js +338 -8
- package/docs/README.md +21 -0
- package/docs/data-access.md +108 -0
- package/docs/dtos.md +147 -0
- package/docs/graphql.md +72 -0
- package/docs/limitations.md +55 -0
- package/docs/permissions.md +155 -0
- package/package.json +112 -86
package/RELEASING.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
This repo publishes **two independent packages**, each versioned on its own:
|
|
4
|
+
|
|
5
|
+
- **`@appxdigital/appx-core`** — the runtime library (root `package.json`),
|
|
6
|
+
released via **`v*`** tags (`release.yml`).
|
|
7
|
+
- **`@appxdigital/appx-core-cli`** — the `appx-core` CLI (`cli/package.json`),
|
|
8
|
+
released via **`cli-v*`** tags (`release-cli.yml`).
|
|
9
|
+
|
|
10
|
+
Both follow the same posture: publishing is deliberate — **it only happens when
|
|
11
|
+
you push a git tag** — and production (`latest`) pauses for a human approval
|
|
12
|
+
before it goes live.
|
|
13
|
+
|
|
14
|
+
## Channels
|
|
15
|
+
|
|
16
|
+
Library (`v*` tags):
|
|
17
|
+
|
|
18
|
+
| You push tag | npm dist-tag | Consumers install with | Gated? |
|
|
19
|
+
| ------------------ | ------------ | --------------------------------- | --------------- |
|
|
20
|
+
| `v1.2.3-alpha.N` | `alpha` | `…/appx-core@alpha` | no |
|
|
21
|
+
| `v1.2.3-beta.N` | `beta` | `…/appx-core@beta` | no |
|
|
22
|
+
| `v1.2.3-rc.N` | `next` | `…/appx-core@next` | no |
|
|
23
|
+
| `v1.2.3` | `latest` | `…/appx-core` (default) | **yes** — approval |
|
|
24
|
+
|
|
25
|
+
CLI (`cli-v*` tags) — identical channels for `@appxdigital/appx-core-cli`:
|
|
26
|
+
|
|
27
|
+
| You push tag | npm dist-tag | Consumers install with | Gated? |
|
|
28
|
+
| -------------------- | ------------ | ------------------------------- | --------------- |
|
|
29
|
+
| `cli-v1.2.3-beta.N` | `beta` | `…/appx-core-cli@beta` | no |
|
|
30
|
+
| `cli-v1.2.3` | `latest` | `…/appx-core-cli` (default) | **yes** — approval |
|
|
31
|
+
|
|
32
|
+
Prereleases are excluded from `^1.2.x` range resolution, so a project on a caret
|
|
33
|
+
range never pulls one by accident — testers opt in via the tag or an exact
|
|
34
|
+
version. Merging to `main` **never** publishes; it only runs CI (library build +
|
|
35
|
+
tests on mysql and postgres, plus a CLI smoke check).
|
|
36
|
+
|
|
37
|
+
## Two files, two audiences
|
|
38
|
+
|
|
39
|
+
- **`PRERELEASES.md`** — granular, one entry per alpha/beta build. What a tester
|
|
40
|
+
reads.
|
|
41
|
+
- **`CHANGELOG.md`** — collapsed, one section per production release with
|
|
42
|
+
aggregated changes and consolidated migration steps. What an upgrading project
|
|
43
|
+
reads. When you promote a version, distil its prerelease entries into a single
|
|
44
|
+
`CHANGELOG.md` section.
|
|
45
|
+
|
|
46
|
+
## One-time setup (before the first publish)
|
|
47
|
+
|
|
48
|
+
1. **npm Trusted Publisher (OIDC) — one per package.** On npmjs.com → the
|
|
49
|
+
package → *Settings* → *Trusted Publisher* → GitHub Actions. Add **two**,
|
|
50
|
+
both owner `appxdigital`, repo `appx-core`:
|
|
51
|
+
- `@appxdigital/appx-core` → workflow `release.yml`
|
|
52
|
+
- `@appxdigital/appx-core-cli` → workflow `release-cli.yml`
|
|
53
|
+
|
|
54
|
+
This lets CI mint a short-lived publish token per run — **no `NPM_TOKEN`
|
|
55
|
+
secret is stored anywhere**, and every publish carries supply-chain
|
|
56
|
+
provenance.
|
|
57
|
+
|
|
58
|
+
2. **GitHub `production` environment (the approval gate).** Repo → *Settings* →
|
|
59
|
+
*Environments* → *New environment* → name it exactly `production` → enable
|
|
60
|
+
*Required reviewers* and add yourself (and/or the team). The
|
|
61
|
+
`publish-production` job targets this environment, so a clean tag will wait
|
|
62
|
+
for a click before publishing to `latest`.
|
|
63
|
+
|
|
64
|
+
## Cutting a release
|
|
65
|
+
|
|
66
|
+
The tag must match `package.json`'s version exactly, or the release job fails
|
|
67
|
+
fast (a guard against mistagging). Convenience scripts keep them in lockstep and
|
|
68
|
+
create the tag for you — they **do not push**, so pushing stays a deliberate
|
|
69
|
+
step:
|
|
70
|
+
|
|
71
|
+
There are two per package — a **test build** and a **production release**:
|
|
72
|
+
|
|
73
|
+
| Script | What it does | Tag | Goes to |
|
|
74
|
+
| ------------------------------- | ------------------------------------- | --------------- | ----------------- |
|
|
75
|
+
| `npm run release:beta` | library test build (`…-beta.N`) | `v…-beta.N` | `beta` channel |
|
|
76
|
+
| `npm run release:production` | promote library to everyone | `vX.Y.Z` | `latest` (gated) |
|
|
77
|
+
| `npm run release:cli:beta` | CLI test build (`…-beta.N`) | `cli-v…-beta.N` | `beta` channel |
|
|
78
|
+
| `npm run release:cli:production`| promote CLI to everyone | `cli-vX.Y.Z` | `latest` (gated) |
|
|
79
|
+
|
|
80
|
+
`release:beta` publishes only to people who opt in with `@beta`; `release:production`
|
|
81
|
+
is what everyone gets by default, and it pauses for your approval before going
|
|
82
|
+
live. Each command bumps the version, commits, and creates the tag, then prints
|
|
83
|
+
the exact `git push origin HEAD --follow-tags` for you to run when ready — they
|
|
84
|
+
never push on their own.
|
|
85
|
+
|
|
86
|
+
Rarer cases go through the underlying script directly:
|
|
87
|
+
`node scripts/release.mjs lib alpha` (alpha channel), `… lib minor` (a feature
|
|
88
|
+
bump), or `… lib X.Y.Z-beta.0` (an explicit version — e.g. the first beta of a
|
|
89
|
+
version already sitting in `package.json`).
|
|
90
|
+
|
|
91
|
+
The manual steps below show what those scripts do under the hood.
|
|
92
|
+
|
|
93
|
+
### Beta (test in projects first)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# set the version + create the matching tag + commit, without auto-pushing
|
|
97
|
+
npm version 0.1.121-beta.0 --message "release: %s"
|
|
98
|
+
git push origin main --follow-tags
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `Release` workflow builds, tests, and publishes to the `beta` dist-tag.
|
|
102
|
+
Then, in a project you want to test against:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm install @appxdigital/appx-core@beta
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Iterate with `npm version prerelease --preid beta` (→ `-beta.1`, `-beta.2`, …),
|
|
109
|
+
each push publishing a new granular `PRERELEASES.md` entry.
|
|
110
|
+
|
|
111
|
+
### Promote to production (`latest`)
|
|
112
|
+
|
|
113
|
+
When the beta is validated in real projects:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# drop the prerelease suffix -> clean version
|
|
117
|
+
npm version 0.1.121 --message "release: %s"
|
|
118
|
+
git push origin main --follow-tags
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The workflow builds and tests, then **pauses** on the `production` environment.
|
|
122
|
+
Approve it in the repo's *Actions* tab → the running release → *Review
|
|
123
|
+
deployments* → *Approve and deploy*. It then publishes to `latest`.
|
|
124
|
+
|
|
125
|
+
> Alternatively, promotion can be a pure retag without republishing:
|
|
126
|
+
> `npm dist-tag add @appxdigital/appx-core@0.1.121-beta.N latest`. The
|
|
127
|
+
> tag-driven workflow above is preferred because it re-runs tests and records
|
|
128
|
+
> provenance for the exact `latest` artifact.
|
|
129
|
+
|
|
130
|
+
### Alpha (rough, one or two projects)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm version 0.1.122-alpha.0 --message "release: %s"
|
|
134
|
+
git push origin main --follow-tags
|
|
135
|
+
# in the project: npm install @appxdigital/appx-core@alpha
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### CLI (`@appxdigital/appx-core-cli`)
|
|
139
|
+
|
|
140
|
+
Same flow, but bump `cli/package.json` and use the `cli-v` tag prefix. The tag
|
|
141
|
+
must match `cli/package.json`'s version.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# beta:
|
|
145
|
+
npm --prefix cli version 1.0.21-beta.0 --message "release cli: %s"
|
|
146
|
+
git commit -am "release cli: 1.0.21-beta.0" # npm version --prefix does not commit
|
|
147
|
+
git tag cli-v1.0.21-beta.0
|
|
148
|
+
git push origin main --follow-tags
|
|
149
|
+
# test: npx @appxdigital/appx-core-cli@beta create …
|
|
150
|
+
|
|
151
|
+
# production (gated — approve in the Actions tab):
|
|
152
|
+
npm --prefix cli version 1.0.21 --message "release cli: %s"
|
|
153
|
+
git commit -am "release cli: 1.0.21"
|
|
154
|
+
git tag cli-v1.0.21
|
|
155
|
+
git push origin main --follow-tags
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
> `npm version --prefix cli` bumps `cli/package.json` but does not create the
|
|
159
|
+
> git commit/tag (that only happens when run in the repo root). Commit and tag
|
|
160
|
+
> explicitly with the `cli-v` prefix as shown.
|
|
161
|
+
|
|
162
|
+
## Manual fallback
|
|
163
|
+
|
|
164
|
+
If CI is unavailable, you can still publish by hand: `rimraf dist && npm run
|
|
165
|
+
build && npm publish --tag <dist-tag> --access public` for the library, and
|
|
166
|
+
`cd cli && npm publish --tag <dist-tag> --access public` for the CLI. This
|
|
167
|
+
bypasses the tests, the approval gate, and provenance, so prefer the tag-driven
|
|
168
|
+
flow.
|
|
@@ -3,6 +3,14 @@ import { Operation } from '@prisma/client/runtime/library';
|
|
|
3
3
|
export declare const PermissionPlaceholder: {
|
|
4
4
|
USER_ID: any;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Batch (`*Many`) actions inherit the singular rule when not declared
|
|
8
|
+
* explicitly, so a config can define just `create` / `update` / `delete`.
|
|
9
|
+
* One-directional: the singular is canonical (declaring only the `*Many`
|
|
10
|
+
* variant does not enable the singular). Consumed by the Prisma proxy
|
|
11
|
+
* (`selectPermission`) and the `RbacGuard`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SINGULAR_ACTION: Record<string, string>;
|
|
6
14
|
/** Detect delegate keys on an arbitrary Prisma client type C */
|
|
7
15
|
type DelegateKeyOf<C> = {
|
|
8
16
|
[K in keyof C]: C[K] extends {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissionsConfigTypes.d.ts","sourceRoot":"","sources":["../../../src/common/config/permissionsConfigTypes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,MAAM,EAAE,YAAY,IAAI,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAC,SAAS,EAAC,MAAM,gCAAgC,CAAC;AAEzD,eAAO,MAAM,qBAAqB,EAAE;IAChC,OAAO,EAAE,GAAG,CAAC;CAGhB,CAAC;AAEF,gEAAgE;AAChE,KAAK,aAAa,CAAC,CAAC,IAAI;KACnB,CAAC,IAAI,MAAM,CAAC,GACb,CAAC,CAAC,CAAC,CAAC,SAAS;QAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAA;KAAC,GAAG,CAAC,GAAG,KAAK;CAC/D,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC;AAEpB,mDAAmD;AACnD,KAAK,iBAAiB,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC;AAE7C,8DAA8D;AAC9D,KAAK,QAAQ,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,IACjD,iBAAiB,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5D,4EAA4E;AAC5E,KAAK,cAAc,CACf,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IAErB,CAAC,SAAS,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,GAC5B,CAAC,SAAS,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAC/E,KAAK,CAAC;AAEpB,oDAAoD;AACpD,KAAK,WAAW,CACZ,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IACrB,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE5B,0CAA0C;AAC1C,KAAK,UAAU,CACX,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IACrB;KACC,CAAC,IAAI,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,CAAC,GAAG,KAAK;CACnE,CAAC,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAEpC,iEAAiE;AACjE,KAAK,aAAa,CACd,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IACrB,OAAO,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAEzC,oFAAoF;AACpF,KAAK,MAAM,CACP,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACrB,CAAC,SAAS,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,IAC7B,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEtC,4DAA4D;AAC5D,KAAK,OAAO,CACR,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACrB,CAAC,SAAS,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,IAE7B,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,SAAS;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAA;CAAC,GAAG,CAAC,GACzC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,SAAS;IAAC,KAAK,EAAE,MAAM,CAAC,CAAA;CAAC,GAAG,CAAC,GACxC,KAAK,CAAC;AAElB,0DAA0D;AAC1D,KAAK,gBAAgB,CACjB,CAAC,SAAS,aAAa,EACvB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IACrB;KACC,CAAC,IAAI,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,GAAG,CAAC;CAC7C,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAEhC,6DAA6D;AAE7D,MAAM,MAAM,eAAe,CACvB,CAAC,SAAS,QAAQ,CAAC,aAAa,CAAC,EACjC,CAAC,SAAS,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,IAC5C,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAEjC,MAAM,WAAW,gBAAgB,CAC7B,CAAC,SAAS,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,EAC3D,CAAC,SAAS,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,GAAG,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC;IAEjF,UAAU,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,eAAe,CACvB,CAAC,SAAS,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,IAE3D;KACK,CAAC,IAAI,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC;CAC7E,GAAG;IACJ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG;QACtB,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;QACjC,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC/B,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,gBAAgB,CACxB,CAAC,SAAS,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,IAC3D;IACA,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAC7B,CAAC,SAAS,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,IAC3D;KACC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC;CACzC,CAAC"}
|
|
1
|
+
{"version":3,"file":"permissionsConfigTypes.d.ts","sourceRoot":"","sources":["../../../src/common/config/permissionsConfigTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PermissionPlaceholder = void 0;
|
|
3
|
+
exports.SINGULAR_ACTION = exports.PermissionPlaceholder = void 0;
|
|
4
4
|
exports.PermissionPlaceholder = {
|
|
5
5
|
USER_ID: '$USER_ID',
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Batch (`*Many`) actions inherit the singular rule when not declared
|
|
9
|
+
* explicitly, so a config can define just `create` / `update` / `delete`.
|
|
10
|
+
* One-directional: the singular is canonical (declaring only the `*Many`
|
|
11
|
+
* variant does not enable the singular). Consumed by the Prisma proxy
|
|
12
|
+
* (`selectPermission`) and the `RbacGuard`.
|
|
13
|
+
*/
|
|
14
|
+
exports.SINGULAR_ACTION = {
|
|
15
|
+
createMany: 'create',
|
|
16
|
+
updateMany: 'update',
|
|
17
|
+
deleteMany: 'delete',
|
|
18
|
+
};
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
export declare const PERMISSION_METADATA_KEY = "permission";
|
|
2
|
+
export declare const EXPOSE_MODELS_METADATA_KEY = "expose_models";
|
|
2
3
|
export declare const Permission: (action: string, expose_models?: string[]) => import("@nestjs/common").CustomDecorator<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Expose one or more models for the duration of the request — WITHOUT requiring
|
|
6
|
+
* a permission action. Unlike `@Permission(action, models)`, this sets no
|
|
7
|
+
* action, so `RbacGuard` does not demand a role permission for the route. Use it
|
|
8
|
+
* for a public / GUEST endpoint that must read a model the caller isn't
|
|
9
|
+
* otherwise granted (e.g. an email-availability check) without having to add a
|
|
10
|
+
* `GUEST` rule for that model. Exposure is scoped to the request: the listed
|
|
11
|
+
* models skip row / field filtering only inside this handler.
|
|
12
|
+
*
|
|
13
|
+
* Combine with `@Permission(...)` if you also want an action check; both
|
|
14
|
+
* sources of exposed models are merged.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ExposeModels: (...models: string[]) => import("@nestjs/common").CustomDecorator<string>;
|
|
3
17
|
//# sourceMappingURL=permission.decorator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission.decorator.d.ts","sourceRoot":"","sources":["../../../src/common/decorators/permission.decorator.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"permission.decorator.d.ts","sourceRoot":"","sources":["../../../src/common/decorators/permission.decorator.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,eAAe,CAAC;AACpD,eAAO,MAAM,0BAA0B,kBAAkB,CAAC;AAE1D,eAAO,MAAM,UAAU,WAAY,MAAM,kBAAiB,MAAM,EAAE,qDACD,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,cAAe,MAAM,EAAE,qDACG,CAAC"}
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Permission = exports.PERMISSION_METADATA_KEY = void 0;
|
|
3
|
+
exports.ExposeModels = exports.Permission = exports.EXPOSE_MODELS_METADATA_KEY = exports.PERMISSION_METADATA_KEY = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
exports.PERMISSION_METADATA_KEY = 'permission';
|
|
6
|
+
exports.EXPOSE_MODELS_METADATA_KEY = 'expose_models';
|
|
6
7
|
const Permission = (action, expose_models = []) => (0, common_1.SetMetadata)(exports.PERMISSION_METADATA_KEY, { action, expose_models });
|
|
7
8
|
exports.Permission = Permission;
|
|
9
|
+
/**
|
|
10
|
+
* Expose one or more models for the duration of the request — WITHOUT requiring
|
|
11
|
+
* a permission action. Unlike `@Permission(action, models)`, this sets no
|
|
12
|
+
* action, so `RbacGuard` does not demand a role permission for the route. Use it
|
|
13
|
+
* for a public / GUEST endpoint that must read a model the caller isn't
|
|
14
|
+
* otherwise granted (e.g. an email-availability check) without having to add a
|
|
15
|
+
* `GUEST` rule for that model. Exposure is scoped to the request: the listed
|
|
16
|
+
* models skip row / field filtering only inside this handler.
|
|
17
|
+
*
|
|
18
|
+
* Combine with `@Permission(...)` if you also want an action check; both
|
|
19
|
+
* sources of exposed models are merged.
|
|
20
|
+
*/
|
|
21
|
+
const ExposeModels = (...models) => (0, common_1.SetMetadata)(exports.EXPOSE_MODELS_METADATA_KEY, models);
|
|
22
|
+
exports.ExposeModels = ExposeModels;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rbac.guard.d.ts","sourceRoot":"","sources":["../../../src/common/guards/rbac.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EACX,gBAAgB,EAInB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"rbac.guard.d.ts","sourceRoot":"","sources":["../../../src/common/guards/rbac.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EACX,gBAAgB,EAInB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAC,qBAAqB,EAAmC,MAAM,kCAAkC,CAAC;AAEzG,qBACa,SAAU,YAAW,WAAW;IAErC,OAAO,CAAC,SAAS;IACiB,OAAO,CAAC,iBAAiB;gBADnD,SAAS,EAAE,SAAS,EACc,iBAAiB,EAAE,qBAAqB;IAGhF,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CA0CjE"}
|
|
@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
|
|
|
17
17
|
const core_1 = require("@nestjs/core");
|
|
18
18
|
const permission_decorator_1 = require("../decorators/permission.decorator");
|
|
19
19
|
const permissions_config_provider_1 = require("../config/permissions.config.provider");
|
|
20
|
+
const permissionsConfigTypes_1 = require("../config/permissionsConfigTypes");
|
|
20
21
|
let RbacGuard = exports.RbacGuard = class RbacGuard {
|
|
21
22
|
constructor(reflector, permissionsConfig) {
|
|
22
23
|
this.reflector = reflector;
|
|
@@ -38,7 +39,13 @@ let RbacGuard = exports.RbacGuard = class RbacGuard {
|
|
|
38
39
|
if (!rolePermissions) {
|
|
39
40
|
throw new common_1.ForbiddenException(`No permissions defined for role ${user.role} on model ${model}`);
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
// A `*Many` action inherits its singular rule when not defined explicitly
|
|
43
|
+
// (createMany → create, updateMany → update, deleteMany → delete) — mirrors
|
|
44
|
+
// the data-access proxy, so a config can declare just the singular form.
|
|
45
|
+
let permission = rolePermissions[action];
|
|
46
|
+
if (!permission && permissionsConfigTypes_1.SINGULAR_ACTION[action]) {
|
|
47
|
+
permission = rolePermissions[permissionsConfigTypes_1.SINGULAR_ACTION[action]];
|
|
48
|
+
}
|
|
42
49
|
if (!permission) {
|
|
43
50
|
throw new common_1.ForbiddenException(`Action ${action} not allowed for role ${user.role} on model ${model}`);
|
|
44
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAoB,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAK5C,qBACa,iBAAkB,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IALzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;gBAG1B,aAAa,EAAE,aAAa,EACrC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa;IAKxC,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAoB,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAK5C,qBACa,iBAAkB,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IALzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;gBAG1B,aAAa,EAAE,aAAa,EACrC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa;IAKxC,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC;CAmF3E"}
|
|
@@ -31,39 +31,55 @@ let PrismaInterceptor = exports.PrismaInterceptor = class PrismaInterceptor {
|
|
|
31
31
|
const req = handlerType === 'graphql'
|
|
32
32
|
? context.getArgByIndex(2) // GraphQL context
|
|
33
33
|
: context.switchToHttp().getRequest(); // HTTP context
|
|
34
|
-
// Attach expose_models metadata if needed
|
|
34
|
+
// Attach expose_models metadata if needed. Models can come from
|
|
35
|
+
// @Permission(action, models) or the standalone @ExposeModels(...); both
|
|
36
|
+
// are merged so a route can expose models with or without a permission.
|
|
35
37
|
const permissionMetadata = this.reflector.get(permission_decorator_1.PERMISSION_METADATA_KEY, context.getHandler()) || {};
|
|
38
|
+
const standaloneExposed = this.reflector.get(permission_decorator_1.EXPOSE_MODELS_METADATA_KEY, context.getHandler()) || [];
|
|
39
|
+
const exposedModels = [...(permissionMetadata['expose_models'] || []), ...standaloneExposed];
|
|
36
40
|
return new rxjs_1.Observable((observer) => {
|
|
37
41
|
prisma_service_1.CorePrismaContext.run({
|
|
38
|
-
exposedModels
|
|
42
|
+
exposedModels,
|
|
39
43
|
}, () => {
|
|
40
44
|
const useTransaction = this.reflector.get('useTransaction', context.getHandler()) ?? this.defaultUseTransaction === 'true';
|
|
41
45
|
if (useTransaction) {
|
|
46
|
+
// Run the handler inside the transaction and CAPTURE its result,
|
|
47
|
+
// but do not emit the response yet: the client must not see a
|
|
48
|
+
// success response until the transaction has actually committed.
|
|
49
|
+
// Emitting inside the transaction (before it commits) races the
|
|
50
|
+
// commit — an immediate read-after-write can miss the row.
|
|
51
|
+
let handlerResult;
|
|
42
52
|
this.prismaService
|
|
43
53
|
.$transaction(async (transactionClient) => {
|
|
44
54
|
nestjs_request_context_1.RequestContext.currentContext.req.prisma = transactionClient;
|
|
45
|
-
await new Promise((resolve, reject) => {
|
|
55
|
+
handlerResult = await new Promise((resolve, reject) => {
|
|
56
|
+
let captured;
|
|
46
57
|
next
|
|
47
58
|
.handle()
|
|
48
|
-
.pipe((0, operators_1.
|
|
59
|
+
.pipe((0, operators_1.catchError)((err) => {
|
|
49
60
|
const handledError = (0, error_handler_1.handleError)(err);
|
|
50
61
|
reject(handledError);
|
|
51
62
|
return (0, rxjs_1.throwError)(() => handledError);
|
|
52
63
|
}))
|
|
53
64
|
.subscribe({
|
|
54
|
-
next: (result) =>
|
|
65
|
+
next: (result) => {
|
|
66
|
+
captured = result;
|
|
67
|
+
},
|
|
55
68
|
complete: () => {
|
|
56
69
|
delete nestjs_request_context_1.RequestContext.currentContext.req.prisma;
|
|
57
|
-
|
|
58
|
-
resolve(null);
|
|
70
|
+
resolve(captured);
|
|
59
71
|
},
|
|
60
72
|
error: (err) => {
|
|
61
73
|
delete nestjs_request_context_1.RequestContext.currentContext.req.prisma;
|
|
62
|
-
observer.error(err);
|
|
63
74
|
reject(err);
|
|
64
75
|
},
|
|
65
76
|
});
|
|
66
77
|
});
|
|
78
|
+
})
|
|
79
|
+
.then(() => {
|
|
80
|
+
// The transaction has committed — now it is safe to respond.
|
|
81
|
+
observer.next(handlerResult);
|
|
82
|
+
observer.complete();
|
|
67
83
|
})
|
|
68
84
|
.catch((err) => {
|
|
69
85
|
delete nestjs_request_context_1.RequestContext.currentContext.req.prisma;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-time configuration helpers used by the scaffold's `main.ts` /
|
|
3
|
+
* `app.module.ts`. They fail fast on insecure or ambiguous configuration
|
|
4
|
+
* instead of silently falling back.
|
|
5
|
+
*/
|
|
6
|
+
/** Minimum length we require of a session signing secret. */
|
|
7
|
+
export declare const MIN_SESSION_SECRET_LENGTH = 32;
|
|
8
|
+
export interface CoreSessionOptionsInput {
|
|
9
|
+
/** The session signing secret. Required — no fallback. */
|
|
10
|
+
secret?: string;
|
|
11
|
+
/** The express-session store (e.g. `CorePrismaSessionStore`). */
|
|
12
|
+
store: any;
|
|
13
|
+
/** Cookie name. */
|
|
14
|
+
cookieName?: string;
|
|
15
|
+
/** Session TTL in seconds (drives cookie `maxAge`). Default 86400. */
|
|
16
|
+
ttlSeconds?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Force the `Secure` cookie flag. Defaults to `true` in production
|
|
19
|
+
* (`NODE_ENV === 'production'`), `false` otherwise so local HTTP dev works.
|
|
20
|
+
*/
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
/** `SameSite` policy. Default `'lax'`. */
|
|
23
|
+
sameSite?: boolean | 'lax' | 'strict' | 'none';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds hardened `express-session` options.
|
|
27
|
+
*
|
|
28
|
+
* - **:** throws if `secret` is missing or shorter than
|
|
29
|
+
* {@link MIN_SESSION_SECRET_LENGTH}. There is no hardcoded fallback — a
|
|
30
|
+
* missing secret must stop the boot, never silently sign sessions with a
|
|
31
|
+
* public constant.
|
|
32
|
+
* - **:** sets `cookie: { httpOnly, secure, sameSite, maxAge }` so session
|
|
33
|
+
* cookies are not readable by JS, are `Secure` in production, and carry a
|
|
34
|
+
* `SameSite` policy.
|
|
35
|
+
*
|
|
36
|
+
* Pass the result straight to `session(...)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildCoreSessionOptions(input: CoreSessionOptionsInput): {
|
|
39
|
+
secret: string;
|
|
40
|
+
resave: boolean;
|
|
41
|
+
saveUninitialized: boolean;
|
|
42
|
+
name: string | undefined;
|
|
43
|
+
store: any;
|
|
44
|
+
cookie: {
|
|
45
|
+
httpOnly: boolean;
|
|
46
|
+
secure: boolean;
|
|
47
|
+
sameSite: boolean | "none" | "lax" | "strict";
|
|
48
|
+
maxAge: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the env file paths for `ConfigModule.forRoot({ envFilePath })`.
|
|
53
|
+
*
|
|
54
|
+
* Returns `['.env.<NODE_ENV>', '.env']`. `ConfigModule` gives the **first**
|
|
55
|
+
* existing file precedence, so environment-specific values in `.env.<NODE_ENV>`
|
|
56
|
+
* win, and a plain `.env` (what `appx-core create` generates) is loaded as a
|
|
57
|
+
* base/fallback. Without the `.env` fallback a freshly scaffolded project —
|
|
58
|
+
* which ships only `.env` — would load nothing and then fail the * `SESSION_SECRET` check at boot.
|
|
59
|
+
*
|
|
60
|
+
* **:** throws if `NODE_ENV` is unset instead of silently defaulting to
|
|
61
|
+
* `development` — a production deploy that forgets `NODE_ENV` must fail fast
|
|
62
|
+
* rather than load `.env.development` (dev DB creds, loose CORS, insecure
|
|
63
|
+
* cookies). Set `NODE_ENV` explicitly (`production` / `development` / `test`).
|
|
64
|
+
*/
|
|
65
|
+
export declare function coreEnvFilePath(): string[];
|
|
66
|
+
//# sourceMappingURL=core-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-config.d.ts","sourceRoot":"","sources":["../../../src/common/setup/core-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACpC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,EAAE,GAAG,CAAC;IACX,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAClD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB;;;;;;;;;;;;EAuBrE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAS1C"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Boot-time configuration helpers used by the scaffold's `main.ts` /
|
|
4
|
+
* `app.module.ts`. They fail fast on insecure or ambiguous configuration
|
|
5
|
+
* instead of silently falling back.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.coreEnvFilePath = exports.buildCoreSessionOptions = exports.MIN_SESSION_SECRET_LENGTH = void 0;
|
|
9
|
+
/** Minimum length we require of a session signing secret. */
|
|
10
|
+
exports.MIN_SESSION_SECRET_LENGTH = 32;
|
|
11
|
+
/**
|
|
12
|
+
* Builds hardened `express-session` options.
|
|
13
|
+
*
|
|
14
|
+
* - **:** throws if `secret` is missing or shorter than
|
|
15
|
+
* {@link MIN_SESSION_SECRET_LENGTH}. There is no hardcoded fallback — a
|
|
16
|
+
* missing secret must stop the boot, never silently sign sessions with a
|
|
17
|
+
* public constant.
|
|
18
|
+
* - **:** sets `cookie: { httpOnly, secure, sameSite, maxAge }` so session
|
|
19
|
+
* cookies are not readable by JS, are `Secure` in production, and carry a
|
|
20
|
+
* `SameSite` policy.
|
|
21
|
+
*
|
|
22
|
+
* Pass the result straight to `session(...)`.
|
|
23
|
+
*/
|
|
24
|
+
function buildCoreSessionOptions(input) {
|
|
25
|
+
const { secret, store, cookieName, ttlSeconds = 86400, secure, sameSite = 'lax' } = input;
|
|
26
|
+
if (!secret || secret.length < exports.MIN_SESSION_SECRET_LENGTH) {
|
|
27
|
+
throw new Error(`SESSION_SECRET must be set and at least ${exports.MIN_SESSION_SECRET_LENGTH} characters. ` +
|
|
28
|
+
'Refusing to start with a missing or weak session secret.');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
secret,
|
|
32
|
+
resave: false,
|
|
33
|
+
saveUninitialized: false,
|
|
34
|
+
name: cookieName,
|
|
35
|
+
store,
|
|
36
|
+
cookie: {
|
|
37
|
+
httpOnly: true,
|
|
38
|
+
secure: secure ?? process.env.NODE_ENV === 'production',
|
|
39
|
+
sameSite,
|
|
40
|
+
maxAge: ttlSeconds * 1000,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.buildCoreSessionOptions = buildCoreSessionOptions;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves the env file paths for `ConfigModule.forRoot({ envFilePath })`.
|
|
47
|
+
*
|
|
48
|
+
* Returns `['.env.<NODE_ENV>', '.env']`. `ConfigModule` gives the **first**
|
|
49
|
+
* existing file precedence, so environment-specific values in `.env.<NODE_ENV>`
|
|
50
|
+
* win, and a plain `.env` (what `appx-core create` generates) is loaded as a
|
|
51
|
+
* base/fallback. Without the `.env` fallback a freshly scaffolded project —
|
|
52
|
+
* which ships only `.env` — would load nothing and then fail the * `SESSION_SECRET` check at boot.
|
|
53
|
+
*
|
|
54
|
+
* **:** throws if `NODE_ENV` is unset instead of silently defaulting to
|
|
55
|
+
* `development` — a production deploy that forgets `NODE_ENV` must fail fast
|
|
56
|
+
* rather than load `.env.development` (dev DB creds, loose CORS, insecure
|
|
57
|
+
* cookies). Set `NODE_ENV` explicitly (`production` / `development` / `test`).
|
|
58
|
+
*/
|
|
59
|
+
function coreEnvFilePath() {
|
|
60
|
+
const env = process.env.NODE_ENV;
|
|
61
|
+
if (!env) {
|
|
62
|
+
throw new Error('NODE_ENV must be explicitly set (e.g. "production" or "development"). ' +
|
|
63
|
+
'Refusing to default to development.');
|
|
64
|
+
}
|
|
65
|
+
return [`.env.${env}`, '.env'];
|
|
66
|
+
}
|
|
67
|
+
exports.coreEnvFilePath = coreEnvFilePath;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { INestApplication, ValidationPipeOptions } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal CORS options accepted by {@link setupCoreSecurity}. Mirrors the
|
|
4
|
+
* subset of Nest's `CorsOptions` that most apps set; extra keys are passed
|
|
5
|
+
* through to `app.enableCors` untouched. Declared locally to avoid a fragile
|
|
6
|
+
* deep import into Nest internals.
|
|
7
|
+
*/
|
|
8
|
+
export interface CoreCorsOptions {
|
|
9
|
+
origin?: string | string[] | boolean | RegExp | (string | RegExp)[];
|
|
10
|
+
credentials?: boolean;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Options for {@link setupCoreSecurity}. Each concern can be disabled by
|
|
15
|
+
* passing `false`, or tuned by passing an options object that is merged over
|
|
16
|
+
* the secure defaults.
|
|
17
|
+
*/
|
|
18
|
+
export interface CoreSecurityOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Global `ValidationPipe`. Secure default:
|
|
21
|
+
* `{ transform: true, whitelist: true, forbidNonWhitelisted: true }`.
|
|
22
|
+
*
|
|
23
|
+
* `whitelist` strips body properties that carry no validation decorator;
|
|
24
|
+
* `forbidNonWhitelisted` turns such properties into a `400` instead of
|
|
25
|
+
* silently dropping them. Combined with DTOs that declare every accepted
|
|
26
|
+
* field, this rejects unknown body properties on `POST /auth/register`
|
|
27
|
+
* and every other class-validated request body.
|
|
28
|
+
*
|
|
29
|
+
* Pass `false` to skip (you must then register your own pipe), or an
|
|
30
|
+
* options object to override individual flags.
|
|
31
|
+
*/
|
|
32
|
+
validationPipe?: ValidationPipeOptions | false;
|
|
33
|
+
/**
|
|
34
|
+
* CORS. Default: `{ origin: 'http://localhost:3000', credentials: true }`
|
|
35
|
+
* — the scaffold's historical default. Read `origin` from configuration
|
|
36
|
+
* (e.g. a `CORS_ORIGIN` env var) and pass it here so the allowed origin is
|
|
37
|
+
* deployment-configurable rather than hardcoded.
|
|
38
|
+
*
|
|
39
|
+
* Guardrail: `origin: '*'` together with `credentials: true` throws at
|
|
40
|
+
* setup — that combination is rejected by browsers and is a common footgun.
|
|
41
|
+
*
|
|
42
|
+
* Pass `false` to skip (you manage CORS yourself).
|
|
43
|
+
*/
|
|
44
|
+
cors?: CoreCorsOptions | false;
|
|
45
|
+
/**
|
|
46
|
+
* Security headers via Helmet: `Content-Security-Policy`,
|
|
47
|
+
* `X-Content-Type-Options`, `Strict-Transport-Security`, frame-ancestors, etc.
|
|
48
|
+
* Default: enabled with Helmet's defaults. Pass an options object to tune
|
|
49
|
+
* (e.g. a custom CSP for the AdminJS UI), or `false` to skip.
|
|
50
|
+
*/
|
|
51
|
+
helmet?: Record<string, any> | false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Applies AppX Core's baseline HTTP hardening to a Nest application.
|
|
55
|
+
*
|
|
56
|
+
* Call once in `main.ts`, after `NestFactory.create(...)` and before
|
|
57
|
+
* `app.listen(...)`. New scaffolds call it for you; existing projects should
|
|
58
|
+
* add the call when upgrading — see the CHANGELOG migration note for the
|
|
59
|
+
* version that introduced each concern.
|
|
60
|
+
*/
|
|
61
|
+
export declare function setupCoreSecurity(app: INestApplication, options?: CoreSecurityOptions): void;
|
|
62
|
+
//# sourceMappingURL=setup-core-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-core-security.d.ts","sourceRoot":"","sources":["../../../src/common/setup/setup-core-security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAkB,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;IAE/C;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,eAAe,GAAG,KAAK,CAAC;IAE/B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC7B,GAAG,EAAE,gBAAgB,EACrB,OAAO,GAAE,mBAAwB,GAClC,IAAI,CAkCN"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupCoreSecurity = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* Applies AppX Core's baseline HTTP hardening to a Nest application.
|
|
7
|
+
*
|
|
8
|
+
* Call once in `main.ts`, after `NestFactory.create(...)` and before
|
|
9
|
+
* `app.listen(...)`. New scaffolds call it for you; existing projects should
|
|
10
|
+
* add the call when upgrading — see the CHANGELOG migration note for the
|
|
11
|
+
* version that introduced each concern.
|
|
12
|
+
*/
|
|
13
|
+
function setupCoreSecurity(app, options = {}) {
|
|
14
|
+
if (options.validationPipe !== false) {
|
|
15
|
+
app.useGlobalPipes(new common_1.ValidationPipe({
|
|
16
|
+
transform: true,
|
|
17
|
+
whitelist: true,
|
|
18
|
+
forbidNonWhitelisted: true,
|
|
19
|
+
...(options.validationPipe ?? {}),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
if (options.cors !== false) {
|
|
23
|
+
const cors = options.cors ?? {};
|
|
24
|
+
const origin = cors.origin ?? 'http://localhost:3000';
|
|
25
|
+
const credentials = cors.credentials ?? true;
|
|
26
|
+
if (credentials && origin === '*') {
|
|
27
|
+
throw new Error("setupCoreSecurity: CORS origin '*' is incompatible with credentials:true — " +
|
|
28
|
+
'browsers reject it and it exposes credentialed endpoints to every origin. ' +
|
|
29
|
+
'Set an explicit origin (e.g. via a CORS_ORIGIN env var) or disable credentials.');
|
|
30
|
+
}
|
|
31
|
+
app.enableCors({ ...cors, origin, credentials });
|
|
32
|
+
}
|
|
33
|
+
if (options.helmet !== false) {
|
|
34
|
+
// Lazy require: helmet is a peer dependency, only needed when enabled.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
36
|
+
const helmet = require('helmet');
|
|
37
|
+
app.use(typeof options.helmet === 'object' ? helmet(options.helmet) : helmet());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.setupCoreSecurity = setupCoreSecurity;
|
|
@@ -43,6 +43,10 @@ console.log('Generating Services...');
|
|
|
43
43
|
(0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-services.js')}`, {
|
|
44
44
|
stdio: 'inherit',
|
|
45
45
|
});
|
|
46
|
+
console.log('Generating DTOs...');
|
|
47
|
+
(0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-dtos.js')}`, {
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
});
|
|
46
50
|
console.log('Generating Controllers...');
|
|
47
51
|
(0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-controllers.js')}`, {
|
|
48
52
|
stdio: 'inherit',
|