Skip to content

Allow to add and invite users #1

@LeoMartinDev

Description

@LeoMartinDev

Overview

Allow admins to invite new users to a Voy instance via email. An admin creates an invitation from the Settings panel, a unique link is generated and displayed, and the invitee uses that link to set a password and gain access.

User stories

  • As an admin, I can navigate to a new Users section in Settings and see a list of existing users and pending invitations.
  • As an admin, I can invite a new user by entering their email address (and optionally assigning them a role: user or admin).
  • As an admin, I can copy the generated invitation link to share it manually.
  • As an admin, I can revoke a pending invitation.
  • As an invited user, I can open the invitation link, set a display name and password, and immediately access the search engine.
  • As an invited user, the link expires after 7 days and becomes invalid once used.

Technical scope

Backend

  • Add an invitation table to the Drizzle schema:
    • id, email, role (user | admin), token (unique, random), invitedBy (FK → user.id), expiresAt, createdAt, usedAt (nullable — null = still pending)
  • Add server functions (admin-only, following the pattern of api-keys.ts):
    • createInvitation(email, role) — checks no account already exists for that email, generates a secure token, inserts a row
    • listInvitations() — returns pending (unused, non-expired) invitations
    • revokeInvitation(id) — deletes the row
  • Add public server functions (no auth required):
    • getInvitation(token) — returns the invitation if valid (not used, not expired), 404 otherwise
    • acceptInvitation(token, name, password) — validates the token, calls auth.api.createUser() with the stored email and role, marks usedAt, auto-signs in the new user

Frontend — Settings > Users (admin-only)

  • New route: src/routes/_authed/settings/users.tsx
  • New beforeLoad guard (same pattern as settings/api.tsx)
  • New sidebar entry in adminNavItems in settings-sidebar.tsx
  • UI:
    • Table of existing users (name, email, role, joined date) — via auth.api.listUsers() from the Better Auth admin plugin
    • "Invite user" button → dialog with email + role fields
    • On submit: call createInvitation, display the generated link in a copyable input
    • Table of pending invitations (email, role, invited by, expires at) with a "Revoke" action

Frontend — Accept invitation page (public)

  • New route: src/routes/invite/$token.tsx (outside _authed, same pattern as /setup and /login)
  • Loads the invitation via getInvitation(token) in beforeLoad — redirects to / or shows an error if invalid/expired
  • Form: display name + password + confirm password
  • On submit: call acceptInvitation, redirect to / on success

i18n

  • Add keys for all new UI strings in both en and fr locale files.

Out of scope

  • Sending invitation emails automatically (no SMTP config exists — the link is displayed for the admin to share manually)
  • Bulk invitations
  • Invitation resend

Acceptance criteria

  • An admin can generate an invitation link for a given email and role
  • The invitation link is valid for 7 days and single-use
  • An invited user can set their name and password via the link and is immediately signed in
  • Expired or already-used links show a clear error message
  • Pending invitations are listed and can be revoked by an admin
  • All new strings are translated in English and French
  • A Drizzle migration is generated for the new invitation table

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions