Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions lib/services/supabase_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,91 @@ class SupabaseService {
}
}

Future<Map<String, dynamic>> changeTeamMemberRole({
required String userId,
required String newRole, // 'admin' or 'member'
}) async {
try {
if (!_isInitialized) {
return {
'success': false,
'error': 'Supabase not initialized',
};
}

final currentUser = _client.auth.currentUser;
if (currentUser == null) {
return {
'success': false,
'error': 'User not authenticated',
};
}

// Verify current user is an admin and both users are in the same team
final currentUserProfile = await _client
.from('users')
.select('role, team_id')
.eq('id', currentUser.id)
.single();

if (currentUserProfile['role'] != 'admin') {
return {
'success': false,
'error': 'Only admins can change member roles',
};
}

final targetUserProfile = await _client
.from('users')
.select('team_id, role')
.eq('id', userId)
.single();

if (currentUserProfile['team_id'] != targetUserProfile['team_id']) {
return {
'success': false,
'error': 'Users must be in the same team',
};
}

// Validate new role
if (newRole != 'admin' && newRole != 'member') {
return {
'success': false,
'error': 'Invalid role: $newRole',
};
}

// Update the role
final response = await _client
.from('users')
.update({'role': newRole})
.eq('id', userId)
.select();

if (response.isEmpty) {
return {
'success': false,
'error':
'Update failed - RLS policy may be blocking. Please check database permissions.',
};
}

debugPrint('Successfully updated role to $newRole for user $userId');
return {
'success': true,
'message': 'Role updated successfully',
};
} catch (e, stackTrace) {
debugPrint('Error changing user role: $e');
debugPrint('Stack trace: $stackTrace');
return {
'success': false,
'error': e.toString(),
};
}
}

Future<bool> teamExists(String teamId) async {
try {
if (!_isInitialized) return false;
Expand Down
74 changes: 66 additions & 8 deletions sqls/02_user_auth_policies.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
-- First, drop all existing policies
-- Drop helper functions
DROP FUNCTION IF EXISTS is_admin_in_same_team(UUID);
DROP FUNCTION IF EXISTS get_current_user_role();

-- Drop policies on teams table (organized by operation: SELECT, INSERT, UPDATE)
DROP POLICY IF EXISTS "Team members can view their team" ON teams;
DROP POLICY IF EXISTS "Only admins can update their team" ON teams;
DROP POLICY IF EXISTS "Users can view members of their team" ON users;
DROP POLICY IF EXISTS "Users can update their own profile" ON users;
DROP POLICY IF EXISTS "Allow authenticated users to insert users" ON users;
DROP POLICY IF EXISTS "Allow team creation" ON teams;
DROP POLICY IF EXISTS "Allow authenticated users to insert teams" ON teams;
DROP POLICY IF EXISTS "Only admins can update their team" ON teams;

-- Drop policies on users table (organized by operation: SELECT, INSERT, UPDATE)
DROP POLICY IF EXISTS "Users can view themselves" ON users;
DROP POLICY IF EXISTS "Users can view team members" ON users;
DROP POLICY IF EXISTS "Users can view members of their team" ON users; -- Legacy policy
DROP POLICY IF EXISTS "Allow user creation" ON users;
DROP POLICY IF EXISTS "Allow authenticated users to insert users" ON users; -- Legacy policy
DROP POLICY IF EXISTS "Users can update their own profile" ON users; -- Legacy policy
DROP POLICY IF EXISTS "Users can update their own profile (except role)" ON users;
DROP POLICY IF EXISTS "Admins can manage roles within their team" ON users;

-- Temporarily disable RLS to make sure we can fix everything
ALTER TABLE teams DISABLE ROW LEVEL SECURITY;
Expand All @@ -14,6 +26,29 @@ ALTER TABLE users DISABLE ROW LEVEL SECURITY;
CREATE OR REPLACE VIEW user_teams AS
SELECT id, team_id FROM users;

-- Helper function to check if current user is admin in same team (bypasses RLS)
CREATE OR REPLACE FUNCTION is_admin_in_same_team(target_team_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM users
WHERE id = auth.uid()
AND role = 'admin'
AND team_id = target_team_id
AND team_id IS NOT NULL
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER STABLE;

-- Helper function to get current user's role (bypasses RLS)
CREATE OR REPLACE FUNCTION get_current_user_role()
RETURNS TEXT AS $$
BEGIN
RETURN (SELECT role FROM users WHERE id = auth.uid());
END;
$$ LANGUAGE plpgsql SECURITY DEFINER STABLE;

-- Re-enable RLS
ALTER TABLE teams ENABLE ROW LEVEL SECURITY;
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
Expand Down Expand Up @@ -47,9 +82,32 @@ CREATE POLICY "Users can view team members"
)
);

CREATE POLICY "Users can update their own profile"
ON users FOR UPDATE
USING (id = auth.uid());
CREATE POLICY "Users can update their own profile (except role)"
ON users
FOR UPDATE
USING (id = auth.uid())
WITH CHECK (
id = auth.uid()
-- Prevent users from changing their own role
AND role = get_current_user_role()
);

-- Policy for admins to update roles of team members
-- This policy allows admins to update the role field of users in their team
CREATE POLICY "Admins can manage roles within their team"
ON users
FOR UPDATE
USING (
-- Check: current user is admin in same team (using helper function to avoid recursion)
users.team_id IS NOT NULL
AND is_admin_in_same_team(users.team_id)
-- Prevent admins from changing their own role
AND users.id <> auth.uid()
)
WITH CHECK (
-- Validate the new role value
role IN ('admin', 'member')
);

CREATE POLICY "Allow user creation"
ON users FOR INSERT
Expand Down