Overview
OpenCouncil implements a flexible user management system that supports both public users and administrators with varying levels of access. The system uses magic link authentication and provides fine-grained administrative rights that can be scoped to specific cities, parties, or individual people.User types
The system recognizes two primary user types:Public users
Citizens who sign up to receive notifications about council meetings based on their location and topic preferences. They can also create and view highlights.
Administrative users
Users with special permissions to manage content, view admin interfaces, and perform privileged operations. Admins are created by super admins.
Authentication system
OpenCouncil uses Auth.js (NextAuth v5) with email-based magic link authentication powered by Resend.No passwords are used. This eliminates password management overhead and improves security.
User creation and onboarding
Public user creation
Public users are created automatically when they:- Sign up for notifications for a city
- Submit a petition
- Create a highlight (requires login)
Administrative user creation
Super admins can create administrative users through the admin interface:Administrative rights system
Administrative rights are managed through theAdministers table, which creates granular permissions.
Permission scopes
Administrative rights can be scoped to different entities:- City admin
- Party admin
- Person admin
- Super admin
Scope: Specific cityPermissions:
- Manage meetings for that city
- Create and edit subjects
- Manage city-specific settings
- View admin interfaces for the city
{ userId, cityId, partyId: null, personId: null }Authorization helpers
OpenCouncil provides two key authorization methods insrc/lib/auth.ts:
User data model
TheUser model tracks essential information:
prisma/schema.prisma
Key fields
onboarded
onboarded
Tracks whether the user has completed initial setup (e.g., set notification preferences)
allowContact
allowContact
Indicates whether the user consents to being contacted by administrators
isSuperAdmin
isSuperAdmin
Boolean flag granting platform-wide administrative privileges
phone
phone
Optional phone number for WhatsApp/SMS notifications
Administers table
TheAdministers model creates many-to-many relationships between users and administrative scopes:
prisma/schema.prisma
The unique constraint ensures a user can only have one administrative relationship per unique combination of city, party, and person.
Admin UI implementation
The admin interface should be implemented at/admin using shadcn components with good UX.
Required functionality
User list
Display all users with:
- Email, name, onboarded status
- Super admin badge
- Administrative scopes (city, party, person)
Create user
Form to create new users:
- Email input (required)
- Name input (required)
- Send invitation button
Manage admin rights
Interface to add/remove
Administers records:- Select user
- Choose scope type (city/party/person)
- Select specific entity
- Save relationship
UI components
User table
- Sortable columns
- Search/filter functionality
- Status badges (onboarded, super admin)
- Action buttons (edit, invite, delete)
Rights editor
- Dropdown to select user
- Radio buttons for scope type
- Searchable select for entity
- Add/remove buttons with confirmation
User profile management
Users can manage their own information through/profile:
Personal information
Personal information
- Name
- Email (read-only)
- Phone number
- Contact preferences
Notification preferences
Notification preferences
- List of cities with active preferences
- Edit button → redirects to
/{cityId}/notifications - Unsubscribe button → deletes preference
- Add notifications for another city
Administrative roles
Administrative roles
- View assigned administrative scopes
- Read-only display (managed by super admins)
Security best practices
Always await authorization
Always await authorization
Use appropriate method
Use appropriate method
- UI components: Use
isUserAuthorizedToEdit()(returns boolean) - API routes: Use
withUserAuthorizedToEdit()(throws error)
Server-side checks only
Server-side checks only
Never rely on client-side authorization. Always verify permissions on the server for sensitive operations.
Principle of least privilege
Principle of least privilege
Grant users the minimum permissions needed. Use scoped admin rights instead of super admin when possible.
Integration with other systems
Notifications
Users can sign up for notifications, which creates aNotificationPreference record. See the notifications system guide for details.
Highlights
Users can create highlights if:- They are logged in, AND
- Either:
- The city has
highlightCreationPermission: EVERYONE, OR - The user is an admin for that city
- The city has
Petitions
Anonymous users can submit petitions, which creates aUser record if the email doesn’t exist. The petition system uses the same magic link flow.
File reference
Key implementation files
src/auth.ts- Auth.js configurationsrc/lib/auth.ts- Authorization helper functionssrc/lib/db/notifications.ts- User preference managementsrc/app/[locale]/(other)/profile/page.tsx- User profile pageprisma/schema.prisma- User and Administers models (lines 639-741)