@@ -29,9 +29,11 @@ import {
2929 executions ,
3030 type ExecutionStatusType ,
3131 type MembershipInsert ,
32+ type MembershipRow ,
3233 memberships ,
3334 type OrganizationInsert ,
3435 OrganizationRole ,
36+ type OrganizationRoleType ,
3537 organizations ,
3638 Plan ,
3739 type PlanType ,
@@ -1703,67 +1705,117 @@ export async function deleteOrganization(
17031705}
17041706
17051707/**
1706- * Add or update a user's membership in an organization (only admins and owners can do this)
1708+ * Check if a user is the owner of an organization
1709+ *
1710+ * @param db Database instance
1711+ * @param organizationId Organization ID
1712+ * @param userId User ID to check
1713+ * @returns True if user is the organization owner, false otherwise
1714+ */
1715+ export async function isOrganizationOwner (
1716+ db : ReturnType < typeof createDatabase > ,
1717+ organizationId : string ,
1718+ userId : string
1719+ ) : Promise < boolean > {
1720+ const [ user ] = await db
1721+ . select ( )
1722+ . from ( users )
1723+ . where ( eq ( users . id , userId ) )
1724+ . limit ( 1 ) ;
1725+
1726+ return user ?. organizationId === organizationId ;
1727+ }
1728+
1729+ /**
1730+ * Add or update a user's membership in an organization
1731+ *
1732+ * Role-based permissions:
1733+ * - Only owners and admins can add/update memberships
1734+ * - Only owners can assign admin roles
1735+ * - Only owners can assign owner roles (but owner role cannot be changed)
1736+ * - Members cannot add/update memberships
17071737 *
17081738 * @param db Database instance
17091739 * @param organizationIdOrHandle Organization ID or handle
1710- * @param targetUserId User ID to add/update membership for
1740+ * @param targetUserEmail Email of the user to add/update membership for
17111741 * @param role Role to assign (member, admin, owner)
17121742 * @param adminUserId User ID of the admin/owner making the change
1713- * @returns The created or updated membership record, or null if permission denied
1743+ * @returns The created or updated membership record, or null if permission denied or user not found
17141744 */
17151745export async function addOrUpdateMembership (
17161746 db : ReturnType < typeof createDatabase > ,
17171747 organizationIdOrHandle : string ,
1718- targetUserId : string ,
1748+ targetUserEmail : string ,
17191749 role : OrganizationRoleType ,
17201750 adminUserId : string
17211751) : Promise < MembershipRow | null > {
1722- // First, verify the admin user has permission (admin or owner)
1723- const [ adminMembership ] = await db
1752+ // Get the organization ID first
1753+ const [ organization ] = await db
17241754 . select ( )
1725- . from ( memberships )
1726- . innerJoin ( organizations , eq ( memberships . organizationId , organizations . id ) )
1727- . where (
1728- and (
1729- eq ( memberships . userId , adminUserId ) ,
1730- getOrganizationCondition ( organizationIdOrHandle ) ,
1731- inArray ( memberships . role , [
1732- OrganizationRole . ADMIN ,
1733- OrganizationRole . OWNER ,
1734- ] )
1735- )
1736- )
1755+ . from ( organizations )
1756+ . where ( getOrganizationCondition ( organizationIdOrHandle ) )
17371757 . limit ( 1 ) ;
17381758
1739- if ( ! adminMembership ) {
1740- return null ; // Admin user doesn't have permission
1759+ if ( ! organization ) {
1760+ return null ; // Organization not found
17411761 }
17421762
1743- // Additional check: only owners can assign the owner role
1744- if (
1745- role === OrganizationRole . OWNER &&
1746- adminMembership . memberships . role !== OrganizationRole . OWNER
1747- ) {
1748- return null ; // Only owners can assign owner role
1763+ const organizationId = organization . id ;
1764+
1765+ // Check if the admin user is the organization owner
1766+ const isAdminOwner = await isOrganizationOwner ( db , organizationId , adminUserId ) ;
1767+
1768+ // If not the owner, check if they have admin role
1769+ let hasAdminRole = false ;
1770+ if ( ! isAdminOwner ) {
1771+ const [ adminMembership ] = await db
1772+ . select ( )
1773+ . from ( memberships )
1774+ . where (
1775+ and (
1776+ eq ( memberships . userId , adminUserId ) ,
1777+ eq ( memberships . organizationId , organizationId ) ,
1778+ eq ( memberships . role , OrganizationRole . ADMIN )
1779+ )
1780+ )
1781+ . limit ( 1 ) ;
1782+ hasAdminRole = ! ! adminMembership ;
17491783 }
17501784
1751- const organizationId = adminMembership . organizations . id ;
1752- const now = new Date ( ) ;
1785+ // Permission check: Only owners and admins can add/update memberships
1786+ if ( ! isAdminOwner && ! hasAdminRole ) {
1787+ return null ; // User doesn't have permission
1788+ }
1789+
1790+ // Role assignment restrictions
1791+ if ( role === OrganizationRole . OWNER ) {
1792+ return null ; // Owner role cannot be assigned - there's only one owner (the creator)
1793+ }
17531794
1754- // CRITICAL: Prevent changing the role of the main owner in their personal organization
1755- // Check if the target user is the main owner of their personal organization
1795+ if ( role === OrganizationRole . ADMIN && ! isAdminOwner ) {
1796+ return null ; // Only owners can assign admin roles
1797+ }
1798+
1799+ // Look up the target user by email
17561800 const [ targetUser ] = await db
17571801 . select ( )
17581802 . from ( users )
1759- . where ( eq ( users . id , targetUserId ) )
1803+ . where ( eq ( users . email , targetUserEmail ) )
17601804 . limit ( 1 ) ;
17611805
1762- if ( targetUser && targetUser . organizationId === organizationId ) {
1763- // This is the user's personal organization - their role cannot be changed
1764- return null ; // Cannot change role of main owner in their personal organization
1806+ if ( ! targetUser ) {
1807+ return null ; // User not found with this email
17651808 }
17661809
1810+ const targetUserId = targetUser . id ;
1811+
1812+ // Prevent adding the organization owner as a member (they're already the owner)
1813+ if ( targetUser . organizationId === organizationId ) {
1814+ return null ; // Cannot add/change role of the organization owner
1815+ }
1816+
1817+ const now = new Date ( ) ;
1818+
17671819 // Check if the target user is already a member
17681820 const [ existingMembership ] = await db
17691821 . select ( )
@@ -1813,39 +1865,85 @@ export async function addOrUpdateMembership(
18131865}
18141866
18151867/**
1816- * Delete a user's membership from an organization (only admins and owners can do this)
1868+ * Delete a user's membership from an organization
1869+ *
1870+ * Role-based permissions:
1871+ * - Only owners and admins can remove memberships
1872+ * - The organization owner cannot be removed
1873+ * - Users cannot remove themselves
1874+ * - Only owners can remove admins
18171875 *
18181876 * @param db Database instance
18191877 * @param organizationIdOrHandle Organization ID or handle
1820- * @param targetUserId User ID to remove from the organization
1878+ * @param targetUserEmail Email of the user to remove from the organization
18211879 * @param adminUserId User ID of the admin/owner making the change
18221880 * @returns True if membership was deleted, false if permission denied or not found
18231881 */
18241882export async function deleteMembership (
18251883 db : ReturnType < typeof createDatabase > ,
18261884 organizationIdOrHandle : string ,
1827- targetUserId : string ,
1885+ targetUserEmail : string ,
18281886 adminUserId : string
18291887) : Promise < boolean > {
1830- // First, verify the admin user has permission (admin or owner)
1831- const [ adminMembership ] = await db
1888+ // Get the organization ID first
1889+ const [ organization ] = await db
18321890 . select ( )
1833- . from ( memberships )
1834- . innerJoin ( organizations , eq ( memberships . organizationId , organizations . id ) )
1835- . where (
1836- and (
1837- eq ( memberships . userId , adminUserId ) ,
1838- getOrganizationCondition ( organizationIdOrHandle ) ,
1839- inArray ( memberships . role , [
1840- OrganizationRole . ADMIN ,
1841- OrganizationRole . OWNER ,
1842- ] )
1891+ . from ( organizations )
1892+ . where ( getOrganizationCondition ( organizationIdOrHandle ) )
1893+ . limit ( 1 ) ;
1894+
1895+ if ( ! organization ) {
1896+ return false ; // Organization not found
1897+ }
1898+
1899+ const organizationId = organization . id ;
1900+
1901+ // Check if the admin user is the organization owner
1902+ const isAdminOwner = await isOrganizationOwner ( db , organizationId , adminUserId ) ;
1903+
1904+ // If not the owner, check if they have admin role
1905+ let hasAdminRole = false ;
1906+ if ( ! isAdminOwner ) {
1907+ const [ adminMembership ] = await db
1908+ . select ( )
1909+ . from ( memberships )
1910+ . where (
1911+ and (
1912+ eq ( memberships . userId , adminUserId ) ,
1913+ eq ( memberships . organizationId , organizationId ) ,
1914+ eq ( memberships . role , OrganizationRole . ADMIN )
1915+ )
18431916 )
1844- )
1917+ . limit ( 1 ) ;
1918+ hasAdminRole = ! ! adminMembership ;
1919+ }
1920+
1921+ // Permission check: Only owners and admins can remove memberships
1922+ if ( ! isAdminOwner && ! hasAdminRole ) {
1923+ return false ; // User doesn't have permission
1924+ }
1925+
1926+ // Look up the target user by email
1927+ const [ targetUser ] = await db
1928+ . select ( )
1929+ . from ( users )
1930+ . where ( eq ( users . email , targetUserEmail ) )
18451931 . limit ( 1 ) ;
18461932
1847- if ( ! adminMembership ) {
1848- return false ; // Admin user doesn't have permission
1933+ if ( ! targetUser ) {
1934+ return false ; // User not found with this email
1935+ }
1936+
1937+ const targetUserId = targetUser . id ;
1938+
1939+ // Prevent removing the organization owner
1940+ if ( targetUser . organizationId === organizationId ) {
1941+ return false ; // Cannot remove the organization owner
1942+ }
1943+
1944+ // Prevent users from removing themselves
1945+ if ( targetUserId === adminUserId ) {
1946+ return false ; // Users cannot remove their own membership
18491947 }
18501948
18511949 // Get the target user's membership to check their role
@@ -1855,7 +1953,7 @@ export async function deleteMembership(
18551953 . where (
18561954 and (
18571955 eq ( memberships . userId , targetUserId ) ,
1858- eq ( memberships . organizationId , adminMembership . organizations . id )
1956+ eq ( memberships . organizationId , organizationId )
18591957 )
18601958 )
18611959 . limit ( 1 ) ;
@@ -1864,33 +1962,9 @@ export async function deleteMembership(
18641962 return false ; // Target user is not a member
18651963 }
18661964
1867- // Additional check: only owners can delete other owners
1868- if (
1869- targetMembership . role === OrganizationRole . OWNER &&
1870- adminMembership . memberships . role !== OrganizationRole . OWNER
1871- ) {
1872- return false ; // Only owners can delete other owners
1873- }
1874-
1875- // Prevent users from deleting themselves
1876- if ( targetUserId === adminUserId ) {
1877- return false ; // Users cannot delete their own membership
1878- }
1879-
1880- // CRITICAL: Prevent removing the main owner from their personal organization
1881- // Check if the target user is the main owner of their personal organization
1882- const [ targetUser ] = await db
1883- . select ( )
1884- . from ( users )
1885- . where ( eq ( users . id , targetUserId ) )
1886- . limit ( 1 ) ;
1887-
1888- if (
1889- targetUser &&
1890- targetUser . organizationId === adminMembership . organizations . id
1891- ) {
1892- // This is the user's personal organization - they cannot be removed as the main owner
1893- return false ; // Cannot remove main owner from their personal organization
1965+ // Only owners can remove admins
1966+ if ( targetMembership . role === OrganizationRole . ADMIN && ! isAdminOwner ) {
1967+ return false ; // Only owners can remove admins
18941968 }
18951969
18961970 // Delete the membership
@@ -1899,7 +1973,7 @@ export async function deleteMembership(
18991973 . where (
19001974 and (
19011975 eq ( memberships . userId , targetUserId ) ,
1902- eq ( memberships . organizationId , adminMembership . organizations . id )
1976+ eq ( memberships . organizationId , organizationId )
19031977 )
19041978 )
19051979 . returning ( { id : memberships . userId } ) ;
0 commit comments