First Commit. Duplicate bunext-sqlite preset

This commit is contained in:
2026-10-18 16:47:11 +01:00
commit 81e4a8aecc
718 changed files with 41695 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import isPassThroughUser from "./is-pass-through-user";
import { UserManagementUserTypes } from "@/src/dict/user-types-dict";
type Params = {
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
};
/**
* Whether the actor may create new user records. User type assignment on
* the new record is still limited by the assignment matrix.
*/
export default function canCreateUser({ user_types }: Params): boolean {
if (isPassThroughUser({ user_types })) {
return true;
}
return checkUserAccess({
user_types,
includes: UserManagementUserTypes,
}).success;
}
+23
View File
@@ -0,0 +1,23 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import isPassThroughUser from "./is-pass-through-user";
import { UserManagementUserTypes } from "@/src/dict/user-types-dict";
type Params = {
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
};
/**
* Whether the actor may list (view) all user records, not just their
* own. Pass-through actors always pass.
*/
export default function canListUsers({ user_types }: Params): boolean {
if (isPassThroughUser({ user_types })) {
return true;
}
return checkUserAccess({
user_types,
includes: UserManagementUserTypes,
}).success;
}
+26
View File
@@ -0,0 +1,26 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import isPassThroughUser from "./is-pass-through-user";
import { UserManagementUserTypes } from "@/src/dict/user-types-dict";
type Params = {
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
};
/**
* Whether the actor may manage user type records (insert or delete rows
* in the `user_types` table). Which individual types they may grant or
* remove is still enforced per type by the assignment matrix.
*/
export default function canManageUserTypes({
user_types,
}: Params): boolean {
if (isPassThroughUser({ user_types })) {
return true;
}
return checkUserAccess({
user_types,
includes: UserManagementUserTypes,
}).success;
}
+36
View File
@@ -0,0 +1,36 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import type { UserType } from "@/src/types";
import userTypeAssignment from "./user-type-assignment";
type Params = {
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
target_user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
};
/**
* Whether the actor may revoke a user (i.e. add the `revoked` user type).
*
* - A user holding the `super_admin` type can never be revoked.
* - Otherwise governed by the assignment matrix for the `revoked` type:
* pass-through actors (`super_admin`) may revoke anyone, and `admin`
* may revoke users who do not hold an excluded protected type.
*/
export default function canRevokeUser({
user_types,
target_user_types,
}: Params): boolean {
if (target_user_types?.some((ut) => ut.user_type === "super_admin")) {
return false;
}
const target_user_type_values: UserType[] | undefined = target_user_types
?.map((ut) => ut.user_type)
.filter((ut) => Boolean(ut)) as UserType[] | undefined;
return userTypeAssignment({
actor_user_types: user_types,
target_user_type: "revoked",
target_user_types: target_user_type_values,
action: "assign",
});
}
+85
View File
@@ -0,0 +1,85 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import type { UserType } from "@/src/types";
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import isPassThroughUser from "./is-pass-through-user";
type Params = {
actor_user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
actor_user_id?: number | string;
target_user_id?: number | string;
target_user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
/**
* The keys of the fields being updated (e.g. ["first_name", "email"]).
* Used to restrict contact fields on protected users.
*/
update_fields?: string[];
};
/**
* Fields treated as contact information.
*/
export const ContactUpdateFields = ["email"];
/**
* User types whose contact fields (email) are protected from changes by
* non-pass-through actors.
*/
export const ContactProtectedUserTypes: UserType[] = ["super_admin"];
/**
* Whether the actor may update a user record.
*
* - Any user may update their own record (profile fields only; user type
* changes are still governed by the assignment matrix).
* - Pass-through actors (`super_admin`) may update anyone.
* - `admin` may edit the basic info of any user but cannot change the
* email of protected users (`super_admin`). User type changes are
* still governed by the assignment matrix.
* - Everyone else can only update their own record.
*/
export default function canUpdateUser({
actor_user_types,
actor_user_id,
target_user_id,
target_user_types,
update_fields,
}: Params): boolean {
if (
target_user_id &&
actor_user_id &&
String(target_user_id) === String(actor_user_id)
) {
return true;
}
if (isPassThroughUser({ user_types: actor_user_types })) {
return true;
}
const can_edit_basic_info = checkUserAccess({
user_types: actor_user_types,
includes: ["admin"],
});
if (!can_edit_basic_info.success) {
return false;
}
const touches_contacts = update_fields?.some((field) =>
ContactUpdateFields.includes(field),
);
if (!touches_contacts) {
return true;
}
if (
target_user_types?.some((ut) =>
ContactProtectedUserTypes.includes(ut.user_type as UserType),
)
) {
return false;
}
return true;
}
@@ -0,0 +1,25 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
import type { UserType } from "@/src/types";
import userTypeAssignment from "./user-type-assignment";
type Params = {
actor_user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
user_types: UserType[];
};
/**
* Filters a list of user types down to the ones the actor may assign
* (grant or remove), per the assignment matrix.
*/
export default function filterAssignableUserTypes({
actor_user_types,
user_types,
}: Params): UserType[] {
return user_types.filter((target_user_type) =>
userTypeAssignment({
actor_user_types,
target_user_type,
action: "assign",
}),
);
}
+193
View File
@@ -0,0 +1,193 @@
import { describe, expect, test } from "bun:test";
import {
canCreateUser,
canListUsers,
canManageUserTypes,
canRevokeUser,
canUpdateUser,
filterAssignableUserTypes,
isPassThroughUser,
} from "./";
import {
ALL_USER_TYPES,
makeUserTypes,
} from "./user-type-assignment.test-fixtures";
describe("isPassThroughUser", () => {
test("super_admin is pass-through", () => {
expect(isPassThroughUser({ user_types: makeUserTypes("super_admin") })).toBe(true);
});
test("regular types are not pass-through", () => {
for (const type of ["admin", "president", "secretary"]) {
expect(
isPassThroughUser({ user_types: makeUserTypes(type) }),
).toBe(false);
}
});
});
describe("canListUsers / canCreateUser / canManageUserTypes", () => {
test("pass-through always passes", () => {
for (const fn of [canListUsers, canCreateUser, canManageUserTypes]) {
expect(
fn({ user_types: makeUserTypes("super_admin") }),
).toBe(true);
}
});
test("admin passes", () => {
expect(canListUsers({ user_types: makeUserTypes("admin") })).toBe(true);
expect(canCreateUser({ user_types: makeUserTypes("admin") })).toBe(true);
expect(
canManageUserTypes({ user_types: makeUserTypes("admin") }),
).toBe(true);
});
test("non-admin types fail", () => {
for (const type of ["auditor", "president", "secretary", "read_only"]) {
expect(
canListUsers({ user_types: makeUserTypes(type) }),
).toBe(false);
expect(
canCreateUser({ user_types: makeUserTypes(type) }),
).toBe(false);
expect(
canManageUserTypes({ user_types: makeUserTypes(type) }),
).toBe(false);
}
});
});
describe("canUpdateUser", () => {
test("actors may update their own record", () => {
expect(
canUpdateUser({
actor_user_types: makeUserTypes("auditor"),
actor_user_id: 5,
target_user_id: 5,
}),
).toBe(true);
});
test("pass-through may update anyone including contacts", () => {
expect(
canUpdateUser({
actor_user_types: makeUserTypes("super_admin"),
actor_user_id: 1,
target_user_id: 2,
target_user_types: makeUserTypes("super_admin"),
update_fields: ["email"],
}),
).toBe(true);
});
test("admin may edit basic info of others", () => {
expect(
canUpdateUser({
actor_user_types: makeUserTypes("admin"),
actor_user_id: 1,
target_user_id: 2,
update_fields: ["first_name"],
}),
).toBe(true);
});
test("admin cannot change contacts of protected users", () => {
expect(
canUpdateUser({
actor_user_types: makeUserTypes("admin"),
actor_user_id: 1,
target_user_id: 2,
target_user_types: makeUserTypes("super_admin"),
update_fields: ["email"],
}),
).toBe(false);
});
test("non-admin actors cannot update others", () => {
expect(
canUpdateUser({
actor_user_types: makeUserTypes("auditor"),
actor_user_id: 1,
target_user_id: 2,
update_fields: ["first_name"],
}),
).toBe(false);
});
});
describe("canRevokeUser", () => {
test("super_admin targets can never be revoked", () => {
expect(
canRevokeUser({
user_types: makeUserTypes("super_admin"),
target_user_types: makeUserTypes("super_admin"),
}),
).toBe(false);
});
test("pass-through may revoke anyone else", () => {
expect(
canRevokeUser({
user_types: makeUserTypes("super_admin"),
target_user_types: makeUserTypes("admin"),
}),
).toBe(true);
});
test("admin may revoke revocable targets", () => {
expect(
canRevokeUser({
user_types: makeUserTypes("admin"),
target_user_types: makeUserTypes("read_only"),
}),
).toBe(true);
});
test("admin cannot revoke protected targets", () => {
expect(
canRevokeUser({
user_types: makeUserTypes("admin"),
target_user_types: makeUserTypes("admin"),
}),
).toBe(false);
expect(
canRevokeUser({
user_types: makeUserTypes("admin"),
target_user_types: makeUserTypes("president"),
}),
).toBe(false);
});
});
describe("filterAssignableUserTypes", () => {
test("super_admin can assign everything", () => {
const assignable = filterAssignableUserTypes({
actor_user_types: makeUserTypes("super_admin"),
user_types: ALL_USER_TYPES,
});
expect(assignable).toHaveLength(ALL_USER_TYPES.length);
});
test("admin sees only assignable types", () => {
const assignable = filterAssignableUserTypes({
actor_user_types: makeUserTypes("admin"),
user_types: ALL_USER_TYPES,
});
expect(assignable).toContain("auditor");
expect(assignable).toContain("read_only");
expect(assignable).not.toContain("admin");
expect(assignable).not.toContain("super_admin");
expect(assignable).not.toContain("president");
expect(assignable).not.toContain("secretary");
});
test("secretary can assign nothing", () => {
const assignable = filterAssignableUserTypes({
actor_user_types: makeUserTypes("secretary"),
user_types: ALL_USER_TYPES,
});
expect(assignable).toHaveLength(0);
});
});
+8
View File
@@ -0,0 +1,8 @@
export { default as userTypeAssignment } from "./user-type-assignment";
export { default as canCreateUser } from "./can-create-user";
export { default as canListUsers } from "./can-list-users";
export { default as canManageUserTypes } from "./can-manage-user-types";
export { default as canRevokeUser } from "./can-revoke-user";
export { default as canUpdateUser } from "./can-update-user";
export { default as filterAssignableUserTypes } from "./filter-assignable-user-types";
export { default as isPassThroughUser } from "./is-pass-through-user";
+14
View File
@@ -0,0 +1,14 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
type Params = {
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
};
/**
* Pass-through actors that bypass the assignment matrix: `super_admin`.
*/
export default function isPassThroughUser({ user_types }: Params): boolean {
return Boolean(
user_types?.find((ut) => ut.user_type === "super_admin"),
);
}
+12
View File
@@ -0,0 +1,12 @@
import type { BUN_SQLITE_BSQLITE_USER_TYPES } from "@/db/types/db";
type Params = {
user_types?: BUN_SQLITE_BSQLITE_USER_TYPES[] | null;
};
/**
* Whether this is a `read_only` user
*/
export default function isReadOnlyUser({ user_types }: Params): boolean {
return Boolean(user_types?.find((ty) => ty.user_type == "read_only"));
}
@@ -0,0 +1,72 @@
import type {
UserType,
UserTypeAssignmentMatrixType,
} from "@/src/types";
/**
* Synthetic types (`orphan`, `board_member`) exercise the matrix interpreter
* independently of the preset's real `UserTypes`.
*/
export const ALL_USER_TYPES = [
"super_admin",
"admin",
"auditor",
"president",
"vice_president",
"secretary",
"orphan",
"revoked",
"read_only",
"board_member",
] as UserType[];
/**
* Matrix keyed by any type name, cast to the real matrix type for the
* interpreter.
*/
type FixtureMatrix = Record<string, any>;
type UserTypesInput = string[];
export function makeUserTypes(...types: UserTypesInput): { user_type: string }[] {
return types.map((user_type) => ({ user_type }));
}
export function makeMatrix(
overrides?: FixtureMatrix,
): UserTypeAssignmentMatrixType {
const base: FixtureMatrix = {
super_admin: {},
admin: { allowed_actors: [] },
president: { allowed_actors: [] },
vice_president: { allowed_actors: [] },
secretary: { allowed_actors: [] },
board_member: { allowed_actors: ["admin"] },
auditor: { allowed_actors: ["admin"] },
orphan: { allowed_actors: ["admin"] },
read_only: {
allowed_actors: {
admin: { excluded_targets: ["super_admin"] },
},
},
revoked: {
allowed_actors: {
admin: {
allowed_targets: ["board_member", "auditor", "orphan"],
excluded_targets: [
"super_admin",
"admin",
"president",
"vice_president",
"secretary",
],
},
},
},
};
return {
...base,
...overrides,
} as UserTypeAssignmentMatrixType;
}
+193
View File
@@ -0,0 +1,193 @@
import { describe, expect, test } from "bun:test";
import type { UserType } from "@/src/types";
import userTypeAssignment from "./user-type-assignment";
import {
ALL_USER_TYPES,
makeMatrix,
makeUserTypes,
} from "./user-type-assignment.test-fixtures";
const ASSIGNMENT_MATRIX = makeMatrix();
const DELETION_MATRIX = makeMatrix();
function assign(params: {
actor: string[];
target_type: string;
target?: string[];
action?: "assign" | "delete";
}) {
return userTypeAssignment({
actor_user_types: makeUserTypes(...params.actor),
target_user_type: params.target_type as UserType,
target_user_types: params.target as UserType[],
action: params.action || "assign",
assignment_matrix: ASSIGNMENT_MATRIX,
deletion_matrix: DELETION_MATRIX,
});
}
describe("userTypeAssignment", () => {
test("pass-through (super_admin) can assign anything", () => {
for (const target of ALL_USER_TYPES) {
expect(
assign({ actor: ["super_admin"], target_type: target }),
).toBe(true);
}
});
test("pass-through (super_admin) can delete anything", () => {
for (const target of ALL_USER_TYPES) {
expect(
assign({
actor: ["super_admin"],
target_type: target,
action: "delete",
}),
).toBe(true);
}
});
test("no actors allowed for super_admin", () => {
expect(
assign({ actor: ["admin"], target_type: "super_admin" }),
).toBe(false);
expect(
assign({
actor: ["admin"],
target_type: "super_admin",
action: "delete",
}),
).toBe(false);
});
test("empty allowed_actors means pass-through only", () => {
for (const target of ["admin", "president", "vice_president", "secretary"]) {
expect(assign({ actor: ["admin"], target_type: target })).toBe(
false,
);
}
});
test("array allowed_actors matches any actor", () => {
expect(assign({ actor: ["admin"], target_type: "board_member" })).toBe(
true,
);
expect(assign({ actor: ["auditor"], target_type: "board_member" })).toBe(
false,
);
expect(assign({ actor: ["admin"], target_type: "auditor" })).toBe(true);
});
test("per-actor rules respect excluded targets", () => {
expect(
assign({
actor: ["admin"],
target_type: "read_only",
target: ["admin"],
}),
).toBe(true);
expect(
assign({
actor: ["admin"],
target_type: "read_only",
target: ["super_admin"],
}),
).toBe(false);
});
test("per-actor rules respect allowed targets", () => {
expect(
assign({
actor: ["admin"],
target_type: "revoked",
target: ["board_member"],
}),
).toBe(true);
expect(
assign({
actor: ["admin"],
target_type: "revoked",
target: ["admin"],
}),
).toBe(false);
});
test("missing target user types fail allowed_targets checks", () => {
expect(
assign({
actor: ["admin"],
target_type: "revoked",
target: [],
}),
).toBe(false);
});
test("same_as clones rules of another actor", () => {
const matrix = makeMatrix({
read_only: {
allowed_actors: {
admin: { excluded_targets: ["super_admin"] },
auditor: { same_as: "admin" },
},
},
});
expect(
userTypeAssignment({
actor_user_types: makeUserTypes("auditor"),
target_user_type: "read_only",
target_user_types: ["admin"] as UserType[],
action: "assign",
assignment_matrix: matrix,
deletion_matrix: matrix,
}),
).toBe(true);
expect(
userTypeAssignment({
actor_user_types: makeUserTypes("auditor"),
target_user_type: "read_only",
target_user_types: ["super_admin"] as UserType[],
action: "assign",
assignment_matrix: matrix,
deletion_matrix: matrix,
}),
).toBe(false);
});
test("all flag grants unconditional access", () => {
const matrix = makeMatrix({
board_member: {
allowed_actors: {
admin: { all: true },
},
},
});
expect(
userTypeAssignment({
actor_user_types: makeUserTypes("admin"),
target_user_type: "board_member" as UserType,
target_user_types: ["super_admin"] as UserType[],
action: "assign",
assignment_matrix: matrix,
deletion_matrix: matrix,
}),
).toBe(true);
});
});
describe("unknown user types", () => {
test("types missing from the matrix are denied instead of throwing", () => {
expect(
userTypeAssignment({
actor_user_types: makeUserTypes("admin"),
target_user_type: "not_in_matrix" as UserType,
action: "assign",
assignment_matrix: makeMatrix(),
deletion_matrix: makeMatrix(),
}),
).toBe(false);
});
});
+165
View File
@@ -0,0 +1,165 @@
import type {
UserAssignmentFnParams,
UserTypeAssignmentRules,
} from "@/src/types";
import {
UserTypeAssignmentMatrix,
UserTypeDeletionMatrix,
} from "@/src/dict/user-types-dict";
import isPassThroughUser from "./is-pass-through-user";
/**
* Whether the actor may assign or remove `target_user_type` on a user.
* Governed by `UserTypeAssignmentMatrix` and `UserTypeDeletionMatrix` and
* applied symmetrically to granting and removing. Pass-through actors
* (`super_admin`) can always assign.
*/
export default function userTypeAssignment({
actor_user_types,
target_user_type,
target_user_types,
action,
assignment_matrix = UserTypeAssignmentMatrix,
deletion_matrix = UserTypeDeletionMatrix,
debug,
}: UserAssignmentFnParams): boolean {
if (isPassThroughUser({ user_types: actor_user_types })) {
if (debug) {
console.log(
`[RBAC] Pass-through actor detected for [${target_user_type}], access granted.`,
);
}
return true;
}
const matrix = action == "assign" ? assignment_matrix : deletion_matrix;
const assignable_by = matrix[target_user_type];
if (!assignable_by?.allowed_actors) {
if (debug) {
console.log(
`[RBAC] No allowed actors for [${target_user_type}] in the ${action} matrix.`,
);
}
return false;
}
const is_target_user_type_excluded =
target_user_types && assignable_by.excluded_targets
? target_user_types.find((tut) =>
assignable_by.excluded_targets?.find((ex) => ex == tut),
)
: false;
if (is_target_user_type_excluded) {
if (debug) {
console.log(
`[RBAC] Target user type [${target_user_types?.join(",")}] excluded for [${target_user_type}].`,
);
}
return false;
}
const actor_type_set = new Set<string>(
actor_user_types
?.map((ut) => ut.user_type)
.filter((ut): ut is string => Boolean(ut)),
);
if (Array.isArray(assignable_by.allowed_actors)) {
if (!assignable_by.allowed_actors.length) {
if (debug) {
console.log(
`[RBAC] Actor not allowed for [${target_user_type}], allowed actors list is empty.`,
);
}
return false;
}
return assignable_by.allowed_actors.some((actor_type) =>
actor_type_set.has(actor_type),
);
}
const allowed_actors_matrix = assignable_by.allowed_actors as Record<
string,
UserTypeAssignmentRules | undefined
>;
if (!Object.keys(allowed_actors_matrix).length) {
if (debug) {
console.log(
`[RBAC] No allowed actors matrix for [${target_user_type}].`,
);
}
return false;
}
return Array.from(actor_type_set).some((actor_type) => {
let actor_rules = allowed_actors_matrix[actor_type];
if (
actor_rules?.same_as &&
allowed_actors_matrix[actor_rules.same_as]
) {
actor_rules = {
...actor_rules,
...allowed_actors_matrix[actor_rules.same_as],
};
}
if (actor_rules?.all) {
if (debug) {
console.log(
`[RBAC] Actor [${actor_type}] has all access for [${target_user_type}].`,
);
}
return true;
}
if (!actor_rules?.allowed_targets && !actor_rules?.excluded_targets) {
if (debug) {
console.log(
`[RBAC] No actor rules for [${actor_type}] on [${target_user_type}].`,
);
}
return false;
}
if (
actor_rules.allowed_targets &&
!actor_rules.allowed_targets.find((t) =>
target_user_types?.includes(t),
)
) {
if (debug) {
console.log(
`[RBAC] Target user types [${target_user_types?.join(",")}] not included for [${actor_type}] -> [${target_user_type}].`,
);
}
return false;
}
if (
actor_rules.excluded_targets &&
actor_rules.excluded_targets.find((t) =>
target_user_types?.includes(t),
)
) {
if (debug) {
console.log(
`[RBAC] Target user types [${target_user_types?.join(",")}] excluded for [${actor_type}] -> [${target_user_type}].`,
);
}
return false;
}
if (debug) {
console.log(
`[RBAC] Actor [${actor_type}] granted [${action}] of [${target_user_type}].`,
);
}
return true;
});
}