Skip to content

OTP verify returns 'User not found' for new registrations when allowNewUserRegistration is enabled #770

@lane711

Description

@lane711

Bug Description

When allowNewUserRegistration is set to true in the OTP plugin settings, the /auth/otp/request endpoint correctly generates an OTP code and sends the verification email to new (unregistered) email addresses. However, when the user submits the code to /auth/otp/verify, it returns a 404 "User not found" error because the user record was never created in the users table.

Steps to Reproduce

  1. Enable new user registration in the OTP plugin: set allowNewUserRegistration: true in the plugins table settings for otp-login
  2. Request an OTP for an email that has no existing user record (e.g., newuser@example.com)
  3. OTP code is generated in otp_codes table and email is sent ✅
  4. Submit the received code to /auth/otp/verify with the same email
  5. Result: 404 "User not found" — verification fails

Root Cause

In the OTP request handler (/auth/otp/request), when allowNewUserRegistration is true, the code skips the early return for unknown emails and proceeds to generate + send the OTP. But it never creates a user record.

Then in the verify handler (/auth/otp/verify), after the code is validated successfully, the handler queries:

const user = await db.prepare('SELECT id, email, role, is_active FROM users WHERE email = ?')
  .bind(normalizedEmail).first();
if (!user) {
  return c.json({ error: "User not found" }, 404);
}

Since no user was ever created, this always fails for new registrations.

Expected Behavior

When allowNewUserRegistration is true and a valid OTP code is verified for an email with no existing user, the verify endpoint should auto-create a new user record (with role viewer, is_active: 1, email_verified: 1) and then proceed with authentication.

Affected Code

  • Request handler: src/plugins/core-plugins/otp-login-plugin — correctly allows new emails through but doesn't create user
  • Verify handler: Same plugin — expects user to exist, no fallback creation

Suggested Fix

In the /auth/otp/verify handler, after the code is verified as valid but before the user lookup, add user creation logic:

let user = await db.prepare('SELECT id, email, role, is_active FROM users WHERE email = ?')
  .bind(normalizedEmail).first();

if (!user && settings.allowNewUserRegistration) {
  const id = crypto.randomUUID();
  const now = Date.now();
  const username = normalizedEmail.split('@')[0] + '_' + id.slice(0, 6);
  await db.prepare(
    `INSERT INTO users (id, email, username, first_name, last_name, role, is_active, email_verified, created_at, updated_at)
     VALUES (?, ?, ?, '', '', 'viewer', 1, 1, ?, ?)`
  ).bind(id, normalizedEmail, username, now, now).run();

  user = { id, email: normalizedEmail, role: 'viewer', is_active: 1 };
}

if (!user) {
  return c.json({ error: "User not found" }, 404);
}

Environment

  • SonicJS version: @sonicjs-cms/core@2.10.0
  • Runtime: Cloudflare Workers
  • Database: D1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions