Files
study-bible-project/server/signup.js
T
nmemmertandClaude Sonnet 5.5 54cb1c217d
Build and Push Docker Image / build-and-push (push) Successful in 6m47s
Invite-only sign-up, present mode, series, handouts, templates, dictation, progress, sharing
- Admin: sign-up Open / Invite only / Closed with single-use invite links
- Present mode for scripts (auto-scroll) and outlines (step through, pace vs planned minutes)
- Series: group studies on the dashboard, filter by series, calendar file for a study's sessions
- Lesson handout: printable page per session (passage, outline, questions, writing lines)
- Save a chunk's notes as your own study template (synced)
- Voice dictation for notes and scripts (browser speech recognition)
- Reading progress: map of the 66 books and a calendar of days read, synced and merged across devices
- Export highlights and bookmarks as Markdown or Word, grouped by named colour
- Verse of the day push to the ntfy topic (needs IQBIBLE_API_KEY)
- Sharing: co-teacher editing via single-use invite links; read-only links can expire and take a passcode

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 10:26:43 -04:00

26 lines
1.5 KiB
JavaScript

// Who may create an account. The administrator picks one of three settings:
// open anyone who can reach the server (the original behaviour)
// invite only someone with an unused invite code from the administrator
// closed nobody new
// The very first account can always be made, so a fresh install can't lock itself out.
import { randomBytes } from 'crypto';
export const SIGNUP_MODES = ['open', 'invite', 'closed'];
export const isSignupMode = (m) => SIGNUP_MODES.includes(m);
// -> { ok: true } or { ok: false, status, error }. `inviteOk` says whether the code given is a valid unused one.
export function checkSignup({ mode, userCount, inviteOk }) {
if (userCount === 0 || mode === 'open') return { ok: true };
if (mode === 'closed') return { ok: false, status: 403, error: 'Sign-up is closed. Ask the administrator for an account.' };
if (!inviteOk) return { ok: false, status: 403, error: 'Sign-up needs an invite code from the administrator.' };
return { ok: true };
}
// An easy-to-read invite code (no look-alike characters), e.g. K7QP-4MXD-9TRW.
const ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
export function generateInviteCode() {
const chars = Array.from(randomBytes(12), (b) => ALPHABET[b % ALPHABET.length]).join('');
return `${chars.slice(0, 4)}-${chars.slice(4, 8)}-${chars.slice(8, 12)}`;
}
export const normalizeInviteCode = (code) => String(code ?? '').toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/^(.{4})(.{4})(.{4})$/, '$1-$2-$3');