@stacksjs/auth 0.74.31 → 0.74.33
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/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/rbac.js +1 -1
- package/dist/referrals.d.ts +26 -0
- package/dist/referrals.js +1 -0
- package/dist/register.d.ts +1 -1
- package/dist/register.js +1 -1
- package/package.json +14 -14
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./authentication";export*from"./authenticator";export*from"./client";export*from"./middleware";export*from"./rate-limiter";export*from"./magic-link";export*from"./passkey";export*from"./password/reset";export*from"./register";export*from"./user";export*from"./tokens";export*from"./gate";export*from"./policy";export*from"./authorizable";export*from"./permissions";export*from"./rbac";export{createBqbRbacStore}from"./rbac-store-bqb";export{DEFAULT_ROLE_PACKS,seedDefaultRoles}from"./rbac-seed";export*from"./email-verification";export*from"./session-auth";export*from"./cookie-auth";export*from"./request-token";export*from"./page-gate";export*from"./socials";export{generateTOTP,verifyTOTP,generateTOTPSecret,totpKeyUri}from"@stacksjs/ts-auth";export*from"./two-factor";export*from"./team";
|
|
1
|
+
export*from"./authentication";export*from"./authenticator";export*from"./client";export*from"./middleware";export*from"./rate-limiter";export*from"./magic-link";export*from"./passkey";export*from"./password/reset";export*from"./register";export*from"./user";export*from"./tokens";export*from"./gate";export*from"./policy";export*from"./authorizable";export*from"./permissions";export*from"./rbac";export{createBqbRbacStore}from"./rbac-store-bqb";export{DEFAULT_ROLE_PACKS,seedDefaultRoles}from"./rbac-seed";export*from"./email-verification";export*from"./session-auth";export*from"./cookie-auth";export*from"./request-token";export*from"./page-gate";export*from"./socials";export{generateTOTP,verifyTOTP,generateTOTPSecret,totpKeyUri}from"@stacksjs/ts-auth";export*from"./two-factor";export*from"./team";export*from"./referrals";
|
package/dist/rbac.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var {require}=import.meta;export class RbacEntityNotFoundError extends Error{entity;value;guardName;constructor(entity,value,guardName){const label=entity==="role"?"Role":"Permission";super(`${label} '${value}' not found.`);this.entity=entity;this.value=value;this.guardName=guardName;this.name="RbacEntityNotFoundError"}}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 new RbacEntityNotFoundError("role",roleName,guardName);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 new RbacEntityNotFoundError("role",name,guardName);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 new RbacEntityNotFoundError("permission",permissionName,guardName);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 new RbacEntityNotFoundError("permission",name,guardName);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 new RbacEntityNotFoundError("role",roleName,guardName);const permission=await findPermission(permissionName,guardName);if(!permission)throw new RbacEntityNotFoundError("permission",permissionName,guardName);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 new RbacEntityNotFoundError("role",roleName,guardName);const permissionIds=[];for(const name of permissionNames){const perm=await findPermission(name,guardName);if(!perm)throw new RbacEntityNotFoundError("permission",name,guardName);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;
|
|
1
|
+
var {require}=import.meta;export class RbacEntityNotFoundError extends Error{entity;value;guardName;constructor(entity,value,guardName){const label=entity==="role"?"Role":"Permission";super(`${label} '${value}' not found.`);this.entity=entity;this.value=value;this.guardName=guardName;this.name="RbacEntityNotFoundError"}}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 raw=user.id;if(typeof raw!=="number"&&typeof raw!=="string"&&typeof raw!=="bigint")throw TypeError(`RBAC user id must be a positive number, got ${typeof raw} (${String(raw)})`);const id=Number(raw);if(!Number.isInteger(id)||id<=0)throw TypeError(`RBAC user id must be a positive number, got ${typeof raw} (${String(raw)})`);if(!Number.isSafeInteger(id))throw TypeError(`RBAC user id exceeds the safe integer range, got ${String(raw)}`);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 new RbacEntityNotFoundError("role",roleName,guardName);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 new RbacEntityNotFoundError("role",name,guardName);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 new RbacEntityNotFoundError("permission",permissionName,guardName);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 new RbacEntityNotFoundError("permission",name,guardName);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 new RbacEntityNotFoundError("role",roleName,guardName);const permission=await findPermission(permissionName,guardName);if(!permission)throw new RbacEntityNotFoundError("permission",permissionName,guardName);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 new RbacEntityNotFoundError("role",roleName,guardName);const permissionIds=[];for(const name of permissionNames){const perm=await findPermission(name,guardName);if(!perm)throw new RbacEntityNotFoundError("permission",name,guardName);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;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare function normalizeReferralCode(value: unknown): string | null;
|
|
2
|
+
/** Stable, unguessable share code. The unique user constraint handles concurrent creation. */
|
|
3
|
+
export declare function createReferralCode(ownerId: number, database?: ReferralDatabase): Promise<string>;
|
|
4
|
+
/**
|
|
5
|
+
* First attribution wins. Call only from trusted new-account creation, never a
|
|
6
|
+
* public claim endpoint. Pass the registration transaction to commit together.
|
|
7
|
+
* Invalid, unknown, repeated, and self referrals do not disrupt registration.
|
|
8
|
+
*/
|
|
9
|
+
export declare function attributeReferral(newUserId: number, input: unknown, database?: ReferralDatabase): Promise<boolean>;
|
|
10
|
+
/** Server-only conversion hook. Replays preserve the first qualification time. */
|
|
11
|
+
export declare function qualifyReferral(referredUserId: number, database?: ReferralDatabase): Promise<void>;
|
|
12
|
+
/** Aggregate only: a referrer never receives another account's email or profile. */
|
|
13
|
+
export declare function referralSummary(ownerId: number, database?: ReferralDatabase): Promise<ReferralSummary>;
|
|
14
|
+
/** Minimal database contract, also accepted by transaction-scoped connections. */
|
|
15
|
+
export declare interface ReferralDatabase {
|
|
16
|
+
unsafe: (sql: string, bindings?: unknown[]) => { execute: () => Promise<unknown> }
|
|
17
|
+
}
|
|
18
|
+
export declare interface ReferralCode {
|
|
19
|
+
code: string
|
|
20
|
+
user_id: number
|
|
21
|
+
}
|
|
22
|
+
export declare interface ReferralSummary {
|
|
23
|
+
code: string | null
|
|
24
|
+
referred: number
|
|
25
|
+
qualified: number
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomBytes}from"node:crypto";export function normalizeReferralCode(value){if(typeof value!=="string")return null;const code=value.trim().toLowerCase();return/^[a-f0-9]{24}$/.test(code)?code:null}function userId(value){if(!Number.isSafeInteger(value)||value<1)throw TypeError("A positive integer user ID is required.")}async function connection(database){return database??(await import("@stacksjs/database")).db}async function rows(database,sql,bindings){const result=await database.unsafe(sql,bindings).execute();if(Array.isArray(result))return result;if(result&&typeof result==="object"&&"rows"in result&&Array.isArray(result.rows))return result.rows;throw Error("The referral query returned an unsupported result.")}async function duplicate(error){const{isUniqueViolation}=await import("@stacksjs/orm");return isUniqueViolation(error)}export async function createReferralCode(ownerId,database){userId(ownerId);const db=await connection(database);for(let attempt=0;attempt<5;attempt++){const existing=await rows(db,"SELECT code, user_id FROM referral_codes WHERE user_id = ?",[ownerId]);if(existing[0])return existing[0].code;const code=randomBytes(12).toString("hex");try{await db.unsafe("INSERT INTO referral_codes (user_id, code) VALUES (?, ?)",[ownerId,code]).execute();return code}catch(error){if(!await duplicate(error))throw error}}throw Error("Could not allocate a referral code. Please retry.")}export async function attributeReferral(newUserId,input,database){userId(newUserId);const code=normalizeReferralCode(input);if(!code)return!1;const db=await connection(database),owner=(await rows(db,"SELECT code, user_id FROM referral_codes WHERE code = ?",[code]))[0];if(!owner||Number(owner.user_id)===newUserId)return!1;try{await db.unsafe("INSERT INTO referrals (referrer_id, referred_user_id, code, status) VALUES (?, ?, ?, ?)",[Number(owner.user_id),newUserId,code,"registered"]).execute();return!0}catch(error){if(await duplicate(error))return!1;throw error}}export async function qualifyReferral(referredUserId,database){userId(referredUserId);const db=await connection(database),now=new Date().toISOString().slice(0,19).replace("T"," ");await db.unsafe("UPDATE referrals SET status = ?, qualified_at = ?, updated_at = ? WHERE referred_user_id = ? AND status = ?",["qualified",now,now,referredUserId,"registered"]).execute()}export async function referralSummary(ownerId,database){userId(ownerId);const db=await connection(database),codes=await rows(db,"SELECT code, user_id FROM referral_codes WHERE user_id = ?",[ownerId]),counts=await rows(db,"SELECT COUNT(*) AS total, SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) AS qualified FROM referrals WHERE referrer_id = ?",["qualified",ownerId]);return{code:codes[0]?.code??null,referred:Number(counts[0]?.total??0),qualified:Number(counts[0]?.qualified??0)}}
|
package/dist/register.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { NewUser } from '@stacksjs/orm';
|
|
|
14
14
|
*
|
|
15
15
|
* Additive, so `const { token } = await register(...)` is unaffected.
|
|
16
16
|
*/
|
|
17
|
-
export declare function register(credentials: NewUser): Promise<RegistrationResult>;
|
|
17
|
+
export declare function register(credentials: NewUser & { referralCode?: string }): Promise<RegistrationResult>;
|
|
18
18
|
/**
|
|
19
19
|
* What a successful registration hands back: a complete session, matching
|
|
20
20
|
* `Auth.loginUsingId()`.
|
package/dist/register.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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===!1)return new HttpError(409,"Email already exists");return new HttpError(422,"Registration could not be completed. Please check your details and try again.")}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");const{plainTextToken,refreshToken,expiresIn}=await Auth.createTokenForUser(user,{name:"user-auth-token"});return{token:plainTextToken,refreshToken,expiresIn}}
|
|
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";import{attributeReferral}from"./referrals";function duplicateEmailError(){if(config.auth?.registration?.preventEnumeration===!1)return new HttpError(409,"Email already exists");return new HttpError(422,"Registration could not be completed. Please check your details and try again.")}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");if(credentials.referralCode)await attributeReferral(Number(created.id),credentials.referralCode,trx);return Number(created.id)}),user=await User.find(userId);if(!user)throw Error("Failed to retrieve created user");const{plainTextToken,refreshToken,expiresIn}=await Auth.createTokenForUser(user,{name:"user-auth-token"});return{token:plainTextToken,refreshToken,expiresIn}}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/auth",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.33",
|
|
6
6
|
"description": "A more simplistic way to authenticate.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"prepublishOnly": "bun run build"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@stacksjs/bun-router": "^0.1.
|
|
61
|
-
"@stacksjs/cache": "0.74.
|
|
62
|
-
"@stacksjs/config": "0.74.
|
|
63
|
-
"@stacksjs/database": "0.74.
|
|
64
|
-
"@stacksjs/email": "0.74.
|
|
65
|
-
"@stacksjs/env": "0.74.
|
|
66
|
-
"@stacksjs/error-handling": "0.74.
|
|
67
|
-
"@stacksjs/logging": "0.74.
|
|
68
|
-
"@stacksjs/orm": "0.74.
|
|
69
|
-
"@stacksjs/path": "0.74.
|
|
70
|
-
"@stacksjs/router": "0.74.
|
|
71
|
-
"@stacksjs/security": "0.74.
|
|
72
|
-
"@stacksjs/storage": "0.74.
|
|
60
|
+
"@stacksjs/bun-router": "^0.1.15",
|
|
61
|
+
"@stacksjs/cache": "0.74.33",
|
|
62
|
+
"@stacksjs/config": "0.74.33",
|
|
63
|
+
"@stacksjs/database": "0.74.33",
|
|
64
|
+
"@stacksjs/email": "0.74.33",
|
|
65
|
+
"@stacksjs/env": "0.74.33",
|
|
66
|
+
"@stacksjs/error-handling": "0.74.33",
|
|
67
|
+
"@stacksjs/logging": "0.74.33",
|
|
68
|
+
"@stacksjs/orm": "0.74.33",
|
|
69
|
+
"@stacksjs/path": "0.74.33",
|
|
70
|
+
"@stacksjs/router": "0.74.33",
|
|
71
|
+
"@stacksjs/security": "0.74.33",
|
|
72
|
+
"@stacksjs/storage": "0.74.33",
|
|
73
73
|
"@stacksjs/ts-auth": "^0.4.4",
|
|
74
74
|
"ts-qr-codes": "^0.1.8"
|
|
75
75
|
},
|