@appxdigital/appx-core 0.1.124 → 0.1.125-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/PRERELEASES.md +20 -0
- package/README.md +11 -0
- package/dist/common/interfaces/user.interface.d.ts +12 -0
- package/dist/common/interfaces/user.interface.d.ts.map +1 -1
- package/dist/common/types.d.ts +1 -1
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/utils/coerce-id.util.d.ts +30 -0
- package/dist/common/utils/coerce-id.util.d.ts.map +1 -0
- package/dist/common/utils/coerce-id.util.js +27 -0
- package/dist/config/generate-session-schema.js +18 -1
- 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 +4 -1
- package/dist/modules/auth/auth.service.d.ts +3 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +3 -2
- package/dist/modules/auth/session/session-serializer.d.ts.map +1 -1
- package/dist/modules/auth/session/session-serializer.js +5 -1
- package/dist/modules/core/core.service.d.ts.map +1 -1
- package/dist/modules/core/core.service.js +5 -9
- package/docs/authentication.md +40 -0
- package/docs/limitations.md +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,31 @@ This project follows the `MAJOR.MINOR.PATCH` scheme in `package.json`.
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
## [0.1.125] — 2026-07-29
|
|
18
|
+
|
|
19
|
+
A string (uuid/cuid) `User.id` is now supported end-to-end, and the CLI's `create` gains a non-interactive mode plus far better failure diagnostics.
|
|
20
|
+
|
|
21
|
+
### String `User.id` support (library)
|
|
22
|
+
|
|
23
|
+
The framework was already id-type-agnostic in generic CRUD (it reads the PK type from the schema before coercing), but the auth module hardcoded `Int` in a few places — most importantly `SessionSerializer.deserializeUser`, which did `Number(userId)`: `NaN` for a uuid, so **session auth failed on every request** for string-id projects. All of it is fixed via one shared `coerceId(delegate, id)` helper (a `String` PK passes through, anything else is number-coerced):
|
|
24
|
+
|
|
25
|
+
- `SessionSerializer.deserializeUser` — was `Number(userId)`.
|
|
26
|
+
- `GET /auth/sessions/:userId` — was `parseInt(userId, 10)`, which silently truncates a uuid starting with digits (`'3f2a…'` → `3`, the **wrong user's sessions**).
|
|
27
|
+
- `getSessionsByUserId` / `revokeRefreshTokensForUser` accept `UserId` (`string | number`, exported); the `express-session` `SessionData.userId` augmentation is widened the same way.
|
|
28
|
+
- `coerceId` and `UserId` are exported from the package root for consumer overrides.
|
|
29
|
+
|
|
30
|
+
**Using a string id is a schema-only change** — set `User.id String @default(uuid())` and switch `Session.userId` / `UserRefreshToken.userId` to `String` with it. No code overrides needed. See the new section in [`docs/authentication.md`](./docs/authentication.md#using-a-string-uuid--cuid-userid). Int-keyed projects are unaffected (drop-in).
|
|
31
|
+
|
|
32
|
+
### CLI (`@appxdigital/appx-core-cli`)
|
|
33
|
+
|
|
34
|
+
- **Non-interactive `create`.** `appx-core create --yes` (or any value flag) skips the wizard — for CI, scripts, and non-TTY contexts. Unset values take the wizard's defaults; the database check fails fast with the reason instead of re-prompting. Flags: `--name`, `--db-provider`, `--db-host`, `--db-port`, `--db-user`, `--db-password`, `--db-name`, `--show-output`.
|
|
35
|
+
- **`create` failures now show the real error.** Hidden commands piped their output to nowhere, so an init failure surfaced as an opaque stack trace with `stdout/stderr: null`; the tail of the failing command's output is now printed.
|
|
36
|
+
- **Prisma is preflighted and invoked from the project.** After `npm install`, `create` verifies the project's own `node_modules/.bin/prisma` exists (an `~/.npmrc` with `legacy-peer-deps`/`omit=peer` silently skips the peer deps that carry it) and runs every migrate step via that explicit path — never a `PATH` lookup.
|
|
37
|
+
|
|
38
|
+
Drop-in: no changes required in existing projects.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
17
42
|
## [0.1.124] — 2026-07-23
|
|
18
43
|
|
|
19
44
|
### Fixed
|
package/PRERELEASES.md
CHANGED
|
@@ -31,6 +31,26 @@ Prereleases are excluded from normal semver range resolution, so a project on
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
+
## [0.1.125-beta.0] — library
|
|
35
|
+
|
|
36
|
+
Prerelease of the string-`User.id` support that ships in `[0.1.125]` — see
|
|
37
|
+
[`CHANGELOG.md`](./CHANGELOG.md#01125--2026-07-29) for the full notes. Same
|
|
38
|
+
content as the production cut; published to `beta` so a uuid-keyed project can
|
|
39
|
+
validate before promoting.
|
|
40
|
+
|
|
41
|
+
- Session auth, `GET /auth/sessions/:userId`, and refresh-token revocation now
|
|
42
|
+
coerce ids to the `User.id` type from your schema (`coerceId`, exported).
|
|
43
|
+
- `UserId` (`string | number`) exported; `SessionData.userId` widened.
|
|
44
|
+
- Int-keyed projects: drop-in, no changes.
|
|
45
|
+
|
|
46
|
+
## [cli-1.0.25-beta.0] — CLI
|
|
47
|
+
|
|
48
|
+
Prerelease of the CLI changes in `[0.1.125]`'s CLI section: non-interactive
|
|
49
|
+
`create --yes` (+ per-answer flags), failure output no longer swallowed, prisma
|
|
50
|
+
preflighted and run from the project's own `node_modules/.bin`. Also includes
|
|
51
|
+
the self-update mechanism and channel-matched library pinning shipped in
|
|
52
|
+
cli-1.0.23.
|
|
53
|
+
|
|
34
54
|
## [0.1.121-beta.16] — unreleased
|
|
35
55
|
|
|
36
56
|
**Fix — `AuthModule.forRoot({ controller: false })` now actually omits the
|
package/README.md
CHANGED
|
@@ -147,6 +147,17 @@ appx-core create
|
|
|
147
147
|
cd <your-project>
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
**Non-interactive (CI / scripts).** Passing `--yes` — or any value flag — skips the wizard entirely; anything not given uses the wizard's default. No prompt is ever opened, so it's safe without a TTY. The database check still runs and fails fast with the reason instead of re-prompting.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
appx-core create --yes \
|
|
154
|
+
--name my-app \
|
|
155
|
+
--db-provider mysql --db-host 127.0.0.1 --db-port 3306 \
|
|
156
|
+
--db-user root --db-password secret --db-name my_app_db
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Flags: `--name`, `--db-provider mysql|postgresql`, `--db-host`, `--db-port`, `--db-user`, `--db-password`, `--db-name`, `--show-output`. File upload isn't configured on this path — run `appx-core setup:fileupload` inside the project afterwards.
|
|
160
|
+
|
|
150
161
|
### 3) Configure Environment
|
|
151
162
|
|
|
152
163
|
`appx-core create` **already writes a `.env`** at the project root, with secure
|
|
@@ -3,6 +3,18 @@ export interface UserCreateInput {
|
|
|
3
3
|
password: string;
|
|
4
4
|
name?: string;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The type of a user's primary key as accepted at API boundaries. `User.id` is a
|
|
8
|
+
* `String` (uuid/cuid) or an `Int` (autoincrement) depending on the consumer's
|
|
9
|
+
* schema, so anything that *accepts* a user id (route params, service methods,
|
|
10
|
+
* session data) should accept both. Framework code coerces to the concrete type
|
|
11
|
+
* at the data-access boundary via `coerceId`.
|
|
12
|
+
*
|
|
13
|
+
* Note `User.id` below stays `string`: the authenticated principal is treated as
|
|
14
|
+
* an opaque string id throughout auth/session, and widening it here would force
|
|
15
|
+
* casts in consumer override code. Use `UserId` for *inputs*, not the principal.
|
|
16
|
+
*/
|
|
17
|
+
export type UserId = string | number;
|
|
6
18
|
export interface User {
|
|
7
19
|
id: string;
|
|
8
20
|
email: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.interface.d.ts","sourceRoot":"","sources":["../../../src/common/interfaces/user.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,IAAI;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|
|
1
|
+
{"version":3,"file":"user.interface.d.ts","sourceRoot":"","sources":["../../../src/common/interfaces/user.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAErC,MAAM,WAAW,IAAI;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|
package/dist/common/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,iBAAiB,CAAC;AAEzB,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEhD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,OAAO,CAAC;QACd,UAAU,OAAO;YACb,MAAM,CAAC,EAAE,YAAY,CAAC;YAEtB,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;YAEjD,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;YAE3C,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;SAC3B;QAED,UAAU,IAAK,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;SACzC;KACJ;CACJ;AAED,OAAO,QAAQ,iBAAiB,CAAC;IAC7B,UAAU,WAAW;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,iBAAiB,CAAC;AAEzB,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEhD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,OAAO,CAAC;QACd,UAAU,OAAO;YACb,MAAM,CAAC,EAAE,YAAY,CAAC;YAEtB,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;YAEjD,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;YAE3C,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;SAC3B;QAED,UAAU,IAAK,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;SACzC;KACJ;CACJ;AAED,OAAO,QAAQ,iBAAiB,CAAC;IAC7B,UAAU,WAAW;QAIjB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;KAC3B;CACJ"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape we need off a Prisma model delegate: its runtime field metadata.
|
|
3
|
+
* A delegate (e.g. `prisma.user`) exposes `fields.id.typeName` — the scalar
|
|
4
|
+
* type name of the primary key ('String', 'Int', 'BigInt', …). This passes
|
|
5
|
+
* through the PrismaService proxy unchanged (the proxy only wraps method calls).
|
|
6
|
+
*/
|
|
7
|
+
export interface IdBearingDelegate {
|
|
8
|
+
fields?: {
|
|
9
|
+
id?: {
|
|
10
|
+
typeName?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Coerce an id — as it arrives over HTTP (route params) or from a session
|
|
16
|
+
* (always a string) — to the runtime type the model's primary key expects.
|
|
17
|
+
*
|
|
18
|
+
* String primary keys (uuid/cuid) pass through unchanged; every other PK type
|
|
19
|
+
* is converted with `Number()`, matching Prisma's expectation for `Int` ids.
|
|
20
|
+
* The type is read from the delegate's own field metadata, so this is correct
|
|
21
|
+
* whether `User.id` is `Int @default(autoincrement())` or `String @default(uuid())`.
|
|
22
|
+
*
|
|
23
|
+
* This is the single source of truth for id coercion — generic CRUD
|
|
24
|
+
* (`CoreService`) and the auth module both call it, so a string-keyed model
|
|
25
|
+
* behaves consistently everywhere. Do NOT reintroduce `Number(id)` / `parseInt`
|
|
26
|
+
* at call sites: `Number('3f2a…')` is `NaN` and `parseInt('3f2a…', 10)` is `3`,
|
|
27
|
+
* i.e. a silent wrong-row lookup for UUID ids.
|
|
28
|
+
*/
|
|
29
|
+
export declare function coerceId(delegate: IdBearingDelegate, id: string | number): string | number;
|
|
30
|
+
//# sourceMappingURL=coerce-id.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coerce-id.util.d.ts","sourceRoot":"","sources":["../../../src/common/utils/coerce-id.util.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAC9B,MAAM,CAAC,EAAE;QAAC,EAAE,CAAC,EAAE;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAM1F"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.coerceId = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* Coerce an id — as it arrives over HTTP (route params) or from a session
|
|
7
|
+
* (always a string) — to the runtime type the model's primary key expects.
|
|
8
|
+
*
|
|
9
|
+
* String primary keys (uuid/cuid) pass through unchanged; every other PK type
|
|
10
|
+
* is converted with `Number()`, matching Prisma's expectation for `Int` ids.
|
|
11
|
+
* The type is read from the delegate's own field metadata, so this is correct
|
|
12
|
+
* whether `User.id` is `Int @default(autoincrement())` or `String @default(uuid())`.
|
|
13
|
+
*
|
|
14
|
+
* This is the single source of truth for id coercion — generic CRUD
|
|
15
|
+
* (`CoreService`) and the auth module both call it, so a string-keyed model
|
|
16
|
+
* behaves consistently everywhere. Do NOT reintroduce `Number(id)` / `parseInt`
|
|
17
|
+
* at call sites: `Number('3f2a…')` is `NaN` and `parseInt('3f2a…', 10)` is `3`,
|
|
18
|
+
* i.e. a silent wrong-row lookup for UUID ids.
|
|
19
|
+
*/
|
|
20
|
+
function coerceId(delegate, id) {
|
|
21
|
+
const idField = delegate?.fields?.id;
|
|
22
|
+
if (!idField) {
|
|
23
|
+
throw new common_1.BadRequestException(`Model does not have an 'id' field`);
|
|
24
|
+
}
|
|
25
|
+
return idField.typeName === 'String' ? String(id) : Number(id);
|
|
26
|
+
}
|
|
27
|
+
exports.coerceId = coerceId;
|
|
@@ -39,6 +39,22 @@ const doesSessionModelExist = () => {
|
|
|
39
39
|
}
|
|
40
40
|
return false;
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Read the type of `User.id` from schema.prisma so the generated `Session.userId`
|
|
44
|
+
* foreign key matches it. A consumer who changes `User.id` to `String` (uuid/cuid)
|
|
45
|
+
* must not have an `Int` FK written back by tooling. Defaults to `Int` when the
|
|
46
|
+
* User model or its id can't be found.
|
|
47
|
+
*/
|
|
48
|
+
const getUserIdType = () => {
|
|
49
|
+
if (!fs.existsSync(schemaPath))
|
|
50
|
+
return 'Int';
|
|
51
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
52
|
+
const userModel = schemaContent.match(/model\s+User\s*{[\s\S]*?}/i);
|
|
53
|
+
if (!userModel)
|
|
54
|
+
return 'Int';
|
|
55
|
+
const idField = userModel[0].match(/^\s*id\s+(\w+)/m);
|
|
56
|
+
return idField ? idField[1] : 'Int';
|
|
57
|
+
};
|
|
42
58
|
/**
|
|
43
59
|
* Ensure that the session schema exists if it is not already defined in schema.prisma
|
|
44
60
|
*/
|
|
@@ -49,13 +65,14 @@ const ensureSessionSchemaExists = () => {
|
|
|
49
65
|
}
|
|
50
66
|
if (!fs.existsSync(sessionSchemaPath)) {
|
|
51
67
|
console.log('Session schema not found in session.prisma. Creating it...');
|
|
68
|
+
const userIdType = getUserIdType();
|
|
52
69
|
const sessionSchemaContent = `
|
|
53
70
|
model Session {
|
|
54
71
|
id String @id
|
|
55
72
|
sid String @unique
|
|
56
73
|
data String
|
|
57
74
|
expiresAt DateTime
|
|
58
|
-
userId
|
|
75
|
+
userId ${userIdType}?
|
|
59
76
|
}
|
|
60
77
|
`;
|
|
61
78
|
fs.writeFileSync(sessionSchemaPath, sessionSchemaContent);
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export * from './common/guards/user-population.guard';
|
|
|
24
24
|
export * from './modules/common.module';
|
|
25
25
|
export * from './common/interceptors/prisma.interceptor';
|
|
26
26
|
export * from './common/decorators/scalar-field.decorator';
|
|
27
|
+
export * from './common/utils/coerce-id.util';
|
|
28
|
+
export * from './common/interfaces/user.interface';
|
|
27
29
|
export * from './common/config/permissionsConfigTypes';
|
|
28
30
|
export * from './common/config/adminConfigType';
|
|
29
31
|
export * from './common/config/permissions.config.provider';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAI3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,cAAc,yBAAyB,CAAC;AACxC,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oBAAoB,CAAC;AACnC,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAI3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,cAAc,yBAAyB,CAAC;AACxC,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oBAAoB,CAAC;AACnC,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,8 @@ __exportStar(require("./common/guards/user-population.guard"), exports);
|
|
|
43
43
|
__exportStar(require("./modules/common.module"), exports);
|
|
44
44
|
__exportStar(require("./common/interceptors/prisma.interceptor"), exports);
|
|
45
45
|
__exportStar(require("./common/decorators/scalar-field.decorator"), exports);
|
|
46
|
+
__exportStar(require("./common/utils/coerce-id.util"), exports);
|
|
47
|
+
__exportStar(require("./common/interfaces/user.interface"), exports);
|
|
46
48
|
__exportStar(require("./common/config/permissionsConfigTypes"), exports);
|
|
47
49
|
__exportStar(require("./common/config/adminConfigType"), exports);
|
|
48
50
|
__exportStar(require("./common/config/permissions.config.provider"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAI1C,qBACa,cAAc;IACX,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW;gBAAxB,WAAW,EAAE,WAAW;IAGjD,QAAQ,CAAS,WAAW,EAAE,WAAW;;;;IAMzC,KAAK,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAW/C,MAAM,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhE,EAAE,CAAQ,GAAG,EAAE,OAAO;;;IAQtB,iBAAiB,CAAQ,GAAG,EAAE,OAAO;;;IAOrC,gBAAgB,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAM1D,cAAc,CAAkB,MAAM,EAAE,MAAM,EAAS,GAAG,EAAE,OAAO;;;
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAI1C,qBACa,cAAc;IACX,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW;gBAAxB,WAAW,EAAE,WAAW;IAGjD,QAAQ,CAAS,WAAW,EAAE,WAAW;;;;IAMzC,KAAK,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAW/C,MAAM,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhE,EAAE,CAAQ,GAAG,EAAE,OAAO;;;IAQtB,iBAAiB,CAAQ,GAAG,EAAE,OAAO;;;IAOrC,gBAAgB,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAM1D,cAAc,CAAkB,MAAM,EAAE,MAAM,EAAS,GAAG,EAAE,OAAO;;;IAgBnE,oBAAoB,CACF,SAAS,EAAE,MAAM,EAC9B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ;IAgBlB,QAAQ,CAAQ,GAAG,EAAE,OAAO;;;;;IAM5B,aAAa,CAAQ,GAAG,EAAE,OAAO;;;;IAOjC,SAAS,CAAQ,GAAG,EAAE,OAAO;;;CAQtC"}
|
|
@@ -70,7 +70,10 @@ let AuthController = exports.AuthController = class AuthController {
|
|
|
70
70
|
if (req.user?.role !== 'ADMIN') {
|
|
71
71
|
throw new common_1.ForbiddenException('Access denied.');
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
// Pass the raw param through; the service coerces it to the User PK's
|
|
74
|
+
// actual type. `parseInt('3f2a…', 10)` was `3` — a silent wrong-user
|
|
75
|
+
// lookup for uuid ids — while `parseInt('f3a2…')` was `NaN`.
|
|
76
|
+
const sessions = await this.authService.getSessionsByUserId(userId);
|
|
74
77
|
return { sessions };
|
|
75
78
|
}
|
|
76
79
|
async closeSpecificSession(sessionId, req, res) {
|
|
@@ -4,6 +4,7 @@ import { PrismaService } from '../../prisma/prisma.service';
|
|
|
4
4
|
import { ConfigService } from '@nestjs/config';
|
|
5
5
|
import { Request, Response } from 'express';
|
|
6
6
|
import { JwtService } from "@nestjs/jwt";
|
|
7
|
+
import { UserId } from '../../common/interfaces/user.interface';
|
|
7
8
|
export declare class AuthService {
|
|
8
9
|
protected readonly userService: UserService;
|
|
9
10
|
protected readonly prisma: PrismaService;
|
|
@@ -24,7 +25,7 @@ export declare class AuthService {
|
|
|
24
25
|
getCurrentUser(req: Request): Promise<any>;
|
|
25
26
|
getActiveSessions(req: Request): Promise<any>;
|
|
26
27
|
closeAllUserSessions(req: Request, res: Response): Promise<void>;
|
|
27
|
-
getSessionsByUserId(userId:
|
|
28
|
+
getSessionsByUserId(userId: UserId): Promise<any>;
|
|
28
29
|
closeSpecificSession(sessionId: string): Promise<boolean>;
|
|
29
30
|
validateJwtPayload(payload: any): Promise<any>;
|
|
30
31
|
private generateTokens;
|
|
@@ -38,6 +39,6 @@ export declare class AuthService {
|
|
|
38
39
|
refresh_token: string;
|
|
39
40
|
user: any;
|
|
40
41
|
}>;
|
|
41
|
-
revokeRefreshTokensForUser(userId:
|
|
42
|
+
revokeRefreshTokensForUser(userId: UserId): Promise<void>;
|
|
42
43
|
}
|
|
43
44
|
//# sourceMappingURL=auth.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAC,UAAU,EAAiB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAC,UAAU,EAAiB,MAAM,aAAa,CAAC;AAGvD,OAAO,EAAC,MAAM,EAAC,MAAM,wCAAwC,CAAC;AAI9D,qBACa,WAAW;IAIhB,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW;IAC3C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa;IACxC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU;IACzC,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,aAAa;IANnD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAGpB,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa;IAK7C,QAAQ,CAAC,WAAW,EAAE,WAAW;;;;IA2BjC,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAC,CAAC;IA8B1D,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBlD,YAAY,CACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACtB,OAAO,CAAC,GAAG,CAAC;IAuBT,cAAc,CAAC,GAAG,EAAE,OAAO;IAW3B,iBAAiB,CAAC,GAAG,EAAE,OAAO;IAW9B,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ;IA0BhD,mBAAmB,CAAC,MAAM,EAAE,MAAM;IAMlC,oBAAoB,CAAC,SAAS,EAAE,MAAM;IAOtC,kBAAkB,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YActC,cAAc;IAkCtB,aAAa,CAAC,yBAAyB,EAAE,GAAG,GAAG,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAC,CAAC;IA4B3G,OAAO,CAAC,WAAW;IAUb,QAAQ,CAAC,iBAAiB,EAAE,GAAG,GAAG,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAC,CAAC;IAcnG,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlE"}
|
|
@@ -40,6 +40,7 @@ const prisma_service_1 = require("../../prisma/prisma.service");
|
|
|
40
40
|
const config_1 = require("@nestjs/config");
|
|
41
41
|
const jwt_1 = require("@nestjs/jwt");
|
|
42
42
|
const cuid2_1 = require("@paralleldrive/cuid2");
|
|
43
|
+
const coerce_id_util_1 = require("../../common/utils/coerce-id.util");
|
|
43
44
|
let AuthService = exports.AuthService = class AuthService {
|
|
44
45
|
constructor(userService, prisma, jwtService, configService) {
|
|
45
46
|
this.userService = userService;
|
|
@@ -172,7 +173,7 @@ let AuthService = exports.AuthService = class AuthService {
|
|
|
172
173
|
}
|
|
173
174
|
async getSessionsByUserId(userId) {
|
|
174
175
|
return this.prisma.session.findMany({
|
|
175
|
-
where: { userId, expiresAt: { gte: new Date() } },
|
|
176
|
+
where: { userId: (0, coerce_id_util_1.coerceId)(this.prisma.user, userId), expiresAt: { gte: new Date() } },
|
|
176
177
|
});
|
|
177
178
|
}
|
|
178
179
|
async closeSpecificSession(sessionId) {
|
|
@@ -272,7 +273,7 @@ let AuthService = exports.AuthService = class AuthService {
|
|
|
272
273
|
}
|
|
273
274
|
async revokeRefreshTokensForUser(userId) {
|
|
274
275
|
await this.prisma.userRefreshToken.updateMany({
|
|
275
|
-
where: { userId: userId, revokedAt: null },
|
|
276
|
+
where: { userId: (0, coerce_id_util_1.coerceId)(this.prisma.user, userId), revokedAt: null },
|
|
276
277
|
data: { revokedAt: new Date() },
|
|
277
278
|
},
|
|
278
279
|
// @ts-ignore
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-serializer.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/session/session-serializer.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAC,aAAa,EAAC,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"session-serializer.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/session/session-serializer.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAC,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAG7D,qBACa,iBAAkB,SAAQ,kBAAkB;IACzC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAIzC,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ;IAIjC,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ;CAwBvD"}
|
|
@@ -13,6 +13,7 @@ exports.SessionSerializer = void 0;
|
|
|
13
13
|
const common_1 = require("@nestjs/common");
|
|
14
14
|
const passport_1 = require("@nestjs/passport");
|
|
15
15
|
const prisma_service_1 = require("../../../prisma/prisma.service");
|
|
16
|
+
const coerce_id_util_1 = require("../../../common/utils/coerce-id.util");
|
|
16
17
|
let SessionSerializer = exports.SessionSerializer = class SessionSerializer extends passport_1.PassportSerializer {
|
|
17
18
|
constructor(prisma) {
|
|
18
19
|
super();
|
|
@@ -27,7 +28,10 @@ let SessionSerializer = exports.SessionSerializer = class SessionSerializer exte
|
|
|
27
28
|
}
|
|
28
29
|
try {
|
|
29
30
|
const user = await this.prisma.user.findFirstOrThrow({
|
|
30
|
-
|
|
31
|
+
// Coerce to the User PK's actual type. `Number(userId)` here made
|
|
32
|
+
// session auth fail closed for string (uuid/cuid) ids — Number of
|
|
33
|
+
// a uuid is NaN, so findFirstOrThrow threw on every request.
|
|
34
|
+
where: { id: (0, coerce_id_util_1.coerceId)(this.prisma.user, userId) },
|
|
31
35
|
select: {
|
|
32
36
|
id: true,
|
|
33
37
|
role: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.service.d.ts","sourceRoot":"","sources":["../../../src/modules/core/core.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.service.d.ts","sourceRoot":"","sources":["../../../src/modules/core/core.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAEjD,qBACa,WAAW,CAAC,CAAC;IAElB,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;gBAA9B,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;IAIrD;;;;OAIG;IACG,OAAO,CAAC,MAAM,GAAE,GAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQ7C;;;;;OAKG;IACG,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,GAAE,GAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;IAYxD;;;;OAIG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAUtC;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;IAQnC;;;;;OAKG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;IAW5D;;;;OAIG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAejD,OAAO,CAAC,yBAAyB;CAGpC"}
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.CoreService = void 0;
|
|
13
13
|
const common_1 = require("@nestjs/common");
|
|
14
14
|
const error_handler_1 = require("../../common/utils/error-handler");
|
|
15
|
+
const coerce_id_util_1 = require("../../common/utils/coerce-id.util");
|
|
15
16
|
let CoreService = exports.CoreService = class CoreService {
|
|
16
17
|
constructor(modelDelegate) {
|
|
17
18
|
this.modelDelegate = modelDelegate;
|
|
@@ -105,17 +106,12 @@ let CoreService = exports.CoreService = class CoreService {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
/*
|
|
108
|
-
* Generates a 'where' clause based on the model's ID field.
|
|
109
|
+
* Generates a 'where' clause based on the model's ID field. Coercion to the
|
|
110
|
+
* PK's concrete type (String passes through, otherwise Number) is centralised
|
|
111
|
+
* in `coerceId` and shared with the auth module.
|
|
109
112
|
*/
|
|
110
113
|
_generateWhereFromIdField(id) {
|
|
111
|
-
|
|
112
|
-
if (!idField) {
|
|
113
|
-
throw new common_1.BadRequestException(`Model does not have an 'id' field`);
|
|
114
|
-
}
|
|
115
|
-
if (idField.typeName != "String") {
|
|
116
|
-
id = Number(id);
|
|
117
|
-
}
|
|
118
|
-
return { id: id };
|
|
114
|
+
return { id: (0, coerce_id_util_1.coerceId)(this.modelDelegate, id) };
|
|
119
115
|
}
|
|
120
116
|
};
|
|
121
117
|
exports.CoreService = CoreService = __decorate([
|
package/docs/authentication.md
CHANGED
|
@@ -218,3 +218,43 @@ exported from `@appxdigital/appx-core`. To override inner functionality:
|
|
|
218
218
|
|
|
219
219
|
If a class you need to extend isn't exported, that's a framework gap — open it
|
|
220
220
|
rather than deep-importing from `dist/`.
|
|
221
|
+
|
|
222
|
+
## Using a string (UUID / cuid) `User.id`
|
|
223
|
+
|
|
224
|
+
The scaffold defaults to `User.id Int @default(autoincrement())`, but a **string
|
|
225
|
+
primary key is fully supported** — teams migrating in from Supabase, Firebase,
|
|
226
|
+
Hasura, etc. usually keep their existing UUIDs. The framework reads the id's
|
|
227
|
+
type from your schema and coerces ids to it at the data-access boundary
|
|
228
|
+
(`coerceId`), so generic CRUD, sessions, and the auth module all handle a string
|
|
229
|
+
`User.id` without any override.
|
|
230
|
+
|
|
231
|
+
To use it, change the primary key **and the two foreign keys that reference it**
|
|
232
|
+
together — they must match:
|
|
233
|
+
|
|
234
|
+
```prisma
|
|
235
|
+
model User {
|
|
236
|
+
id String @id @default(uuid()) // or @default(cuid())
|
|
237
|
+
// …
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
model Session {
|
|
241
|
+
// …
|
|
242
|
+
userId String? // was Int?
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
model UserRefreshToken {
|
|
246
|
+
// …
|
|
247
|
+
userId String // was Int
|
|
248
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
That's the whole change — no code overrides are required. A `prisma db pull`
|
|
253
|
+
against an existing UUID-keyed database already emits `String @id`, so the pulled
|
|
254
|
+
schema and the framework agree.
|
|
255
|
+
|
|
256
|
+
> **Why it's just the schema:** ids arrive over HTTP and in sessions as strings.
|
|
257
|
+
> The framework never hardcodes `Number(id)` — it converts based on the PK type,
|
|
258
|
+
> so a `String` id passes through untouched while an `Int` id is still parsed.
|
|
259
|
+
> Accept a user id as `UserId` (`string | number`) in your own signatures if you
|
|
260
|
+
> extend the auth service; it's exported from the package root.
|
package/docs/limitations.md
CHANGED
|
@@ -52,6 +52,7 @@ Generated CRUD accepts nested `create`/`connect` **one level deep only** — a `
|
|
|
52
52
|
- **No audit logging of denials.** A refused request returns `403` but emits no structured audit event; per-request diagnostics are opt-in via `prismaService.debugQueries(true)`.
|
|
53
53
|
- **Exposed models are a full bypass.** `@ExposeModels` / `@Permission(a, [models])` / `withExposedModels` disable row *and* field filtering for the listed models within that scope. Keep the scope as narrow as possible.
|
|
54
54
|
- **`connect` defaults to one rule per target model; override per relation when needed.** By default every foreign key that points at model `T` is authorized by the single `T.connect` rule for the role. When a specific relation needs a different (stricter) rule — "a class's teacher must be a teacher" — declare a **relation-scoped** `connect` on the source model (`Source[role].relations[field].connect`); it is ANDed with `T.connect`, or stands alone if `T` has none. See [permissions.md](./permissions.md#two-places-to-declare-a-connect-rule). A relation rule can only strengthen, never loosen, the target rule.
|
|
55
|
+
- **A string `User.id` needs its two foreign keys changed with it.** `User.id` may be `String` (uuid/cuid) instead of the scaffold's `Int` — the framework is id-type-agnostic and coerces accordingly (see [authentication.md](./authentication.md#using-a-string-uuid--cuid-userid)). But `Session.userId` and `UserRefreshToken.userId` must be switched to `String` in the same edit; leaving one as `Int` is a schema/relation mismatch Prisma will reject at migrate time. Change all three together.
|
|
55
56
|
- **The config is validated at boot; a bad config stops the app.** A create condition that references a relation, or a required foreign key whose target lacks a `connect` rule for a role that can create, is a startup error (not a runtime one). This is deliberate — the alternative is a silent runtime `403`. The error message names exactly what to fix.
|
|
56
57
|
|
|
57
58
|
---
|
package/package.json
CHANGED