@tenxyte/core 0.1.5 → 0.9.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/README.md +184 -0
- package/dist/index.cjs +951 -496
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1709 -1265
- package/dist/index.d.ts +1709 -1265
- package/dist/index.js +919 -464
- package/dist/index.js.map +1 -1
- package/package.json +70 -66
- package/src/client.ts +50 -21
- package/src/http/client.ts +162 -162
- package/src/http/index.ts +1 -1
- package/src/http/interceptors.ts +117 -117
- package/src/index.ts +7 -7
- package/src/modules/ai.ts +178 -0
- package/src/modules/auth.ts +116 -95
- package/src/modules/b2b.ts +177 -0
- package/src/modules/rbac.ts +207 -160
- package/src/modules/security.ts +313 -122
- package/src/modules/user.ts +95 -80
- package/src/storage/cookie.ts +39 -39
- package/src/storage/index.ts +29 -29
- package/src/storage/localStorage.ts +75 -75
- package/src/storage/memory.ts +30 -30
- package/src/types/index.ts +152 -150
- package/src/utils/base64url.ts +25 -0
- package/src/utils/device_info.ts +94 -94
- package/src/utils/events.ts +71 -71
- package/src/utils/jwt.ts +51 -51
- package/tests/http.test.ts +144 -144
- package/tests/modules/auth.test.ts +93 -93
- package/tests/modules/rbac.test.ts +95 -95
- package/tests/modules/security.test.ts +85 -75
- package/tests/modules/user.test.ts +76 -76
- package/tests/storage.test.ts +96 -96
- package/tests/utils.test.ts +71 -71
- package/tsup.config.ts +10 -10
- package/vitest.config.ts +7 -7
package/src/modules/rbac.ts
CHANGED
|
@@ -1,160 +1,207 @@
|
|
|
1
|
-
import { TenxyteHttpClient } from '../http/client';
|
|
2
|
-
import { decodeJwt, DecodedTenxyteToken } from '../utils/jwt';
|
|
3
|
-
|
|
4
|
-
export interface Role {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
is_default?: boolean;
|
|
9
|
-
permissions?: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface Permission {
|
|
13
|
-
id: string;
|
|
14
|
-
code: string;
|
|
15
|
-
name: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class RbacModule {
|
|
20
|
-
private cachedToken: string | null = null;
|
|
21
|
-
|
|
22
|
-
constructor(private client: TenxyteHttpClient) { }
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Cache a
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
async
|
|
109
|
-
return this.client.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
async
|
|
124
|
-
return this.client.
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
1
|
+
import { TenxyteHttpClient } from '../http/client';
|
|
2
|
+
import { decodeJwt, DecodedTenxyteToken } from '../utils/jwt';
|
|
3
|
+
|
|
4
|
+
export interface Role {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
is_default?: boolean;
|
|
9
|
+
permissions?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Permission {
|
|
13
|
+
id: string;
|
|
14
|
+
code: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class RbacModule {
|
|
20
|
+
private cachedToken: string | null = null;
|
|
21
|
+
|
|
22
|
+
constructor(private client: TenxyteHttpClient) { }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Cache a decoded JWT payload locally to perform parameter-less synchronous permission checks.
|
|
26
|
+
* Usually invoked automatically by the system upon login or token refresh.
|
|
27
|
+
* @param token - The raw JWT access token encoded string.
|
|
28
|
+
*/
|
|
29
|
+
setToken(token: string | null) {
|
|
30
|
+
this.cachedToken = token;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private getDecodedToken(token?: string): DecodedTenxyteToken | null {
|
|
34
|
+
const t = token || this.cachedToken;
|
|
35
|
+
if (!t) return null;
|
|
36
|
+
return decodeJwt(t);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Synchronous Checks --- //
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Synchronously deeply inspects the cached (or provided) JWT to determine if the user has a specific Role.
|
|
43
|
+
* @param role - The exact code name of the Role.
|
|
44
|
+
* @param token - (Optional) Provide a specific token overriding the cached one.
|
|
45
|
+
*/
|
|
46
|
+
hasRole(role: string, token?: string): boolean {
|
|
47
|
+
const decoded = this.getDecodedToken(token);
|
|
48
|
+
if (!decoded?.roles) return false;
|
|
49
|
+
return decoded.roles.includes(role);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Evaluates if the active session holds AT LEAST ONE of the listed Roles.
|
|
54
|
+
* @param roles - An array of Role codes.
|
|
55
|
+
*/
|
|
56
|
+
hasAnyRole(roles: string[], token?: string): boolean {
|
|
57
|
+
const decoded = this.getDecodedToken(token);
|
|
58
|
+
if (!decoded?.roles) return false;
|
|
59
|
+
return roles.some(r => decoded.roles!.includes(r));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Evaluates if the active session holds ALL of the listed Roles concurrently.
|
|
64
|
+
* @param roles - An array of Role codes.
|
|
65
|
+
*/
|
|
66
|
+
hasAllRoles(roles: string[], token?: string): boolean {
|
|
67
|
+
const decoded = this.getDecodedToken(token);
|
|
68
|
+
if (!decoded?.roles) return false;
|
|
69
|
+
return roles.every(r => decoded.roles!.includes(r));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Synchronously deeply inspects the cached (or provided) JWT to determine if the user has a specific granular Permission.
|
|
74
|
+
* @param permission - The exact code name of the Permission (e.g., 'invoices.read').
|
|
75
|
+
*/
|
|
76
|
+
hasPermission(permission: string, token?: string): boolean {
|
|
77
|
+
const decoded = this.getDecodedToken(token);
|
|
78
|
+
if (!decoded?.permissions) return false;
|
|
79
|
+
return decoded.permissions.includes(permission);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Evaluates if the active session holds AT LEAST ONE of the listed Permissions.
|
|
84
|
+
*/
|
|
85
|
+
hasAnyPermission(permissions: string[], token?: string): boolean {
|
|
86
|
+
const decoded = this.getDecodedToken(token);
|
|
87
|
+
if (!decoded?.permissions) return false;
|
|
88
|
+
return permissions.some(p => decoded.permissions!.includes(p));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Evaluates if the active session holds ALL of the listed Permissions concurrently.
|
|
93
|
+
*/
|
|
94
|
+
hasAllPermissions(permissions: string[], token?: string): boolean {
|
|
95
|
+
const decoded = this.getDecodedToken(token);
|
|
96
|
+
if (!decoded?.permissions) return false;
|
|
97
|
+
return permissions.every(p => decoded.permissions!.includes(p));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- Roles CRUD --- //
|
|
101
|
+
|
|
102
|
+
/** Fetch all application global Roles structure */
|
|
103
|
+
async listRoles(): Promise<Role[]> {
|
|
104
|
+
return this.client.get<Role[]>('/api/v1/auth/roles/');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Create a new architectural Role inside Tenxyte */
|
|
108
|
+
async createRole(data: { name: string; description?: string; permission_codes?: string[]; is_default?: boolean }): Promise<Role> {
|
|
109
|
+
return this.client.post<Role>('/api/v1/auth/roles/', data);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Get detailed metadata defining a single bounded Role */
|
|
113
|
+
async getRole(roleId: string): Promise<Role> {
|
|
114
|
+
return this.client.get<Role>(`/api/v1/auth/roles/${roleId}/`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Modify properties bounding a Role */
|
|
118
|
+
async updateRole(roleId: string, data: { name?: string; description?: string; permission_codes?: string[]; is_default?: boolean }): Promise<Role> {
|
|
119
|
+
return this.client.put<Role>(`/api/v1/auth/roles/${roleId}/`, data);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Unbind and destruct a Role from the global Tenant. (Dangerous, implies cascading permission unbindings) */
|
|
123
|
+
async deleteRole(roleId: string): Promise<void> {
|
|
124
|
+
return this.client.delete<void>(`/api/v1/auth/roles/${roleId}/`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Role Permissions Management --- //
|
|
128
|
+
|
|
129
|
+
async getRolePermissions(roleId: string): Promise<Permission[]> {
|
|
130
|
+
return this.client.get<Permission[]>(`/api/v1/auth/roles/${roleId}/permissions/`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async addPermissionsToRole(roleId: string, permission_codes: string[]): Promise<void> {
|
|
134
|
+
return this.client.post<void>(`/api/v1/auth/roles/${roleId}/permissions/`, { permission_codes });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async removePermissionsFromRole(roleId: string, permission_codes: string[]): Promise<void> {
|
|
138
|
+
return this.client.delete<void>(`/api/v1/auth/roles/${roleId}/permissions/`, {
|
|
139
|
+
// Note: DELETE request with body is supported via our fetch wrapper if enabled,
|
|
140
|
+
// or we might need to rely on query strings. The schema specifies body or query.
|
|
141
|
+
// Let's pass it in body via a custom config or URL params.
|
|
142
|
+
body: { permission_codes }
|
|
143
|
+
} as any);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- Permissions CRUD --- //
|
|
147
|
+
|
|
148
|
+
/** Enumerates all available fine-grained Permissions inside this Tenant scope. */
|
|
149
|
+
async listPermissions(): Promise<Permission[]> {
|
|
150
|
+
return this.client.get<Permission[]>('/api/v1/auth/permissions/');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Bootstraps a new granular Permission flag (e.g. `billing.refund`). */
|
|
154
|
+
async createPermission(data: { code: string; name: string; description?: string; parent_code?: string }): Promise<Permission> {
|
|
155
|
+
return this.client.post<Permission>('/api/v1/auth/permissions/', data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Retrieves an existing atomic Permission construct. */
|
|
159
|
+
async getPermission(permissionId: string): Promise<Permission> {
|
|
160
|
+
return this.client.get<Permission>(`/api/v1/auth/permissions/${permissionId}/`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Edits the human readable description or structural dependencies of a Permission. */
|
|
164
|
+
async updatePermission(permissionId: string, data: { name?: string; description?: string }): Promise<Permission> {
|
|
165
|
+
return this.client.put<Permission>(`/api/v1/auth/permissions/${permissionId}/`, data);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Destroys an atomic Permission permanently. Any Roles referencing it will be stripped of this grant automatically. */
|
|
169
|
+
async deletePermission(permissionId: string): Promise<void> {
|
|
170
|
+
return this.client.delete<void>(`/api/v1/auth/permissions/${permissionId}/`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- Direct Assignment (Users) --- //
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Attach a given Role globally to a user entity.
|
|
177
|
+
* Use sparingly if B2B multi-tenancy contexts are preferred.
|
|
178
|
+
*/
|
|
179
|
+
async assignRoleToUser(userId: string, roleCode: string): Promise<void> {
|
|
180
|
+
return this.client.post<void>(`/api/v1/auth/users/${userId}/roles/`, { role_code: roleCode });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Unbind a global Role from a user entity.
|
|
185
|
+
*/
|
|
186
|
+
async removeRoleFromUser(userId: string, roleCode: string): Promise<void> {
|
|
187
|
+
return this.client.delete<void>(`/api/v1/auth/users/${userId}/roles/`, {
|
|
188
|
+
params: { role_code: roleCode }
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Ad-Hoc directly attach specific granular Permissions to a single User, bypassing Role boundaries.
|
|
194
|
+
*/
|
|
195
|
+
async assignPermissionsToUser(userId: string, permissionCodes: string[]): Promise<void> {
|
|
196
|
+
return this.client.post<void>(`/api/v1/auth/users/${userId}/permissions/`, { permission_codes: permissionCodes });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Ad-Hoc strip direct granular Permissions bindings from a specific User.
|
|
201
|
+
*/
|
|
202
|
+
async removePermissionsFromUser(userId: string, permissionCodes: string[]): Promise<void> {
|
|
203
|
+
return this.client.delete<void>(`/api/v1/auth/users/${userId}/permissions/`, {
|
|
204
|
+
body: { permission_codes: permissionCodes }
|
|
205
|
+
} as any);
|
|
206
|
+
}
|
|
207
|
+
}
|