@stacksjs/auth 0.70.258 → 0.70.260
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/dist/authentication.js +1 -415
- package/dist/authenticator.js +1 -14
- package/dist/authorizable.js +1 -28
- package/dist/client.js +1 -26
- package/dist/cookie-auth.js +1 -65
- package/dist/email-verification.js +2 -110
- package/dist/gate.js +1 -203
- package/dist/index.js +1 -28
- package/dist/internal-constants.js +1 -1
- package/dist/middleware.js +1 -28
- package/dist/passkey.js +1 -76
- package/dist/password/reset.js +2 -155
- package/dist/permissions.js +1 -4
- package/dist/policy.js +1 -91
- package/dist/rate-limiter.js +1 -91
- package/dist/rbac-seed.js +1 -30
- package/dist/rbac-store-bqb.js +1 -180
- package/dist/rbac.js +1 -326
- package/dist/register.js +1 -43
- package/dist/session-auth.js +1 -151
- package/dist/team.js +1 -88
- package/dist/token.js +1 -21
- package/dist/tokens.js +42 -373
- package/dist/two-factor.js +1 -113
- package/dist/user.js +1 -38
- package/package.json +13 -6
package/dist/rbac.js
CHANGED
|
@@ -1,326 +1 @@
|
|
|
1
|
-
var {require}=import.meta;class BoundedMap {
|
|
2
|
-
max;
|
|
3
|
-
map = new Map;
|
|
4
|
-
constructor(max) {
|
|
5
|
-
this.max = max;
|
|
6
|
-
}
|
|
7
|
-
get(key) {
|
|
8
|
-
return this.map.get(key);
|
|
9
|
-
}
|
|
10
|
-
has(key) {
|
|
11
|
-
return this.map.has(key);
|
|
12
|
-
}
|
|
13
|
-
set(key, value) {
|
|
14
|
-
if (this.map.has(key))
|
|
15
|
-
this.map.delete(key);
|
|
16
|
-
this.map.set(key, value);
|
|
17
|
-
if (this.map.size > this.max) {
|
|
18
|
-
const oldest = this.map.keys().next().value;
|
|
19
|
-
if (oldest !== void 0)
|
|
20
|
-
this.map.delete(oldest);
|
|
21
|
-
}
|
|
22
|
-
return this;
|
|
23
|
-
}
|
|
24
|
-
delete(key) {
|
|
25
|
-
return this.map.delete(key);
|
|
26
|
-
}
|
|
27
|
-
clear() {
|
|
28
|
-
this.map.clear();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
const RBAC_USER_CACHE_MAX = 1e4, cache = {
|
|
32
|
-
userRoles: new BoundedMap(RBAC_USER_CACHE_MAX),
|
|
33
|
-
userPermissions: new BoundedMap(RBAC_USER_CACHE_MAX),
|
|
34
|
-
rolePermissions: new BoundedMap(RBAC_USER_CACHE_MAX),
|
|
35
|
-
roles: new Map,
|
|
36
|
-
permissions: new Map
|
|
37
|
-
};
|
|
38
|
-
let store = null;
|
|
39
|
-
export function setRbacStore(rbacStore) {
|
|
40
|
-
store = rbacStore;
|
|
41
|
-
flushRbacCache();
|
|
42
|
-
}
|
|
43
|
-
function getStore() {
|
|
44
|
-
if (!store) {
|
|
45
|
-
const { createBqbRbacStore } = require("./rbac-store-bqb");
|
|
46
|
-
store = createBqbRbacStore();
|
|
47
|
-
}
|
|
48
|
-
return store;
|
|
49
|
-
}
|
|
50
|
-
export function flushRbacCache() {
|
|
51
|
-
cache.userRoles.clear();
|
|
52
|
-
cache.userPermissions.clear();
|
|
53
|
-
cache.rolePermissions.clear();
|
|
54
|
-
cache.roles.clear();
|
|
55
|
-
cache.permissions.clear();
|
|
56
|
-
}
|
|
57
|
-
function getUserId(user) {
|
|
58
|
-
if (typeof user === "number") {
|
|
59
|
-
if (!Number.isFinite(user) || user <= 0)
|
|
60
|
-
throw TypeError("RBAC user id must be a positive number");
|
|
61
|
-
return user;
|
|
62
|
-
}
|
|
63
|
-
const id = user.id;
|
|
64
|
-
if (typeof id !== "number" || !Number.isFinite(id) || id <= 0)
|
|
65
|
-
throw TypeError(`RBAC user id must be a positive number, got ${typeof id} (${String(id)})`);
|
|
66
|
-
return id;
|
|
67
|
-
}
|
|
68
|
-
export async function createRole(name, guardName = "web", description) {
|
|
69
|
-
const role = await getStore().createRole(name, guardName, description);
|
|
70
|
-
cache.roles.set(`${name}:${guardName}`, role);
|
|
71
|
-
return role;
|
|
72
|
-
}
|
|
73
|
-
export async function findRole(name, guardName = "web") {
|
|
74
|
-
const cacheKey = `${name}:${guardName}`;
|
|
75
|
-
if (cache.roles.has(cacheKey))
|
|
76
|
-
return cache.roles.get(cacheKey);
|
|
77
|
-
const role = await getStore().findRoleByName(name, guardName);
|
|
78
|
-
if (role)
|
|
79
|
-
cache.roles.set(cacheKey, role);
|
|
80
|
-
return role;
|
|
81
|
-
}
|
|
82
|
-
export async function deleteRole(name, guardName = "web") {
|
|
83
|
-
const role = await findRole(name, guardName);
|
|
84
|
-
if (role) {
|
|
85
|
-
await getStore().deleteRole(role.id);
|
|
86
|
-
cache.roles.delete(`${name}:${guardName}`);
|
|
87
|
-
flushRbacCache();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export async function getAllRoles(guardName) {
|
|
91
|
-
return getStore().getAllRoles(guardName);
|
|
92
|
-
}
|
|
93
|
-
export async function createPermission(name, guardName = "web", description) {
|
|
94
|
-
const permission = await getStore().createPermission(name, guardName, description);
|
|
95
|
-
cache.permissions.set(`${name}:${guardName}`, permission);
|
|
96
|
-
return permission;
|
|
97
|
-
}
|
|
98
|
-
export async function findPermission(name, guardName = "web") {
|
|
99
|
-
const cacheKey = `${name}:${guardName}`;
|
|
100
|
-
if (cache.permissions.has(cacheKey))
|
|
101
|
-
return cache.permissions.get(cacheKey);
|
|
102
|
-
const permission = await getStore().findPermissionByName(name, guardName);
|
|
103
|
-
if (permission)
|
|
104
|
-
cache.permissions.set(cacheKey, permission);
|
|
105
|
-
return permission;
|
|
106
|
-
}
|
|
107
|
-
export async function deletePermission(name, guardName = "web") {
|
|
108
|
-
const permission = await findPermission(name, guardName);
|
|
109
|
-
if (permission) {
|
|
110
|
-
await getStore().deletePermission(permission.id);
|
|
111
|
-
cache.permissions.delete(`${name}:${guardName}`);
|
|
112
|
-
flushRbacCache();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
export async function getAllPermissions(guardName) {
|
|
116
|
-
return getStore().getAllPermissions(guardName);
|
|
117
|
-
}
|
|
118
|
-
export async function getUserRoles(user) {
|
|
119
|
-
const userId = getUserId(user);
|
|
120
|
-
if (cache.userRoles.has(userId))
|
|
121
|
-
return cache.userRoles.get(userId);
|
|
122
|
-
const roles = await getStore().getUserRoles(userId);
|
|
123
|
-
cache.userRoles.set(userId, roles);
|
|
124
|
-
return roles;
|
|
125
|
-
}
|
|
126
|
-
export async function assignRole(user, roleName, guardName = "web") {
|
|
127
|
-
const userId = getUserId(user), role = await findRole(roleName, guardName);
|
|
128
|
-
if (!role)
|
|
129
|
-
throw Error(`Role '${roleName}' not found.`);
|
|
130
|
-
await getStore().assignRoleToUser(userId, role.id);
|
|
131
|
-
cache.userRoles.delete(userId);
|
|
132
|
-
cache.userPermissions.delete(userId);
|
|
133
|
-
}
|
|
134
|
-
export async function removeRole(user, roleName, guardName = "web") {
|
|
135
|
-
const userId = getUserId(user), role = await findRole(roleName, guardName);
|
|
136
|
-
if (!role)
|
|
137
|
-
return;
|
|
138
|
-
await getStore().removeRoleFromUser(userId, role.id);
|
|
139
|
-
cache.userRoles.delete(userId);
|
|
140
|
-
cache.userPermissions.delete(userId);
|
|
141
|
-
}
|
|
142
|
-
export async function removeAllRoles(user) {
|
|
143
|
-
const userId = getUserId(user);
|
|
144
|
-
await getStore().removeAllRolesFromUser(userId);
|
|
145
|
-
cache.userRoles.delete(userId);
|
|
146
|
-
cache.userPermissions.delete(userId);
|
|
147
|
-
}
|
|
148
|
-
export async function syncRoles(user, roleNames, guardName = "web") {
|
|
149
|
-
const userId = getUserId(user), preservedRoleIds = (await getUserRoles(userId)).filter((role) => role.guard_name !== guardName).map((role) => role.id), roleIds = [];
|
|
150
|
-
for (const name of new Set(roleNames)) {
|
|
151
|
-
const role = await findRole(name, guardName);
|
|
152
|
-
if (!role)
|
|
153
|
-
throw Error(`Role '${name}' not found.`);
|
|
154
|
-
roleIds.push(role.id);
|
|
155
|
-
}
|
|
156
|
-
await getStore().syncUserRoles(userId, [...preservedRoleIds, ...roleIds]);
|
|
157
|
-
cache.userRoles.delete(userId);
|
|
158
|
-
cache.userPermissions.delete(userId);
|
|
159
|
-
}
|
|
160
|
-
export async function hasRole(user, roleName, guardName = "web") {
|
|
161
|
-
return (await getUserRoles(user)).some((r) => r.name === roleName && r.guard_name === guardName);
|
|
162
|
-
}
|
|
163
|
-
export async function hasAnyRole(user, roleNames, guardName = "web") {
|
|
164
|
-
const roles = await getUserRoles(user);
|
|
165
|
-
return roleNames.some((name) => roles.some((r) => r.name === name && r.guard_name === guardName));
|
|
166
|
-
}
|
|
167
|
-
export async function hasAllRoles(user, roleNames, guardName = "web") {
|
|
168
|
-
const roles = await getUserRoles(user);
|
|
169
|
-
return roleNames.every((name) => roles.some((r) => r.name === name && r.guard_name === guardName));
|
|
170
|
-
}
|
|
171
|
-
export async function getUserPermissions(user) {
|
|
172
|
-
const userId = getUserId(user);
|
|
173
|
-
if (cache.userPermissions.has(userId))
|
|
174
|
-
return cache.userPermissions.get(userId);
|
|
175
|
-
const directPermissions = await getStore().getUserDirectPermissions(userId), roles = await getUserRoles(user), rolePermissions = [];
|
|
176
|
-
for (const role of roles) {
|
|
177
|
-
const perms = await getRolePermissions(role.id);
|
|
178
|
-
rolePermissions.push(...perms);
|
|
179
|
-
}
|
|
180
|
-
const seen = new Set, allPermissions = [];
|
|
181
|
-
for (const perm of [...directPermissions, ...rolePermissions])
|
|
182
|
-
if (!seen.has(perm.id)) {
|
|
183
|
-
seen.add(perm.id);
|
|
184
|
-
allPermissions.push(perm);
|
|
185
|
-
}
|
|
186
|
-
cache.userPermissions.set(userId, allPermissions);
|
|
187
|
-
return allPermissions;
|
|
188
|
-
}
|
|
189
|
-
export async function givePermission(user, permissionName, guardName = "web") {
|
|
190
|
-
const userId = getUserId(user), permission = await findPermission(permissionName, guardName);
|
|
191
|
-
if (!permission)
|
|
192
|
-
throw Error(`Permission '${permissionName}' not found.`);
|
|
193
|
-
await getStore().assignPermissionToUser(userId, permission.id);
|
|
194
|
-
cache.userPermissions.delete(userId);
|
|
195
|
-
}
|
|
196
|
-
export async function revokePermission(user, permissionName, guardName = "web") {
|
|
197
|
-
const userId = getUserId(user), permission = await findPermission(permissionName, guardName);
|
|
198
|
-
if (!permission)
|
|
199
|
-
return;
|
|
200
|
-
await getStore().removePermissionFromUser(userId, permission.id);
|
|
201
|
-
cache.userPermissions.delete(userId);
|
|
202
|
-
}
|
|
203
|
-
export async function revokeAllPermissions(user) {
|
|
204
|
-
const userId = getUserId(user);
|
|
205
|
-
await getStore().removeAllPermissionsFromUser(userId);
|
|
206
|
-
cache.userPermissions.delete(userId);
|
|
207
|
-
}
|
|
208
|
-
export async function syncPermissions(user, permissionNames, guardName = "web") {
|
|
209
|
-
const userId = getUserId(user), preservedPermissionIds = (await getStore().getUserDirectPermissions(userId)).filter((permission) => permission.guard_name !== guardName).map((permission) => permission.id), permissionIds = [];
|
|
210
|
-
for (const name of new Set(permissionNames)) {
|
|
211
|
-
const perm = await findPermission(name, guardName);
|
|
212
|
-
if (!perm)
|
|
213
|
-
throw Error(`Permission '${name}' not found.`);
|
|
214
|
-
permissionIds.push(perm.id);
|
|
215
|
-
}
|
|
216
|
-
await getStore().syncUserPermissions(userId, [...preservedPermissionIds, ...permissionIds]);
|
|
217
|
-
cache.userPermissions.delete(userId);
|
|
218
|
-
}
|
|
219
|
-
export async function hasPermission(user, permissionName, guardName = "web") {
|
|
220
|
-
return (await getUserPermissions(user)).some((p) => p.name === permissionName && p.guard_name === guardName);
|
|
221
|
-
}
|
|
222
|
-
export async function hasAnyPermission(user, permissionNames, guardName = "web") {
|
|
223
|
-
const permissions = await getUserPermissions(user);
|
|
224
|
-
return permissionNames.some((name) => permissions.some((p) => p.name === name && p.guard_name === guardName));
|
|
225
|
-
}
|
|
226
|
-
export async function hasAllPermissions(user, permissionNames, guardName = "web") {
|
|
227
|
-
const permissions = await getUserPermissions(user);
|
|
228
|
-
return permissionNames.every((name) => permissions.some((p) => p.name === name && p.guard_name === guardName));
|
|
229
|
-
}
|
|
230
|
-
export async function getRolePermissions(roleId) {
|
|
231
|
-
if (cache.rolePermissions.has(roleId))
|
|
232
|
-
return cache.rolePermissions.get(roleId);
|
|
233
|
-
const permissions = await getStore().getRolePermissions(roleId);
|
|
234
|
-
cache.rolePermissions.set(roleId, permissions);
|
|
235
|
-
return permissions;
|
|
236
|
-
}
|
|
237
|
-
export async function givePermissionToRole(roleName, permissionName, guardName = "web") {
|
|
238
|
-
const role = await findRole(roleName, guardName);
|
|
239
|
-
if (!role)
|
|
240
|
-
throw Error(`Role '${roleName}' not found.`);
|
|
241
|
-
const permission = await findPermission(permissionName, guardName);
|
|
242
|
-
if (!permission)
|
|
243
|
-
throw Error(`Permission '${permissionName}' not found.`);
|
|
244
|
-
await getStore().assignPermissionToRole(role.id, permission.id);
|
|
245
|
-
cache.rolePermissions.delete(role.id);
|
|
246
|
-
cache.userPermissions.clear();
|
|
247
|
-
}
|
|
248
|
-
export async function revokePermissionFromRole(roleName, permissionName, guardName = "web") {
|
|
249
|
-
const role = await findRole(roleName, guardName);
|
|
250
|
-
if (!role)
|
|
251
|
-
return;
|
|
252
|
-
const permission = await findPermission(permissionName, guardName);
|
|
253
|
-
if (!permission)
|
|
254
|
-
return;
|
|
255
|
-
await getStore().removePermissionFromRole(role.id, permission.id);
|
|
256
|
-
cache.rolePermissions.delete(role.id);
|
|
257
|
-
cache.userPermissions.clear();
|
|
258
|
-
}
|
|
259
|
-
export async function syncRolePermissions(roleName, permissionNames, guardName = "web") {
|
|
260
|
-
const role = await findRole(roleName, guardName);
|
|
261
|
-
if (!role)
|
|
262
|
-
throw Error(`Role '${roleName}' not found.`);
|
|
263
|
-
const permissionIds = [];
|
|
264
|
-
for (const name of permissionNames) {
|
|
265
|
-
const perm = await findPermission(name, guardName);
|
|
266
|
-
if (!perm)
|
|
267
|
-
throw Error(`Permission '${name}' not found.`);
|
|
268
|
-
permissionIds.push(perm.id);
|
|
269
|
-
}
|
|
270
|
-
await getStore().syncRolePermissions(role.id, permissionIds);
|
|
271
|
-
cache.rolePermissions.delete(role.id);
|
|
272
|
-
cache.userPermissions.clear();
|
|
273
|
-
}
|
|
274
|
-
export function withRbac(user) {
|
|
275
|
-
const userId = getUserId(user);
|
|
276
|
-
return Object.assign(user, {
|
|
277
|
-
hasRole: (roleName, guardName) => hasRole(userId, roleName, guardName),
|
|
278
|
-
hasAnyRole: (roleNames, guardName) => hasAnyRole(userId, roleNames, guardName),
|
|
279
|
-
hasAllRoles: (roleNames, guardName) => hasAllRoles(userId, roleNames, guardName),
|
|
280
|
-
hasPermission: (permissionName, guardName) => hasPermission(userId, permissionName, guardName),
|
|
281
|
-
hasAnyPermission: (permissionNames, guardName) => hasAnyPermission(userId, permissionNames, guardName),
|
|
282
|
-
hasAllPermissions: (permissionNames, guardName) => hasAllPermissions(userId, permissionNames, guardName),
|
|
283
|
-
getRoles: () => getUserRoles(userId),
|
|
284
|
-
getPermissions: () => getUserPermissions(userId),
|
|
285
|
-
assignRole: (roleName, guardName) => assignRole(userId, roleName, guardName),
|
|
286
|
-
removeRole: (roleName, guardName) => removeRole(userId, roleName, guardName),
|
|
287
|
-
syncRoles: (roleNames, guardName) => syncRoles(userId, roleNames, guardName),
|
|
288
|
-
givePermission: (permissionName, guardName) => givePermission(userId, permissionName, guardName),
|
|
289
|
-
revokePermission: (permissionName, guardName) => revokePermission(userId, permissionName, guardName),
|
|
290
|
-
syncPermissions: (permissionNames, guardName) => syncPermissions(userId, permissionNames, guardName)
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
export const Rbac = {
|
|
294
|
-
setStore: setRbacStore,
|
|
295
|
-
flushCache: flushRbacCache,
|
|
296
|
-
createRole,
|
|
297
|
-
findRole,
|
|
298
|
-
deleteRole,
|
|
299
|
-
getAllRoles,
|
|
300
|
-
createPermission,
|
|
301
|
-
findPermission,
|
|
302
|
-
deletePermission,
|
|
303
|
-
getAllPermissions,
|
|
304
|
-
getUserRoles,
|
|
305
|
-
assignRole,
|
|
306
|
-
removeRole,
|
|
307
|
-
removeAllRoles,
|
|
308
|
-
syncRoles,
|
|
309
|
-
hasRole,
|
|
310
|
-
hasAnyRole,
|
|
311
|
-
hasAllRoles,
|
|
312
|
-
getUserPermissions,
|
|
313
|
-
givePermission,
|
|
314
|
-
revokePermission,
|
|
315
|
-
revokeAllPermissions,
|
|
316
|
-
syncPermissions,
|
|
317
|
-
hasPermission,
|
|
318
|
-
hasAnyPermission,
|
|
319
|
-
hasAllPermissions,
|
|
320
|
-
getRolePermissions,
|
|
321
|
-
givePermissionToRole,
|
|
322
|
-
revokePermissionFromRole,
|
|
323
|
-
syncRolePermissions,
|
|
324
|
-
withRbac
|
|
325
|
-
};
|
|
326
|
-
export default Rbac;
|
|
1
|
+
var {require}=import.meta;class BoundedMap{max;map=new Map;constructor(max){this.max=max}get(key){return this.map.get(key)}has(key){return this.map.has(key)}set(key,value){if(this.map.has(key))this.map.delete(key);this.map.set(key,value);if(this.map.size>this.max){const oldest=this.map.keys().next().value;if(oldest!==void 0)this.map.delete(oldest)}return this}delete(key){return this.map.delete(key)}clear(){this.map.clear()}}const RBAC_USER_CACHE_MAX=1e4,cache={userRoles:new BoundedMap(RBAC_USER_CACHE_MAX),userPermissions:new BoundedMap(RBAC_USER_CACHE_MAX),rolePermissions:new BoundedMap(RBAC_USER_CACHE_MAX),roles:new Map,permissions:new Map};let store=null;export function setRbacStore(rbacStore){store=rbacStore;flushRbacCache()}function getStore(){if(!store){const{createBqbRbacStore}=require("./rbac-store-bqb");store=createBqbRbacStore()}return store}export function flushRbacCache(){cache.userRoles.clear();cache.userPermissions.clear();cache.rolePermissions.clear();cache.roles.clear();cache.permissions.clear()}function getUserId(user){if(typeof user==="number"){if(!Number.isFinite(user)||user<=0)throw TypeError("RBAC user id must be a positive number");return user}const id=user.id;if(typeof id!=="number"||!Number.isFinite(id)||id<=0)throw TypeError(`RBAC user id must be a positive number, got ${typeof id} (${String(id)})`);return id}export async function createRole(name,guardName="web",description){const role=await getStore().createRole(name,guardName,description);cache.roles.set(`${name}:${guardName}`,role);return role}export async function findRole(name,guardName="web"){const cacheKey=`${name}:${guardName}`;if(cache.roles.has(cacheKey))return cache.roles.get(cacheKey);const role=await getStore().findRoleByName(name,guardName);if(role)cache.roles.set(cacheKey,role);return role}export async function deleteRole(name,guardName="web"){const role=await findRole(name,guardName);if(role){await getStore().deleteRole(role.id);cache.roles.delete(`${name}:${guardName}`);flushRbacCache()}}export async function getAllRoles(guardName){return getStore().getAllRoles(guardName)}export async function createPermission(name,guardName="web",description){const permission=await getStore().createPermission(name,guardName,description);cache.permissions.set(`${name}:${guardName}`,permission);return permission}export async function findPermission(name,guardName="web"){const cacheKey=`${name}:${guardName}`;if(cache.permissions.has(cacheKey))return cache.permissions.get(cacheKey);const permission=await getStore().findPermissionByName(name,guardName);if(permission)cache.permissions.set(cacheKey,permission);return permission}export async function deletePermission(name,guardName="web"){const permission=await findPermission(name,guardName);if(permission){await getStore().deletePermission(permission.id);cache.permissions.delete(`${name}:${guardName}`);flushRbacCache()}}export async function getAllPermissions(guardName){return getStore().getAllPermissions(guardName)}export async function getUserRoles(user){const userId=getUserId(user);if(cache.userRoles.has(userId))return cache.userRoles.get(userId);const roles=await getStore().getUserRoles(userId);cache.userRoles.set(userId,roles);return roles}export async function assignRole(user,roleName,guardName="web"){const userId=getUserId(user),role=await findRole(roleName,guardName);if(!role)throw Error(`Role '${roleName}' not found.`);await getStore().assignRoleToUser(userId,role.id);cache.userRoles.delete(userId);cache.userPermissions.delete(userId)}export async function removeRole(user,roleName,guardName="web"){const userId=getUserId(user),role=await findRole(roleName,guardName);if(!role)return;await getStore().removeRoleFromUser(userId,role.id);cache.userRoles.delete(userId);cache.userPermissions.delete(userId)}export async function removeAllRoles(user){const userId=getUserId(user);await getStore().removeAllRolesFromUser(userId);cache.userRoles.delete(userId);cache.userPermissions.delete(userId)}export async function syncRoles(user,roleNames,guardName="web"){const userId=getUserId(user),preservedRoleIds=(await getUserRoles(userId)).filter((role)=>role.guard_name!==guardName).map((role)=>role.id),roleIds=[];for(const name of new Set(roleNames)){const role=await findRole(name,guardName);if(!role)throw Error(`Role '${name}' not found.`);roleIds.push(role.id)}await getStore().syncUserRoles(userId,[...preservedRoleIds,...roleIds]);cache.userRoles.delete(userId);cache.userPermissions.delete(userId)}export async function hasRole(user,roleName,guardName="web"){return(await getUserRoles(user)).some((r)=>r.name===roleName&&r.guard_name===guardName)}export async function hasAnyRole(user,roleNames,guardName="web"){const roles=await getUserRoles(user);return roleNames.some((name)=>roles.some((r)=>r.name===name&&r.guard_name===guardName))}export async function hasAllRoles(user,roleNames,guardName="web"){const roles=await getUserRoles(user);return roleNames.every((name)=>roles.some((r)=>r.name===name&&r.guard_name===guardName))}export async function getUserPermissions(user){const userId=getUserId(user);if(cache.userPermissions.has(userId))return cache.userPermissions.get(userId);const directPermissions=await getStore().getUserDirectPermissions(userId),roles=await getUserRoles(user),rolePermissions=[];for(const role of roles){const perms=await getRolePermissions(role.id);rolePermissions.push(...perms)}const seen=new Set,allPermissions=[];for(const perm of[...directPermissions,...rolePermissions])if(!seen.has(perm.id)){seen.add(perm.id);allPermissions.push(perm)}cache.userPermissions.set(userId,allPermissions);return allPermissions}export async function givePermission(user,permissionName,guardName="web"){const userId=getUserId(user),permission=await findPermission(permissionName,guardName);if(!permission)throw Error(`Permission '${permissionName}' not found.`);await getStore().assignPermissionToUser(userId,permission.id);cache.userPermissions.delete(userId)}export async function revokePermission(user,permissionName,guardName="web"){const userId=getUserId(user),permission=await findPermission(permissionName,guardName);if(!permission)return;await getStore().removePermissionFromUser(userId,permission.id);cache.userPermissions.delete(userId)}export async function revokeAllPermissions(user){const userId=getUserId(user);await getStore().removeAllPermissionsFromUser(userId);cache.userPermissions.delete(userId)}export async function syncPermissions(user,permissionNames,guardName="web"){const userId=getUserId(user),preservedPermissionIds=(await getStore().getUserDirectPermissions(userId)).filter((permission)=>permission.guard_name!==guardName).map((permission)=>permission.id),permissionIds=[];for(const name of new Set(permissionNames)){const perm=await findPermission(name,guardName);if(!perm)throw Error(`Permission '${name}' not found.`);permissionIds.push(perm.id)}await getStore().syncUserPermissions(userId,[...preservedPermissionIds,...permissionIds]);cache.userPermissions.delete(userId)}export async function hasPermission(user,permissionName,guardName="web"){return(await getUserPermissions(user)).some((p)=>p.name===permissionName&&p.guard_name===guardName)}export async function hasAnyPermission(user,permissionNames,guardName="web"){const permissions=await getUserPermissions(user);return permissionNames.some((name)=>permissions.some((p)=>p.name===name&&p.guard_name===guardName))}export async function hasAllPermissions(user,permissionNames,guardName="web"){const permissions=await getUserPermissions(user);return permissionNames.every((name)=>permissions.some((p)=>p.name===name&&p.guard_name===guardName))}export async function getRolePermissions(roleId){if(cache.rolePermissions.has(roleId))return cache.rolePermissions.get(roleId);const permissions=await getStore().getRolePermissions(roleId);cache.rolePermissions.set(roleId,permissions);return permissions}export async function givePermissionToRole(roleName,permissionName,guardName="web"){const role=await findRole(roleName,guardName);if(!role)throw Error(`Role '${roleName}' not found.`);const permission=await findPermission(permissionName,guardName);if(!permission)throw Error(`Permission '${permissionName}' not found.`);await getStore().assignPermissionToRole(role.id,permission.id);cache.rolePermissions.delete(role.id);cache.userPermissions.clear()}export async function revokePermissionFromRole(roleName,permissionName,guardName="web"){const role=await findRole(roleName,guardName);if(!role)return;const permission=await findPermission(permissionName,guardName);if(!permission)return;await getStore().removePermissionFromRole(role.id,permission.id);cache.rolePermissions.delete(role.id);cache.userPermissions.clear()}export async function syncRolePermissions(roleName,permissionNames,guardName="web"){const role=await findRole(roleName,guardName);if(!role)throw Error(`Role '${roleName}' not found.`);const permissionIds=[];for(const name of permissionNames){const perm=await findPermission(name,guardName);if(!perm)throw Error(`Permission '${name}' not found.`);permissionIds.push(perm.id)}await getStore().syncRolePermissions(role.id,permissionIds);cache.rolePermissions.delete(role.id);cache.userPermissions.clear()}export function withRbac(user){const userId=getUserId(user);return Object.assign(user,{hasRole:(roleName,guardName)=>hasRole(userId,roleName,guardName),hasAnyRole:(roleNames,guardName)=>hasAnyRole(userId,roleNames,guardName),hasAllRoles:(roleNames,guardName)=>hasAllRoles(userId,roleNames,guardName),hasPermission:(permissionName,guardName)=>hasPermission(userId,permissionName,guardName),hasAnyPermission:(permissionNames,guardName)=>hasAnyPermission(userId,permissionNames,guardName),hasAllPermissions:(permissionNames,guardName)=>hasAllPermissions(userId,permissionNames,guardName),getRoles:()=>getUserRoles(userId),getPermissions:()=>getUserPermissions(userId),assignRole:(roleName,guardName)=>assignRole(userId,roleName,guardName),removeRole:(roleName,guardName)=>removeRole(userId,roleName,guardName),syncRoles:(roleNames,guardName)=>syncRoles(userId,roleNames,guardName),givePermission:(permissionName,guardName)=>givePermission(userId,permissionName,guardName),revokePermission:(permissionName,guardName)=>revokePermission(userId,permissionName,guardName),syncPermissions:(permissionNames,guardName)=>syncPermissions(userId,permissionNames,guardName)})}export const Rbac={setStore:setRbacStore,flushCache:flushRbacCache,createRole,findRole,deleteRole,getAllRoles,createPermission,findPermission,deletePermission,getAllPermissions,getUserRoles,assignRole,removeRole,removeAllRoles,syncRoles,hasRole,hasAnyRole,hasAllRoles,getUserPermissions,givePermission,revokePermission,revokeAllPermissions,syncPermissions,hasPermission,hasAnyPermission,hasAllPermissions,getRolePermissions,givePermissionToRole,revokePermissionFromRole,syncRolePermissions,withRbac};export default Rbac;
|
package/dist/register.js
CHANGED
|
@@ -1,43 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { db } from "@stacksjs/database";
|
|
3
|
-
import { HttpError } from "@stacksjs/error-handling";
|
|
4
|
-
import { User } from "@stacksjs/orm";
|
|
5
|
-
import { makeHash } from "@stacksjs/security";
|
|
6
|
-
import { Auth } from "./authentication";
|
|
7
|
-
import { isUniqueViolation } from "./rbac-store-bqb";
|
|
8
|
-
function duplicateEmailError() {
|
|
9
|
-
if (config.auth?.registration?.preventEnumeration)
|
|
10
|
-
return new HttpError(422, "Registration could not be completed. Please check your details and try again.");
|
|
11
|
-
return new HttpError(409, "Email already exists");
|
|
12
|
-
}
|
|
13
|
-
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
14
|
-
export async function register(credentials) {
|
|
15
|
-
const { email, password, name } = credentials;
|
|
16
|
-
if (typeof email !== "string" || email.length > 254 || !EMAIL_RE.test(email))
|
|
17
|
-
throw new HttpError(422, "Email address is invalid");
|
|
18
|
-
if (typeof password !== "string" || password.length < 8)
|
|
19
|
-
throw new HttpError(422, "Password must be at least 8 characters");
|
|
20
|
-
const hashedPassword = await makeHash(password, { algorithm: "bcrypt" }), userId = await db.transaction(async (rawTrx) => {
|
|
21
|
-
const trx = rawTrx;
|
|
22
|
-
if (await trx.selectFrom("users").where("email", "=", email).selectAll().executeTakeFirst())
|
|
23
|
-
throw duplicateEmailError();
|
|
24
|
-
try {
|
|
25
|
-
await trx.insertInto("users").values({
|
|
26
|
-
email,
|
|
27
|
-
password: hashedPassword,
|
|
28
|
-
name
|
|
29
|
-
}).execute();
|
|
30
|
-
} catch (err) {
|
|
31
|
-
if (isUniqueViolation(err))
|
|
32
|
-
throw duplicateEmailError();
|
|
33
|
-
throw err;
|
|
34
|
-
}
|
|
35
|
-
const created = await trx.selectFrom("users").where("email", "=", email).selectAll().executeTakeFirst();
|
|
36
|
-
if (!created)
|
|
37
|
-
throw Error("Failed to retrieve created user");
|
|
38
|
-
return Number(created.id);
|
|
39
|
-
}), user = await User.find(userId);
|
|
40
|
-
if (!user)
|
|
41
|
-
throw Error("Failed to retrieve created user");
|
|
42
|
-
return { token: await Auth.createToken(user, "user-auth-token") };
|
|
43
|
-
}
|
|
1
|
+
import{config}from"@stacksjs/config";import{db}from"@stacksjs/database";import{HttpError}from"@stacksjs/error-handling";import{User}from"@stacksjs/orm";import{makeHash}from"@stacksjs/security";import{Auth}from"./authentication";import{isUniqueViolation}from"./rbac-store-bqb";function duplicateEmailError(){if(config.auth?.registration?.preventEnumeration)return new HttpError(422,"Registration could not be completed. Please check your details and try again.");return new HttpError(409,"Email already exists")}const EMAIL_RE=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;export async function register(credentials){const{email,password,name}=credentials;if(typeof email!=="string"||email.length>254||!EMAIL_RE.test(email))throw new HttpError(422,"Email address is invalid");if(typeof password!=="string"||password.length<8)throw new HttpError(422,"Password must be at least 8 characters");const hashedPassword=await makeHash(password,{algorithm:"bcrypt"}),userId=await db.transaction(async(rawTrx)=>{const trx=rawTrx;if(await trx.selectFrom("users").where("email","=",email).selectAll().executeTakeFirst())throw duplicateEmailError();try{await trx.insertInto("users").values({email,password:hashedPassword,name}).execute()}catch(err){if(isUniqueViolation(err))throw duplicateEmailError();throw err}const created=await trx.selectFrom("users").where("email","=",email).selectAll().executeTakeFirst();if(!created)throw Error("Failed to retrieve created user");return Number(created.id)}),user=await User.find(userId);if(!user)throw Error("Failed to retrieve created user");return{token:await Auth.createToken(user,"user-auth-token")}}
|
package/dist/session-auth.js
CHANGED
|
@@ -1,151 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { log } from "@stacksjs/logging";
|
|
3
|
-
import { User } from "@stacksjs/orm";
|
|
4
|
-
import { verifyHash } from "@stacksjs/security";
|
|
5
|
-
import { config } from "@stacksjs/config";
|
|
6
|
-
import { db, sqlDateTime, parseSqlDateTime } from "@stacksjs/database";
|
|
7
|
-
import { getCurrentRequest } from "@stacksjs/router";
|
|
8
|
-
import { DUMMY_BCRYPT_HASH } from "./internal-constants";
|
|
9
|
-
import { RateLimiter } from "./rate-limiter";
|
|
10
|
-
function generateSessionId() {
|
|
11
|
-
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
12
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
13
|
-
}
|
|
14
|
-
function readRequestFingerprint(override) {
|
|
15
|
-
if (override)
|
|
16
|
-
return {
|
|
17
|
-
ip: override.ip ?? null,
|
|
18
|
-
userAgent: override.userAgent ?? null
|
|
19
|
-
};
|
|
20
|
-
const req = getCurrentRequest();
|
|
21
|
-
if (!req)
|
|
22
|
-
return { ip: null, userAgent: null };
|
|
23
|
-
const headers = req.headers, fwd = headers?.get?.("x-forwarded-for") || headers?.get?.("X-Forwarded-For") || "", realIp = headers?.get?.("x-real-ip") || headers?.get?.("X-Real-IP") || "", ip = (fwd ? fwd.split(",")[0].trim() : "") || realIp || null, userAgent = headers?.get?.("user-agent") || headers?.get?.("User-Agent") || null;
|
|
24
|
-
return { ip, userAgent };
|
|
25
|
-
}
|
|
26
|
-
export function fingerprintMismatch(enforce, stored, current) {
|
|
27
|
-
if (!enforce)
|
|
28
|
-
return !1;
|
|
29
|
-
const enforceIp = enforce === !0 || !!enforce.ip, enforceUa = enforce === !0 || !!enforce.userAgent;
|
|
30
|
-
if (!enforceIp && !enforceUa)
|
|
31
|
-
return !1;
|
|
32
|
-
if (enforceIp && current.ip != null && stored.ip != null && String(stored.ip) !== current.ip)
|
|
33
|
-
return !0;
|
|
34
|
-
if (enforceUa && current.userAgent != null && stored.userAgent != null && String(stored.userAgent) !== current.userAgent)
|
|
35
|
-
return !0;
|
|
36
|
-
return !1;
|
|
37
|
-
}
|
|
38
|
-
function sessionFingerprintRejected(session) {
|
|
39
|
-
return fingerprintMismatch(config.auth?.session?.enforceFingerprint, { ip: session.ip_address, userAgent: session.user_agent }, readRequestFingerprint());
|
|
40
|
-
}
|
|
41
|
-
export async function sessionLogin(email, password, fingerprint) {
|
|
42
|
-
const normalizedEmail = (email || "").toLowerCase(), isRateLimited = await RateLimiter.isRateLimited(normalizedEmail), user = await User.where("email", email).first(), hashToVerify = user?.password || DUMMY_BCRYPT_HASH, isValid = await verifyHash(password, hashToVerify);
|
|
43
|
-
if (isRateLimited)
|
|
44
|
-
throw new HttpError(429, "Too many login attempts. Please try again later.");
|
|
45
|
-
if (!isValid || !user) {
|
|
46
|
-
await RateLimiter.recordFailedAttempt(normalizedEmail);
|
|
47
|
-
throw new HttpError(401, "Invalid credentials");
|
|
48
|
-
}
|
|
49
|
-
await RateLimiter.resetAttempts(normalizedEmail);
|
|
50
|
-
const sessionId = generateSessionId(), expiresAt = new Date(Date.now() + 86400000), { ip, userAgent } = readRequestFingerprint(fingerprint);
|
|
51
|
-
log.debug(`[auth] Session created for user#${user.id}`);
|
|
52
|
-
try {
|
|
53
|
-
await db.insertInto("sessions").values({
|
|
54
|
-
id: sessionId,
|
|
55
|
-
user_id: user.id,
|
|
56
|
-
ip_address: ip,
|
|
57
|
-
user_agent: userAgent,
|
|
58
|
-
payload: "{}",
|
|
59
|
-
last_activity: Math.floor(Date.now() / 1000),
|
|
60
|
-
expires_at: sqlDateTime(expiresAt)
|
|
61
|
-
}).execute();
|
|
62
|
-
} catch (err) {
|
|
63
|
-
log.error(`[auth] Session persistence failed for user#${user.id}: ${err.message}`);
|
|
64
|
-
throw new HttpError(500, "Session could not be created. Ensure the `sessions` table exists (run `./buddy migrate`).");
|
|
65
|
-
}
|
|
66
|
-
return { user, sessionId };
|
|
67
|
-
}
|
|
68
|
-
export async function sessionLogout(sessionId) {
|
|
69
|
-
log.debug("[auth] Session destroyed");
|
|
70
|
-
try {
|
|
71
|
-
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
72
|
-
} catch (err) {
|
|
73
|
-
log.debug(`[auth] Session destroy failed: ${err.message}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
export async function sessionDestroyAll(userId) {
|
|
77
|
-
try {
|
|
78
|
-
await db.deleteFrom("sessions").where("user_id", "=", userId).execute();
|
|
79
|
-
} catch (err) {
|
|
80
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
-
if (message.includes("sessions") && /no such table|does not exist|doesn't exist/i.test(message))
|
|
82
|
-
return;
|
|
83
|
-
throw err;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
export async function sessionUser(sessionId) {
|
|
87
|
-
try {
|
|
88
|
-
const session = await db.selectFrom("sessions").where("id", "=", sessionId).selectAll().executeTakeFirst();
|
|
89
|
-
if (!session)
|
|
90
|
-
return;
|
|
91
|
-
const expiresAt = parseSqlDateTime(session.expires_at)?.getTime() ?? 0;
|
|
92
|
-
if (Date.now() > expiresAt) {
|
|
93
|
-
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (sessionFingerprintRejected(session)) {
|
|
97
|
-
log.warn("[auth] Session fingerprint mismatch \u2014 rejecting request (possible hijack, session left intact)");
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
return await User.find(session.user_id);
|
|
101
|
-
} catch {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
export async function sessionCheck(sessionId) {
|
|
106
|
-
try {
|
|
107
|
-
const session = await db.selectFrom("sessions").where("id", "=", sessionId).selectAll().executeTakeFirst();
|
|
108
|
-
if (!session)
|
|
109
|
-
return !1;
|
|
110
|
-
const expiresAt = parseSqlDateTime(session.expires_at)?.getTime() ?? 0;
|
|
111
|
-
if (Date.now() > expiresAt) {
|
|
112
|
-
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
113
|
-
return !1;
|
|
114
|
-
}
|
|
115
|
-
if (sessionFingerprintRejected(session))
|
|
116
|
-
return !1;
|
|
117
|
-
return !0;
|
|
118
|
-
} catch {
|
|
119
|
-
return !1;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
export async function sessionRefresh(sessionId, ttlMs = 86400000) {
|
|
123
|
-
try {
|
|
124
|
-
const session = await db.selectFrom("sessions").where("id", "=", sessionId).selectAll().executeTakeFirst();
|
|
125
|
-
if (!session)
|
|
126
|
-
return !1;
|
|
127
|
-
const expiresAt = parseSqlDateTime(session.expires_at)?.getTime() ?? 0;
|
|
128
|
-
if (Date.now() > expiresAt) {
|
|
129
|
-
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
130
|
-
return !1;
|
|
131
|
-
}
|
|
132
|
-
if (sessionFingerprintRejected(session))
|
|
133
|
-
return !1;
|
|
134
|
-
const newExpiry = new Date(Date.now() + ttlMs);
|
|
135
|
-
await db.updateTable("sessions").set({
|
|
136
|
-
expires_at: sqlDateTime(newExpiry),
|
|
137
|
-
last_activity: Math.floor(Date.now() / 1000)
|
|
138
|
-
}).where("id", "=", sessionId).execute();
|
|
139
|
-
return !0;
|
|
140
|
-
} catch {
|
|
141
|
-
return !1;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
export const SessionAuth = {
|
|
145
|
-
login: sessionLogin,
|
|
146
|
-
logout: sessionLogout,
|
|
147
|
-
destroyAll: sessionDestroyAll,
|
|
148
|
-
user: sessionUser,
|
|
149
|
-
check: sessionCheck,
|
|
150
|
-
refresh: sessionRefresh
|
|
151
|
-
};
|
|
1
|
+
import{HttpError}from"@stacksjs/error-handling";import{log}from"@stacksjs/logging";import{User}from"@stacksjs/orm";import{verifyHash}from"@stacksjs/security";import{config}from"@stacksjs/config";import{db,sqlDateTime,parseSqlDateTime}from"@stacksjs/database";import{getCurrentRequest}from"@stacksjs/router";import{DUMMY_BCRYPT_HASH}from"./internal-constants";import{RateLimiter}from"./rate-limiter";function generateSessionId(){const bytes=crypto.getRandomValues(new Uint8Array(32));return Array.from(bytes,(b)=>b.toString(16).padStart(2,"0")).join("")}function readRequestFingerprint(override){if(override)return{ip:override.ip??null,userAgent:override.userAgent??null};const req=getCurrentRequest();if(!req)return{ip:null,userAgent:null};const headers=req.headers,fwd=headers?.get?.("x-forwarded-for")||headers?.get?.("X-Forwarded-For")||"",realIp=headers?.get?.("x-real-ip")||headers?.get?.("X-Real-IP")||"",ip=(fwd?fwd.split(",")[0].trim():"")||realIp||null,userAgent=headers?.get?.("user-agent")||headers?.get?.("User-Agent")||null;return{ip,userAgent}}export function fingerprintMismatch(enforce,stored,current){if(!enforce)return!1;const enforceIp=enforce===!0||!!enforce.ip,enforceUa=enforce===!0||!!enforce.userAgent;if(!enforceIp&&!enforceUa)return!1;if(enforceIp&¤t.ip!=null&&stored.ip!=null&&String(stored.ip)!==current.ip)return!0;if(enforceUa&¤t.userAgent!=null&&stored.userAgent!=null&&String(stored.userAgent)!==current.userAgent)return!0;return!1}function sessionFingerprintRejected(session){return fingerprintMismatch(config.auth?.session?.enforceFingerprint,{ip:session.ip_address,userAgent:session.user_agent},readRequestFingerprint())}export async function sessionLogin(email,password,fingerprint){const normalizedEmail=(email||"").toLowerCase(),isRateLimited=await RateLimiter.isRateLimited(normalizedEmail),user=await User.where("email",email).first(),hashToVerify=user?.password||DUMMY_BCRYPT_HASH,isValid=await verifyHash(password,hashToVerify);if(isRateLimited)throw new HttpError(429,"Too many login attempts. Please try again later.");if(!isValid||!user){await RateLimiter.recordFailedAttempt(normalizedEmail);throw new HttpError(401,"Invalid credentials")}await RateLimiter.resetAttempts(normalizedEmail);const sessionId=generateSessionId(),expiresAt=new Date(Date.now()+86400000),{ip,userAgent}=readRequestFingerprint(fingerprint);log.debug(`[auth] Session created for user#${user.id}`);try{await db.insertInto("sessions").values({id:sessionId,user_id:user.id,ip_address:ip,user_agent:userAgent,payload:"{}",last_activity:Math.floor(Date.now()/1000),expires_at:sqlDateTime(expiresAt)}).execute()}catch(err){log.error(`[auth] Session persistence failed for user#${user.id}: ${err.message}`);throw new HttpError(500,"Session could not be created. Ensure the `sessions` table exists (run `./buddy migrate`).")}return{user,sessionId}}export async function sessionLogout(sessionId){log.debug("[auth] Session destroyed");try{await db.deleteFrom("sessions").where("id","=",sessionId).execute()}catch(err){log.debug(`[auth] Session destroy failed: ${err.message}`)}}export async function sessionDestroyAll(userId){try{await db.deleteFrom("sessions").where("user_id","=",userId).execute()}catch(err){const message=err instanceof Error?err.message:String(err);if(message.includes("sessions")&&/no such table|does not exist|doesn't exist/i.test(message))return;throw err}}export async function sessionUser(sessionId){try{const session=await db.selectFrom("sessions").where("id","=",sessionId).selectAll().executeTakeFirst();if(!session)return;const expiresAt=parseSqlDateTime(session.expires_at)?.getTime()??0;if(Date.now()>expiresAt){await db.deleteFrom("sessions").where("id","=",sessionId).execute();return}if(sessionFingerprintRejected(session)){log.warn("[auth] Session fingerprint mismatch \u2014 rejecting request (possible hijack, session left intact)");return}return await User.find(session.user_id)}catch{return}}export async function sessionCheck(sessionId){try{const session=await db.selectFrom("sessions").where("id","=",sessionId).selectAll().executeTakeFirst();if(!session)return!1;const expiresAt=parseSqlDateTime(session.expires_at)?.getTime()??0;if(Date.now()>expiresAt){await db.deleteFrom("sessions").where("id","=",sessionId).execute();return!1}if(sessionFingerprintRejected(session))return!1;return!0}catch{return!1}}export async function sessionRefresh(sessionId,ttlMs=86400000){try{const session=await db.selectFrom("sessions").where("id","=",sessionId).selectAll().executeTakeFirst();if(!session)return!1;const expiresAt=parseSqlDateTime(session.expires_at)?.getTime()??0;if(Date.now()>expiresAt){await db.deleteFrom("sessions").where("id","=",sessionId).execute();return!1}if(sessionFingerprintRejected(session))return!1;const newExpiry=new Date(Date.now()+ttlMs);await db.updateTable("sessions").set({expires_at:sqlDateTime(newExpiry),last_activity:Math.floor(Date.now()/1000)}).where("id","=",sessionId).execute();return!0}catch{return!1}}export const SessionAuth={login:sessionLogin,logout:sessionLogout,destroyAll:sessionDestroyAll,user:sessionUser,check:sessionCheck,refresh:sessionRefresh};
|
package/dist/team.js
CHANGED
|
@@ -1,88 +1 @@
|
|
|
1
|
-
import { config }
|
|
2
|
-
import { db } from "@stacksjs/database";
|
|
3
|
-
import { Auth } from "./authentication";
|
|
4
|
-
import { sessionUser } from "./session-auth";
|
|
5
|
-
export const ACTIVE_TEAM_COOKIE = "active_team";
|
|
6
|
-
export function selectActiveTeam(opts) {
|
|
7
|
-
const priority = opts.rolePriority ?? { owner: 0, admin: 1 }, sorted = [...opts.memberships ?? []].sort((a, b) => (priority[a.role ?? ""] ?? 2) - (priority[b.role ?? ""] ?? 2)), active = opts.activeTeamId == null ? null : Number(opts.activeTeamId);
|
|
8
|
-
if (active != null && Number.isFinite(active)) {
|
|
9
|
-
const member = sorted.find((x) => Number(x.team_id) === active);
|
|
10
|
-
if (member)
|
|
11
|
-
return { teamId: active, role: member.role ?? null };
|
|
12
|
-
if (opts.allowAnyTeam)
|
|
13
|
-
return { teamId: active, role: "admin" };
|
|
14
|
-
}
|
|
15
|
-
const first = sorted[0];
|
|
16
|
-
return first ? { teamId: Number(first.team_id), role: first.role ?? null } : { teamId: null, role: null };
|
|
17
|
-
}
|
|
18
|
-
export function getActiveTeamPreference(request) {
|
|
19
|
-
const raw = request.cookies?.get(ACTIVE_TEAM_COOKIE);
|
|
20
|
-
if (!raw)
|
|
21
|
-
return null;
|
|
22
|
-
const n = Number(raw);
|
|
23
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
24
|
-
}
|
|
25
|
-
export function buildActiveTeamCookie(teamId, opts = {}) {
|
|
26
|
-
const maxAge = Math.max(0, Math.floor(opts.maxAgeSeconds ?? 31536000)), parts = [`${ACTIVE_TEAM_COOKIE}=${encodeURIComponent(String(teamId))}`, "Path=/", "HttpOnly", "SameSite=Lax", `Max-Age=${maxAge}`];
|
|
27
|
-
if (opts.secure)
|
|
28
|
-
parts.push("Secure");
|
|
29
|
-
return parts.join("; ");
|
|
30
|
-
}
|
|
31
|
-
export function clearActiveTeamCookie() {
|
|
32
|
-
return `${ACTIVE_TEAM_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
|
|
33
|
-
}
|
|
34
|
-
export async function resolveAuthenticatedMembership(request) {
|
|
35
|
-
const user = await resolveAuthenticatedUser(request);
|
|
36
|
-
if (!user?.id)
|
|
37
|
-
return null;
|
|
38
|
-
const memberships = await db.selectFrom("team_members").where("user_id", "=", user.id).where("status", "=", "active").select(["team_id", "role"]).execute();
|
|
39
|
-
if (memberships.length === 0)
|
|
40
|
-
return null;
|
|
41
|
-
const picked = selectActiveTeam({
|
|
42
|
-
memberships: memberships.map((m) => ({
|
|
43
|
-
team_id: Number(m.team_id),
|
|
44
|
-
role: m.role == null ? null : String(m.role)
|
|
45
|
-
})),
|
|
46
|
-
activeTeamId: getActiveTeamPreference(request)
|
|
47
|
-
});
|
|
48
|
-
return picked.teamId != null ? { teamId: picked.teamId, role: picked.role ?? "" } : null;
|
|
49
|
-
}
|
|
50
|
-
export async function resolveTeamContext(request, opts = {}) {
|
|
51
|
-
const empty = { user: null, teamId: null, role: null, teams: [], activeTeamId: null }, guardName = config.auth?.default || "api", user = (config.auth?.guards?.[guardName]?.driver || "token") === "session" ? await resolveSessionUser(request) : await resolveTokenUser(request);
|
|
52
|
-
if (!user?.id)
|
|
53
|
-
return empty;
|
|
54
|
-
const memberships = await db.selectFrom("team_members").where("user_id", "=", user.id).where("status", "=", "active").select(["team_id", "role"]).execute(), allowAny = opts.allowAnyTeam ? !!opts.allowAnyTeam(user) : !1, activeTeamId = getActiveTeamPreference(request), picked = selectActiveTeam({
|
|
55
|
-
memberships: memberships.map((m) => ({ team_id: Number(m.team_id), role: String(m.role) })),
|
|
56
|
-
activeTeamId,
|
|
57
|
-
allowAnyTeam: allowAny
|
|
58
|
-
}), roleByTeam = new Map(memberships.map((m) => [Number(m.team_id), String(m.role)]));
|
|
59
|
-
let teamRows = [];
|
|
60
|
-
if (allowAny)
|
|
61
|
-
teamRows = await db.selectFrom("teams").select(["id", "name"]).execute();
|
|
62
|
-
else {
|
|
63
|
-
const ids = [...roleByTeam.keys()];
|
|
64
|
-
teamRows = ids.length ? await db.selectFrom("teams").whereIn("id", ids).select(["id", "name"]).execute() : [];
|
|
65
|
-
}
|
|
66
|
-
const teams = teamRows.map((t) => ({ id: Number(t.id), name: String(t.name), role: roleByTeam.get(Number(t.id)) || "viewer" })).sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
-
return { user, teamId: picked.teamId, role: picked.role, teams, activeTeamId };
|
|
68
|
-
}
|
|
69
|
-
export async function resolveAuthenticatedTeamId(request) {
|
|
70
|
-
const membership = await resolveAuthenticatedMembership(request);
|
|
71
|
-
return membership ? membership.teamId : null;
|
|
72
|
-
}
|
|
73
|
-
export async function resolveAuthenticatedUser(request) {
|
|
74
|
-
const guardName = config.auth?.default || "api", user = ((config.auth?.guards?.[guardName] || { driver: "token" }).driver || "token") === "session" ? await resolveSessionUser(request) : await resolveTokenUser(request);
|
|
75
|
-
return user?.id ? user : void 0;
|
|
76
|
-
}
|
|
77
|
-
async function resolveSessionUser(request) {
|
|
78
|
-
const sessionId = request.cookies?.get("session_id");
|
|
79
|
-
if (!sessionId)
|
|
80
|
-
return;
|
|
81
|
-
return sessionUser(sessionId);
|
|
82
|
-
}
|
|
83
|
-
async function resolveTokenUser(request) {
|
|
84
|
-
const cookieName = config.auth?.defaultTokenName || "auth-token", bearer = (typeof request.bearerToken === "function" ? request.bearerToken() : void 0) ?? request.cookies?.get(cookieName);
|
|
85
|
-
if (!bearer)
|
|
86
|
-
return;
|
|
87
|
-
return Auth.getUserFromToken(bearer);
|
|
88
|
-
}
|
|
1
|
+
import{config}from"@stacksjs/config";import{db}from"@stacksjs/database";import{Auth}from"./authentication";import{sessionUser}from"./session-auth";export const ACTIVE_TEAM_COOKIE="active_team";export function selectActiveTeam(opts){const priority=opts.rolePriority??{owner:0,admin:1},sorted=[...opts.memberships??[]].sort((a,b)=>(priority[a.role??""]??2)-(priority[b.role??""]??2)),active=opts.activeTeamId==null?null:Number(opts.activeTeamId);if(active!=null&&Number.isFinite(active)){const member=sorted.find((x)=>Number(x.team_id)===active);if(member)return{teamId:active,role:member.role??null};if(opts.allowAnyTeam)return{teamId:active,role:"admin"}}const first=sorted[0];return first?{teamId:Number(first.team_id),role:first.role??null}:{teamId:null,role:null}}export function getActiveTeamPreference(request){const raw=request.cookies?.get(ACTIVE_TEAM_COOKIE);if(!raw)return null;const n=Number(raw);return Number.isFinite(n)&&n>0?n:null}export function buildActiveTeamCookie(teamId,opts={}){const maxAge=Math.max(0,Math.floor(opts.maxAgeSeconds??31536000)),parts=[`${ACTIVE_TEAM_COOKIE}=${encodeURIComponent(String(teamId))}`,"Path=/","HttpOnly","SameSite=Lax",`Max-Age=${maxAge}`];if(opts.secure)parts.push("Secure");return parts.join("; ")}export function clearActiveTeamCookie(){return`${ACTIVE_TEAM_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`}export async function resolveAuthenticatedMembership(request){const user=await resolveAuthenticatedUser(request);if(!user?.id)return null;const memberships=await db.selectFrom("team_members").where("user_id","=",user.id).where("status","=","active").select(["team_id","role"]).execute();if(memberships.length===0)return null;const picked=selectActiveTeam({memberships:memberships.map((m)=>({team_id:Number(m.team_id),role:m.role==null?null:String(m.role)})),activeTeamId:getActiveTeamPreference(request)});return picked.teamId!=null?{teamId:picked.teamId,role:picked.role??""}:null}export async function resolveTeamContext(request,opts={}){const empty={user:null,teamId:null,role:null,teams:[],activeTeamId:null},guardName=config.auth?.default||"api",user=(config.auth?.guards?.[guardName]?.driver||"token")==="session"?await resolveSessionUser(request):await resolveTokenUser(request);if(!user?.id)return empty;const memberships=await db.selectFrom("team_members").where("user_id","=",user.id).where("status","=","active").select(["team_id","role"]).execute(),allowAny=opts.allowAnyTeam?!!opts.allowAnyTeam(user):!1,activeTeamId=getActiveTeamPreference(request),picked=selectActiveTeam({memberships:memberships.map((m)=>({team_id:Number(m.team_id),role:String(m.role)})),activeTeamId,allowAnyTeam:allowAny}),roleByTeam=new Map(memberships.map((m)=>[Number(m.team_id),String(m.role)]));let teamRows=[];if(allowAny)teamRows=await db.selectFrom("teams").select(["id","name"]).execute();else{const ids=[...roleByTeam.keys()];teamRows=ids.length?await db.selectFrom("teams").whereIn("id",ids).select(["id","name"]).execute():[]}const teams=teamRows.map((t)=>({id:Number(t.id),name:String(t.name),role:roleByTeam.get(Number(t.id))||"viewer"})).sort((a,b)=>a.name.localeCompare(b.name));return{user,teamId:picked.teamId,role:picked.role,teams,activeTeamId}}export async function resolveAuthenticatedTeamId(request){const membership=await resolveAuthenticatedMembership(request);return membership?membership.teamId:null}export async function resolveAuthenticatedUser(request){const guardName=config.auth?.default||"api",user=((config.auth?.guards?.[guardName]||{driver:"token"}).driver||"token")==="session"?await resolveSessionUser(request):await resolveTokenUser(request);return user?.id?user:void 0}async function resolveSessionUser(request){const sessionId=request.cookies?.get("session_id");if(!sessionId)return;return sessionUser(sessionId)}async function resolveTokenUser(request){const cookieName=config.auth?.defaultTokenName||"auth-token",bearer=(typeof request.bearerToken==="function"?request.bearerToken():void 0)??request.cookies?.get(cookieName);if(!bearer)return;return Auth.getUserFromToken(bearer)}
|