Skip to main content
Urbicon UI
Back to Recipes

Invitation-Gated Registration

Admin-minted invitations gate sign-up: the RegisterPage only succeeds for an invited email.

Live Preview

Create account

Already have an account? Sign in

Key Features

  • Registration requires an admin-created invitation for the exact email
  • InvitationManager admin panel to create list and revoke invitations
  • Invitation claimed atomically (markUsedIfUnused) so one invite yields one account
  • Same 403 for any uninvited email so registration status never leaks
  • Email verification link sent on successful registration
  • Auto-login on success via the established session

Code

Invitation-Gated Registration — full flow

// 1. src/routes/api/auth/register/+server.ts — the bundled register handler
import { createRegisterHandler } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth-setup';
export const { POST } = createRegisterHandler(authDeps);

// 2. src/lib/server/invitations.ts — the bundled invitation handlers.
//    You only supply the authorization gate (who may manage invitations)
//    and the roles an invite may carry; validation, the invitedBy scoping
//    (session user, never the body) and {error,code} responses ship with it.
import { createInvitationHandlers } from '@urbicon-ui/auth/server';
import { authDeps } from './auth-setup';

export const invitations = createInvitationHandlers(authDeps, {
  authorize: (user) => user.role === 'ADMIN',
  roles: ['ADMIN', 'USER']
});

// src/routes/api/invitations/+server.ts — create + list
import { invitations } from '$lib/server/invitations';
export const POST = invitations.POST;
export const GET = invitations.GET;

// src/routes/api/invitations/[id]/+server.ts — revoke (InvitationManager's delete)
import { invitations } from '$lib/server/invitations';
export const DELETE = invitations.DELETE;

// 3. src/routes/auth/register/+page.svelte
<script lang="ts">
  import { RegisterPage } from '@urbicon-ui/auth';
  import { en } from '@urbicon-ui/auth/i18n/en';
  import { goto } from '$app/navigation';
  import { page } from '$app/state';
</script>

<!-- The invite link is /auth/register?token=<secret>&email=<invitee>. The token
     IS the proof of invitation — without it the request is refused. -->
<RegisterPage
  t={en}
  token={page.url.searchParams.get('token') ?? ''}
  defaultEmail={page.url.searchParams.get('email') ?? ''}
  onSuccess={() => goto('/')}
/>

// 4. src/routes/admin/invitations/+page.svelte — admin panel
<script lang="ts">
  import { InvitationManager } from '@urbicon-ui/auth';
  import { en } from '@urbicon-ui/auth/i18n/en';

  const roles = [
    { value: 'ADMIN', label: 'Admin' },
    { value: 'USER', label: 'User' }
  ];
</script>

<InvitationManager t={en} {roles} apiPath="/api/invitations" />