mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-11 06:34:44 -05:00
- Add plugin store system with dynamic loading of robot actions - Implement plugin store API routes and database schema - Update experiment designer to support plugin-based actions - Refactor environment configuration and sidebar navigation - Improve authentication session handling with additional user details - Update Tailwind CSS configuration and global styles - Remove deprecated files and consolidate project structure
8.3 KiB
8.3 KiB
Authentication & Permissions System
Overview
HRIStudio implements a robust authentication and role-based access control (RBAC) system using NextAuth.js (Auth.js) and a custom permissions framework. This system ensures secure access to resources and proper isolation of sensitive data.
Authentication
NextAuth Configuration
export const authConfig = {
adapter: DrizzleAdapter(db),
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Credential validation logic
}
})
],
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
name: user.firstName && user.lastName
? `${user.firstName} ${user.lastName}`
: null,
firstName: user.firstName,
lastName: user.lastName,
},
}),
},
pages: {
signIn: '/auth/signin',
},
};
Session Management
interface Session {
user: {
id: string;
email: string;
firstName: string | null;
lastName: string | null;
} & DefaultSession["user"];
}
Role-Based Access Control
Role Hierarchy
-
Owner
- Single owner per study
- Full control over all aspects
- Can delete study or transfer ownership
- Can manage all other roles
-
Admin
- Multiple admins allowed
- Can manage participants and experiments
- Cannot delete study or transfer ownership
- Can invite and manage other users (except Owner)
-
Principal Investigator (PI)
- Scientific oversight role
- Full access to participant data
- Can manage experiment protocols
- Cannot modify core study settings
-
Wizard
- Operates robots during experiments
- Can control live experiment sessions
- Limited view of participant data
- Cannot modify study design
-
Researcher
- Can view and analyze data
- Access to anonymized information
- Cannot modify study or participant data
- Cannot run experiment trials
-
Observer
- Can view live experiments
- Access to anonymized data
- Can add annotations
- Cannot modify any study aspects
Permission Categories
export const PERMISSIONS = {
// Study Management
CREATE_STUDY: "create_study",
DELETE_STUDY: "delete_study",
EDIT_STUDY: "edit_study",
TRANSFER_OWNERSHIP: "transfer_ownership",
// Participant Management
VIEW_PARTICIPANTS: "view_participants",
ADD_PARTICIPANT: "add_participant",
EDIT_PARTICIPANT: "edit_participant",
DELETE_PARTICIPANT: "delete_participant",
VIEW_PARTICIPANT_NAMES: "view_participant_names",
// Experiment Management
CREATE_EXPERIMENT: "create_experiment",
EDIT_EXPERIMENT: "edit_experiment",
DELETE_EXPERIMENT: "delete_experiment",
RUN_EXPERIMENT: "run_experiment",
// Data Access
EXPORT_DATA: "export_data",
VIEW_ANALYTICS: "view_analytics",
// User Management
INVITE_USERS: "invite_users",
MANAGE_ROLES: "manage_roles",
} as const;
Role-Permission Matrix
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
OWNER: Object.values(PERMISSIONS),
ADMIN: [
PERMISSIONS.EDIT_STUDY,
PERMISSIONS.VIEW_PARTICIPANTS,
PERMISSIONS.ADD_PARTICIPANT,
PERMISSIONS.EDIT_PARTICIPANT,
PERMISSIONS.DELETE_PARTICIPANT,
PERMISSIONS.VIEW_PARTICIPANT_NAMES,
PERMISSIONS.CREATE_EXPERIMENT,
PERMISSIONS.EDIT_EXPERIMENT,
PERMISSIONS.DELETE_EXPERIMENT,
PERMISSIONS.RUN_EXPERIMENT,
PERMISSIONS.EXPORT_DATA,
PERMISSIONS.VIEW_ANALYTICS,
PERMISSIONS.INVITE_USERS,
PERMISSIONS.MANAGE_ROLES,
],
PRINCIPAL_INVESTIGATOR: [
PERMISSIONS.VIEW_PARTICIPANTS,
PERMISSIONS.ADD_PARTICIPANT,
PERMISSIONS.EDIT_PARTICIPANT,
PERMISSIONS.VIEW_PARTICIPANT_NAMES,
PERMISSIONS.CREATE_EXPERIMENT,
PERMISSIONS.EDIT_EXPERIMENT,
PERMISSIONS.RUN_EXPERIMENT,
PERMISSIONS.EXPORT_DATA,
PERMISSIONS.VIEW_ANALYTICS,
],
// ... additional role permissions
};
Implementation
Permission Checking
export async function checkPermissions({
studyId,
permission,
session,
}: {
studyId?: number;
permission: Permission;
session: Session | null;
}): Promise<void> {
if (!session?.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You must be logged in to perform this action",
});
}
// Anyone who is logged in can create a study
if (!studyId) {
if (permission === "CREATE_STUDY") {
return;
}
throw new TRPCError({
code: "BAD_REQUEST",
message: "Study ID is required for this action",
});
}
const membership = await db.query.studyMembers.findFirst({
where: and(
eq(studyMembers.studyId, studyId),
eq(studyMembers.userId, session.user.id),
),
});
if (!membership) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You do not have permission to perform this action",
});
}
const normalizedRole = membership.role.toUpperCase() as keyof typeof ROLE_PERMISSIONS;
const permittedActions = ROLE_PERMISSIONS[normalizedRole] ?? [];
if (normalizedRole === "OWNER") {
return;
}
if (!permittedActions.includes(permission)) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You do not have permission to perform this action",
});
}
}
Database Schema
export const studyMembers = createTable("study_member", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
studyId: integer("study_id")
.notNull()
.references(() => studies.id, { onDelete: "cascade" }),
userId: varchar("user_id", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
role: studyRoleEnum("role").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
UI Integration
Protected Routes
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const { data: session, status } = useSession()
const router = useRouter()
useEffect(() => {
if (status === "unauthenticated") {
router.replace("/auth/signin")
}
}, [status, router])
if (!session) {
return null
}
return (
// Layout content
);
}
Conditional Rendering
export function ParticipantDetails({ participant, role }: Props) {
const canViewIdentifiableInfo = [
ROLES.OWNER,
ROLES.ADMIN,
ROLES.PRINCIPAL_INVESTIGATOR
].includes(role);
return (
<div>
{canViewIdentifiableInfo ? (
<div>
<h3>{participant.name}</h3>
<p>{participant.email}</p>
</div>
) : (
<div>
<h3>Participant {participant.id}</h3>
<p>[Redacted]</p>
</div>
)}
</div>
);
}
Security Considerations
Password Handling
- Passwords are hashed using bcrypt before storage
- Minimum password requirements enforced
- Rate limiting on authentication attempts
Session Security
- CSRF protection enabled
- Secure session cookies
- Session expiration and renewal
Data Access
- Row-level security through role checks
- Audit logging of sensitive operations
- Data encryption at rest
Best Practices
-
Permission Checking:
- Always check permissions before sensitive operations
- Use the checkPermissions utility consistently
- Include proper error messages
-
Role Assignment:
- Validate role assignments
- Maintain role hierarchy
- Prevent privilege escalation
-
UI Security:
- Hide sensitive UI elements based on permissions
- Clear error messages without exposing internals
- Proper loading states during authentication
-
Audit Trail:
- Log authentication attempts
- Track permission changes
- Monitor sensitive data access
Future Enhancements
-
Advanced Authentication:
- Multi-factor authentication
- OAuth provider integration
- SSO support
-
Enhanced Permissions:
- Custom role creation
- Temporary permissions
- Permission inheritance
-
Audit System:
- Detailed activity logging
- Security alerts
- Compliance reporting
-
UI Improvements:
- Role management interface
- Permission visualization
- Audit log viewer