admin can open registration for 5 minutes
authorPhiTux <redacted>
Fri, 3 Oct 2025 21:17:27 +0000 (23:17 +0200)
committerPhiTux <redacted>
Fri, 3 Oct 2025 21:17:27 +0000 (23:17 +0200)
backend/handlers/admin.go
backend/handlers/users.go
backend/main.go
backend/utils/helpers.go
frontend/src/lib/settings/Admin.svelte
frontend/src/routes/login/+page.svelte

index 930b8fedfbb44810148f051f82f6fdadfb4089cb..e62cfa3459fb5a5a84118c5f8084bd3924d565ce 100644 (file)
@@ -9,6 +9,7 @@ import (
        "strconv"
        "strings"
        "syscall"
+       "time"
 
        "github.com/phitux/dailytxt/backend/utils"
 )
@@ -288,3 +289,39 @@ func DeleteOldData(w http.ResponseWriter, r *http.Request) {
                "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,
+       })
+}
index 435c89575b1661ed378d397ed5c0ffcf7971b849..540af3052f49d9f36c727b6e39f6e9d222a399dd 100644 (file)
@@ -219,9 +219,13 @@ func Login(w http.ResponseWriter, r *http.Request) {
 }
 
 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(),
        })
 }
 
@@ -234,7 +238,7 @@ type RegisterRequest struct {
 // 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
        }
index 630215e6d3f43ec760f813900f9857904d5c6a9c..a68637844d4cddac3cbda37046106f7b2814a05c 100644 (file)
@@ -109,6 +109,7 @@ func main() {
        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
index c3a020fc8160e64a2c4d8653700db782eb7c7feb..fc76d6bb6ebdbd0277740d7bdb9b234037e1d5b6 100644 (file)
@@ -9,6 +9,7 @@ import (
        "os"
        "strconv"
        "strings"
+       "sync"
        "time"
 )
 
@@ -47,6 +48,39 @@ type AppSettings struct {
 // 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
index 81d4b2bca947f8da9f374e9c04a88803cecc1d02..70342ff82393d518f8f60608fd12985c12df549d 100644 (file)
@@ -1,5 +1,5 @@
 <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';
@@ -8,6 +8,7 @@
        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();
@@ -85,6 +95,9 @@
                        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>
index 2021701ca5a054a3cfc2a317441a03e4f5ac8844..61166472f0e19f02fba1a9dc3bc4e5669f3cf929 100644 (file)
@@ -27,6 +27,8 @@
        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">
git clone https://git.99rst.org/PROJECT