mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Add DISABLE_REGISTRATION env var to block public sign-ups
When DISABLE_REGISTRATION=true (or 1), POST /api/auth/register returns 403 with a clear error. OAuth endpoints (/google, /microsoft) also refuse to auto-create new accounts — existing OAuth users can still sign in. First-user setup (empty users table) is always allowed so a fresh install can still be initialized. GET /api/auth/config now returns registration_enabled so the login view can hide the "Create Account" button and the trial banner when registration is off. Absence of the flag is treated as enabled for back-compat with older servers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ea86d70475
commit
4392bb460a
|
|
@ -59,6 +59,7 @@ The server starts on port 3001 (HTTP). If SSL certificates are present in `serve
|
|||
| `PORT` | HTTP port | `3001` |
|
||||
| `HTTPS_PORT` | HTTPS port (used when SSL certs are present) | `3443` |
|
||||
| `SELF_HOSTED` | First user gets all features unlocked | `false` |
|
||||
| `DISABLE_REGISTRATION` | Block new account creation (including OAuth auto-signup). First-user setup on an empty DB is still allowed. | `false` |
|
||||
| `APP_URL` | Your public URL (used for Stripe callbacks) | _(none)_ |
|
||||
| `JWT_SECRET` | JWT signing key (auto-generated if not set) | _(auto)_ |
|
||||
| `SSL_CERT` | Path to SSL certificate | `server/certs/cert.pem` |
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ async function loadAuthConfig() {
|
|||
export async function render(container) {
|
||||
const config = await loadAuthConfig();
|
||||
const isSetup = config.needsSetup;
|
||||
// registration_enabled may be absent on older servers — treat as enabled for back-compat
|
||||
const canRegister = config.registration_enabled !== false;
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:16px">
|
||||
|
|
@ -26,7 +28,7 @@ export async function render(container) {
|
|||
<p style="color:var(--text-secondary);font-size:13px;margin-top:4px">
|
||||
${isSetup ? 'Create your admin account to get started' : 'Sign in to manage your displays'}
|
||||
</p>
|
||||
${isSetup ? '' : '<p style="color:var(--warning);font-size:12px;margin-top:8px">New accounts get a 14-day free Pro trial</p>'}
|
||||
${!isSetup && canRegister ? '<p style="color:var(--warning);font-size:12px;margin-top:8px">New accounts get a 14-day free Pro trial</p>' : ''}
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:24px">
|
||||
|
|
@ -49,7 +51,7 @@ export async function render(container) {
|
|||
<button class="btn btn-primary" id="loginBtn" style="width:100%;justify-content:center;padding:10px">
|
||||
${isSetup ? 'Create Admin Account' : 'Sign In'}
|
||||
</button>
|
||||
${!isSetup ? `
|
||||
${!isSetup && canRegister ? `
|
||||
<button class="btn btn-secondary" id="showRegisterBtn" style="width:100%;justify-content:center;padding:10px;margin-top:8px">
|
||||
Create Account
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -39,4 +39,7 @@ module.exports = {
|
|||
emailWebhookUrl: process.env.EMAIL_WEBHOOK_URL || '',
|
||||
// Self-hosted mode: if true, first user gets enterprise plan and no billing
|
||||
selfHosted: process.env.SELF_HOSTED === 'true',
|
||||
// Disable public registration (OAuth auto-signup is also blocked when set).
|
||||
// First-user setup is still allowed so a fresh install can be initialized.
|
||||
disableRegistration: ['true', '1'].includes(String(process.env.DISABLE_REGISTRATION || '').toLowerCase()),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,8 +25,19 @@ function logSuccessfulLogin(userId, email, ip) {
|
|||
|
||||
// ==================== Local Auth ====================
|
||||
|
||||
// Returns true if new account creation is allowed at this moment.
|
||||
// First-user setup (empty DB) is always allowed so a fresh install can be initialized.
|
||||
function canRegister() {
|
||||
if (!config.disableRegistration) return true;
|
||||
const userCount = db.prepare('SELECT COUNT(*) as count FROM users').get().count;
|
||||
return userCount === 0;
|
||||
}
|
||||
|
||||
// Register
|
||||
router.post('/register', (req, res) => {
|
||||
if (!canRegister()) {
|
||||
return res.status(403).json({ error: 'Public registration is disabled. Contact your administrator.' });
|
||||
}
|
||||
const { email, password, name } = req.body;
|
||||
if (!email || !password) return res.status(400).json({ error: 'Email and password required' });
|
||||
if (password.length < 8) return res.status(400).json({ error: 'Password must be at least 8 characters' });
|
||||
|
|
@ -94,6 +105,9 @@ router.post('/google', async (req, res) => {
|
|||
let user = db.prepare('SELECT * FROM users WHERE email = ?').get(email.toLowerCase());
|
||||
|
||||
if (!user) {
|
||||
if (!canRegister()) {
|
||||
return res.status(403).json({ error: 'Public registration is disabled. Contact your administrator.' });
|
||||
}
|
||||
const id = uuidv4();
|
||||
const userCount = db.prepare('SELECT COUNT(*) as count FROM users').get().count;
|
||||
const role = userCount === 0 ? 'superadmin' : 'user';
|
||||
|
|
@ -169,6 +183,9 @@ router.post('/microsoft', async (req, res) => {
|
|||
let user = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
|
||||
|
||||
if (!user) {
|
||||
if (!canRegister()) {
|
||||
return res.status(403).json({ error: 'Public registration is disabled. Contact your administrator.' });
|
||||
}
|
||||
const id = uuidv4();
|
||||
const userCount = db.prepare('SELECT COUNT(*) as count FROM users').get().count;
|
||||
const role = userCount === 0 ? 'superadmin' : 'user';
|
||||
|
|
@ -297,6 +314,7 @@ router.get('/config', (req, res) => {
|
|||
microsoftTenantId: config.microsoftTenantId,
|
||||
localEnabled: true,
|
||||
needsSetup: userCount === 0,
|
||||
registration_enabled: !config.disableRegistration || userCount === 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue