From 0e496f7a005931de179ddb2d204f24eb41c3976a Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 19 Mar 2026 10:13:18 +0100 Subject: [PATCH] fix(auth): add missing reset-password page to 13 apps All SvelteKit web apps now have complete auth flows: - login, register, forgot-password, and reset-password Changes: - Add reset-password page to: chat, clock, contacts, context, manadeck, nutriphi, planta, presi, questions, skilltree, todo, zitare, photos - Add forgot-password page to photos (was also missing) - Add resetPasswordWithToken() method to all 13 auth stores - Each page customized with app-specific logo, colors, branding Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 236 ++++++++++++++++++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 16 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../(auth)/forgot-password/+page.svelte | 29 +++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 44 ++++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ .../apps/web/src/lib/stores/auth.svelte.ts | 21 ++ .../routes/(auth)/reset-password/+page.svelte | 174 +++++++++++++ 27 files changed, 2797 insertions(+) create mode 100644 apps/chat/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/clock/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/contacts/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/context/apps/web/src/lib/stores/auth.svelte.ts create mode 100644 apps/context/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/manadeck/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/nutriphi/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/photos/apps/web/src/routes/(auth)/forgot-password/+page.svelte create mode 100644 apps/photos/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/planta/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/presi/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/questions/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/skilltree/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/todo/apps/web/src/routes/(auth)/reset-password/+page.svelte create mode 100644 apps/zitare/apps/web/src/routes/(auth)/reset-password/+page.svelte diff --git a/apps/chat/apps/web/src/lib/stores/auth.svelte.ts b/apps/chat/apps/web/src/lib/stores/auth.svelte.ts index 2d969ee6f..d50b3c91a 100644 --- a/apps/chat/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/chat/apps/web/src/lib/stores/auth.svelte.ts @@ -218,6 +218,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Resend verification email */ diff --git a/apps/chat/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/chat/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..cfca62ae4 --- /dev/null +++ b/apps/chat/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - ManaChat + + +
+
+ + + ManaChat + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/clock/apps/web/src/lib/stores/auth.svelte.ts b/apps/clock/apps/web/src/lib/stores/auth.svelte.ts index c5d223ad1..9174d2d7b 100644 --- a/apps/clock/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/clock/apps/web/src/lib/stores/auth.svelte.ts @@ -217,6 +217,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Resend verification email */ diff --git a/apps/clock/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/clock/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..54d42004f --- /dev/null +++ b/apps/clock/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Clock + + +
+
+ + + Clock + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/contacts/apps/web/src/lib/stores/auth.svelte.ts b/apps/contacts/apps/web/src/lib/stores/auth.svelte.ts index cfe3c2c11..2973c217b 100644 --- a/apps/contacts/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/contacts/apps/web/src/lib/stores/auth.svelte.ts @@ -218,6 +218,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Resend verification email */ diff --git a/apps/contacts/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/contacts/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..e1b58595a --- /dev/null +++ b/apps/contacts/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Contacts + + +
+
+ + + Contacts + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/context/apps/web/src/lib/stores/auth.svelte.ts b/apps/context/apps/web/src/lib/stores/auth.svelte.ts new file mode 100644 index 000000000..6f6d1f339 --- /dev/null +++ b/apps/context/apps/web/src/lib/stores/auth.svelte.ts @@ -0,0 +1,236 @@ +/** + * Auth Store - Manages authentication state using Svelte 5 runes + * Uses Mana Core Auth + */ + +import { browser } from '$app/environment'; +import { initializeWebAuth, type UserData } from '@manacore/shared-auth'; + +// Get auth URL dynamically at runtime - fallback for SSR and client +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001'; +} + +// Lazy initialization to avoid SSR issues with localStorage +let _authService: ReturnType['authService'] | null = null; +let _tokenManager: ReturnType['tokenManager'] | null = null; + +function getAuthService() { + if (!browser) return null; + if (!_authService) { + const auth = initializeWebAuth({ + baseUrl: getAuthUrl(), + }); + _authService = auth.authService; + _tokenManager = auth.tokenManager; + } + return _authService; +} + +function getTokenManager() { + if (!browser) return null; + getAuthService(); + return _tokenManager; +} + +// State +let user = $state(null); +let loading = $state(true); +let initialized = $state(false); + +export const authStore = { + get user() { + return user; + }, + get loading() { + return loading; + }, + get isAuthenticated() { + return !!user; + }, + get initialized() { + return initialized; + }, + + async initialize() { + if (initialized) return; + + const authService = getAuthService(); + if (!authService) { + initialized = true; + loading = false; + return; + } + + loading = true; + try { + let authenticated = await authService.isAuthenticated(); + + if (!authenticated) { + const ssoResult = await authService.trySSO(); + if (ssoResult.success) { + authenticated = true; + } + } + + if (authenticated) { + const userData = await authService.getUserFromToken(); + user = userData; + } + initialized = true; + } catch (error) { + console.error('Failed to initialize auth:', error); + user = null; + } finally { + loading = false; + } + }, + + async signIn(email: string, password: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.signIn(email, password); + + if (!result.success) { + return { success: false, error: result.error || 'Login failed' }; + } + + const userData = await authService.getUserFromToken(); + user = userData; + + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + + async signUp(email: string, password: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server', needsVerification: false }; + } + + try { + const sourceAppUrl = browser ? window.location.origin : undefined; + const result = await authService.signUp(email, password, sourceAppUrl); + + if (!result.success) { + return { success: false, error: result.error || 'Signup failed', needsVerification: false }; + } + + if (result.needsVerification) { + return { success: true, needsVerification: true }; + } + + const signInResult = await this.signIn(email, password); + return { ...signInResult, needsVerification: false }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage, needsVerification: false }; + } + }, + + async signOut() { + const authService = getAuthService(); + if (!authService) { + user = null; + return; + } + + try { + await authService.signOut(); + user = null; + } catch (error) { + console.error('Sign out error:', error); + user = null; + } + }, + + async resetPassword(email: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.forgotPassword(email); + + if (!result.success) { + return { success: false, error: result.error || 'Password reset failed' }; + } + + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + + async resendVerificationEmail(email: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const sourceAppUrl = browser ? window.location.origin : undefined; + const result = await authService.resendVerificationEmail(email, sourceAppUrl); + + if (!result.success) { + return { success: false, error: result.error || 'Failed to resend verification email' }; + } + + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + + async getAccessToken() { + const authService = getAuthService(); + if (!authService) { + return null; + } + return await authService.getAppToken(); + }, + + async getValidToken(): Promise { + const tokenManager = getTokenManager(); + if (!tokenManager) { + return null; + } + return await tokenManager.getValidToken(); + }, +}; diff --git a/apps/context/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/context/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..8e7149319 --- /dev/null +++ b/apps/context/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Context + + +
+
+ + + Context + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/manadeck/apps/web/src/lib/stores/auth.svelte.ts b/apps/manadeck/apps/web/src/lib/stores/auth.svelte.ts index 67cd650f0..6c0e8c53b 100644 --- a/apps/manadeck/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/manadeck/apps/web/src/lib/stores/auth.svelte.ts @@ -126,6 +126,22 @@ export const authStore = { return authService.forgotPassword(email); }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Resend verification email */ diff --git a/apps/manadeck/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/manadeck/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..f47fbf310 --- /dev/null +++ b/apps/manadeck/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - ManaDeck + + +
+
+ + + ManaDeck + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/nutriphi/apps/web/src/lib/stores/auth.svelte.ts b/apps/nutriphi/apps/web/src/lib/stores/auth.svelte.ts index 2f47b0cf2..c646e6126 100644 --- a/apps/nutriphi/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/nutriphi/apps/web/src/lib/stores/auth.svelte.ts @@ -215,6 +215,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Resend verification email */ diff --git a/apps/nutriphi/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/nutriphi/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..2bb687fe6 --- /dev/null +++ b/apps/nutriphi/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - NutriPhi + + +
+
+ + + NutriPhi + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/photos/apps/web/src/lib/stores/auth.svelte.ts b/apps/photos/apps/web/src/lib/stores/auth.svelte.ts index 3f79614eb..2069217ce 100644 --- a/apps/photos/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/photos/apps/web/src/lib/stores/auth.svelte.ts @@ -190,6 +190,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + async resendVerificationEmail(email: string) { const authService = getAuthService(); if (!authService) { diff --git a/apps/photos/apps/web/src/routes/(auth)/forgot-password/+page.svelte b/apps/photos/apps/web/src/routes/(auth)/forgot-password/+page.svelte new file mode 100644 index 000000000..6a9fb04c2 --- /dev/null +++ b/apps/photos/apps/web/src/routes/(auth)/forgot-password/+page.svelte @@ -0,0 +1,29 @@ + + + + Forgot Password - Photos + + + diff --git a/apps/photos/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/photos/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..aff8868c3 --- /dev/null +++ b/apps/photos/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Photos + + +
+
+ + + Photos + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/planta/apps/web/src/lib/stores/auth.svelte.ts b/apps/planta/apps/web/src/lib/stores/auth.svelte.ts index 49d23ed19..f83ca6872 100644 --- a/apps/planta/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/planta/apps/web/src/lib/stores/auth.svelte.ts @@ -177,6 +177,50 @@ export const authStore = { } }, + /** + * Send password reset email + */ + async resetPassword(email: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.forgotPassword(email); + + if (!result.success) { + return { success: false, error: result.error || 'Password reset failed' }; + } + + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + async getValidToken(): Promise { const tokenManager = getTokenManager(); if (!tokenManager) { diff --git a/apps/planta/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/planta/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..2963e64e8 --- /dev/null +++ b/apps/planta/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Planta + + +
+
+ + + Planta + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/presi/apps/web/src/lib/stores/auth.svelte.ts b/apps/presi/apps/web/src/lib/stores/auth.svelte.ts index 2342c964a..bfd8e6abc 100644 --- a/apps/presi/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/presi/apps/web/src/lib/stores/auth.svelte.ts @@ -193,6 +193,27 @@ export const auth = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Get access token for API calls */ diff --git a/apps/presi/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/presi/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..27cc51f24 --- /dev/null +++ b/apps/presi/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Presi + + +
+
+ + + Presi + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/questions/apps/web/src/lib/stores/auth.svelte.ts b/apps/questions/apps/web/src/lib/stores/auth.svelte.ts index f50d0b801..060d80f61 100644 --- a/apps/questions/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/questions/apps/web/src/lib/stores/auth.svelte.ts @@ -192,6 +192,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + async getValidToken(): Promise { const tokenManager = getTokenManager(); if (!tokenManager) { diff --git a/apps/questions/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/questions/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..490658906 --- /dev/null +++ b/apps/questions/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Questions + + +
+
+ + + Questions + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/skilltree/apps/web/src/lib/stores/auth.svelte.ts b/apps/skilltree/apps/web/src/lib/stores/auth.svelte.ts index 6c4d2229a..87cc0cd14 100644 --- a/apps/skilltree/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/skilltree/apps/web/src/lib/stores/auth.svelte.ts @@ -210,6 +210,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + async getAccessToken() { const authService = getAuthService(); if (!authService) { diff --git a/apps/skilltree/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/skilltree/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..5c870bd84 --- /dev/null +++ b/apps/skilltree/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - SkillTree + + +
+
+ + + SkillTree + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts index a3a1c64ba..4b6bffd34 100644 --- a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts @@ -240,6 +240,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Get access token for API calls (raw token, no refresh) * @deprecated Use getValidToken() instead for automatic refresh diff --git a/apps/todo/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/todo/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..5a5d07586 --- /dev/null +++ b/apps/todo/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Todo + + +
+
+ + + Todo + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+
diff --git a/apps/zitare/apps/web/src/lib/stores/auth.svelte.ts b/apps/zitare/apps/web/src/lib/stores/auth.svelte.ts index 0abd3021c..bad6eb84e 100644 --- a/apps/zitare/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/zitare/apps/web/src/lib/stores/auth.svelte.ts @@ -218,6 +218,27 @@ export const authStore = { } }, + /** + * Reset password with token (from reset email link) + */ + async resetPasswordWithToken(token: string, newPassword: string) { + const authService = getAuthService(); + if (!authService) { + return { success: false, error: 'Auth not available on server' }; + } + + try { + const result = await authService.resetPassword(token, newPassword); + if (!result.success) { + return { success: false, error: result.error || 'Failed to reset password' }; + } + return { success: true }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: errorMessage }; + } + }, + /** * Get access token for API calls (raw token, no refresh) * @deprecated Use getValidToken() instead for automatic refresh diff --git a/apps/zitare/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/zitare/apps/web/src/routes/(auth)/reset-password/+page.svelte new file mode 100644 index 000000000..64b50e2b5 --- /dev/null +++ b/apps/zitare/apps/web/src/routes/(auth)/reset-password/+page.svelte @@ -0,0 +1,174 @@ + + + + Reset Password - Zitare + + +
+
+ + + Zitare + +
+ +
+
+
+

Reset Password

+

+ {#if success}Password reset successfully + {:else if hasToken}Enter your new password + {:else}Invalid or missing token{/if} +

+
+ + {#if success} +
+
+
+

+ Your password has been reset successfully. You will be redirected to the login page + shortly. +

+ + Go to login + +
+
+ {:else if hasToken} +
+
+ {#if error} +
+ {error} +
+ {/if} +
+
+ + +

At least 8 characters

+
+
+ + +
+ +
+
+
+ {:else} +
+
+
⚠️
+

+ This password reset link is invalid or has expired. +

+ + Request a new link + +
+
+ {/if} +
+
+