feat: add comprehensive permission tests and enhance permission checks in components

- Introduced new test files for permission checks, including `check-permission.test.ts`, `enterprise-only-resources.test.ts`, `resolve-permissions.test.ts`, and `service-access.test.ts`.
- Implemented permission checks in various components to ensure actions are gated by user permissions, including `ShowTraefikConfig`, `UpdateTraefikConfig`, `ShowVolumes`, `ShowDomains`, and others.
- Enhanced the logic for displaying UI elements based on user permissions, ensuring that only authorized users can access or modify resources.
This commit is contained in:
Mauricio Siu
2026-03-15 16:42:48 -06:00
parent 8eace173b9
commit 8127dc4536
118 changed files with 25020 additions and 5430 deletions

View File

@@ -1,6 +1,7 @@
import { relations, sql } from "drizzle-orm";
import {
boolean,
index,
integer,
pgTable,
text,
@@ -69,6 +70,36 @@ export const organization = pgTable("organization", {
.references(() => user.id, { onDelete: "cascade" }),
});
export const organizationRole = pgTable(
"organization_role",
{
id: text("id")
.primaryKey()
.$defaultFn(() => nanoid()),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
role: text("role").notNull(),
permission: text("permission").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
},
(table) => [
index("organizationRole_organizationId_idx").on(table.organizationId),
index("organizationRole_role_idx").on(table.role),
],
);
export const organizationRoleRelations = relations(
organizationRole,
({ one }) => ({
organization: one(organization, {
fields: [organizationRole.organizationId],
references: [organization.id],
}),
}),
);
export const organizationRelations = relations(
organization,
({ one, many }) => ({
@@ -80,6 +111,7 @@ export const organizationRelations = relations(
projects: many(projects),
members: many(member),
ssoProviders: many(ssoProvider),
roles: many(organizationRole),
}),
);
@@ -93,7 +125,7 @@ export const member = pgTable("member", {
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
role: text("role").notNull().$type<"owner" | "member" | "admin">(),
role: text("role").notNull().$type<"owner" | "member" | "admin" | (string & {})>(),
createdAt: timestamp("created_at").notNull(),
teamId: text("team_id"),
isDefault: boolean("is_default").notNull().default(false),
@@ -148,7 +180,7 @@ export const invitation = pgTable("invitation", {
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
email: text("email").notNull(),
role: text("role").$type<"owner" | "member" | "admin">(),
role: text("role").$type<"owner" | "member" | "admin" | (string & {})>(),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: text("inviter_id")

View File

@@ -0,0 +1,96 @@
import { relations } from "drizzle-orm";
import { index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { nanoid } from "nanoid";
import { organization } from "./account";
import { user } from "./user";
export const auditLog = pgTable(
"audit_log",
{
id: text("id")
.primaryKey()
.$defaultFn(() => nanoid()),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
userEmail: text("user_email").notNull(),
userRole: text("user_role").notNull(),
action: text("action").notNull(),
resourceType: text("resource_type").notNull(),
resourceId: text("resource_id"),
resourceName: text("resource_name"),
metadata: text("metadata"),
createdAt: timestamp("created_at").defaultNow().notNull(),
},
(t) => ({
orgIdx: index("auditLog_organizationId_idx").on(t.organizationId),
userIdx: index("auditLog_userId_idx").on(t.userId),
createdAtIdx: index("auditLog_createdAt_idx").on(t.createdAt),
}),
);
export const auditLogRelations = relations(auditLog, ({ one }) => ({
organization: one(organization, {
fields: [auditLog.organizationId],
references: [organization.id],
}),
user: one(user, {
fields: [auditLog.userId],
references: [user.id],
}),
}));
export type AuditLog = typeof auditLog.$inferSelect;
export type NewAuditLog = typeof auditLog.$inferInsert;
export type AuditAction =
| "create"
| "update"
| "delete"
| "deploy"
| "cancel"
| "redeploy"
| "login"
| "logout"
| "restore"
| "run"
| "start"
| "stop"
| "reload"
| "rebuild"
| "move";
export type AuditResourceType =
| "project"
| "service"
| "environment"
| "deployment"
| "user"
| "customRole"
| "domain"
| "certificate"
| "registry"
| "server"
| "sshKey"
| "gitProvider"
| "destination"
| "notification"
| "settings"
| "session"
| "port"
| "redirect"
| "security"
| "schedule"
| "backup"
| "volumeBackup"
| "docker"
| "swarm"
| "previewDeployment"
| "organization"
| "cluster"
| "mount"
| "application"
| "compose";

View File

@@ -1,5 +1,6 @@
export * from "./account";
export * from "./ai";
export * from "./audit-log";
export * from "./application";
export * from "./backups";
export * from "./bitbucket";