"monochromeBackgroundColor": "#ececec",
"checkForUpdates": true,
"includeTestVersions": false,
+ "requirePasswordOnPageLoad": false,
}
}
"available_backup_codes": availableBackupCodes,
})
}
+
+// ValidatePasswordRequest represents the validate password request body
+type ValidatePasswordRequest struct {
+ Password string `json:"password"`
+}
+
+// ValidatePassword validates the user's password for re-authentication
+func ValidatePassword(w http.ResponseWriter, r *http.Request) {
+ // Get user ID from context
+ userID, ok := r.Context().Value(utils.UserIDKey).(int)
+ if !ok {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Parse the request body
+ var req ValidatePasswordRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // Validate password using the same method as login
+ derived_key, available_backup_codes, _ := utils.CheckPasswordForUser(userID, req.Password)
+
+ utils.JSONResponse(w, http.StatusOK, map[string]any{
+ "valid": derived_key != "",
+ "available_backup_codes": available_backup_codes,
+ })
+}
mux.HandleFunc("POST /users/changeUsername", middleware.RequireAuth(handlers.ChangeUsername))
mux.HandleFunc("POST /users/deleteAccount", middleware.RequireAuth(handlers.DeleteAccount))
mux.HandleFunc("POST /users/createBackupCodes", middleware.RequireAuth(handlers.CreateBackupCodes))
+ mux.HandleFunc("POST /users/validatePassword", middleware.RequireAuth(handlers.ValidatePassword))
mux.HandleFunc("GET /users/statistics", middleware.RequireAuth(handlers.GetStatistics))
mux.HandleFunc("POST /logs/saveLog", middleware.RequireAuth(handlers.SaveLog))
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
import * as bootstrap from 'bootstrap';
- import { offcanvasIsOpen, sameDate } from '$lib/helpers.js';
+ import { offcanvasIsOpen, sameDate, isAuthenticated } from '$lib/helpers.js';
import { getTranslate } from '@tolgee/svelte';
const { t } = getTranslate();
};
$effect(() => {
- if (window.location.href) {
+ if ($isAuthenticated && window.location.href) {
setTimeout(() => {
oc = document.querySelector('.offcanvas');
oc.addEventListener('hidden.bs.offcanvas', () => {
import { onMount } from 'svelte';
import * as bootstrap from 'bootstrap';
import Tag from './Tag.svelte';
- import { offcanvasIsOpen, sameDate } from '$lib/helpers.js';
+ import { isAuthenticated, offcanvasIsOpen, sameDate } from '$lib/helpers.js';
import { API_URL } from '$lib/APIurl.js';
import axios from 'axios';
import { cal } from '$lib/calendarStore.js';
}
$effect(() => {
- if (window.location.href) {
+ if ($isAuthenticated && window.location.href) {
setTimeout(() => {
oc = document.querySelector('.offcanvas');
oc.addEventListener('hidden.bs.offcanvas', () => {
);
}
+export const isAuthenticated = writable(false);
+
+// Function to check if page load authentication is required
+function needsReauthentication() {
+ isAuthenticated.subscribe((value) => {
+ if (value) return false;
+ })
+
+ if (typeof window === 'undefined') return false;
+
+ // Check localStorage for re-auth requirement
+ const requireReauth = localStorage.getItem('requirePasswordOnPageLoad');
+
+ if (requireReauth !== 'true') {
+ isAuthenticated.set(true);
+ }
+
+ return requireReauth === 'true';
+}
+
function generateNeonMesh(dark) {
/* const baseColors = ['#ff00ff', '#00ffff', '#ffea00', '#ff0080', '#00ff80', '#ff4500']; */
const baseColors = ["#ff00ff", "#00ffff", "#ffea00", "#ff0080", "#00ff80", "#ff4500",
return json[language] || '';
}
-export { formatBytes, sameDate, loadFlagEmoji, generateNeonMesh };
+export { formatBytes, sameDate, needsReauthentication, generateNeonMesh, loadFlagEmoji };
export let alwaysShowSidenav = writable(true);
import * as bootstrap from 'bootstrap';
import Fa from 'svelte-fa';
import { goto } from '$app/navigation';
- import { onMount } from 'svelte';
+ import { onDestroy, onMount } from 'svelte';
import {
readingMode,
settings,
import { API_URL } from '$lib/APIurl.js';
import { tags } from '$lib/tagStore.js';
import TagModal from '$lib/TagModal.svelte';
- import { alwaysShowSidenav, generateNeonMesh, loadFlagEmoji } from '$lib/helpers.js';
+ import {
+ alwaysShowSidenav,
+ generateNeonMesh,
+ loadFlagEmoji,
+ needsReauthentication,
+ isAuthenticated
+ } from '$lib/helpers.js';
import { templates } from '$lib/templateStore';
import {
faRightFromBracket,
}
});
+ onDestroy(() => {
+ $isAuthenticated = false;
+ });
+
onMount(() => {
+ let needsReauth = needsReauthentication();
+
+ // Check if re-authentication is needed FIRST
+ if (!$isAuthenticated && needsReauth) {
+ // Save current route for return after reauth
+ localStorage.setItem('returnAfterReauth', window.location.pathname);
+ goto('/reauth');
+ return; // Stop further initialization
+ }
+
+ // Normal initialization only if authenticated
getUserSettings();
getTemplates();
getVersionInfo();
$settings = response.data;
$tempSettings = JSON.parse(JSON.stringify($settings));
aLookBackYears = $settings.aLookBackYears.toString();
+
+ // Save re-auth setting to localStorage for immediate availability
+ localStorage.setItem(
+ 'requirePasswordOnPageLoad',
+ $settings.requirePasswordOnPageLoad.toString()
+ );
+
updateLanguage();
// set background
if (response.data.success) {
$settings = $tempSettings;
+ // Save re-auth setting to localStorage for immediate availability
+ localStorage.setItem(
+ 'requirePasswordOnPageLoad',
+ $tempSettings.requirePasswordOnPageLoad.toString()
+ );
+
// update language
updateLanguage();
</div>
</div>
<div id="loginonreload">
- <h5>Login bei Reload</h5>
- Bla<br />
- blub <br />
- bla <br />
- blub <br />
+ {#if $tempSettings.requirePasswordOnPageLoad !== $settings.requirePasswordOnPageLoad}
+ {@render unsavedChanges()}
+ {/if}
+
+ <h5>🔒 {$t('settings.reauth.title')}</h5>
+ {$t('settings.reauth.description')}
+
+ <div class="form-check form-switch mt-2">
+ <input
+ class="form-check-input"
+ bind:checked={$tempSettings.requirePasswordOnPageLoad}
+ type="checkbox"
+ role="switch"
+ id="requirePasswordOnPageLoadSwitch"
+ />
+ <label class="form-check-label" for="requirePasswordOnPageLoadSwitch">
+ {$t('settings.reauth.label')}
+ </label>
+ </div>
</div>
</div>
import { faCloudArrowDown } from '@fortawesome/free-solid-svg-icons';
import { Fa } from 'svelte-fa';
import ImageViewer from '$lib/ImageViewer.svelte';
- import { alwaysShowSidenav } from '$lib/helpers.js';
+ import { alwaysShowSidenav, needsReauthentication, isAuthenticated } from '$lib/helpers.js';
import { getTranslate } from '@tolgee/svelte';
const { t } = getTranslate();
let observer;
onMount(() => {
+ /* if (!$isAuthenticated && needsReauthentication()) {
+ return;
+ } */
+
loadMonthForReading();
// Highlights automatically the day in the calendar, when the log is in the viewport
import { formatBytes, alwaysShowSidenav, sameDate } from '$lib/helpers.js';
import ImageViewer from '$lib/ImageViewer.svelte';
import TemplateDropdown from '$lib/TemplateDropdown.svelte';
- import { templates, insertTemplate } from '$lib/templateStore';
+ import { insertTemplate } from '$lib/templateStore';
import ALookBack from '$lib/ALookBack.svelte';
import { marked } from 'marked';
import { T, getTranslate } from '@tolgee/svelte';
let aLookBack = $state([]);
function getALookBack() {
- if (!$settings.useALookBack) {
+ // Skip if settings not loaded yet
+ if (!$settings || $settings.useALookBack === undefined || !$settings.useALookBack) {
aLookBack = [];
return;
}
});
}
+ // Re-trigger aLookBack when settings are loaded/changed
+ $effect(() => {
+ if ($settings && $settings.useALookBack !== undefined) {
+ getALookBack();
+ }
+ });
+
const imageExtensions = ['jpeg', 'jpg', 'gif', 'png', 'webp'];
//TODO: support svg? -> minsize is necessary...
import { goto } from '$app/navigation';
import { API_URL } from '$lib/APIurl.js';
import { getTranslate, getTolgee } from '@tolgee/svelte';
- import { loadFlagEmoji } from '$lib/helpers.js';
+ import { isAuthenticated, loadFlagEmoji } from '$lib/helpers.js';
const { t } = getTranslate();
const tolgee = getTolgee(['language']);
handleMigrationProgress(response.data.username);
} else {
+ $isAuthenticated = true;
localStorage.setItem('user', response.data.username);
goto('/write');
}
--- /dev/null
+<script>
+ import { goto } from '$app/navigation';
+ import { onMount } from 'svelte';
+ import axios from 'axios';
+ import { API_URL } from '$lib/APIurl.js';
+ import Fa from 'svelte-fa';
+ import { faLock } from '@fortawesome/free-solid-svg-icons';
+ import { generateNeonMesh, isAuthenticated } from '$lib/helpers';
+ import { getTranslate } from '@tolgee/svelte';
+ import logo from '$lib/assets/locked_heart_with_keyhole.svg';
+
+ const { t } = getTranslate();
+
+ let password = $state('');
+ let isValidating = $state(false);
+ let error = $state('');
+
+ onMount(() => {
+ // get browser default-light/dark-mode settings
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ generateNeonMesh(prefersDark);
+
+ // Focus password input
+ const passwordInput = document.getElementById('reauth-password');
+ if (passwordInput) {
+ passwordInput.focus();
+ }
+ });
+
+ async function validatePassword() {
+ if (isValidating || !password.trim()) return;
+
+ isValidating = true;
+ error = '';
+
+ try {
+ const response = await axios.post(API_URL + '/users/validatePassword', {
+ password: password
+ });
+
+ if (response.data.valid) {
+ $isAuthenticated = true;
+
+ // Authentication successful - return to original route
+ const returnPath = localStorage.getItem('returnAfterReauth') || '/write';
+ localStorage.removeItem('returnAfterReauth');
+ goto(returnPath);
+ } else {
+ error = $t('reauth.wrong_password');
+ password = '';
+
+ // Focus password input
+ const passwordInput = document.getElementById('reauth-password');
+ if (passwordInput) {
+ passwordInput.focus();
+ }
+ }
+ } catch (err) {
+ console.error('Password validation error:', err);
+ error = $t('reauth.authentication_error');
+ } finally {
+ isValidating = false;
+ }
+ }
+
+ function handleKeydown(event) {
+ if (event.key === 'Enter') {
+ validatePassword();
+ }
+ }
+</script>
+
+<div class="background container-fluid h-100 d-flex align-items-center justify-content-center">
+ <div class="card shadow-lg" style="max-width: 400px; width: 100%;">
+ <div class="card-header text-center bg-primary text-white">
+ <div class="d-flex justify-content-center align-items-center mb-3">
+ <img src={logo} alt="" width="50px" />
+ <span class="dailytxt ms-2">DailyTxT</span>
+ </div>
+ <h3 class="mb-0">{$t('reauth.title')}</h3>
+ </div>
+ <div class="card-body p-4">
+ <p class="text-muted text-center mb-4">
+ {$t('reauth.description')}
+ </p>
+
+ <form onsubmit={validatePassword}>
+ <div class="mb-3 position-relative">
+ <input
+ id="reauth-password"
+ type="password"
+ bind:value={password}
+ onkeydown={handleKeydown}
+ class="form-control form-control-lg pe-5"
+ placeholder={$t('login.password')}
+ disabled={isValidating}
+ required
+ />
+ </div>
+
+ {#if error}
+ <div class="alert alert-danger">{error}</div>
+ {/if}
+
+ <button
+ type="submit"
+ class="btn btn-primary btn-lg w-100"
+ disabled={isValidating || !password.trim()}
+ >
+ {#if isValidating}
+ <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"
+ ></span>
+ {/if}
+ {$t('reauth.confirmButton')}
+ </button>
+ </form>
+ </div>
+ </div>
+</div>
+
+<style scoped>
+ .dailytxt {
+ color: #f57c00;
+ font-size: 2rem;
+ font-weight: 500;
+ }
+
+ .container-fluid {
+ /* background: linear-gradient(135deg, #899cf1 0%, #a24b4b 100%); */
+ min-height: 100vh;
+ }
+
+ .card {
+ backdrop-filter: blur(10px);
+ background: rgba(255, 255, 255, 0.5);
+ border: none;
+ border-radius: 10px;
+ }
+
+ .card-header {
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ }
+</style>