Skip to main content
DelightBridge uses Google OAuth for authentication and implements a permission-based access control system. This guide explains how the authentication flow works and how admin access is controlled.

Google OAuth Flow

DelightBridge authenticates users through Google OAuth using NextAuth.js. The authentication configuration is split across two files:
  • auth.config.ts - Base authorization callbacks
  • auth.ts - Google provider setup and user provisioning

Sign In Process

1

User clicks Sign In

Users are redirected to Google’s OAuth consent screen to authorize the application.
2

Authorization Check

After Google authentication, DelightBridge checks if the user is authorized:
const isAllowed =
  ADMIN_EMAILS.includes(email) ||
  !!member;

if (!isAllowed) return '/login?error=unauthorized';
Users must either be in the ADMIN_EMAILS list or exist in the workspace_members table.
3

Permission Assignment

The system assigns permissions based on admin status:
const isAdmin = ADMIN_EMAILS.includes(email);
const permission = isAdmin ? 'admin' : (member?.permission ?? 'view');
Admin users always receive admin permission, regardless of database settings.
4

User Record Created

User information is stored in the users table with their Google profile data:
await db.insert(users).values({
  id: `user-${googleId}`,
  googleId,
  email,
  name: profile.name ?? email,
  picture: profile.picture ?? null,
  permission,
})

Admin Email Configuration

Admin access is controlled through the ADMIN_EMAILS environment variable and a default admin list.

Setting Admin Emails

Add admin emails to your .env.local file:
Emails in ADMIN_EMAILS are automatically normalized (trimmed and lowercased) before comparison.

How Admin Emails Work

The getAdminEmails() function from admin-emails.ts:3 merges environment-configured emails with default admins:
export function getAdminEmails() {
  const envAdmins = (process.env.ADMIN_EMAILS ?? '')
    .split(',')
    .map((email) => email.trim().toLowerCase())
    .filter(Boolean);

  return Array.from(new Set([...DEFAULT_ADMIN_EMAILS, ...envAdmins]));
}
Default admin emails are defined in admin-emails.ts:1:
const DEFAULT_ADMIN_EMAILS = ['[email protected]'];
Admin emails always receive admin permission, even if they’re added to workspace_members with a lower permission level. This is enforced in the session callback.

Session Management

DelightBridge maintains user sessions through NextAuth with additional permission enforcement.

Session Callback

The session callback in auth.ts:62 runs on every request to verify and update user permissions:
async session({ session, token }) {
  if (session.user && token.sub) {
    const [user] = await db
      .select()
      .from(users)
      .where(eq(users.googleId, token.sub));
      
    if (user) {
      const isAdminEmail = ADMIN_EMAILS.includes(
        user.email.trim().toLowerCase()
      );
      
      // Upgrade permission if user is now an admin
      if (isAdminEmail && user.permission !== 'admin') {
        await db
          .update(users)
          .set({ permission: 'admin' })
          .where(eq(users.id, user.id));
      }
      
      session.user.id = user.id;
      session.user.permission = isAdminEmail ? 'admin' : user.permission;
    }
  }
  return session;
}
This callback automatically upgrades users to admin if their email is added to ADMIN_EMAILS after their initial login.

Requiring Authentication in API Routes

API routes use helper functions from session.ts to enforce authentication:
import { requireSession, requirePermission, requireAdminSession } from '@/lib/session';

// Require any authenticated user
export async function GET() {
  const { session, unauthorized } = await requireSession();
  if (unauthorized) return unauthorized;
  
  // Your logic here
}

// Require specific permissions
export async function POST() {
  const { session, unauthorized, forbidden } = await requirePermission(['admin', 'send']);
  if (unauthorized) return unauthorized;
  if (forbidden) return forbidden;
  
  // Your logic here
}

// Require admin only
export async function DELETE() {
  const { unauthorized, forbidden } = await requireAdminSession();
  if (unauthorized) return unauthorized;
  if (forbidden) return forbidden;
  
  // Your logic here
}

Workspace Members

Non-admin users must be added to the workspace_members table to access DelightBridge.

Adding Members

Admins can add workspace members through the Settings modal or via the API:
POST /api/members
{
  "email": "[email protected]",
  "permission": "edit"
}
The system automatically normalizes emails and enforces admin permissions:
const normalizedEmail = email.trim().toLowerCase();
const nextPermission = ADMIN_EMAILS.includes(normalizedEmail) 
  ? 'admin' 
  : permission;

Member Login Flow

  1. User authenticates with Google OAuth
  2. System checks workspace_members table for their email
  3. If found, user is granted access with their assigned permission level
  4. If not found and not in ADMIN_EMAILS, login is rejected with unauthorized error
// In auth.ts signIn callback
const [member] = await db
  .select({ permission: workspaceMembers.permission })
  .from(workspaceMembers)
  .where(eq(workspaceMembers.email, email));

const isAllowed = ADMIN_EMAILS.includes(email) || !!member;

if (!isAllowed) return '/login?error=unauthorized';
The member’s permission from the database is used unless they’re an admin email.

Protected Routes

The authorized callback in auth.config.ts:8 protects all routes except /login:
authorized({ auth, request: { nextUrl } }) {
  const isLoggedIn = !!auth?.user;
  const isLoginPage = nextUrl.pathname === '/login';

  if (isLoginPage) {
    if (isLoggedIn) return Response.redirect(new URL('/', nextUrl));
    return true;
  }

  if (!isLoggedIn) return false;
  return true;
}
Unauthenticated users are automatically redirected to /login.

Environment Variables

Required for Google OAuth:
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
ADMIN_EMAILS=[email protected],[email protected]
See the OAuth Setup guide for instructions on obtaining Google OAuth credentials.

Troubleshooting

User Cannot Log In

The user’s email is not in ADMIN_EMAILS and doesn’t exist in workspace_members. Add them as a workspace member first.
Check browser console for errors. Ensure GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are correctly set.
The session callback auto-corrects this on next request. Have the user refresh the page or log out and back in.

Permission Issues

The session callback updates permissions on the next request. Ask the user to refresh the page.
Only emails in ADMIN_EMAILS can have admin permission. Add the email to your environment variable instead.

Build docs developers (and LLMs) love