@dudousxd/nestjs-authz-prisma 0.2.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 +14 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/authz-rbac.module.d.ts +53 -0
- package/dist/authz-rbac.module.d.ts.map +1 -0
- package/dist/authz-rbac.module.js +174 -0
- package/dist/authz-rbac.module.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/prisma-authz.store.d.ts +52 -0
- package/dist/prisma-authz.store.d.ts.map +1 -0
- package/dist/prisma-authz.store.js +187 -0
- package/dist/prisma-authz.store.js.map +1 -0
- package/dist/prisma-client.d.ts +95 -0
- package/dist/prisma-client.d.ts.map +1 -0
- package/dist/prisma-client.js +63 -0
- package/dist/prisma-client.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @dudousxd/nestjs-authz-prisma
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4](https://github.com/DavideCarvalho/nestjs-authz/pull/4) [`8b7711d`](https://github.com/DavideCarvalho/nestjs-authz/commit/8b7711d11bdb25b3407fea742f6c1158afb36296) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - New package: `@dudousxd/nestjs-authz-prisma` — a Prisma RBAC persistence adapter mirroring
|
|
8
|
+
`@dudousxd/nestjs-authz-typeorm`. Exposes a `PRISMA_CLIENT` DI token plus a minimal
|
|
9
|
+
**structural** `PrismaAuthzClientLike` interface (no `@prisma/client` import / no
|
|
10
|
+
`prisma generate` step), a `PrismaAuthzStore` with the same method surface, and
|
|
11
|
+
`AuthzRbacModule.forRoot/forRootAsync` registering the core `ROLE_PROVIDER` +
|
|
12
|
+
`PERMISSION_PROVIDER` seams. The schema is consumer-managed (the required `Role`/
|
|
13
|
+
`Permission`/`RolePermission`/`UserRole` models are documented in the README);
|
|
14
|
+
`ensureSchema` is a no-op.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @dudousxd/nestjs-authz-prisma
|
|
2
|
+
|
|
3
|
+
Prisma RBAC persistence for [`@dudousxd/nestjs-authz`](https://github.com/DavideCarvalho/nestjs-authz) —
|
|
4
|
+
roles, permissions, and a Gate seam, with **zero connection ownership** (your app owns the
|
|
5
|
+
`PrismaClient`; this package never opens a connection and never imports `@prisma/client`).
|
|
6
|
+
|
|
7
|
+
This is the Prisma sibling of `@dudousxd/nestjs-authz-typeorm`: identical store surface and
|
|
8
|
+
`AuthzRbacModule`, consuming a **structural** Prisma client interface.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @dudousxd/nestjs-authz-prisma @dudousxd/nestjs-authz
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
No `@prisma/client` is required by this package — it depends only on the structural
|
|
17
|
+
`PrismaAuthzClientLike` interface, which a real `PrismaClient` satisfies. There is **no
|
|
18
|
+
`prisma generate` step** introduced by this adapter.
|
|
19
|
+
|
|
20
|
+
## Schema (consumer-managed)
|
|
21
|
+
|
|
22
|
+
Prisma is schema-first: add the four RBAC models to your `schema.prisma` and apply them with
|
|
23
|
+
`prisma migrate` / `prisma db push`. The user is referenced **by id only** — this package
|
|
24
|
+
never owns a users table.
|
|
25
|
+
|
|
26
|
+
```prisma
|
|
27
|
+
model Role {
|
|
28
|
+
id String @id
|
|
29
|
+
name String @unique
|
|
30
|
+
guard String?
|
|
31
|
+
createdAt DateTime @default(now())
|
|
32
|
+
|
|
33
|
+
@@map("authz_roles")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
model Permission {
|
|
37
|
+
id String @id
|
|
38
|
+
name String @unique
|
|
39
|
+
guard String?
|
|
40
|
+
createdAt DateTime @default(now())
|
|
41
|
+
|
|
42
|
+
@@map("authz_permissions")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
model RolePermission {
|
|
46
|
+
roleId String
|
|
47
|
+
permissionId String
|
|
48
|
+
|
|
49
|
+
@@id([roleId, permissionId])
|
|
50
|
+
@@map("authz_role_permission")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
model UserRole {
|
|
54
|
+
userType String
|
|
55
|
+
userId String
|
|
56
|
+
roleId String
|
|
57
|
+
|
|
58
|
+
@@id([userType, userId, roleId])
|
|
59
|
+
@@index([userType, userId])
|
|
60
|
+
@@map("authz_user_role")
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> Unlike the TypeORM/MikroORM adapters there is **no auto-create** — `store.ensureSchema()`
|
|
65
|
+
> is a no-op. Manage the schema with Prisma migrations.
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { AuthzRbacModule } from '@dudousxd/nestjs-authz-prisma';
|
|
71
|
+
import { PrismaClient } from '@prisma/client';
|
|
72
|
+
|
|
73
|
+
const prisma = new PrismaClient();
|
|
74
|
+
|
|
75
|
+
@Module({
|
|
76
|
+
imports: [
|
|
77
|
+
// Pass the client directly — the module builds the store. (Or pass a pre-built `store`.)
|
|
78
|
+
AuthzRbacModule.forRoot({ client: prisma }),
|
|
79
|
+
],
|
|
80
|
+
})
|
|
81
|
+
export class AppModule {}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or build the store yourself and inject the client via the `PRISMA_CLIENT` token:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { PRISMA_CLIENT, PrismaAuthzStore } from '@dudousxd/nestjs-authz-prisma';
|
|
88
|
+
|
|
89
|
+
AuthzRbacModule.forRootAsync({
|
|
90
|
+
inject: [PRISMA_CLIENT],
|
|
91
|
+
useFactory: (client) => ({ store: new PrismaAuthzStore(client) }),
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Once wired, the Gate consults persisted RBAC:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
await store.givePermissionToRole('editor', 'posts.publish');
|
|
99
|
+
await store.assignRole({ type: 'user', id: 7 }, 'editor');
|
|
100
|
+
|
|
101
|
+
gate.forUser(user).allows('posts.publish'); // true (PERMISSION_PROVIDER seam)
|
|
102
|
+
gate.forUser(user).hasRole('editor'); // true (ROLE_PROVIDER seam)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { PrismaAuthzStore } from './prisma-authz.store.js';
|
|
3
|
+
import { type PrismaAuthzClientLike } from './prisma-client.js';
|
|
4
|
+
import type { UserRef } from './types.js';
|
|
5
|
+
/** Injection token holding the {@link PrismaAuthzStore} the RBAC module manages. */
|
|
6
|
+
export declare const AUTHZ_RBAC_STORE: unique symbol;
|
|
7
|
+
/** Injection token holding the resolved {@link AuthzRbacModuleOptions}. */
|
|
8
|
+
export declare const AUTHZ_RBAC_OPTIONS: unique symbol;
|
|
9
|
+
/**
|
|
10
|
+
* Map the Gate's current user (whatever the app's auth layer produced) to a
|
|
11
|
+
* {@link UserRef} the store can key on. Defaults to reading `{ type, id }` / `{ id }`.
|
|
12
|
+
*/
|
|
13
|
+
export type UserRefMapper = (user: unknown) => UserRef | undefined;
|
|
14
|
+
export interface AuthzRbacModuleOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The Prisma RBAC store, OR the raw Prisma client. Pass `store` if you already built a
|
|
17
|
+
* {@link PrismaAuthzStore}; pass `client` to let the module build one (a real
|
|
18
|
+
* `PrismaClient` structurally satisfies {@link PrismaAuthzClientLike}).
|
|
19
|
+
*/
|
|
20
|
+
store?: PrismaAuthzStore;
|
|
21
|
+
/** The app-owned Prisma client; used to build the store when `store` is omitted. */
|
|
22
|
+
client?: PrismaAuthzClientLike;
|
|
23
|
+
/**
|
|
24
|
+
* Prisma is consumer-managed (schema-first) — there is no auto-create. This flag exists
|
|
25
|
+
* for parity; the store's `ensureSchema` is a no-op regardless. Default `false`.
|
|
26
|
+
*/
|
|
27
|
+
autoCreateSchema?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Derive a {@link UserRef} from the Gate's current user. Defaults to
|
|
30
|
+
* {@link defaultUserRefMapper} (`{ type, id }` or `{ id }`).
|
|
31
|
+
*/
|
|
32
|
+
userRefFrom?: UserRefMapper;
|
|
33
|
+
}
|
|
34
|
+
export interface AuthzRbacModuleAsyncOptions {
|
|
35
|
+
imports?: unknown[];
|
|
36
|
+
inject?: unknown[];
|
|
37
|
+
useFactory: (...args: unknown[]) => Promise<AuthzRbacModuleOptions> | AuthzRbacModuleOptions;
|
|
38
|
+
}
|
|
39
|
+
/** Default mapping: accept a `{ type, id }` ref, a `{ id }` object, or a bare id. */
|
|
40
|
+
export declare function defaultUserRefMapper(user: unknown): UserRef | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Wires persisted RBAC into the core authz Gate. The app provides the Prisma client (via
|
|
43
|
+
* `client`, or a pre-built `store`, through `inject`/`useFactory`) — this module NEVER
|
|
44
|
+
* hardcodes a connection token. On init it registers the RBAC permission check into the
|
|
45
|
+
* Gate via the {@link PERMISSION_PROVIDER} seam, so `gate.allows('some.permission')`
|
|
46
|
+
* consults persisted permissions. Prisma's schema is consumer-managed (no auto-create).
|
|
47
|
+
*/
|
|
48
|
+
export declare class AuthzRbacModule {
|
|
49
|
+
static forRoot(options: AuthzRbacModuleOptions): DynamicModule;
|
|
50
|
+
static forRootAsync(options: AuthzRbacModuleAsyncOptions): DynamicModule;
|
|
51
|
+
private static commonProviders;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=authz-rbac.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-rbac.module.d.ts","sourceRoot":"","sources":["../src/authz-rbac.module.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,KAAK,aAAa,EAQnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,eAAoD,CAAC;AAClF,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,eAAsD,CAAC;AAEtF;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,oFAAoF;IACpF,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;CAC9F;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAOvE;AAuED;;;;;;GAMG;AACH,qBACa,eAAe;IAC1B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,aAAa;IAa9D,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,2BAA2B,GAAG,aAAa;IAsBxE,OAAO,CAAC,MAAM,CAAC,eAAe;CAS/B"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
var AuthzRbacModule_1;
|
|
14
|
+
import { PERMISSION_PROVIDER, ROLE_PROVIDER, } from '@dudousxd/nestjs-authz';
|
|
15
|
+
import { Inject, Injectable, Module, Optional, } from '@nestjs/common';
|
|
16
|
+
import { PrismaAuthzStore } from './prisma-authz.store.js';
|
|
17
|
+
/** Injection token holding the {@link PrismaAuthzStore} the RBAC module manages. */
|
|
18
|
+
export const AUTHZ_RBAC_STORE = Symbol.for('@dudousxd/nestjs-authz-prisma:store');
|
|
19
|
+
/** Injection token holding the resolved {@link AuthzRbacModuleOptions}. */
|
|
20
|
+
export const AUTHZ_RBAC_OPTIONS = Symbol.for('@dudousxd/nestjs-authz-prisma:options');
|
|
21
|
+
/** Default mapping: accept a `{ type, id }` ref, a `{ id }` object, or a bare id. */
|
|
22
|
+
export function defaultUserRefMapper(user) {
|
|
23
|
+
if (user == null)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof user === 'string' || typeof user === 'number')
|
|
26
|
+
return user;
|
|
27
|
+
const u = user;
|
|
28
|
+
if (u.id == null)
|
|
29
|
+
return undefined;
|
|
30
|
+
const id = u.id;
|
|
31
|
+
return typeof u.type === 'string' ? { type: u.type, id } : { id };
|
|
32
|
+
}
|
|
33
|
+
/** Resolve a concrete store from the options (building one from `client` when needed). */
|
|
34
|
+
function resolveStore(options) {
|
|
35
|
+
if (options.store)
|
|
36
|
+
return options.store;
|
|
37
|
+
if (options.client)
|
|
38
|
+
return new PrismaAuthzStore(options.client);
|
|
39
|
+
throw new Error('AuthzRbacModule (prisma): provide either `store` or `client` in the options.');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The RBAC {@link PermissionProvider} the Gate consults (via the shared
|
|
43
|
+
* {@link PERMISSION_PROVIDER} token). Grants a named ability when the current user holds
|
|
44
|
+
* the matching persisted permission — the Laravel/spatie `Gate::before` grant.
|
|
45
|
+
*/
|
|
46
|
+
let RbacPermissionProvider = class RbacPermissionProvider {
|
|
47
|
+
options;
|
|
48
|
+
store;
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.store = resolveStore(options);
|
|
52
|
+
}
|
|
53
|
+
// The core `PermissionProvider` interface passes an optional `resource` (3rd
|
|
54
|
+
// arg); it is intentionally ignored here. RBAC grants are model-less,
|
|
55
|
+
// named-ability grants (the Laravel/spatie `Gate::before` grant), so the
|
|
56
|
+
// verdict never depends on a specific resource instance.
|
|
57
|
+
async hasPermission(user, permission) {
|
|
58
|
+
const map = this.options.userRefFrom ?? defaultUserRefMapper;
|
|
59
|
+
const ref = map(user);
|
|
60
|
+
if (ref === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
return this.store.userHasPermission(ref, permission);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
RbacPermissionProvider = __decorate([
|
|
66
|
+
Injectable(),
|
|
67
|
+
__param(0, Inject(AUTHZ_RBAC_OPTIONS)),
|
|
68
|
+
__metadata("design:paramtypes", [Object])
|
|
69
|
+
], RbacPermissionProvider);
|
|
70
|
+
/**
|
|
71
|
+
* The RBAC {@link RoleProvider} the Gate consults (via the shared {@link ROLE_PROVIDER}
|
|
72
|
+
* token) for coarse role-checks (`gate.hasRole('teacher')`, `@Roles('admin')`). Returns
|
|
73
|
+
* the role names the current user holds in the persisted store. Core unions these with
|
|
74
|
+
* any roles read off the user object by the default `RoleResolver`.
|
|
75
|
+
*/
|
|
76
|
+
let RbacRoleProvider = class RbacRoleProvider {
|
|
77
|
+
options;
|
|
78
|
+
store;
|
|
79
|
+
constructor(options) {
|
|
80
|
+
this.options = options;
|
|
81
|
+
this.store = resolveStore(options);
|
|
82
|
+
}
|
|
83
|
+
async getRoles(user) {
|
|
84
|
+
const map = this.options.userRefFrom ?? defaultUserRefMapper;
|
|
85
|
+
const ref = map(user);
|
|
86
|
+
if (ref === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
return this.store.getRolesForUser(ref);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
RbacRoleProvider = __decorate([
|
|
92
|
+
Injectable(),
|
|
93
|
+
__param(0, Inject(AUTHZ_RBAC_OPTIONS)),
|
|
94
|
+
__metadata("design:paramtypes", [Object])
|
|
95
|
+
], RbacRoleProvider);
|
|
96
|
+
/**
|
|
97
|
+
* Runs `ensureSchema` on bootstrap when `autoCreateSchema` is enabled. For Prisma this is
|
|
98
|
+
* a no-op (schema is consumer-managed) — kept for parity with the other adapters.
|
|
99
|
+
*/
|
|
100
|
+
let AuthzRbacBootstrap = class AuthzRbacBootstrap {
|
|
101
|
+
options;
|
|
102
|
+
constructor(options) {
|
|
103
|
+
this.options = options;
|
|
104
|
+
}
|
|
105
|
+
async onModuleInit() {
|
|
106
|
+
if (!this.options)
|
|
107
|
+
return;
|
|
108
|
+
if (this.options.autoCreateSchema !== true)
|
|
109
|
+
return;
|
|
110
|
+
await resolveStore(this.options).ensureSchema();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
AuthzRbacBootstrap = __decorate([
|
|
114
|
+
Injectable(),
|
|
115
|
+
__param(0, Optional()),
|
|
116
|
+
__param(0, Inject(AUTHZ_RBAC_OPTIONS)),
|
|
117
|
+
__metadata("design:paramtypes", [Object])
|
|
118
|
+
], AuthzRbacBootstrap);
|
|
119
|
+
/**
|
|
120
|
+
* Wires persisted RBAC into the core authz Gate. The app provides the Prisma client (via
|
|
121
|
+
* `client`, or a pre-built `store`, through `inject`/`useFactory`) — this module NEVER
|
|
122
|
+
* hardcodes a connection token. On init it registers the RBAC permission check into the
|
|
123
|
+
* Gate via the {@link PERMISSION_PROVIDER} seam, so `gate.allows('some.permission')`
|
|
124
|
+
* consults persisted permissions. Prisma's schema is consumer-managed (no auto-create).
|
|
125
|
+
*/
|
|
126
|
+
let AuthzRbacModule = AuthzRbacModule_1 = class AuthzRbacModule {
|
|
127
|
+
static forRoot(options) {
|
|
128
|
+
return {
|
|
129
|
+
module: AuthzRbacModule_1,
|
|
130
|
+
global: true,
|
|
131
|
+
providers: [
|
|
132
|
+
{ provide: AUTHZ_RBAC_OPTIONS, useValue: options },
|
|
133
|
+
{ provide: AUTHZ_RBAC_STORE, useFactory: () => resolveStore(options) },
|
|
134
|
+
...AuthzRbacModule_1.commonProviders(),
|
|
135
|
+
],
|
|
136
|
+
exports: [AUTHZ_RBAC_STORE, PERMISSION_PROVIDER, ROLE_PROVIDER],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
static forRootAsync(options) {
|
|
140
|
+
return {
|
|
141
|
+
module: AuthzRbacModule_1,
|
|
142
|
+
global: true,
|
|
143
|
+
imports: (options.imports ?? []),
|
|
144
|
+
providers: [
|
|
145
|
+
{
|
|
146
|
+
provide: AUTHZ_RBAC_OPTIONS,
|
|
147
|
+
useFactory: options.useFactory,
|
|
148
|
+
inject: (options.inject ?? []),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
provide: AUTHZ_RBAC_STORE,
|
|
152
|
+
useFactory: (opts) => resolveStore(opts),
|
|
153
|
+
inject: [AUTHZ_RBAC_OPTIONS],
|
|
154
|
+
},
|
|
155
|
+
...AuthzRbacModule_1.commonProviders(),
|
|
156
|
+
],
|
|
157
|
+
exports: [AUTHZ_RBAC_STORE, PERMISSION_PROVIDER, ROLE_PROVIDER],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
static commonProviders() {
|
|
161
|
+
return [
|
|
162
|
+
RbacPermissionProvider,
|
|
163
|
+
{ provide: PERMISSION_PROVIDER, useExisting: RbacPermissionProvider },
|
|
164
|
+
RbacRoleProvider,
|
|
165
|
+
{ provide: ROLE_PROVIDER, useExisting: RbacRoleProvider },
|
|
166
|
+
AuthzRbacBootstrap,
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
AuthzRbacModule = AuthzRbacModule_1 = __decorate([
|
|
171
|
+
Module({})
|
|
172
|
+
], AuthzRbacModule);
|
|
173
|
+
export { AuthzRbacModule };
|
|
174
|
+
//# sourceMappingURL=authz-rbac.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-rbac.module.js","sourceRoot":"","sources":["../src/authz-rbac.module.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { PRISMA_CLIENT, type PrismaAuthzClientLike, type PrismaModelDelegate, } from './prisma-client.js';
|
|
2
|
+
export { PrismaAuthzStore } from './prisma-authz.store.js';
|
|
3
|
+
export type { UserAuthz } from './prisma-authz.store.js';
|
|
4
|
+
export { AUTHZ_RBAC_OPTIONS, AUTHZ_RBAC_STORE, AuthzRbacModule, defaultUserRefMapper, } from './authz-rbac.module.js';
|
|
5
|
+
export type { AuthzRbacModuleAsyncOptions, AuthzRbacModuleOptions, UserRefMapper, } from './authz-rbac.module.js';
|
|
6
|
+
export type { UserRef, UserRefInput } from './types.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,2BAA2B,EAC3B,sBAAsB,EACtB,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAGd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type PrismaAuthzClientLike } from './prisma-client.js';
|
|
2
|
+
import type { UserRef } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A user's effective permissions: the role names they hold and the flattened set of
|
|
5
|
+
* permission names granted by those roles.
|
|
6
|
+
*/
|
|
7
|
+
export interface UserAuthz {
|
|
8
|
+
roles: string[];
|
|
9
|
+
permissions: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Prisma-backed RBAC store. Receives the app-owned Prisma client via the
|
|
13
|
+
* {@link PRISMA_CLIENT} DI token — NO internal connection ownership, NO `@prisma/client`
|
|
14
|
+
* import (it consumes the structural {@link PrismaAuthzClientLike}).
|
|
15
|
+
*
|
|
16
|
+
* Same method surface as the TypeORM/MikroORM adapters. The user is referenced BY ID ONLY
|
|
17
|
+
* — this package never owns a users table.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PrismaAuthzStore {
|
|
20
|
+
private readonly client;
|
|
21
|
+
constructor(client: PrismaAuthzClientLike);
|
|
22
|
+
/**
|
|
23
|
+
* No-op: Prisma is schema-first / consumer-managed. Declare the RBAC models in your
|
|
24
|
+
* `schema.prisma` (see {@link PrismaAuthzClientLike}) and apply them with
|
|
25
|
+
* `prisma migrate` / `prisma db push` — this adapter never runs DDL. Present for parity
|
|
26
|
+
* with the other adapters' store surface.
|
|
27
|
+
*/
|
|
28
|
+
ensureSchema(): Promise<void>;
|
|
29
|
+
/** Create the role if absent; returns its id. Idempotent and race-tolerant. */
|
|
30
|
+
createRole(name: string): Promise<string>;
|
|
31
|
+
/** Create the permission if absent; returns its id. Idempotent and race-tolerant. */
|
|
32
|
+
createPermission(name: string): Promise<string>;
|
|
33
|
+
private findRoleId;
|
|
34
|
+
private findPermissionId;
|
|
35
|
+
/** Grant a permission to a role (creating both by name if needed). Idempotent. */
|
|
36
|
+
givePermissionToRole(roleName: string, permissionName: string): Promise<void>;
|
|
37
|
+
/** Revoke a permission from a role. No-op if either is absent or not linked. */
|
|
38
|
+
revokePermissionFromRole(roleName: string, permissionName: string): Promise<void>;
|
|
39
|
+
/** Assign a role to a user (creating the role by name if needed). Idempotent. */
|
|
40
|
+
assignRole(user: UserRef, roleName: string): Promise<void>;
|
|
41
|
+
/** Remove a role from a user. No-op if the role or assignment is absent. */
|
|
42
|
+
removeRole(user: UserRef, roleName: string): Promise<void>;
|
|
43
|
+
/** The role names a user holds. */
|
|
44
|
+
getRolesForUser(user: UserRef): Promise<string[]>;
|
|
45
|
+
/** The flattened, distinct permission names a user has via their roles. */
|
|
46
|
+
getPermissionsForUser(user: UserRef): Promise<string[]>;
|
|
47
|
+
/** A user's roles + effective permissions in one shot. */
|
|
48
|
+
getUserAuthz(user: UserRef): Promise<UserAuthz>;
|
|
49
|
+
/** True when the user holds `permission` through any of their roles. */
|
|
50
|
+
userHasPermission(user: UserRef, permission: string): Promise<boolean>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=prisma-authz.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-authz.store.d.ts","sourceRoot":"","sources":["../src/prisma-authz.store.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAe1C;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,qBACa,gBAAgB;IAGzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,qBAAqB;IAGhD;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAMnC,+EAA+E;IACzE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB/C,qFAAqF;IAC/E,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAevC,UAAU;YAKV,gBAAgB;IAO9B,kFAAkF;IAC5E,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnF,gFAAgF;IAC1E,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASvF,iFAAiF;IAC3E,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhE,4EAA4E;IACtE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAShE,mCAAmC;IAC7B,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAWvD,2EAA2E;IACrE,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAkB7D,0DAA0D;IACpD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAQrD,wEAAwE;IAClE,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAI7E"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
14
|
+
import { PRISMA_CLIENT } from './prisma-client.js';
|
|
15
|
+
// Optional Nest DI decorators — declared structurally so this package does not need a
|
|
16
|
+
// hard runtime dependency on @nestjs/common for the POJO store (the module supplies the
|
|
17
|
+
// real Inject token). The store is usable as a plain POJO: `new PrismaAuthzStore(client)`.
|
|
18
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
19
|
+
/** Normalize a {@link UserRef} to `{ type, id }` with `id` stringified. */
|
|
20
|
+
function normalizeUserRef(ref) {
|
|
21
|
+
if (typeof ref === 'string' || typeof ref === 'number') {
|
|
22
|
+
return { type: 'user', id: String(ref) };
|
|
23
|
+
}
|
|
24
|
+
return { type: ref.type ?? 'user', id: String(ref.id) };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Prisma-backed RBAC store. Receives the app-owned Prisma client via the
|
|
28
|
+
* {@link PRISMA_CLIENT} DI token — NO internal connection ownership, NO `@prisma/client`
|
|
29
|
+
* import (it consumes the structural {@link PrismaAuthzClientLike}).
|
|
30
|
+
*
|
|
31
|
+
* Same method surface as the TypeORM/MikroORM adapters. The user is referenced BY ID ONLY
|
|
32
|
+
* — this package never owns a users table.
|
|
33
|
+
*/
|
|
34
|
+
let PrismaAuthzStore = class PrismaAuthzStore {
|
|
35
|
+
client;
|
|
36
|
+
constructor(client) {
|
|
37
|
+
this.client = client;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* No-op: Prisma is schema-first / consumer-managed. Declare the RBAC models in your
|
|
41
|
+
* `schema.prisma` (see {@link PrismaAuthzClientLike}) and apply them with
|
|
42
|
+
* `prisma migrate` / `prisma db push` — this adapter never runs DDL. Present for parity
|
|
43
|
+
* with the other adapters' store surface.
|
|
44
|
+
*/
|
|
45
|
+
async ensureSchema() {
|
|
46
|
+
// Intentionally empty — see the doc comment.
|
|
47
|
+
}
|
|
48
|
+
// --- roles & permissions (idempotent upserts by name) ---
|
|
49
|
+
/** Create the role if absent; returns its id. Idempotent and race-tolerant. */
|
|
50
|
+
async createRole(name) {
|
|
51
|
+
const existing = await this.findRoleId(name);
|
|
52
|
+
if (existing)
|
|
53
|
+
return existing;
|
|
54
|
+
try {
|
|
55
|
+
const row = await this.client.role.create({
|
|
56
|
+
data: { id: randomUUID(), name, guard: null, createdAt: new Date() },
|
|
57
|
+
});
|
|
58
|
+
return row.id;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// A concurrent insert won the race on the unique `name`; re-read.
|
|
62
|
+
const id = await this.findRoleId(name);
|
|
63
|
+
if (!id)
|
|
64
|
+
throw new Error(`Failed to create or resolve role "${name}".`);
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Create the permission if absent; returns its id. Idempotent and race-tolerant. */
|
|
69
|
+
async createPermission(name) {
|
|
70
|
+
const existing = await this.findPermissionId(name);
|
|
71
|
+
if (existing)
|
|
72
|
+
return existing;
|
|
73
|
+
try {
|
|
74
|
+
const row = await this.client.permission.create({
|
|
75
|
+
data: { id: randomUUID(), name, guard: null, createdAt: new Date() },
|
|
76
|
+
});
|
|
77
|
+
return row.id;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
const id = await this.findPermissionId(name);
|
|
81
|
+
if (!id)
|
|
82
|
+
throw new Error(`Failed to create or resolve permission "${name}".`);
|
|
83
|
+
return id;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async findRoleId(name) {
|
|
87
|
+
const row = await this.client.role.findFirst({ where: { name } });
|
|
88
|
+
return row?.id;
|
|
89
|
+
}
|
|
90
|
+
async findPermissionId(name) {
|
|
91
|
+
const row = await this.client.permission.findFirst({ where: { name } });
|
|
92
|
+
return row?.id;
|
|
93
|
+
}
|
|
94
|
+
// --- role ↔ permission ---
|
|
95
|
+
/** Grant a permission to a role (creating both by name if needed). Idempotent. */
|
|
96
|
+
async givePermissionToRole(roleName, permissionName) {
|
|
97
|
+
const roleId = await this.createRole(roleName);
|
|
98
|
+
const permissionId = await this.createPermission(permissionName);
|
|
99
|
+
const existing = await this.client.rolePermission.findFirst({
|
|
100
|
+
where: { roleId, permissionId },
|
|
101
|
+
});
|
|
102
|
+
if (existing)
|
|
103
|
+
return;
|
|
104
|
+
await this.client.rolePermission.create({ data: { roleId, permissionId } });
|
|
105
|
+
}
|
|
106
|
+
/** Revoke a permission from a role. No-op if either is absent or not linked. */
|
|
107
|
+
async revokePermissionFromRole(roleName, permissionName) {
|
|
108
|
+
const roleId = await this.findRoleId(roleName);
|
|
109
|
+
const permissionId = await this.findPermissionId(permissionName);
|
|
110
|
+
if (!roleId || !permissionId)
|
|
111
|
+
return;
|
|
112
|
+
await this.client.rolePermission.deleteMany({ where: { roleId, permissionId } });
|
|
113
|
+
}
|
|
114
|
+
// --- user ↔ role ---
|
|
115
|
+
/** Assign a role to a user (creating the role by name if needed). Idempotent. */
|
|
116
|
+
async assignRole(user, roleName) {
|
|
117
|
+
const { type, id } = normalizeUserRef(user);
|
|
118
|
+
const roleId = await this.createRole(roleName);
|
|
119
|
+
const existing = await this.client.userRole.findFirst({
|
|
120
|
+
where: { userType: type, userId: id, roleId },
|
|
121
|
+
});
|
|
122
|
+
if (existing)
|
|
123
|
+
return;
|
|
124
|
+
await this.client.userRole.create({ data: { userType: type, userId: id, roleId } });
|
|
125
|
+
}
|
|
126
|
+
/** Remove a role from a user. No-op if the role or assignment is absent. */
|
|
127
|
+
async removeRole(user, roleName) {
|
|
128
|
+
const { type, id } = normalizeUserRef(user);
|
|
129
|
+
const roleId = await this.findRoleId(roleName);
|
|
130
|
+
if (!roleId)
|
|
131
|
+
return;
|
|
132
|
+
await this.client.userRole.deleteMany({ where: { userType: type, userId: id, roleId } });
|
|
133
|
+
}
|
|
134
|
+
// --- queries ---
|
|
135
|
+
/** The role names a user holds. */
|
|
136
|
+
async getRolesForUser(user) {
|
|
137
|
+
const { type, id } = normalizeUserRef(user);
|
|
138
|
+
const assignments = await this.client.userRole.findMany({
|
|
139
|
+
where: { userType: type, userId: id },
|
|
140
|
+
});
|
|
141
|
+
if (assignments.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
const roleIds = assignments.map((a) => a.roleId);
|
|
144
|
+
const roles = await this.client.role.findMany({ where: { id: { in: roleIds } } });
|
|
145
|
+
return roles.map((r) => r.name);
|
|
146
|
+
}
|
|
147
|
+
/** The flattened, distinct permission names a user has via their roles. */
|
|
148
|
+
async getPermissionsForUser(user) {
|
|
149
|
+
const { type, id } = normalizeUserRef(user);
|
|
150
|
+
const assignments = await this.client.userRole.findMany({
|
|
151
|
+
where: { userType: type, userId: id },
|
|
152
|
+
});
|
|
153
|
+
if (assignments.length === 0)
|
|
154
|
+
return [];
|
|
155
|
+
const roleIds = assignments.map((a) => a.roleId);
|
|
156
|
+
const links = await this.client.rolePermission.findMany({
|
|
157
|
+
where: { roleId: { in: roleIds } },
|
|
158
|
+
});
|
|
159
|
+
if (links.length === 0)
|
|
160
|
+
return [];
|
|
161
|
+
const permissionIds = [...new Set(links.map((l) => l.permissionId))];
|
|
162
|
+
const permissions = await this.client.permission.findMany({
|
|
163
|
+
where: { id: { in: permissionIds } },
|
|
164
|
+
});
|
|
165
|
+
return [...new Set(permissions.map((p) => p.name))];
|
|
166
|
+
}
|
|
167
|
+
/** A user's roles + effective permissions in one shot. */
|
|
168
|
+
async getUserAuthz(user) {
|
|
169
|
+
const [roles, permissions] = await Promise.all([
|
|
170
|
+
this.getRolesForUser(user),
|
|
171
|
+
this.getPermissionsForUser(user),
|
|
172
|
+
]);
|
|
173
|
+
return { roles, permissions };
|
|
174
|
+
}
|
|
175
|
+
/** True when the user holds `permission` through any of their roles. */
|
|
176
|
+
async userHasPermission(user, permission) {
|
|
177
|
+
const permissions = await this.getPermissionsForUser(user);
|
|
178
|
+
return permissions.includes(permission);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
PrismaAuthzStore = __decorate([
|
|
182
|
+
Injectable(),
|
|
183
|
+
__param(0, Inject(PRISMA_CLIENT)),
|
|
184
|
+
__metadata("design:paramtypes", [Object])
|
|
185
|
+
], PrismaAuthzStore);
|
|
186
|
+
export { PrismaAuthzStore };
|
|
187
|
+
//# sourceMappingURL=prisma-authz.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-authz.store.js","sourceRoot":"","sources":["../src/prisma-authz.store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural typings + DI token for the Prisma client consumed by
|
|
3
|
+
* {@link PrismaAuthzStore}.
|
|
4
|
+
*
|
|
5
|
+
* The adapter deliberately does NOT import a generated `@prisma/client` type.
|
|
6
|
+
* Instead it depends on a minimal structural interface describing only the model
|
|
7
|
+
* delegate methods it uses. This keeps the package free of any `prisma generate`
|
|
8
|
+
* step and decouples it from the consumer's generated client location/version — a
|
|
9
|
+
* real `PrismaClient` instance structurally satisfies {@link PrismaAuthzClientLike},
|
|
10
|
+
* so you can inject it directly.
|
|
11
|
+
*
|
|
12
|
+
* ## Required Prisma models
|
|
13
|
+
*
|
|
14
|
+
* The consumer's `schema.prisma` must declare these models (the user is referenced
|
|
15
|
+
* **by id only** — this package never owns a users table):
|
|
16
|
+
*
|
|
17
|
+
* ```prisma
|
|
18
|
+
* model Role {
|
|
19
|
+
* id String @id
|
|
20
|
+
* name String @unique
|
|
21
|
+
* guard String?
|
|
22
|
+
* createdAt DateTime @default(now())
|
|
23
|
+
*
|
|
24
|
+
* @@map("authz_roles")
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* model Permission {
|
|
28
|
+
* id String @id
|
|
29
|
+
* name String @unique
|
|
30
|
+
* guard String?
|
|
31
|
+
* createdAt DateTime @default(now())
|
|
32
|
+
*
|
|
33
|
+
* @@map("authz_permissions")
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* model RolePermission {
|
|
37
|
+
* roleId String
|
|
38
|
+
* permissionId String
|
|
39
|
+
*
|
|
40
|
+
* @@id([roleId, permissionId])
|
|
41
|
+
* @@map("authz_role_permission")
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* model UserRole {
|
|
45
|
+
* userType String
|
|
46
|
+
* userId String
|
|
47
|
+
* roleId String
|
|
48
|
+
*
|
|
49
|
+
* @@id([userType, userId, roleId])
|
|
50
|
+
* @@index([userType, userId])
|
|
51
|
+
* @@map("authz_user_role")
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Apply it with `prisma migrate` / `prisma db push` — this adapter is
|
|
56
|
+
* consumer-managed and never runs DDL.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Minimal structural view of a Prisma model delegate (the methods the store calls).
|
|
60
|
+
*
|
|
61
|
+
* The arg/return types are intentionally `any`: a generated Prisma delegate's method
|
|
62
|
+
* signatures are far narrower (model-specific), and a structural `unknown`/`Record` here
|
|
63
|
+
* would make a real `PrismaClient` *not* assignable to this interface (arg positions are
|
|
64
|
+
* contravariant). `any` keeps a concrete client structurally compatible without pulling in
|
|
65
|
+
* `@prisma/client`.
|
|
66
|
+
*/
|
|
67
|
+
export interface PrismaModelDelegate {
|
|
68
|
+
create(args: {
|
|
69
|
+
data: any;
|
|
70
|
+
}): Promise<any>;
|
|
71
|
+
findFirst(args: {
|
|
72
|
+
where: any;
|
|
73
|
+
}): Promise<any>;
|
|
74
|
+
findMany(args: {
|
|
75
|
+
where: any;
|
|
76
|
+
}): Promise<any[]>;
|
|
77
|
+
deleteMany(args: {
|
|
78
|
+
where: any;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
count: number;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
/** Minimal structural view of a Prisma client exposing the four RBAC models. */
|
|
84
|
+
export interface PrismaAuthzClientLike {
|
|
85
|
+
role: PrismaModelDelegate;
|
|
86
|
+
permission: PrismaModelDelegate;
|
|
87
|
+
rolePermission: PrismaModelDelegate;
|
|
88
|
+
userRole: PrismaModelDelegate;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* DI token for the app-provided Prisma client ({@link PrismaAuthzClientLike})
|
|
92
|
+
* injected into {@link PrismaAuthzStore}.
|
|
93
|
+
*/
|
|
94
|
+
export declare const PRISMA_CLIENT: unique symbol;
|
|
95
|
+
//# sourceMappingURL=prisma-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-client.d.ts","sourceRoot":"","sources":["../src/prisma-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAElC,MAAM,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE1C,SAAS,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE9C,QAAQ,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAE/C,UAAU,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAED,gFAAgF;AAChF,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,UAAU,EAAE,mBAAmB,CAAC;IAChC,cAAc,EAAE,mBAAmB,CAAC;IACpC,QAAQ,EAAE,mBAAmB,CAAC;CAC/B;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,eAAqD,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural typings + DI token for the Prisma client consumed by
|
|
3
|
+
* {@link PrismaAuthzStore}.
|
|
4
|
+
*
|
|
5
|
+
* The adapter deliberately does NOT import a generated `@prisma/client` type.
|
|
6
|
+
* Instead it depends on a minimal structural interface describing only the model
|
|
7
|
+
* delegate methods it uses. This keeps the package free of any `prisma generate`
|
|
8
|
+
* step and decouples it from the consumer's generated client location/version — a
|
|
9
|
+
* real `PrismaClient` instance structurally satisfies {@link PrismaAuthzClientLike},
|
|
10
|
+
* so you can inject it directly.
|
|
11
|
+
*
|
|
12
|
+
* ## Required Prisma models
|
|
13
|
+
*
|
|
14
|
+
* The consumer's `schema.prisma` must declare these models (the user is referenced
|
|
15
|
+
* **by id only** — this package never owns a users table):
|
|
16
|
+
*
|
|
17
|
+
* ```prisma
|
|
18
|
+
* model Role {
|
|
19
|
+
* id String @id
|
|
20
|
+
* name String @unique
|
|
21
|
+
* guard String?
|
|
22
|
+
* createdAt DateTime @default(now())
|
|
23
|
+
*
|
|
24
|
+
* @@map("authz_roles")
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* model Permission {
|
|
28
|
+
* id String @id
|
|
29
|
+
* name String @unique
|
|
30
|
+
* guard String?
|
|
31
|
+
* createdAt DateTime @default(now())
|
|
32
|
+
*
|
|
33
|
+
* @@map("authz_permissions")
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* model RolePermission {
|
|
37
|
+
* roleId String
|
|
38
|
+
* permissionId String
|
|
39
|
+
*
|
|
40
|
+
* @@id([roleId, permissionId])
|
|
41
|
+
* @@map("authz_role_permission")
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* model UserRole {
|
|
45
|
+
* userType String
|
|
46
|
+
* userId String
|
|
47
|
+
* roleId String
|
|
48
|
+
*
|
|
49
|
+
* @@id([userType, userId, roleId])
|
|
50
|
+
* @@index([userType, userId])
|
|
51
|
+
* @@map("authz_user_role")
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Apply it with `prisma migrate` / `prisma db push` — this adapter is
|
|
56
|
+
* consumer-managed and never runs DDL.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* DI token for the app-provided Prisma client ({@link PrismaAuthzClientLike})
|
|
60
|
+
* injected into {@link PrismaAuthzStore}.
|
|
61
|
+
*/
|
|
62
|
+
export const PRISMA_CLIENT = Symbol.for('@dudousxd/nestjs-authz-prisma:client');
|
|
63
|
+
//# sourceMappingURL=prisma-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-client.js","sourceRoot":"","sources":["../src/prisma-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AA8BH;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reference to a user. Matches nestjs-context's `UserRef` shape (`{ type, id }`),
|
|
3
|
+
* but the store accepts either a full ref or a bare id (defaulting `type` to `'user'`).
|
|
4
|
+
*/
|
|
5
|
+
export interface UserRefInput {
|
|
6
|
+
type?: string;
|
|
7
|
+
id: string | number;
|
|
8
|
+
}
|
|
9
|
+
/** A user reference, or just its id (treated as `{ type: 'user', id }`). */
|
|
10
|
+
export type UserRef = UserRefInput | string | number;
|
|
11
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dudousxd/nestjs-authz-prisma",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Prisma RBAC persistence for @dudousxd/nestjs-authz — roles, permissions, and a Gate seam (zero connection ownership, structural client).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DavideCarvalho/nestjs-authz.git",
|
|
9
|
+
"directory": "packages/prisma"
|
|
10
|
+
},
|
|
11
|
+
"author": "Davi Carvalho <davi@goflip.ai>",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@dudousxd/nestjs-authz": ">=0.1.0",
|
|
29
|
+
"@nestjs/common": ">=10.0.0",
|
|
30
|
+
"@nestjs/core": ">=10.0.0",
|
|
31
|
+
"reflect-metadata": ">=0.1.13"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@nestjs/common": "^11.0.0",
|
|
35
|
+
"@nestjs/core": "^11.0.0",
|
|
36
|
+
"@nestjs/testing": "^11.0.0",
|
|
37
|
+
"@types/node": "^20.0.0",
|
|
38
|
+
"reflect-metadata": "^0.2.2",
|
|
39
|
+
"rxjs": "^7.8.1",
|
|
40
|
+
"typescript": "^5.4.0",
|
|
41
|
+
"vitest": "^3.0.0",
|
|
42
|
+
"@dudousxd/nestjs-authz": "^0.4.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"nestjs",
|
|
49
|
+
"authorization",
|
|
50
|
+
"authz",
|
|
51
|
+
"rbac",
|
|
52
|
+
"roles",
|
|
53
|
+
"permissions",
|
|
54
|
+
"prisma"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc -p tsconfig.json",
|
|
58
|
+
"test": "vitest run --passWithNoTests",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
61
|
+
}
|
|
62
|
+
}
|