"strconv"
"strings"
"syscall"
+ "time"
"github.com/phitux/dailytxt/backend/utils"
)
"success": true,
})
}
+
+// OpenRegistrationTemp allows admin to open registration for a limited time window
+func OpenRegistrationTemp(w http.ResponseWriter, r *http.Request) {
+ // Decode request (admin password + optional seconds)
+ var req struct {
+ AdminPassword string `json:"admin_password"`
+ Seconds int `json:"seconds"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request", http.StatusBadRequest)
+ return
+ }
+
+ adminPassword := os.Getenv("ADMIN_PASSWORD")
+ if adminPassword == "" || req.AdminPassword != adminPassword {
+ http.Error(w, "Invalid admin password", http.StatusUnauthorized)
+ return
+ }
+
+ log.Printf("%v", r.Body)
+
+ // Default duration 5 minutes; optionally allow custom seconds (max 15 min)
+ duration := 5 * 60 // seconds
+ if req.Seconds > 0 && req.Seconds <= 15*60 {
+ duration = req.Seconds
+ }
+
+ utils.SetRegistrationOverride(time.Duration(duration) * time.Second)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ "until": time.Now().Add(time.Duration(duration) * time.Second).Format(time.RFC3339),
+ "duration": duration,
+ })
+}
}
func IsRegistrationAllowed(w http.ResponseWriter, r *http.Request) {
- // Check if registration is allowed
- utils.JSONResponse(w, http.StatusOK, map[string]bool{
- "registration_allowed": utils.Settings.AllowRegistration,
+
+ // Check if registration is allowed (consider env and temporary override)
+ allowed, tempAllowed := utils.IsRegistrationAllowed()
+ utils.JSONResponse(w, http.StatusOK, map[string]any{
+ "registration_allowed": allowed,
+ "temporary_allowed": tempAllowed,
+ "until": utils.GetRegistrationOverrideUntil(),
})
}
// Register handles user registration
// The API endpoint
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
- if !utils.Settings.AllowRegistration {
+ if allowed, temporary := utils.IsRegistrationAllowed(); !allowed && !temporary {
http.Error(w, "Registration is not allowed", http.StatusForbidden)
return
}
mux.HandleFunc("POST /admin/get-data", middleware.RequireAuth(handlers.GetAdminData))
mux.HandleFunc("POST /admin/delete-user", middleware.RequireAuth(handlers.DeleteUser))
mux.HandleFunc("POST /admin/delete-old-data", middleware.RequireAuth(handlers.DeleteOldData))
+ mux.HandleFunc("POST /admin/open-registration", middleware.RequireAuth(handlers.OpenRegistrationTemp))
// Create a handler chain with Timeout, Logger and CORS middleware
// Timeout middleware will be executed first, then Logger, then CORS
"os"
"strconv"
"strings"
+ "sync"
"time"
)
// Global settings
var Settings AppSettings
+// Registration override state (temporary window to allow registration)
+var (
+ registrationOverrideUntil time.Time
+ registrationOverrideMu sync.RWMutex
+)
+
+// SetRegistrationOverride opens registration for the given duration
+func SetRegistrationOverride(d time.Duration) {
+ registrationOverrideMu.Lock()
+ defer registrationOverrideMu.Unlock()
+ registrationOverrideUntil = time.Now().Add(d)
+ Logger.Printf("Registration temporarily opened until %s", registrationOverrideUntil.Format(time.RFC3339))
+}
+
+func GetRegistrationOverrideUntil() time.Time {
+ registrationOverrideMu.RLock()
+ defer registrationOverrideMu.RUnlock()
+ return registrationOverrideUntil
+}
+
+// IsRegistrationAllowed returns whether registration is
+// overall allowed or temporarily allowed
+func IsRegistrationAllowed() (bool, bool) {
+ allowed := Settings.AllowRegistration
+
+ registrationOverrideMu.RLock()
+ until := registrationOverrideUntil
+ registrationOverrideMu.RUnlock()
+ tempAllowed := time.Now().Before(until)
+
+ return allowed, tempAllowed
+}
+
// SetVersion sets the application version
func SetVersion(version string) {
AppVersion = version
<script>
- import { getTranslate } from '@tolgee/svelte';
+ import { getTranslate, getTolgee } from '@tolgee/svelte';
import { API_URL } from '$lib/APIurl';
import axios from 'axios';
import { onDestroy, onMount } from 'svelte';
import * as bootstrap from 'bootstrap';
const { t } = getTranslate();
+ const tolgee = getTolgee(['language']);
let adminPassword = $state('');
let isAdminAuthenticated = $state(false);
let confirmDeleteOldData = $state(false);
let isDeletingOldData = $state(false);
+ // Registration override controls
+ let isOpeningRegistration = $state(false);
+ let registrationAllowed = $state(false);
+ let registrationAllowedTemporary = $state(false);
+ let registrationUntil = $state('');
+ let regStatusError = $state('');
+ let regOpenSuccess = $state(false);
+ let regOpenError = $state('');
+
onMount(() => {
currentUser = localStorage.getItem('user');
resetAdminState();
freeSpace = response.data.free_space;
oldData = response.data.old_data;
appSettings = response.data.app_settings || {};
+
+ // Also check registration status
+ await checkRegistrationAllowed();
} catch (error) {
console.error('Error loading users:', error);
if (error.response?.status === 401) {
}
}
+ async function checkRegistrationAllowed() {
+ regStatusError = '';
+ try {
+ const resp = await axios.get(API_URL + '/users/isRegistrationAllowed');
+ registrationAllowed = !!resp.data.registration_allowed;
+ registrationAllowedTemporary = !!resp.data.temporary_allowed;
+ registrationUntil = resp.data.until || '';
+ } catch (e) {
+ regStatusError = $t('settings.admin.registration_status_error');
+ }
+ }
+
+ async function openRegistrationTemporary() {
+ if (isOpeningRegistration) return;
+ isOpeningRegistration = true;
+
+ regOpenSuccess = false;
+ regOpenError = '';
+ registrationUntil = '';
+ try {
+ const resp = await makeAdminApiCall('/admin/open-registration', { seconds: 300 });
+ if (resp.data?.success) {
+ regOpenSuccess = true;
+ registrationUntil = resp.data.until || '';
+ await checkRegistrationAllowed();
+ } else {
+ regOpenError = $t('settings.admin.registration_open_error');
+ }
+ } catch (e) {
+ regOpenError = e?.response?.data || $t('settings.admin.registration_open_error');
+ } finally {
+ isOpeningRegistration = false;
+ }
+ }
+
// Delete user
async function deleteUser(userId, username) {
if (isDeletingUser) return;
</button>
</div>
+ <!-- Registration Override Card -->
+ {#if !registrationAllowed}
+ <div class="card mt-4">
+ <div class="card-header">
+ <h4 class="card-title mb-0">📝 {$t('settings.admin.registration')}</h4>
+ </div>
+ <div class="card-body">
+ <p class="text-muted mb-3">
+ {$t('settings.admin.registration_description')}
+ </p>
+
+ <div class="d-flex align-items-center gap-3 mb-3">
+ <div>
+ <strong>{$t('settings.admin.current_status')}: </strong>
+ <span
+ class="badge {registrationAllowed || registrationAllowedTemporary
+ ? 'bg-success'
+ : 'bg-danger'}"
+ >
+ {registrationAllowed || registrationAllowedTemporary
+ ? $t('settings.admin.registration_allowed')
+ : $t('settings.admin.registration_blocked')}
+ </span>
+ </div>
+ <button
+ class="btn btn-outline-primary"
+ onclick={openRegistrationTemporary}
+ disabled={isOpeningRegistration}
+ >
+ {#if isOpeningRegistration}
+ <span class="spinner-border spinner-border-sm me-2"></span>
+ {/if}
+ {$t('settings.admin.button_open_5_minutes')}
+ </button>
+ <button class="btn btn-outline-secondary" onclick={checkRegistrationAllowed}>
+ {$t('settings.admin.button_refresh_status')}
+ </button>
+ </div>
+
+ {#if registrationAllowedTemporary && registrationUntil}
+ <div class="alert alert-success" transition:slide>
+ {@html $t('settings.admin.registration_allowed_until', {
+ date_and_time: new Date(registrationUntil).toLocaleString($tolgee.getLanguage(), {
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric'
+ })
+ })}
+ </div>
+ {/if}
+
+ {#if regStatusError}
+ <div class="alert alert-warning" transition:slide>{regStatusError}</div>
+ {/if}
+ {#if regOpenError}
+ <div class="alert alert-danger" transition:slide>{regOpenError}</div>
+ {/if}
+ </div>
+ </div>
+ {/if}
+
<!-- User management card -->
- <div class="card">
+ <div class="card mt-4">
<div class="card-header">
<h4 class="card-title mb-0">👥 {$t('settings.admin.user_management')}</h4>
</div>
let is_migrating = $state(false);
let registration_allowed = $state(true);
+ let registration_allowed_temporary = $state(false);
+ let until = $state('');
let migration_phases = $state([
'creating_new_user',
.get(API_URL + '/users/isRegistrationAllowed')
.then((response) => {
registration_allowed = response.data.registration_allowed;
+ registration_allowed_temporary = response.data.temporary_allowed;
+ until = response.data.until;
})
.catch((error) => {
console.error('Error checking registration allowed:', error);
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#loginAccordion">
<div class="accordion-body">
<div class="alert alert-info">{$t('login.create_account_info')}</div>
- {#if !registration_allowed}
+ {#if !registration_allowed && !registration_allowed_temporary}
<div class="alert alert-danger" role="alert">
{$t('login.alert.registration_not_allowed')}
</div>
+ {:else if until !== ''}
+ <div class="alert alert-warning" role="alert">
+ {@html $t('login.alert.registration_allowed_until', {
+ date_and_time: new Date(until).toLocaleDateString($tolgee.getLanguage(), {
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric'
+ })
+ })}
+ </div>
{/if}
<form onsubmit={handleRegister}>
<div class="form-floating mb-3">
<input
- disabled={!registration_allowed}
+ disabled={!registration_allowed && !registration_allowed_temporary}
type="text"
class="form-control"
id="registerUsername"
</div>
<div class="form-floating mb-3">
<input
- disabled={!registration_allowed}
+ disabled={!registration_allowed && !registration_allowed_temporary}
type="password"
class="form-control"
id="registerPassword"
</div>
<div class="form-floating mb-3">
<input
- disabled={!registration_allowed}
+ disabled={!registration_allowed && !registration_allowed_temporary}
type="password"
class="form-control"
id="registerPassword2"
{/if}
{#if show_registration_success}
<div class="alert alert-success" role="alert">
- {$t('login.alert.registration_success')}
+ {@html $t('login.alert.registration_success')}
</div>
{/if}
{#if show_registration_warning_empty_fields}
<button
type="submit"
class="btn btn-primary"
- disabled={is_registering || !registration_allowed}
+ disabled={is_registering ||
+ (!registration_allowed && !registration_allowed_temporary)}
>
{#if is_registering}
<div class="spinner-border spinner-border-sm" role="status">