Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions packages/backend/src/lib/registrationPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Registration Policy
*
* Single source of truth for whether new local accounts may be created, shared by
* password sign-up (`/api/auth/register`) and OAuth sign-up.
*
* @module lib/registrationPolicy
*/

import type { InstanceSettingsService } from "../services/InstanceSettingsService.js";

/**
* Subset of InstanceSettingsService needed to evaluate the registration policy.
*/
export type RegistrationSettingsSource = Pick<
InstanceSettingsService,
"isRegistrationEnabled" | "isInviteOnly"
>;

/**
* Effective registration policy for the instance.
*/
export interface RegistrationPolicy {
/** Whether new accounts may be created at all */
registrationEnabled: boolean;
/** Whether new accounts require an invitation code */
inviteOnly: boolean;
}

/**
* Check if registration requires an invitation code.
*
* Uses the DB setting first, falling back to the `REQUIRE_INVITATION` env var.
*
* @param settings - Instance settings source
* @returns True if invite-only mode is active
*/
export async function isInviteOnly(settings: RegistrationSettingsSource): Promise<boolean> {
if (await settings.isInviteOnly()) return true;

// Fall back to environment variable for backwards compatibility
const requireInvitation = process.env.REQUIRE_INVITATION;
return requireInvitation === "true" || requireInvitation === "1";
}

/**
* Check if registration is enabled.
*
* Uses the DB setting first; if it is false, `ENABLE_REGISTRATION=false|0`
* keeps it disabled.
*
* @param settings - Instance settings source
* @returns True if registration is enabled
*/
export async function isRegistrationEnabled(
settings: RegistrationSettingsSource,
): Promise<boolean> {
const dbSetting = await settings.isRegistrationEnabled();

if (!dbSetting) {
const envSetting = process.env.ENABLE_REGISTRATION;
if (envSetting === "false" || envSetting === "0") return false;
}

return dbSetting;
}

/**
* Resolve the full registration policy.
*
* @param settings - Instance settings source
* @returns Effective registration policy
*/
export async function getRegistrationPolicy(
settings: RegistrationSettingsSource,
): Promise<RegistrationPolicy> {
const [registrationEnabled, inviteOnly] = await Promise.all([
isRegistrationEnabled(settings),
isInviteOnly(settings),
]);
return { registrationEnabled, inviteOnly };
}
2 changes: 1 addition & 1 deletion packages/backend/src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const repliesQuerySchema = z.object({
// ============================================

/**
* POST /api/users - User registration (same as auth register)
* User registration schema (alias of registerSchema; the legacy POST /api/users endpoint was removed)
*/
export const createUserSchema = registerSchema;

Expand Down
31 changes: 5 additions & 26 deletions packages/backend/src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PasskeyService } from "../services/PasskeyService.js";
import { requireAuth } from "../middleware/auth.js";
import { rateLimit, RateLimitPresets } from "../middleware/rateLimit.js";
import { logger } from "../lib/logger.js";
import { isInviteOnly, isRegistrationEnabled } from "../lib/registrationPolicy.js";
import { toPublicUser } from "../lib/routeUtils.js";
import type { Context } from "hono";

Expand All @@ -21,37 +22,15 @@ const app = new Hono();
* Check if registration requires an invitation code
* Uses DB setting first, falls back to environment variable
*/
const checkIsInviteOnly = async (c: Context): Promise<boolean> => {
// First check DB setting
const instanceSettingsService = c.get("instanceSettingsService");
const dbSetting = await instanceSettingsService.isInviteOnly();

// If DB says invite-only, use that
if (dbSetting) return true;

// Fall back to environment variable for backwards compatibility
const requireInvitation = process.env.REQUIRE_INVITATION;
return requireInvitation === "true" || requireInvitation === "1";
};
const checkIsInviteOnly = (c: Context): Promise<boolean> =>
isInviteOnly(c.get("instanceSettingsService"));

/**
* Check if registration is enabled
* Uses DB setting first, falls back to environment variable
*/
const checkIsRegistrationEnabled = async (c: Context): Promise<boolean> => {
const instanceSettingsService = c.get("instanceSettingsService");
const dbSetting = await instanceSettingsService.isRegistrationEnabled();

// If DB setting exists and is false, registration is disabled
// Fall back to env var ENABLE_REGISTRATION if DB doesn't have a setting
if (!dbSetting) {
const envSetting = process.env.ENABLE_REGISTRATION;
// If env var is explicitly 'false' or '0', registration is disabled
if (envSetting === "false" || envSetting === "0") return false;
}

return dbSetting;
};
const checkIsRegistrationEnabled = (c: Context): Promise<boolean> =>
isRegistrationEnabled(c.get("instanceSettingsService"));

/**
* Registration Settings
Expand Down
Loading
Loading