From f2b584342c54678eaa71fc718109f885724b8b31 Mon Sep 17 00:00:00 2001
From: hawkinslabdev <59891413+hawkinslabdev@users.noreply.github.com>
Date: Mon, 18 May 2026 12:34:47 +0200
Subject: [PATCH 1/5] chore: prepare locale files
---
motomate/src/lib/i18n/locales/de.json | 14 ++++++++++++--
motomate/src/lib/i18n/locales/en.json | 12 +++++++++++-
motomate/src/lib/i18n/locales/es.json | 14 ++++++++++++--
motomate/src/lib/i18n/locales/fr.json | 14 ++++++++++++--
motomate/src/lib/i18n/locales/it.json | 14 ++++++++++++--
motomate/src/lib/i18n/locales/nl.json | 14 ++++++++++++--
motomate/src/lib/i18n/locales/pt.json | 14 ++++++++++++--
7 files changed, 83 insertions(+), 13 deletions(-)
diff --git a/motomate/src/lib/i18n/locales/de.json b/motomate/src/lib/i18n/locales/de.json
index c30a13e..45f3263 100644
--- a/motomate/src/lib/i18n/locales/de.json
+++ b/motomate/src/lib/i18n/locales/de.json
@@ -55,7 +55,10 @@
"passwordMismatch": "Passwörter stimmen nicht überein",
"submit": "Konto erstellen",
"hasAccount": "Bereits ein Konto?",
- "login": "Anmelden"
+ "login": "Anmelden",
+ "closedTitle": "Registrierung geschlossen",
+ "closedBody": "Neue Konten werden derzeit nicht angenommen. Wenden Sie sich an Ihren Administrator.",
+ "closedAction": "Zur Anmeldung"
},
"magicLink": {
"verifying": "Wir melden Sie an…",
@@ -927,7 +930,14 @@
"workflows": "Workflows",
"sourceCode": "Quellcode",
"reportIssue": "Problem melden",
- "tapAgain": "Nochmal tippen zum Offnen"
+ "tapAgain": "Nochmal tippen zum Offnen",
+ "changelog": "Changelog"
+ },
+ "changelog": {
+ "title": "Was ist neu?",
+ "subtitle": "Sieh nach, was sich in den neuesten Versionen geändert hat.",
+ "refresh": "Aktualisieren",
+ "noContent": "Keine Änderungen gefunden. Versuche es später erneut."
}
},
"units": {
diff --git a/motomate/src/lib/i18n/locales/en.json b/motomate/src/lib/i18n/locales/en.json
index 7f36937..9727e2c 100644
--- a/motomate/src/lib/i18n/locales/en.json
+++ b/motomate/src/lib/i18n/locales/en.json
@@ -55,7 +55,10 @@
"passwordMismatch": "Passwords do not match",
"submit": "Create account",
"hasAccount": "Already have an account?",
- "login": "Log in"
+ "login": "Log in",
+ "closedTitle": "Registration closed",
+ "closedBody": "New accounts are not accepted at this time. Contact your administrator for access.",
+ "closedAction": "Go to log in"
},
"magicLink": {
"title": "Magic link",
@@ -737,6 +740,7 @@
"account": "Account",
"notifications": "Notifications",
"workflows": "Workflows",
+ "changelog": "Changelog",
"sourceCode": "Source Code",
"reportIssue": "Report an Issue",
"tapAgain": "Tap again to open"
@@ -929,6 +933,12 @@
"prevPage": "Previous",
"nextPage": "Next",
"pageOf": "Page {page} of {total}"
+ },
+ "changelog": {
+ "title": "What's new?",
+ "subtitle": "See what changed in the latest versions.",
+ "refresh": "Refresh",
+ "noContent": "No changes found. Try again later."
}
},
"layout": {
diff --git a/motomate/src/lib/i18n/locales/es.json b/motomate/src/lib/i18n/locales/es.json
index b9f7449..ae99d29 100644
--- a/motomate/src/lib/i18n/locales/es.json
+++ b/motomate/src/lib/i18n/locales/es.json
@@ -55,7 +55,10 @@
"passwordMismatch": "Las contraseñas no coinciden",
"submit": "Crear cuenta",
"hasAccount": "¿Ya tienes cuenta?",
- "login": "Iniciar sesión"
+ "login": "Iniciar sesión",
+ "closedTitle": "Registro cerrado",
+ "closedBody": "Los nuevos registros no están abiertos. Contacta con tu administrador para obtener acceso.",
+ "closedAction": "Ir a iniciar sesión"
},
"magicLink": {
"verifying": "Iniciando sesión…",
@@ -927,7 +930,14 @@
"workflows": "Flujos de trabajo",
"sourceCode": "Codigo fuente",
"reportIssue": "Reportar un problema",
- "tapAgain": "Toca de nuevo para abrir"
+ "tapAgain": "Toca de nuevo para abrir",
+ "changelog": "Changelog"
+ },
+ "changelog": {
+ "title": "¿Qué hay de nuevo?",
+ "subtitle": "Consulta qué ha cambiado en las últimas versiones.",
+ "refresh": "Actualizar",
+ "noContent": "No se encontraron cambios. Inténtalo más tarde."
}
},
"units": {
diff --git a/motomate/src/lib/i18n/locales/fr.json b/motomate/src/lib/i18n/locales/fr.json
index f7ad560..d181f98 100644
--- a/motomate/src/lib/i18n/locales/fr.json
+++ b/motomate/src/lib/i18n/locales/fr.json
@@ -55,7 +55,10 @@
"passwordMismatch": "Les mots de passe ne correspondent pas",
"submit": "Créer un compte",
"hasAccount": "Déjà un compte ?",
- "login": "Se connecter"
+ "login": "Se connecter",
+ "closedTitle": "Inscription fermée",
+ "closedBody": "Les nouveaux comptes ne sont pas acceptés pour l'instant. Contactez votre administrateur pour obtenir l'accès.",
+ "closedAction": "Aller à la connexion"
},
"magicLink": {
"verifying": "Connexion en cours…",
@@ -927,7 +930,14 @@
"workflows": "Flux de travail",
"sourceCode": "Code source",
"reportIssue": "Signaler un probleme",
- "tapAgain": "Appuyez a nouveau pour ouvrir"
+ "tapAgain": "Appuyez a nouveau pour ouvrir",
+ "changelog": "Changelog"
+ },
+ "changelog": {
+ "title": "Quoi de neuf ?",
+ "subtitle": "Découvrez ce qui a changé dans les dernières versions.",
+ "refresh": "Actualiser",
+ "noContent": "Aucune modification trouvée. Réessayez plus tard."
}
},
"units": {
diff --git a/motomate/src/lib/i18n/locales/it.json b/motomate/src/lib/i18n/locales/it.json
index 33407e3..659d169 100644
--- a/motomate/src/lib/i18n/locales/it.json
+++ b/motomate/src/lib/i18n/locales/it.json
@@ -55,7 +55,10 @@
"passwordMismatch": "Le password non corrispondono",
"submit": "Crea account",
"hasAccount": "Hai già un account?",
- "login": "Accedi"
+ "login": "Accedi",
+ "closedTitle": "Registrazione chiusa",
+ "closedBody": "I nuovi account non sono accettati al momento. Contatta il tuo amministratore per l'accesso.",
+ "closedAction": "Vai al login"
},
"magicLink": {
"verifying": "Accesso in corso…",
@@ -928,7 +931,14 @@
"workflows": "Flussi di lavoro",
"sourceCode": "Codice sorgente",
"reportIssue": "Segnala un problema",
- "tapAgain": "Tocca di nuovo per aprire"
+ "tapAgain": "Tocca di nuovo per aprire",
+ "changelog": "Changelog"
+ },
+ "changelog": {
+ "title": "Cosa c'è di nuovo?",
+ "subtitle": "Scopri cosa è cambiato nelle ultime versioni.",
+ "refresh": "Aggiorna",
+ "noContent": "Nessuna modifica trovata. Riprova più tardi."
}
},
"units": {
diff --git a/motomate/src/lib/i18n/locales/nl.json b/motomate/src/lib/i18n/locales/nl.json
index aa15901..a3f4ceb 100644
--- a/motomate/src/lib/i18n/locales/nl.json
+++ b/motomate/src/lib/i18n/locales/nl.json
@@ -55,7 +55,10 @@
"passwordMismatch": "Wachtwoorden komen niet overeen",
"submit": "Account maken",
"hasAccount": "Heb je al een account?",
- "login": "Inloggen"
+ "login": "Inloggen",
+ "closedTitle": "Registratie gesloten",
+ "closedBody": "Nieuwe accounts worden momenteel niet geaccepteerd. Neem contact op met uw beheerder voor toegang.",
+ "closedAction": "Ga naar inloggen"
},
"magicLink": {
"verifying": "Je wordt ingelogd…",
@@ -927,7 +930,14 @@
"workflows": "Regels",
"sourceCode": "Broncode",
"reportIssue": "Probleem melden",
- "tapAgain": "Tik nogmaals om te openen"
+ "tapAgain": "Tik nogmaals om te openen",
+ "changelog": "Bekijk changelog"
+ },
+ "changelog": {
+ "title": "Wat is er nieuw?",
+ "subtitle": "Bekijk wat er veranderd is in de nieuwste versies.",
+ "refresh": "Vernieuwen",
+ "noContent": "Geen gewijzigen gevonden. Probeer het later opnieuw."
}
},
"units": {
diff --git a/motomate/src/lib/i18n/locales/pt.json b/motomate/src/lib/i18n/locales/pt.json
index e02e775..19b638c 100644
--- a/motomate/src/lib/i18n/locales/pt.json
+++ b/motomate/src/lib/i18n/locales/pt.json
@@ -55,7 +55,10 @@
"passwordMismatch": "As senhas não coincidem",
"submit": "Criar conta",
"hasAccount": "Já tem uma conta?",
- "login": "Iniciar sessão"
+ "login": "Iniciar sessão",
+ "closedTitle": "Registo fechado",
+ "closedBody": "Novos registos não são aceites neste momento. Contacte o seu administrador para obter acesso.",
+ "closedAction": "Ir para o login"
},
"magicLink": {
"verifying": "A iniciar sessão…",
@@ -927,7 +930,14 @@
"workflows": "Fluxos de trabalho",
"sourceCode": "Codigo fonte",
"reportIssue": "Reportar um problema",
- "tapAgain": "Toque novamente para abrir"
+ "tapAgain": "Toque novamente para abrir",
+ "changelog": "Changelog"
+ },
+ "changelog": {
+ "title": "O que há de novo?",
+ "subtitle": "Veja o que mudou nas versões mais recentes.",
+ "refresh": "Atualizar",
+ "noContent": "Nenhuma alteração encontrada. Tente novamente mais tarde."
}
},
"units": {
From 78ff49496a34e9d9b66b8c950b428ee1ae05c9f4 Mon Sep 17 00:00:00 2001
From: hawkinslabdev <59891413+hawkinslabdev@users.noreply.github.com>
Date: Mon, 18 May 2026 12:36:06 +0200
Subject: [PATCH 2/5] feat(changelog): add changelog page and update version
display in settings
---
.../src/routes/(app)/settings/+layout.svelte | 64 ++++++-
.../(app)/settings/changelog/+page.svelte | 157 ++++++++++++++++++
2 files changed, 220 insertions(+), 1 deletion(-)
create mode 100644 motomate/src/routes/(app)/settings/changelog/+page.svelte
diff --git a/motomate/src/routes/(app)/settings/+layout.svelte b/motomate/src/routes/(app)/settings/+layout.svelte
index 9c751dc..9929717 100644
--- a/motomate/src/routes/(app)/settings/+layout.svelte
+++ b/motomate/src/routes/(app)/settings/+layout.svelte
@@ -124,7 +124,12 @@
-
v{appVersion}
+ v{appVersion}
{#if children}
@@ -210,11 +215,53 @@
transform: translateX(0);
}
.settings-nav-version {
+ position: relative;
font-family: 'JetBrains Mono', monospace;
font-size: var(--text-xs);
color: var(--text-subtle);
padding: 0.5rem 1rem;
opacity: 0.7;
+ text-decoration: none;
+ display: block;
+ transition:
+ color 0.1s,
+ opacity 0.1s;
+ }
+ .settings-nav-version:hover {
+ color: var(--text-muted);
+ opacity: 1;
+ }
+ .settings-nav-version--active {
+ color: var(--accent);
+ opacity: 1;
+ }
+ .settings-nav-version::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ top: calc(100% + 4px);
+ left: 1rem;
+ background: var(--bg-muted);
+ border: 1px solid var(--border-strong);
+ color: var(--text);
+ font-family: Inter, system-ui, sans-serif;
+ font-size: var(--text-xs);
+ font-weight: 500;
+ padding: 0.2rem 0.5rem;
+ border-radius: 6px;
+ white-space: nowrap;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.15s ease;
+ z-index: 10;
+ }
+ .settings-nav-version:hover::after,
+ .settings-nav-version:focus-visible::after {
+ opacity: 1;
+ }
+ @media (prefers-reduced-motion: reduce) {
+ .settings-nav-version::after {
+ transition: none;
+ }
}
.settings-nav-divider {
height: 1px;
@@ -266,6 +313,21 @@
transform: translateX(0);
}
.settings-nav-version {
+ border-left: none;
+ border-bottom: 2px solid transparent;
+ border-radius: 0;
+ white-space: nowrap;
+ padding: 0.625rem 0.875rem;
+ min-height: 44px;
+ display: flex;
+ align-items: center;
+ opacity: 1;
+ font-family: 'JetBrains Mono', monospace;
+ }
+ .settings-nav-version--active {
+ border-bottom-color: var(--accent);
+ }
+ .settings-nav-version::after {
display: none;
}
}
diff --git a/motomate/src/routes/(app)/settings/changelog/+page.svelte b/motomate/src/routes/(app)/settings/changelog/+page.svelte
new file mode 100644
index 0000000..1900449
--- /dev/null
+++ b/motomate/src/routes/(app)/settings/changelog/+page.svelte
@@ -0,0 +1,157 @@
+
+
+
+ {#snippet children()}
+
+ {/snippet}
+
+
+{#if blocks.length === 0}
+
+{:else}
+
+ {#each blocks as block}
+
+ v{block.version}
+
+ {#each block.entries as entry}
+ -
+ {#if entry.category}
+ {entry.category}
+ {:else}
+ ·
+ {/if}
+ {#each parseSegments(entry.text) as seg}{#if seg.type === 'issue'}(#{seg.number}){:else}{seg.value}{/if}{/each}
+
+ {/each}
+
+
+ {/each}
+
+{/if}
+
+
From bcc06e2db5fe4cebc2a7cbf075dc8cd590f0c3b5 Mon Sep 17 00:00:00 2001
From: hawkinslabdev <59891413+hawkinslabdev@users.noreply.github.com>
Date: Mon, 18 May 2026 12:46:08 +0200
Subject: [PATCH 3/5] feat(auth): add AUTH_ALLOW_REGISTRATION flag
---
README.md | 1 +
motomate/.env.example | 1 +
motomate/src/lib/auth/registration.ts | 7 +
motomate/src/lib/db/repositories/users.ts | 5 +
motomate/src/lib/i18n/locales/de.json | 3 +-
motomate/src/lib/i18n/locales/en.json | 3 +-
motomate/src/lib/i18n/locales/es.json | 3 +-
motomate/src/lib/i18n/locales/fr.json | 3 +-
motomate/src/lib/i18n/locales/it.json | 3 +-
motomate/src/lib/i18n/locales/nl.json | 3 +-
motomate/src/lib/i18n/locales/pt.json | 3 +-
.../(app)/settings/changelog/+page.server.ts | 22 +++
.../src/routes/(auth)/login/+page.server.ts | 10 +-
motomate/src/routes/(auth)/login/+page.svelte | 12 +-
.../routes/(auth)/register/+page.server.ts | 8 +-
.../src/routes/(auth)/register/+page.svelte | 175 ++++++++++--------
motomate/src/tests/registration-gate.test.ts | 46 +++++
17 files changed, 212 insertions(+), 96 deletions(-)
create mode 100644 motomate/src/lib/auth/registration.ts
create mode 100644 motomate/src/routes/(app)/settings/changelog/+page.server.ts
create mode 100644 motomate/src/tests/registration-gate.test.ts
diff --git a/README.md b/README.md
index 731c269..9f76abb 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ services:
- PUBLIC_APP_ORIGINS=http://localhost
- AUTH_COOKIE_SECURE=false
- AUTH_SECRET=change-me-in-production-min-32-chars
+ - AUTH_ALLOW_REGISTRATION=true
- STORAGE_ADAPTER=local
- BODY_SIZE_LIMIT=20971520
restart: unless-stopped
diff --git a/motomate/.env.example b/motomate/.env.example
index d5d2201..2fcb40a 100644
--- a/motomate/.env.example
+++ b/motomate/.env.example
@@ -4,6 +4,7 @@ DATABASE_URL=./data/motomate.db
# Auth
AUTH_SECRET=change-me-in-production-min-32-chars
AUTH_COOKIE_SECURE=false
+AUTH_ALLOW_REGISTRATION=true
# Storage: 'local' or 's3'
STORAGE_ADAPTER=local
diff --git a/motomate/src/lib/auth/registration.ts b/motomate/src/lib/auth/registration.ts
new file mode 100644
index 0000000..c1b2339
--- /dev/null
+++ b/motomate/src/lib/auth/registration.ts
@@ -0,0 +1,7 @@
+import { env } from '$env/dynamic/private';
+import { hasAnyUser } from '$lib/db/repositories/users.js';
+
+export async function isRegistrationOpen(): Promise
{
+ if (env.AUTH_ALLOW_REGISTRATION !== 'false') return true;
+ return !(await hasAnyUser());
+}
diff --git a/motomate/src/lib/db/repositories/users.ts b/motomate/src/lib/db/repositories/users.ts
index 19c0aa4..fc569fa 100644
--- a/motomate/src/lib/db/repositories/users.ts
+++ b/motomate/src/lib/db/repositories/users.ts
@@ -72,6 +72,11 @@ export async function markOnboardingDone(userId: string): Promise {
.where(eq(users.id, userId));
}
+export async function hasAnyUser(): Promise {
+ const row = await db.query.users.findFirst();
+ return row !== undefined;
+}
+
export async function deleteUser(userId: string): Promise {
await db.delete(users).where(eq(users.id, userId));
}
diff --git a/motomate/src/lib/i18n/locales/de.json b/motomate/src/lib/i18n/locales/de.json
index 45f3263..6f3e15d 100644
--- a/motomate/src/lib/i18n/locales/de.json
+++ b/motomate/src/lib/i18n/locales/de.json
@@ -42,7 +42,8 @@
"rateLimited": "Zu viele Versuche. Bitte versuche es später erneut.",
"invalidFormat": "Ungültiges E-Mail- oder Passwortformat",
"invalidCredentials": "Ungültige E-Mail oder Passwort",
- "invalidEmail": "Bitte gib eine gültige E-Mail-Adresse ein"
+ "invalidEmail": "Bitte gib eine gültige E-Mail-Adresse ein",
+ "registrationClosed": "Die Registrierung ist geschlossen. Wenden Sie sich an Ihren Administrator."
}
},
"register": {
diff --git a/motomate/src/lib/i18n/locales/en.json b/motomate/src/lib/i18n/locales/en.json
index 9727e2c..fa2dbda 100644
--- a/motomate/src/lib/i18n/locales/en.json
+++ b/motomate/src/lib/i18n/locales/en.json
@@ -42,7 +42,8 @@
"rateLimited": "Too many attempts. Please try again later.",
"invalidFormat": "Invalid email or password format",
"invalidCredentials": "Invalid email or password",
- "invalidEmail": "Please enter a valid email address"
+ "invalidEmail": "Please enter a valid email address",
+ "registrationClosed": "Unfortunately, you can't sign up right now. Contact your administrator for access."
}
},
"register": {
diff --git a/motomate/src/lib/i18n/locales/es.json b/motomate/src/lib/i18n/locales/es.json
index ae99d29..94544af 100644
--- a/motomate/src/lib/i18n/locales/es.json
+++ b/motomate/src/lib/i18n/locales/es.json
@@ -42,7 +42,8 @@
"rateLimited": "Demasiados intentos. Por favor, inténtalo de nuevo más tarde.",
"invalidFormat": "Formato de correo electrónico o contraseña inválido",
"invalidCredentials": "Correo electrónico o contraseña inválidos",
- "invalidEmail": "Por favor, introduce una dirección de correo electrónico válida"
+ "invalidEmail": "Por favor, introduce una dirección de correo electrónico válida",
+ "registrationClosed": "El registro está cerrado. Contacta con tu administrador para obtener acceso."
}
},
"register": {
diff --git a/motomate/src/lib/i18n/locales/fr.json b/motomate/src/lib/i18n/locales/fr.json
index d181f98..8e22564 100644
--- a/motomate/src/lib/i18n/locales/fr.json
+++ b/motomate/src/lib/i18n/locales/fr.json
@@ -42,7 +42,8 @@
"rateLimited": "Trop de tentatives. Veuillez réessayer plus tard.",
"invalidFormat": "Format d'e-mail ou de mot de passe invalide",
"invalidCredentials": "E-mail ou mot de passe invalide",
- "invalidEmail": "Veuillez entrer une adresse e-mail valide"
+ "invalidEmail": "Veuillez entrer une adresse e-mail valide",
+ "registrationClosed": "L'inscription est fermée. Contactez votre administrateur pour obtenir l'accès."
}
},
"register": {
diff --git a/motomate/src/lib/i18n/locales/it.json b/motomate/src/lib/i18n/locales/it.json
index 659d169..1688691 100644
--- a/motomate/src/lib/i18n/locales/it.json
+++ b/motomate/src/lib/i18n/locales/it.json
@@ -42,7 +42,8 @@
"rateLimited": "Troppi tentativi. Riprova più tardi.",
"invalidFormat": "Formato email o password non valido",
"invalidCredentials": "Email o password non validi",
- "invalidEmail": "Inserisci un indirizzo email valido"
+ "invalidEmail": "Inserisci un indirizzo email valido",
+ "registrationClosed": "La registrazione è chiusa. Contatta il tuo amministratore per l'accesso."
}
},
"register": {
diff --git a/motomate/src/lib/i18n/locales/nl.json b/motomate/src/lib/i18n/locales/nl.json
index a3f4ceb..0e9fd0b 100644
--- a/motomate/src/lib/i18n/locales/nl.json
+++ b/motomate/src/lib/i18n/locales/nl.json
@@ -42,7 +42,8 @@
"rateLimited": "Te veel pogingen. Probeer het later opnieuw.",
"invalidFormat": "Ongeldig e-mailadres of wachtwoordformaat",
"invalidCredentials": "Ongeldig e-mailadres of wachtwoord",
- "invalidEmail": "Voer een geldig e-mailadres in"
+ "invalidEmail": "Voer een geldig e-mailadres in",
+ "registrationClosed": "Aanmelden is nu niet mogelijk. Neem contact op met uw beheerder voor toegang."
}
},
"register": {
diff --git a/motomate/src/lib/i18n/locales/pt.json b/motomate/src/lib/i18n/locales/pt.json
index 19b638c..f92f524 100644
--- a/motomate/src/lib/i18n/locales/pt.json
+++ b/motomate/src/lib/i18n/locales/pt.json
@@ -42,7 +42,8 @@
"rateLimited": "Demasiadas tentativas. Por favor, tenta novamente mais tarde.",
"invalidFormat": "Formato de e-mail ou palavra-passe inválido",
"invalidCredentials": "E-mail ou palavra-passe inválidos",
- "invalidEmail": "Por favor, introduz um endereço de e-mail válido"
+ "invalidEmail": "Por favor, introduz um endereço de e-mail válido",
+ "registrationClosed": "O registo está fechado. Contacte o seu administrador para obter acesso."
}
},
"register": {
diff --git a/motomate/src/routes/(app)/settings/changelog/+page.server.ts b/motomate/src/routes/(app)/settings/changelog/+page.server.ts
new file mode 100644
index 0000000..fa2687d
--- /dev/null
+++ b/motomate/src/routes/(app)/settings/changelog/+page.server.ts
@@ -0,0 +1,22 @@
+import type { PageServerLoad } from './$types';
+
+const CHANGELOG_URL = 'https://raw.githubusercontent.com/hawkinslabdev/motomate/main/CHANGELOG.md';
+const TTL = 3_600_000;
+
+let cache: { raw: string; at: number } | null = null;
+
+export const load: PageServerLoad = async ({ url }) => {
+ const refresh = url.searchParams.has('refresh');
+ if (!refresh && cache && Date.now() - cache.at < TTL) {
+ return { raw: cache.raw };
+ }
+ try {
+ const res = await fetch(CHANGELOG_URL, { signal: AbortSignal.timeout(8000) });
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const raw = await res.text();
+ cache = { raw, at: Date.now() };
+ return { raw };
+ } catch {
+ return { raw: cache?.raw ?? null };
+ }
+};
diff --git a/motomate/src/routes/(auth)/login/+page.server.ts b/motomate/src/routes/(auth)/login/+page.server.ts
index 7453218..6b2bf56 100644
--- a/motomate/src/routes/(auth)/login/+page.server.ts
+++ b/motomate/src/routes/(auth)/login/+page.server.ts
@@ -1,6 +1,7 @@
import { fail, redirect } from '@sveltejs/kit';
import { lucia } from '$lib/auth/index.js';
import { getUserByEmail, createUser, updateUserSettings } from '$lib/db/repositories/users.js';
+import { isRegistrationOpen } from '$lib/auth/registration.js';
import { createMagicLinkToken, sendMagicLinkEmail } from '$lib/auth/magic-link.js';
import { LoginSchema, MagicLinkRequestSchema } from '$lib/validators/schemas.js';
import { rateLimit } from '$lib/auth/rate-limit.js';
@@ -22,6 +23,7 @@ type AuthErrors = {
invalidFormat: string;
invalidCredentials: string;
invalidEmail: string;
+ registrationClosed: string;
};
};
};
@@ -31,8 +33,7 @@ const localeMessages: Record = { en, de, fr, es, it, nl, pt
const ARGON2_OPTS = { memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1 };
-// Uses a pre-computed hash to ensure every login attempt takes the same amount of time.
-// This prevents abusers from using response speeds to guess if an email address exists in the database.
+// Uses a pre-computed hash to ensure every login attempt takes same amount of time
let _dummyHash: string | undefined;
async function getDummyHash(): Promise {
if (!_dummyHash) _dummyHash = await hash('_timing_dummy_', ARGON2_OPTS);
@@ -41,7 +42,7 @@ async function getDummyHash(): Promise {
export const load: PageServerLoad = async ({ locals }) => {
if (locals.user) redirect(302, '/dashboard');
- return {};
+ return { registrationEnabled: await isRegistrationOpen() };
};
export const actions: Actions = {
@@ -130,6 +131,9 @@ export const actions: Actions = {
// Find or create user (passwordless)
let user = await getUserByEmail(parsed.data.email);
if (!user) {
+ if (!(await isRegistrationOpen())) {
+ return fail(400, { error: errors.registrationClosed });
+ }
user = await createUser({ email: parsed.data.email });
}
diff --git a/motomate/src/routes/(auth)/login/+page.svelte b/motomate/src/routes/(auth)/login/+page.svelte
index 09ca4f9..2e590df 100644
--- a/motomate/src/routes/(auth)/login/+page.svelte
+++ b/motomate/src/routes/(auth)/login/+page.svelte
@@ -2,8 +2,8 @@
import { enhance } from '$app/forms';
import { _ } from '$lib/i18n';
- let { form } = $props<{
- data: Record;
+ let { data, form } = $props<{
+ data: { registrationEnabled: boolean };
form: {
error?: string;
email?: string;
@@ -146,9 +146,11 @@
{/if}
-
+ {#if data.registrationEnabled}
+
+ {/if}
{/if}