web-mojo 2.1.979 → 2.1.980

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +11 -7
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +2 -2
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +2 -2
  11. package/dist/chunks/{ChatView-CVZScWPz.js → ChatView-BXois2IL.js} +2 -2
  12. package/dist/chunks/{ChatView-CVZScWPz.js.map → ChatView-BXois2IL.js.map} +1 -1
  13. package/dist/chunks/{ChatView-C1702ZPZ.js → ChatView-rAfKBqDw.js} +3 -3
  14. package/dist/chunks/{ChatView-C1702ZPZ.js.map → ChatView-rAfKBqDw.js.map} +1 -1
  15. package/dist/chunks/{Dialog-Be1EkdvX.js → Dialog-CENvQT9n.js} +3 -3
  16. package/dist/chunks/{Dialog-Be1EkdvX.js.map → Dialog-CENvQT9n.js.map} +1 -1
  17. package/dist/chunks/{Dialog-YnhCC4C2.js → Dialog-DZqbxTsP.js} +2 -2
  18. package/dist/chunks/{Dialog-YnhCC4C2.js.map → Dialog-DZqbxTsP.js.map} +1 -1
  19. package/dist/chunks/{FormView-CQbR3S82.js → FormView-095xPgXv.js} +42 -10
  20. package/dist/chunks/FormView-095xPgXv.js.map +1 -0
  21. package/dist/chunks/{FormView-DHz1ydYg.js → FormView-DGA3I2IL.js} +3 -3
  22. package/dist/chunks/FormView-DGA3I2IL.js.map +1 -0
  23. package/dist/chunks/{MetricsMiniChartWidget-CHsOuS2O.js → MetricsMiniChartWidget-CVRinHn4.js} +2 -2
  24. package/dist/chunks/{MetricsMiniChartWidget-CHsOuS2O.js.map → MetricsMiniChartWidget-CVRinHn4.js.map} +1 -1
  25. package/dist/chunks/{MetricsMiniChartWidget-DMGjpLSU.js → MetricsMiniChartWidget-Dez6aHCT.js} +2 -2
  26. package/dist/chunks/{MetricsMiniChartWidget-DMGjpLSU.js.map → MetricsMiniChartWidget-Dez6aHCT.js.map} +1 -1
  27. package/dist/chunks/{PDFViewer-BlWEqtY5.js → PDFViewer-CwzGbdOv.js} +2 -2
  28. package/dist/chunks/{PDFViewer-BlWEqtY5.js.map → PDFViewer-CwzGbdOv.js.map} +1 -1
  29. package/dist/chunks/{PDFViewer-BN200UnI.js → PDFViewer-dAEmy7XJ.js} +2 -2
  30. package/dist/chunks/{PDFViewer-BN200UnI.js.map → PDFViewer-dAEmy7XJ.js.map} +1 -1
  31. package/dist/chunks/{TopNav-ueVUoXcv.js → TopNav-Celew98v.js} +2 -2
  32. package/dist/chunks/{TopNav-ueVUoXcv.js.map → TopNav-Celew98v.js.map} +1 -1
  33. package/dist/chunks/{TopNav-DF8w5mgO.js → TopNav-Cj9_6vRl.js} +2 -2
  34. package/dist/chunks/{TopNav-DF8w5mgO.js.map → TopNav-Cj9_6vRl.js.map} +1 -1
  35. package/dist/chunks/{WebApp-gsWqZg8e.js → WebApp-7JKRzXMJ.js} +13 -13
  36. package/dist/chunks/{WebApp-gsWqZg8e.js.map → WebApp-7JKRzXMJ.js.map} +1 -1
  37. package/dist/chunks/{WebApp-m17riaNV.js → WebApp-9HUBOegS.js} +2 -2
  38. package/dist/chunks/{WebApp-m17riaNV.js.map → WebApp-9HUBOegS.js.map} +1 -1
  39. package/dist/docit.cjs.js +1 -1
  40. package/dist/docit.es.js +3 -3
  41. package/dist/index.cjs.js +1 -1
  42. package/dist/index.es.js +7 -7
  43. package/dist/lightbox.cjs.js +1 -1
  44. package/dist/lightbox.es.js +3 -3
  45. package/package.json +1 -1
  46. package/dist/chunks/FormView-CQbR3S82.js.map +0 -1
  47. package/dist/chunks/FormView-DHz1ydYg.js.map +0 -1
package/dist/auth.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/WebApp-m17riaNV.js"),s=require("./chunks/TokenManager-CJBYcVqs.js"),t=require("./chunks/Page-CvbwEoLv.js");class AuthManager{constructor(e,t={}){this.app=e,this.config={autoRefresh:!0,refreshThreshold:5,plugins:{},...t},this.tokenManager=new s.TokenManager,this.isAuthenticated=!1,this.user=null,this.refreshTimer=null,this.plugins=/* @__PURE__ */new Map,this.initialize()}initialize(){this.checkAuthState(),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.app&&(this.app.auth=this)}checkAuthState(){if(this.tokenManager.isValid()){const e=this.tokenManager.getUserInfo();if(e)return this.setAuthState(e),!0}return this.clearAuthState(),!1}async login(e,s,t=!0){const a=await this.app.rest.POST("/api/login",{username:e,password:s});if(a.success&&a.data.status){const{access_token:e,refresh_token:s,user:n}=a.data.data;this.tokenManager.setTokens(e,s,t);const i=this.tokenManager.getUserInfo();return this.setAuthState({...n,...i}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("login",this.user),{success:!0,user:this.user}}const n=a.data?.error||a.message||"Login failed. Please try again.";return this.emit("loginError",{message:n}),{success:!1,message:n}}async register(e){const s=await this.app.rest.POST("/api/register",e);if(s.success&&s.data.status){const{token:e,refreshToken:t,user:a}=s.data.data;this.tokenManager.setTokens(e,t,!0);const n=this.tokenManager.getUserInfo();return this.setAuthState({...a,...n}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("register",this.user),{success:!0,user:this.user}}const t=s.data?.error||s.message||"Registration failed.";return this.emit("registerError",{message:t}),{success:!1,message:t}}async logout(){try{this.tokenManager.getToken()&&this.app.rest.POST("/api/auth/logout").catch(e=>{console.warn("Server logout failed, proceeding with local logout.",e)})}finally{this.clearAuthState(),this.emit("logout")}}async refreshToken(){const e=this.tokenManager.getRefreshToken();if(!e)return this.clearAuthState(),this.emit("tokenExpired"),!1;const s=await this.app.rest.POST("/api/auth/token/refresh",{refreshToken:e});if(s.success&&s.data.status){const{token:e,refreshToken:t}=s.data.data,a=!!localStorage.getItem(this.tokenManager.tokenKey);this.tokenManager.setTokens(e,t,a);const n=this.tokenManager.getUserInfo();return n&&(this.user={...this.user,...n}),this.scheduleTokenRefresh(),this.emit("tokenRefreshed"),!0}return console.error("Token refresh failed:",s.data?.error||s.message),this.clearAuthState(),this.emit("tokenExpired"),!1}setAuthState(e){this.isAuthenticated=!0,this.user=e,this.app?.setState&&this.app.setState("auth",{isAuthenticated:!0,user:e})}clearAuthState(){this.isAuthenticated=!1,this.user=null,this.tokenManager.clearTokens(),this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),this.app?.setState&&this.app.setState("auth",{isAuthenticated:!1,user:null})}scheduleTokenRefresh(){if(this.refreshTimer&&clearTimeout(this.refreshTimer),!this.tokenManager.isValid())return;if(this.tokenManager.isExpiringSoon(this.config.refreshThreshold))return void this.refreshToken();const e=this.tokenManager.getToken(),s=this.tokenManager.decode(e);if(s?.exp){const e=Math.floor(Date.now()/1e3),t=1e3*(s.exp-e-60*this.config.refreshThreshold);t>0&&(this.refreshTimer=setTimeout(()=>{this.refreshToken()},t))}}registerPlugin(e,s){this.plugins.set(e,s),s.initialize(this,this.app)}getPlugin(e){return this.plugins.get(e)||null}async forgotPassword(e,s="code"){const t=await this.app.rest.POST("/api/auth/forgot",{email:e,method:s});if(t.success&&t.data.status)return this.emit("forgotPasswordSuccess",{email:e,method:s}),{success:!0,message:t.data.data?.message};const a=t.data?.error||t.message||"Failed to process request.";return this.emit("forgotPasswordError",{message:a}),{success:!1,message:a}}async resetPasswordWithToken(e,s){const t={token:e,new_password:s},a=await this.app.rest.POST("/api/auth/password/reset/token",t);if(a.success&&a.data.status){const{access_token:e,refresh_token:s,user:t}=a.data.data;this.tokenManager.setTokens(e,s,!0);const n=this.tokenManager.getUserInfo();return this.setAuthState({...t,...n}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("resetPasswordSuccess",this.user),{success:!0,user:this.user}}const n=a.data?.error||a.message||"Failed to reset password.";return this.emit("resetPasswordError",{message:n}),{success:!1,message:n}}async resetPasswordWithCode(e,s,t){const a={email:e,code:s,new_password:t},n=await this.app.rest.POST("/api/auth/password/reset/code",a);if(n.success&&n.data.status){const{access_token:e,refresh_token:s,user:t}=n.data.data;this.tokenManager.setTokens(e,s,!0);const a=this.tokenManager.getUserInfo();return this.setAuthState({...t,...a}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("resetPasswordSuccess",this.user),{success:!0,user:this.user}}const i=n.data?.error||n.message||"Failed to reset password.";return this.emit("resetPasswordError",{message:i}),{success:!1,message:i}}getAuthHeader(){return this.tokenManager.getAuthHeader()}emit(e,s){this.app?.events?.emit&&this.app.events.emit(`auth:${e}`,s)}destroy(){this.refreshTimer&&clearTimeout(this.refreshTimer),this.plugins.forEach(e=>{e.destroy&&e.destroy()}),this.plugins.clear()}}class LoginPage extends t.Page{static pageName="login";static title="Login";static icon="bi-box-arrow-in-right";static route="/login";constructor(e={}){super({...e,pageName:LoginPage.pageName,route:e.route||LoginPage.route,pageIcon:LoginPage.icon,template:e.template}),this.authConfig=e.authConfig||{ui:{title:"My App",logoUrl:"/assets/logo.png",messages:{loginTitle:"Welcome Back",loginSubtitle:"Sign in to your account"}},features:{rememberMe:!1,forgotPassword:!0,registration:!1}}}async onInit(){await super.onInit(),this.data={username:"",password:"",rememberMe:!0,loginIcon:this.options.pageIcon,shaodw:"shadow-lg",isLoading:!1,error:null,showPassword:!1,version:this.getApp().version,passkeySupported:this.getApp().auth?.isPasskeySupported?.()||!1,...this.authConfig.ui,...this.authConfig.features}}async onEnter(){await super.onEnter(),document.title=`${LoginPage.title} - ${this.authConfig.ui.title}`;const e=this.getApp().auth;e?.isAuthenticated?this.getApp().navigate("/"):this.updateData({username:"",password:"",error:null,isLoading:!1})}async onAfterRender(){await super.onAfterRender();const e=this.element.querySelector("#loginUsername");e&&e.focus()}async onActionUpdateField(e,s){const t=s.dataset.field,a="checkbox"===s.type?s.checked:s.value;this.updateData({[t]:a,error:null})}async onActionTogglePassword(e){e.preventDefault(),this.updateData({showPassword:!this.data.showPassword});const s=this.element.querySelector("#loginPassword");s&&(s.type=this.data.showPassword?"text":"password")}async onActionLogin(e){e.preventDefault(),this.data.username=this.element.querySelector("#loginUsername")?.value||"",this.data.password=this.element.querySelector("#loginPassword")?.value||"",await this.updateData({error:null,isLoading:!0},!0);const s=this.getApp().auth;if(!s)return void(await this.updateData({error:"Authentication system not available",isLoading:!1},!0));if(!this.data.username||!this.data.password)return void(await this.updateData({error:"Please enter both username and password",isLoading:!1},!0));const t=await s.login(this.data.username,this.data.password,this.data.rememberMe);if(!t.success){await this.updateData({error:t.message,isLoading:!1},!0);const e=this.element.querySelector("#loginPassword");e&&(e.focus(),e.select())}}async onActionLoginWithPasskey(e){e.preventDefault();const s=this.getApp().auth;if(s?.isPasskeySupported?.()){this.updateData({error:null,isLoading:!0});try{(await s.loginWithPasskey()).success}catch(t){console.error("Passkey login error:",t),this.updateData({error:"Passkey authentication failed. Please try another method.",isLoading:!1})}}else this.getApp().showError("Passkey authentication is not supported")}async onActionRegister(e){e.preventDefault(),this.getApp().navigate("/register")}async onActionForgotPassword(e){e.preventDefault(),this.getApp().navigate("/forgot-password")}async onActionHandleKeyPress(e,s){if("Enter"===e.key)if(e.preventDefault(),"loginUsername"===s.id){const e=this.element.querySelector("#loginPassword");e&&e.focus()}else"loginPassword"===s.id&&await this.onActionLogin(e)}async getViewData(){return{...this.data}}}class RegisterPage extends t.Page{static pageName="auth-register";static title="Register";static icon="bi-person-plus";static route="register";constructor(e={}){super({...e,pageName:RegisterPage.pageName,route:e.route||RegisterPage.route,pageIcon:RegisterPage.icon,template:e.template}),this.authConfig=e.authConfig||{ui:{title:"My App",logoUrl:"/assets/logo.png",messages:{registerTitle:"Create Account",registerSubtitle:"Join us today"}},features:{registration:!0}}}async onInit(){await super.onInit(),this.data={...this.authConfig.ui,...this.authConfig.features,name:"",email:"",password:"",confirmPassword:"",acceptTerms:!1,isLoading:!1,error:null,showPassword:!1,showConfirmPassword:!1,passwordStrength:null,passwordMatch:!0}}async onEnter(){await super.onEnter(),document.title=`${RegisterPage.title} - ${this.authConfig.ui.title}`;const e=this.getApp().auth;e?.isAuthenticated?this.getApp().navigate("/"):this.updateData({name:"",email:"",password:"",confirmPassword:"",acceptTerms:!1,error:null,isLoading:!1,passwordStrength:null,passwordMatch:!0})}async onAfterRender(){await super.onAfterRender();const e=this.element.querySelector("#registerName");e&&e.focus()}async onActionUpdateField(e,s){const t=s.dataset.field,a="checkbox"===s.type?s.checked:s.value;this.updateData({[t]:a}),"password"===t&&this.checkPasswordStrength(a),"password"!==t&&"confirmPassword"!==t||this.checkPasswordMatch(),this.data.error&&this.updateData({error:null})}checkPasswordStrength(e){let s=null;s=0===e.length?null:e.length<6?"weak":e.length<8?"fair":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(e)?"strong":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(e)?"good":"fair",this.updateData({passwordStrength:s},!0)}checkPasswordMatch(){const e=!this.data.confirmPassword||this.data.password===this.data.confirmPassword;this.updateData({passwordMatch:e},!0)}async onActionTogglePassword(e,s){e.preventDefault();const t=s.dataset.passwordField;if("password"===t){this.updateData({showPassword:!this.data.showPassword});const e=this.element.querySelector("#registerPassword");e&&(e.type=this.data.showPassword?"text":"password")}else if("confirmPassword"===t){this.updateData({showConfirmPassword:!this.data.showConfirmPassword});const e=this.element.querySelector("#registerConfirmPassword");e&&(e.type=this.data.showConfirmPassword?"text":"password")}}async onActionRegister(e){if(e.preventDefault(),await this.updateData({error:null,isLoading:!0},!0),!(this.data.name&&this.data.email&&this.data.password&&this.data.confirmPassword))return void(await this.updateData({error:"Please fill in all required fields",isLoading:!1},!0));if(this.data.name.trim().length<2)return void(await this.updateData({error:"Name must be at least 2 characters long",isLoading:!1},!0));if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.data.email))return void(await this.updateData({error:"Please enter a valid email address",isLoading:!1},!0));if(this.data.password.length<6)return void(await this.updateData({error:"Password must be at least 6 characters long",isLoading:!1},!0));if(this.data.password!==this.data.confirmPassword)return void(await this.updateData({error:"Passwords do not match",isLoading:!1},!0));const s=this.getApp().auth;if(!s)return void(await this.updateData({error:"Authentication system not available",isLoading:!1},!0));const t={name:this.data.name.trim(),email:this.data.email.toLowerCase().trim(),password:this.data.password,acceptedTerms:this.data.acceptTerms},a=await s.register(t);a.success||await this.updateData({error:a.message||"Registration failed. Please try again.",isLoading:!1},!0)}async onActionLogin(e){e.preventDefault(),this.getApp().navigate("/login")}async onActionHandleKeyPress(e,s){if("Enter"===e.key){e.preventDefault();const t=["registerName","registerEmail","registerPassword","registerConfirmPassword"],a=t.indexOf(s.id);if(a>=0&&a<t.length-1){const e=this.element.querySelector(`#${t[a+1]}`);e&&e.focus()}else a===t.length-1&&await this.onActionRegister(e)}}async getViewData(){return{...this.data}}}class ForgotPasswordPage extends t.Page{static pageName="auth-forgot-password";static title="Forgot Password";static icon="bi-key";static route="forgot-password";constructor(e={}){super({...e,template:e.template}),this.authConfig=e.authConfig||{passwordResetMethod:"code",ui:{title:"My App"},features:{}}}async onInit(){this.data={...this.authConfig.ui,...this.authConfig.features,passwordResetMethod:this.authConfig.passwordResetMethod,step:"email",isLoading:!1,error:null,email:""}}async onEnter(){document.title=`${ForgotPasswordPage.title} - ${this.authConfig.ui.title}`,this.updateData({step:"email",isLoading:!1,error:null,email:""})}getFormData(e){const s=this.element.querySelector(e);if(!s)return{};const t=new FormData(s);return Object.fromEntries(t.entries())}async onActionRequestReset(){const{email:e}=this.getFormData("#form-request-reset");if(await this.updateData({isLoading:!0,error:null,email:e},!0),!e)return this.updateData({error:"Please enter your email address",isLoading:!1},!0);const s=this.getApp().auth,t=this.authConfig.passwordResetMethod||"code",a=await s.forgotPassword(e,t);"link"===t?(await this.updateData({step:"link_sent",isLoading:!1},!0),a.success||console.error("Forgot password (link) error:",a.message)):a.success?await this.updateData({step:"code",isLoading:!1},!0):await this.updateData({error:a.message,isLoading:!1},!0)}async onActionResetWithCode(){const{code:e,new_password:s,confirm_password:t}=this.getFormData("#form-reset-with-code");if(await this.updateData({isLoading:!0,error:null},!0),!e||!s)return this.updateData({error:"Please enter the code and your new password",isLoading:!1},!0);if(s!==t)return this.updateData({error:"Passwords do not match",isLoading:!1},!0);const a=this.getApp().auth,n=await a.resetPasswordWithCode(this.data.email,e,s);n.success?(await this.updateData({step:"success",isLoading:!1},!0),setTimeout(()=>{this.getApp().showSuccess("Password reset complete. Welcome back!"),this.getApp().navigate("/")},2e3)):await this.updateData({error:n.message,isLoading:!1},!0)}async onActionBackToLogin(){this.getApp().navigate("/login")}get isStepEmail(){return"email"===this.data.step}get isStepCode(){return"code"===this.data.step}get isStepLinkSent(){return"link_sent"===this.data.step}get isStepSuccess(){return"success"===this.data.step}}class ResetPasswordPage extends t.Page{static pageName="auth-reset-password";static title="Reset Password";static icon="bi-key-fill";static route="reset-password";constructor(e={}){super({...e,pageName:ResetPasswordPage.pageName,route:e.route||ResetPasswordPage.route,pageIcon:ResetPasswordPage.icon,template:"auth/pages/ResetPasswordPage.mst"}),this.authConfig=e.authConfig||{ui:{title:"My App",logoUrl:"/assets/logo.png",messages:{resetTitle:"Set New Password",resetSubtitle:"Choose a strong password"}},features:{registration:!0}},this.resetToken=null}async onInit(){await super.onInit(),this.data={...this.authConfig.ui,...this.authConfig.features,password:"",confirmPassword:"",resetToken:"",isLoading:!1,error:null,success:!1,successMessage:null,showPassword:!1,showConfirmPassword:!1,passwordStrength:null,passwordMatch:!0,tokenValid:!1}}async onEnter(){await super.onEnter(),document.title=`${ResetPasswordPage.title} - ${this.authConfig.ui.title}`;const e=new URLSearchParams(window.location.search);this.resetToken=e.get("token")||e.get("login_token")||"",this.resetToken?this.updateData({resetToken:this.resetToken,tokenValid:!0,error:null,success:!1}):this.updateData({error:"Invalid or missing reset token. Please request a new password reset.",tokenValid:!1})}async onAfterRender(){if(await super.onAfterRender(),this.data.tokenValid){const e=this.element.querySelector("#resetPassword");e&&e.focus()}}async onActionUpdateField(e,s){const t=s.dataset.field,a=s.value;this.updateData({[t]:a,error:null}),"password"===t&&this.checkPasswordStrength(a),"password"!==t&&"confirmPassword"!==t||this.checkPasswordMatch()}checkPasswordStrength(e){let s=null;s=0===e.length?null:e.length<6?"weak":e.length<8?"fair":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(e)?"strong":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(e)?"good":"fair",this.updateData({passwordStrength:s},!0)}checkPasswordMatch(){const e=!this.data.confirmPassword||this.data.password===this.data.confirmPassword;this.updateData({passwordMatch:e},!0)}async onActionTogglePassword(e,s){e.preventDefault();const t=s.dataset.passwordField;if("password"===t){this.updateData({showPassword:!this.data.showPassword});const e=this.element.querySelector("#resetPassword");e&&(e.type=this.data.showPassword?"text":"password")}else if("confirmPassword"===t){this.updateData({showConfirmPassword:!this.data.showConfirmPassword});const e=this.element.querySelector("#resetConfirmPassword");e&&(e.type=this.data.showConfirmPassword?"text":"password")}}async onActionResetPassword(e){if(e.preventDefault(),await this.updateData({error:null,isLoading:!0},!0),!this.data.password||!this.data.confirmPassword)return void(await this.updateData({error:"Please enter and confirm your new password",isLoading:!1},!0));if(this.data.password.length<6)return void(await this.updateData({error:"Password must be at least 6 characters long",isLoading:!1},!0));if(this.data.password!==this.data.confirmPassword)return void(await this.updateData({error:"Passwords do not match",isLoading:!1},!0));const s=this.getApp().auth;if(!s)return void(await this.updateData({error:"Authentication system not available",isLoading:!1},!0));const t=await s.resetPasswordWithToken(this.resetToken,this.data.password);t.success?(await this.updateData({success:!0,successMessage:"Password reset successful! Logging you in...",isLoading:!1},!0),setTimeout(()=>{this.getApp().showSuccess("Password reset complete. Welcome back!"),this.getApp().navigate("/")},2e3)):await this.updateData({error:t.message||"Password reset failed. Please try again.",isLoading:!1},!0)}async onActionBackToLogin(e){e.preventDefault(),this.getApp().navigate("/login")}async onActionRegister(e){e.preventDefault(),this.getApp().navigate("/register")}async onActionRequestNew(e){e.preventDefault(),this.getApp().navigate("/forgot-password")}async onActionHandleKeyPress(e,s){if("Enter"===e.key){e.preventDefault();const t=["resetPassword","resetConfirmPassword"],a=t.indexOf(s.id);if(a>=0&&a<t.length-1){const e=this.element.querySelector(`#${t[a+1]}`);e&&e.focus()}else a===t.length-1&&await this.onActionResetPassword(e)}}async getViewData(){return{...this.data}}}const a={};function n(e){const s=e.replace(/^\//,"").replace(/^src\//,"").replace(/\\/g,"/");return a[s]||a[e]}a["extensions/auth/pages/ForgotPasswordPage.mst"]='<div class="auth-page forgot-password-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-8 col-md-8 col-lg-6 col-xl-5">\n <div class="card {{shadow}} border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Header --\x3e\n <div class="text-center mb-4">\n {{#logoUrl}}<img src="{{logoUrl}}" alt="{{title}}" class="mb-3" style="max-height: 60px;">{{/logoUrl}}\n <h2 class="h3 mb-2">{{forgotTitle}}</h2>\n <p class="text-muted">{{forgotSubtitle}}</p>\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/error}}\n\n \x3c!-- Step 1: Email Form --\x3e\n {{#isStepEmail}}\n <form id="form-request-reset" novalidate>\n <div class="mb-3">\n <label for="forgotEmail" class="form-label"><i class="bi bi-envelope me-1"></i>Email Address</label>\n <input type="email" class="form-control form-control-lg" id="forgotEmail" name="email" placeholder="Enter your registered email" required autofocus>\n <div class="form-text">We\'ll send you instructions to reset your password.</div>\n </div>\n <button type="button" class="btn btn-primary btn-lg w-100 mb-3" data-action="requestReset" {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}<span class="spinner-border spinner-border-sm me-2"></span>Sending...{{/isLoading}}\n {{^isLoading}}<i class="bi bi-send me-2"></i>Send Instructions{{/isLoading}}\n </button>\n <button type="button" class="btn btn-outline-secondary btn-lg w-100" data-action="backToLogin" {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-arrow-left me-2"></i>Back to Login\n </button>\n </form>\n <div class="text-center mt-4">\n <p class="text-muted">Remember your password? <a href="#" class="text-decoration-none fw-semibold" data-action="backToLogin">Sign in</a></p>\n </div>\n {{/isStepEmail}}\n\n \x3c!-- Step 2 (Link Method): Confirmation --\x3e\n {{#isStepLinkSent}}\n <div class="text-center">\n <div class="mb-4"><i class="bi bi-envelope-check text-success" style="font-size: 4rem;"></i></div>\n <h3 class="h4 mb-3">Check your email</h3>\n <p class="text-muted">If an account exists for <strong>{{data.email}}</strong>, we have sent instructions for resetting your password.</p>\n <div class="d-grid gap-2 mt-4">\n <button type="button" class="btn btn-primary btn-lg" data-action="backToLogin"><i class="bi bi-arrow-left me-2"></i>Back to Login</button>\n </div>\n </div>\n {{/isStepLinkSent}}\n\n \x3c!-- Step 2 (Code Method): Code Entry Form --\x3e\n {{#isStepCode}}\n <p class="text-muted text-center mb-3">A verification code has been sent to <strong>{{data.email}}</strong>. Please enter it below.</p>\n <form id="form-reset-with-code" novalidate>\n <div class="mb-3">\n <label for="resetCode" class="form-label"><i class="bi bi-shield-lock me-1"></i>Verification Code</label>\n <input type="text" class="form-control form-control-lg" id="resetCode" name="code" placeholder="Enter code" required>\n </div>\n <div class="mb-3">\n <label for="resetPassword" class="form-label"><i class="bi bi-lock me-1"></i>New Password</label>\n <input type="password" class="form-control form-control-lg" id="resetPassword" name="new_password" placeholder="Enter new password" required autocomplete="new-password">\n </div>\n <div class="mb-3">\n <label for="confirmPassword" class="form-label"><i class="bi bi-lock-fill me-1"></i>Confirm New Password</label>\n <input type="password" class="form-control form-control-lg" id="confirmPassword" name="confirm_password" placeholder="Confirm new password" required autocomplete="new-password">\n </div>\n <button type="button" class="btn btn-primary btn-lg w-100" data-action="resetWithCode" {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}<span class="spinner-border spinner-border-sm me-2"></span>Resetting...{{/isLoading}}\n {{^isLoading}}<i class="bi bi-key me-2"></i>Reset Password{{/isLoading}}\n </button>\n </form>\n {{/isStepCode}}\n\n \x3c!-- Step 3 (Code Method): Success --\x3e\n {{#isStepSuccess}}\n <div class="text-center">\n <div class="mb-4"><i class="bi bi-check-circle text-success" style="font-size: 4rem;"></i></div>\n <h3 class="h4 mb-3">Password Reset!</h3>\n <p class="text-muted">Your password has been changed successfully. You will be redirected to the login page shortly.</p>\n </div>\n {{/isStepSuccess}}\n\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n',a["extensions/auth/pages/LoginPage.mst"]='<div class="auth-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-10 col-md-8 col-lg-6 col-xl-5">\n <div class="card {{data.shadow}} border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Logo and Header --\x3e\n <div class="text-center mb-4">\n {{#data.logoUrl}}\n <img src="{{data.logoUrl}}" alt="{{data.title}}" class="mb-3" style="max-height: 60px;">\n {{/data.logoUrl}}\n {{#data.messages.loginTitle}}\n <h2 class="h3 mb-2">{{#data.loginIcon}}<i class="{{data.loginIcon}}"></i> {{/data.loginIcon}}{{data.messages.loginTitle}}</h2>\n {{/data.messages.loginTitle}}\n {{/data.messages.loginSubtitle}}\n <p class="text-muted">{{data.messages.loginSubtitle}}</p>\n {{/data.messages.loginSubtitle}}\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#data.error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{data.error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/data.error}}\n\n \x3c!-- Login Form --\x3e\n <form novalidate>\n \x3c!-- Username/Email Field --\x3e\n <div class="mb-3">\n <label for="loginUsername" class="form-label">\n <i class="bi bi-person me-1"></i>Username or Email\n </label>\n <input\n type="text"\n class="form-control form-control-lg"\n id="loginUsername"\n placeholder="Enter your username or email"\n value="{{username}}"\n data-field="username"\n data-action-keydown="handleKeyPress"\n autocomplete="username"\n required\n autofocus\n {{#isLoading}}disabled{{/isLoading}}>\n </div>\n\n \x3c!-- Password Field --\x3e\n <div class="mb-3">\n <label for="loginPassword" class="form-label">\n <i class="bi bi-lock me-1"></i>Password\n </label>\n <div class="input-group">\n <input\n type="{{#showPassword}}text{{/showPassword}}{{^showPassword}}password{{/showPassword}}"\n class="form-control form-control-lg"\n id="loginPassword"\n placeholder="Enter your password"\n value="{{password}}"\n data-field="password"\n data-action-keydown="handleKeyPress"\n autocomplete="current-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#data.showPassword}}-slash{{/data.showPassword}}"></i>\n </button>\n </div>\n </div>\n\n \x3c!-- Remember Me & Forgot Password --\x3e\n <div class="d-flex justify-content-between align-items-center mb-4">\n {{#data.rememberMe}}\n <div class="form-check">\n <input\n class="form-check-input"\n type="checkbox"\n id="rememberMe"\n data-field="rememberMe"\n data-change-action="updateField"\n autocomplete="off"\n {{#rememberMe}}checked{{/rememberMe}}\n {{#isLoading}}disabled{{/isLoading}}>\n <label class="form-check-label" for="rememberMe">\n Remember me\n </label>\n </div>\n {{/data.rememberMe}}\n {{^data.rememberMe}}<div></div>{{/data.rememberMe}}\n\n {{#data.forgotPassword}}\n <a href="?page=forgot-password" class="text-decoration-none" data-action="forgotPassword">\n Forgot password?\n </a>\n {{/data.forgotPassword}}\n </div>\n\n \x3c!-- Login Button --\x3e\n <button\n type="button"\n class="btn btn-primary btn-lg w-100 mb-3"\n data-action="login"\n {{#data.isLoading}}disabled{{/data.isLoading}}>\n {{#data.isLoading}}\n <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>\n Signing in...\n {{/data.isLoading}}\n {{^data.isLoading}}\n <i class="bi bi-box-arrow-in-right me-2"></i>Sign In\n {{/data.isLoading}}\n </button>\n\n \x3c!-- Alternative Login Methods --\x3e\n {{#data.passkeySupported}}\n <div class="position-relative my-3">\n <hr class="text-muted">\n <span class="position-absolute top-50 start-50 translate-middle bg-white px-3 text-muted small">\n OR\n </span>\n </div>\n\n <button\n type="button"\n class="btn btn-outline-primary btn-lg w-100 mb-2"\n data-action="loginWithPasskey"\n {{#data.isLoading}}disabled{{/data.isLoading}}>\n <i class="bi bi-fingerprint me-2"></i>Sign in with Passkey\n </button>\n {{/data.passkeySupported}}\n </form>\n\n \x3c!-- Register Link --\x3e\n {{#data.registration}}\n <div class="text-center mt-4">\n <p class="mb-0">\n Don\'t have an account?\n <a href="#" class="text-decoration-none fw-semibold" data-action="register">\n Sign up\n </a>\n </p>\n </div>\n {{/data.registration}}\n </div>\n </div>\n\n \x3c!-- Security Notice --\x3e\n \x3c!-- TOS and Privacy Links --\x3e\n <div class="text-center mt-3 auth-footer-links">\n {{#data.termsUrl}}\n <small><a href="{{data.termsUrl}}" target="_blank" rel="noopener noreferrer">Terms of Service</a></small>\n {{/data.termsUrl}}\n {{#data.termsUrl}}{{#data.privacyUrl}}\n <small class="mx-1 text-muted">&middot;</small>\n {{/data.privacyUrl}}{{/data.termsUrl}}\n {{#data.privacyUrl}}\n <small><a href="{{data.privacyUrl}}" target="_blank" rel="noopener noreferrer">Privacy Policy</a></small>\n {{/data.privacyUrl}}\n {{#data.showVersion}}\n <div class="text-muted text-center mt-3">\n <small>version {{data.version}}</small>\n </div>\n {{/data.showVersion}}\n </div>\n </div>\n </div>\n </div>\n</div>\n',a["extensions/auth/pages/RegisterPage.mst"]='<div class="auth-page register-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-10 col-md-8 col-lg-6 col-xl-5">\n <div class="card {{shadow}} border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Logo and Header --\x3e\n <div class="text-center mb-4">\n {{#logoUrl}}\n <img src="{{logoUrl}}" alt="{{title}}" class="mb-3" style="max-height: 60px;">\n {{/logoUrl}}\n <h2 class="h3 mb-2">{{messages.registerTitle}}</h2>\n <p class="text-muted">{{messages.registerSubtitle}}</p>\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/error}}\n\n \x3c!-- Registration Form --\x3e\n <form data-action="register" novalidate>\n \x3c!-- Name Field --\x3e\n <div class="mb-3">\n <label for="registerName" class="form-label">\n <i class="bi bi-person me-1"></i>Full Name\n </label>\n <input\n type="text"\n class="form-control form-control-lg"\n id="registerName"\n placeholder="Enter your full name"\n value="{{name}}"\n data-field="name"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="name"\n required\n autofocus\n {{#isLoading}}disabled{{/isLoading}}>\n </div>\n\n \x3c!-- Email Field --\x3e\n <div class="mb-3">\n <label for="registerEmail" class="form-label">\n <i class="bi bi-envelope me-1"></i>Email Address\n </label>\n <input\n type="email"\n class="form-control form-control-lg"\n id="registerEmail"\n placeholder="name@example.com"\n value="{{email}}"\n data-field="email"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="email"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n </div>\n\n \x3c!-- Password Field --\x3e\n <div class="mb-3">\n <label for="registerPassword" class="form-label">\n <i class="bi bi-lock me-1"></i>Password\n </label>\n <div class="input-group">\n <input\n type="{{#showPassword}}text{{/showPassword}}{{^showPassword}}password{{/showPassword}}"\n class="form-control form-control-lg"\n id="registerPassword"\n placeholder="Create a strong password"\n value="{{password}}"\n data-field="password"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="password"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showPassword}}-slash{{/showPassword}}"></i>\n </button>\n </div>\n\n \x3c!-- Password Strength Indicator --\x3e\n {{#passwordStrength}}\n <div class="mt-2">\n <div class="progress" style="height: 4px;">\n <div class="progress-bar\n {{#passwordStrength.weak}}bg-danger{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}bg-warning{{/passwordStrength.fair}}\n {{#passwordStrength.good}}bg-info{{/passwordStrength.good}}\n {{#passwordStrength.strong}}bg-success{{/passwordStrength.strong}}"\n role="progressbar"\n style="width:\n {{#passwordStrength.weak}}25%{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}50%{{/passwordStrength.fair}}\n {{#passwordStrength.good}}75%{{/passwordStrength.good}}\n {{#passwordStrength.strong}}100%{{/passwordStrength.strong}}">\n </div>\n </div>\n <small class="text-muted mt-1">\n Password strength: {{passwordStrength}}\n </small>\n </div>\n {{/passwordStrength}}\n </div>\n\n \x3c!-- Confirm Password Field --\x3e\n <div class="mb-3">\n <label for="registerConfirmPassword" class="form-label">\n <i class="bi bi-lock-fill me-1"></i>Confirm Password\n </label>\n <div class="input-group">\n <input\n type="{{#showConfirmPassword}}text{{/showConfirmPassword}}{{^showConfirmPassword}}password{{/showConfirmPassword}}"\n class="form-control form-control-lg {{^passwordMatch}}is-invalid{{/passwordMatch}}"\n id="registerConfirmPassword"\n placeholder="Re-enter your password"\n value="{{confirmPassword}}"\n data-field="confirmPassword"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="confirmPassword"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showConfirmPassword}}-slash{{/showConfirmPassword}}"></i>\n </button>\n </div>\n {{^passwordMatch}}\n <div class="invalid-feedback">\n Passwords do not match\n </div>\n {{/passwordMatch}}\n </div>\n\n \x3c!-- Register Button --\x3e\n <button\n type="submit"\n class="btn btn-primary btn-lg w-100 mb-3"\n {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}\n <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>\n Creating account...\n {{/isLoading}}\n {{^isLoading}}\n <i class="bi bi-person-plus me-2"></i>Create Account\n {{/isLoading}}\n </button>\n </form>\n\n \x3c!-- Login Link --\x3e\n <div class="text-center mt-4">\n <p class="mb-0">\n Already have an account?\n <a href="#" class="text-decoration-none fw-semibold" data-action="login">\n Sign in\n </a>\n </p>\n </div>\n </div>\n </div>\n\n \x3c!-- Security Notice --\x3e\n <div class="text-center mt-3">\n <small class="text-muted">\n <i class="bi bi-shield-check me-1"></i>Your information is secure and encrypted\n </small>\n </div>\n </div>\n </div>\n </div>\n</div>\n',a["extensions/auth/pages/ResetPasswordPage.mst"]='<div class="auth-page reset-password-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-10 col-md-8 col-lg-6 col-xl-5">\n <div class="card shadow-lg border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Logo and Header --\x3e\n <div class="text-center mb-4">\n {{#logoUrl}}\n <img src="{{logoUrl}}" alt="{{title}}" class="mb-3" style="max-height: 60px;">\n {{/logoUrl}}\n <h2 class="h3 mb-2">{{messages.resetTitle}}</h2>\n <p class="text-muted">{{messages.resetSubtitle}}</p>\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/error}}\n\n \x3c!-- Success State --\x3e\n {{#success}}\n <div class="alert alert-success d-flex align-items-center" role="alert">\n <i class="bi bi-check-circle-fill me-2"></i>\n <div>\n <strong>Password Reset Complete!</strong><br>\n {{#successMessage}}{{successMessage}}{{/successMessage}}\n {{^successMessage}}Your password has been reset successfully. You can now log in with your new password.{{/successMessage}}\n </div>\n </div>\n\n <div class="text-center">\n <p class="mb-3">\n <i class="bi bi-shield-check text-success" style="font-size: 3rem;"></i>\n </p>\n <p class="text-muted">\n Redirecting you to the login page...\n </p>\n <div class="d-grid gap-2 mt-4">\n <button\n class="btn btn-primary btn-lg"\n data-action="backToLogin">\n <i class="bi bi-box-arrow-in-right me-2"></i>Continue to Login\n </button>\n </div>\n </div>\n {{/success}}\n\n \x3c!-- Reset Form --\x3e\n {{^success}}\n {{#tokenValid}}\n <form data-action="resetPassword" novalidate>\n \x3c!-- New Password Field --\x3e\n <div class="mb-3">\n <label for="resetPassword" class="form-label">\n <i class="bi bi-lock me-1"></i>New Password\n </label>\n <div class="input-group">\n <input\n type="{{#showPassword}}text{{/showPassword}}{{^showPassword}}password{{/showPassword}}"\n class="form-control form-control-lg"\n id="resetPassword"\n placeholder="Enter your new password"\n value="{{password}}"\n data-field="password"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n autofocus\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="password"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showPassword}}-slash{{/showPassword}}"></i>\n </button>\n </div>\n\n \x3c!-- Password Strength Indicator --\x3e\n {{#passwordStrength}}\n <div class="mt-2">\n <div class="progress" style="height: 4px;">\n <div class="progress-bar\n {{#passwordStrength.weak}}bg-danger{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}bg-warning{{/passwordStrength.fair}}\n {{#passwordStrength.good}}bg-info{{/passwordStrength.good}}\n {{#passwordStrength.strong}}bg-success{{/passwordStrength.strong}}"\n role="progressbar"\n style="width:\n {{#passwordStrength.weak}}25%{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}50%{{/passwordStrength.fair}}\n {{#passwordStrength.good}}75%{{/passwordStrength.good}}\n {{#passwordStrength.strong}}100%{{/passwordStrength.strong}}">\n </div>\n </div>\n <small class="text-muted mt-1">\n Password strength: {{passwordStrength}}\n </small>\n </div>\n {{/passwordStrength}}\n </div>\n\n \x3c!-- Confirm Password Field --\x3e\n <div class="mb-4">\n <label for="resetConfirmPassword" class="form-label">\n <i class="bi bi-lock-fill me-1"></i>Confirm New Password\n </label>\n <div class="input-group">\n <input\n type="{{#showConfirmPassword}}text{{/showConfirmPassword}}{{^showConfirmPassword}}password{{/showConfirmPassword}}"\n class="form-control form-control-lg {{^passwordMatch}}is-invalid{{/passwordMatch}}"\n id="resetConfirmPassword"\n placeholder="Re-enter your new password"\n value="{{confirmPassword}}"\n data-field="confirmPassword"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="confirmPassword"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showConfirmPassword}}-slash{{/showConfirmPassword}}"></i>\n </button>\n </div>\n {{^passwordMatch}}\n <div class="invalid-feedback">\n Passwords do not match\n </div>\n {{/passwordMatch}}\n </div>\n\n \x3c!-- Reset Button --\x3e\n <button\n type="submit"\n class="btn btn-primary btn-lg w-100 mb-3"\n {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}\n <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>\n Resetting password...\n {{/isLoading}}\n {{^isLoading}}\n <i class="bi bi-key me-2"></i>Reset Password\n {{/isLoading}}\n </button>\n\n \x3c!-- Back to Login --\x3e\n <button\n type="button"\n class="btn btn-outline-secondary btn-lg w-100"\n data-action="backToLogin"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-arrow-left me-2"></i>Back to Login\n </button>\n </form>\n {{/tokenValid}}\n\n \x3c!-- Invalid Token State --\x3e\n {{^tokenValid}}\n <div class="text-center">\n <div class="mb-4">\n <i class="bi bi-exclamation-triangle text-warning" style="font-size: 4rem;"></i>\n </div>\n <h4 class="text-warning mb-3">Invalid Reset Link</h4>\n <p class="text-muted mb-4">\n This password reset link is invalid or has expired.\n Please request a new password reset.\n </p>\n <div class="d-grid gap-2">\n <button\n class="btn btn-primary btn-lg"\n data-action="requestNew">\n <i class="bi bi-envelope me-2"></i>Request New Reset\n </button>\n <button\n class="btn btn-outline-secondary btn-lg"\n data-action="backToLogin">\n <i class="bi bi-arrow-left me-2"></i>Back to Login\n </button>\n </div>\n </div>\n {{/tokenValid}}\n\n \x3c!-- Additional Links --\x3e\n {{#tokenValid}}{{^success}}\n <div class="text-center mt-4">\n <p class="text-muted mb-2">\n Remember your password?\n <a href="#" class="text-decoration-none fw-semibold" data-action="backToLogin">\n Sign in\n </a>\n </p>\n {{#registration}}\n <p class="text-muted mb-0">\n Don\'t have an account?\n <a href="#" class="text-decoration-none fw-semibold" data-action="register">\n Sign up\n </a>\n </p>\n {{/registration}}\n </div>\n {{/success}}{{/tokenValid}}\n {{/success}}\n </div>\n </div>\n\n \x3c!-- Security Notice --\x3e\n <div class="text-center mt-3">\n <small class="text-muted">\n <i class="bi bi-shield-lock me-1"></i>\n Secure password reset with email verification\n </small>\n </div>\n </div>\n </div>\n </div>\n</div>\n';class AuthApp extends e.WebApp{constructor(e={}){const s={title:e.name||"My App",logoUrl:null,termsUrl:null,privacyUrl:null,theme:{background:"auth-bg-light",panel:"auth-panel-light",...e.ui?.theme||{}},messages:{loginTitle:"Welcome Back",loginSubtitle:"Sign in to your account",registerTitle:"Create Account",registerSubtitle:"Join us today",forgotTitle:"Reset Password",forgotSubtitle:"We'll send you reset instructions",...e.ui?.messages||{}},...e.ui||{}},t={...e,routes:{login:"/login",register:"/register",forgot:"/forgot-password",reset:"/reset-password",...e.routes||{}},loginRedirect:e.loginRedirect||"/",logoutRedirect:e.logoutRedirect||"/login",features:{forgotPassword:!0,registration:!0,rememberMe:!0,...e.features||{}},passwordResetMethod:e.passwordResetMethod||"code",ui:s};super(t),this.auth=new AuthManager(this,t),this.authConfig=t,this.applyAuthTheme(),this.registerAuthPages(),this.setupAuthIntegration(),this.setupAuthGuards()}applyAuthTheme(){const e=this.authConfig.ui.theme;if(!e)return;const s=Array.from(document.body.classList).filter(e=>e.startsWith("auth-bg-")||e.startsWith("auth-panel-"));s.length&&document.body.classList.remove(...s),e.background&&document.body.classList.add(e.background),e.panel&&document.body.classList.add(e.panel)}registerAuthPages(){const e=this.authConfig;this.registerPage("login",LoginPage,{route:e.routes.login,title:"Login",authConfig:e,template:n("extensions/auth/pages/LoginPage.mst")}),e.features.registration&&this.registerPage("register",RegisterPage,{route:e.routes.register,title:"Register",authConfig:e,template:n("extensions/auth/pages/RegisterPage.mst")}),e.features.forgotPassword&&(this.registerPage("forgot-password",ForgotPasswordPage,{route:e.routes.forgot,title:"Reset Password",authConfig:e,template:n("extensions/auth/pages/ForgotPasswordPage.mst")}),this.registerPage("reset-password",ResetPasswordPage,{route:e.routes.reset,title:"Set New Password",authConfig:e,template:n("extensions/auth/pages/ResetPasswordPage.mst")}))}setupAuthIntegration(){this.events.on("auth:login",e=>{this.showSuccess(`Welcome back, ${e.name||e.email}!`),this.navigateAfterLogin()}),this.events.on("auth:logout",()=>{this.navigate(this.authConfig.logoutRedirect)}),this.events.on("auth:register",e=>{this.showSuccess(`Welcome, ${e.name||e.email}! Your account is ready.`),this.navigate(this.authConfig.loginRedirect)}),this.events.on("auth:tokenExpired",()=>{this.showWarning("Your session has expired. Please login again."),this.navigate(this.authConfig.logoutRedirect)})}setupAuthGuards(){this.events.on("route:changed",({pageName:e,path:s})=>{const t=this.getOrCreatePage(e);if(!t)return;const a=t.constructor,n=this.auth.isAuthenticated,i=["login","register","forgot-password","reset-password"].includes(e);if(a.requiresAuth&&!n)return sessionStorage.setItem("auth_redirect",s),this.navigate(this.authConfig.routes.login),void this.showWarning("Please login to access this page.");n&&i&&this.navigate(this.authConfig.loginRedirect)})}navigateAfterLogin(){const e=sessionStorage.getItem("auth_redirect");e?(sessionStorage.removeItem("auth_redirect"),this.navigate(e)):this.navigate(this.authConfig.loginRedirect)}static requireAuth(e){return e.requiresAuth=!0,e}}exports.BUILD_TIME=e.BUILD_TIME,exports.VERSION=e.VERSION,exports.VERSION_INFO=e.VERSION_INFO,exports.VERSION_MAJOR=e.VERSION_MAJOR,exports.VERSION_MINOR=e.VERSION_MINOR,exports.VERSION_REVISION=e.VERSION_REVISION,exports.WebApp=e.WebApp,exports.AuthApp=AuthApp,exports.AuthManager=AuthManager,exports.ForgotPasswordPage=ForgotPasswordPage,exports.LoginPage=LoginPage,exports.PasskeyPlugin=class{constructor(e={}){this.name="passkey",this.config={rpName:"MOJO App",rpId:window?.location?.hostname||"localhost",timeout:6e4,userVerification:"preferred",authenticatorAttachment:"platform",...e},this.authManager=null,this.app=null,this.authService=null}async initialize(e,s){this.authManager=e,this.app=s,this.isSupported()?(this.authManager.loginWithPasskey=this.loginWithPasskey.bind(this),this.authManager.setupPasskey=this.setupPasskey.bind(this),this.authManager.isPasskeySupported=this.isSupported.bind(this)):console.warn("Passkey authentication is not supported in this browser")}isSupported(){return void 0!==window.PublicKeyCredential&&void 0!==navigator.credentials&&"function"==typeof navigator.credentials.create&&"function"==typeof navigator.credentials.get}async loginWithPasskey(){if(!this.isSupported())throw new Error("Passkey authentication is not supported in this browser");try{const e=await this.app.rest.POST("/api/auth/passkey/challenge");if(!e.success||!e.data.data.challenge)throw new Error("No authentication challenge received from server");const s=e.data.data,t={publicKey:{challenge:this.base64ToArrayBuffer(s.challenge),timeout:this.config.timeout,userVerification:this.config.userVerification,rpId:this.config.rpId}},a=await navigator.credentials.get(t);if(!a)throw new Error("No credential received from authenticator");const n={id:a.id,rawId:this.arrayBufferToBase64(a.rawId),type:a.type,response:{authenticatorData:this.arrayBufferToBase64(a.response.authenticatorData),clientDataJSON:this.arrayBufferToBase64(a.response.clientDataJSON),signature:this.arrayBufferToBase64(a.response.signature),userHandle:a.response.userHandle?this.arrayBufferToBase64(a.response.userHandle):null}},i=await this.app.rest.POST("/api/auth/passkey/verify",{credential:n,challengeId:s.challengeId});if(!i.success||!i.data.status)throw new Error(i.data.error||"Passkey verification failed");const{token:r,refreshToken:o,user:d}=i.data.data;this.authManager.tokenManager.setTokens(r,o,!0);const l=this.authManager.tokenManager.getUserInfo();return this.authManager.setAuthState({...d,...l}),this.authManager.config.autoRefresh&&this.authManager.scheduleTokenRefresh(),this.authManager.emit("login",this.authManager.user),{success:!0,user:this.authManager.user}}catch(e){throw console.error("Passkey login error:",e),this.authManager.emit("loginError",e),new Error(e.message||"Passkey authentication failed")}}async setupPasskey(){if(!this.isSupported())throw new Error("Passkey authentication is not supported in this browser");if(!this.authManager.isAuthenticated)throw new Error("User must be authenticated to setup passkey");try{const e=await this.app.rest.POST("/api/auth/passkey/register-options");if(!e.success||!e.data.data.options)throw new Error("No registration options received from server");const s=e.data.data,t=s.options,a={publicKey:{challenge:this.base64ToArrayBuffer(t.challenge),rp:{name:this.config.rpName,id:this.config.rpId},user:{id:this.base64ToArrayBuffer(t.userId),name:t.userName,displayName:t.userDisplayName},pubKeyCredParams:[{alg:-7,type:"public-key"},{alg:-257,type:"public-key"}],authenticatorSelection:{userVerification:this.config.userVerification},timeout:this.config.timeout,attestation:"none"}};this.config.authenticatorAttachment&&(a.publicKey.authenticatorSelection.authenticatorAttachment=this.config.authenticatorAttachment);const n=await navigator.credentials.create(a);if(!n)throw new Error("Failed to create credential");const i={id:n.id,rawId:this.arrayBufferToBase64(n.rawId),type:n.type,response:{attestationObject:this.arrayBufferToBase64(n.response.attestationObject),clientDataJSON:this.arrayBufferToBase64(n.response.clientDataJSON)}},r=await this.app.rest.POST("/api/auth/passkey/register",{credential:i,optionsId:s.optionsId});if(!r.success||!r.data.status)throw new Error(r.data.error||"Failed to register passkey");return this.authManager.emit("passkeySetupSuccess",r.data.data),{success:!0,data:r.data.data}}catch(e){throw console.error("Passkey setup error:",e),this.authManager.emit("passkeySetupError",e),new Error(e.message||"Failed to setup passkey")}}async hasPasskeys(){if(!this.authManager.isAuthenticated)return{success:!1,hasPasskeys:!1};try{const e=await this.app.rest.GET("/api/auth/passkey/list");return{success:e.success,hasPasskeys:e.data.data?.passkeys&&e.data.data.passkeys.length>0,count:e.data.data?.passkeys?e.data.data.passkeys.length:0}}catch(e){return console.error("Error checking passkeys:",e),{success:!1,hasPasskeys:!1}}}async removePasskey(e){if(!this.authManager.isAuthenticated)throw new Error("User must be authenticated to remove passkey");try{const s=await this.app.rest.DELETE("/api/auth/passkey/remove",{credentialId:e});if(!s.success||!s.data.status)throw new Error(s.data.error||"Failed to remove passkey");return this.authManager.emit("passkeyRemoved",{credentialId:e}),{success:!0,data:s.data.data}}catch(s){throw console.error("Error removing passkey:",s),new Error(s.message||"Failed to remove passkey")}}base64ToArrayBuffer(e){const s=atob(e),t=new Uint8Array(s.length);for(let a=0;a<s.length;a++)t[a]=s.charCodeAt(a);return t.buffer}arrayBufferToBase64(e){const s=new Uint8Array(e);let t="";for(let a=0;a<s.byteLength;a++)t+=String.fromCharCode(s[a]);return btoa(t)}getBrowserCompatibility(){return{webAuthnSupported:!!window.PublicKeyCredential,credentialsSupported:!!navigator.credentials,platformSupported:"platform"!==this.config.authenticatorAttachment||window.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable?.(),conditionalMediationSupported:window.PublicKeyCredential?.isConditionalMediationAvailable?.()}}destroy(){this.authManager&&(delete this.authManager.loginWithPasskey,delete this.authManager.setupPasskey,delete this.authManager.isPasskeySupported),this.authManager=null,this.app=null,this.authService=null}},exports.RegisterPage=RegisterPage,exports.ResetPasswordPage=ResetPasswordPage;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/WebApp-9HUBOegS.js"),s=require("./chunks/TokenManager-CJBYcVqs.js"),t=require("./chunks/Page-CvbwEoLv.js");class AuthManager{constructor(e,t={}){this.app=e,this.config={autoRefresh:!0,refreshThreshold:5,plugins:{},...t},this.tokenManager=new s.TokenManager,this.isAuthenticated=!1,this.user=null,this.refreshTimer=null,this.plugins=/* @__PURE__ */new Map,this.initialize()}initialize(){this.checkAuthState(),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.app&&(this.app.auth=this)}checkAuthState(){if(this.tokenManager.isValid()){const e=this.tokenManager.getUserInfo();if(e)return this.setAuthState(e),!0}return this.clearAuthState(),!1}async login(e,s,t=!0){const a=await this.app.rest.POST("/api/login",{username:e,password:s});if(a.success&&a.data.status){const{access_token:e,refresh_token:s,user:n}=a.data.data;this.tokenManager.setTokens(e,s,t);const i=this.tokenManager.getUserInfo();return this.setAuthState({...n,...i}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("login",this.user),{success:!0,user:this.user}}const n=a.data?.error||a.message||"Login failed. Please try again.";return this.emit("loginError",{message:n}),{success:!1,message:n}}async register(e){const s=await this.app.rest.POST("/api/register",e);if(s.success&&s.data.status){const{token:e,refreshToken:t,user:a}=s.data.data;this.tokenManager.setTokens(e,t,!0);const n=this.tokenManager.getUserInfo();return this.setAuthState({...a,...n}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("register",this.user),{success:!0,user:this.user}}const t=s.data?.error||s.message||"Registration failed.";return this.emit("registerError",{message:t}),{success:!1,message:t}}async logout(){try{this.tokenManager.getToken()&&this.app.rest.POST("/api/auth/logout").catch(e=>{console.warn("Server logout failed, proceeding with local logout.",e)})}finally{this.clearAuthState(),this.emit("logout")}}async refreshToken(){const e=this.tokenManager.getRefreshToken();if(!e)return this.clearAuthState(),this.emit("tokenExpired"),!1;const s=await this.app.rest.POST("/api/auth/token/refresh",{refreshToken:e});if(s.success&&s.data.status){const{token:e,refreshToken:t}=s.data.data,a=!!localStorage.getItem(this.tokenManager.tokenKey);this.tokenManager.setTokens(e,t,a);const n=this.tokenManager.getUserInfo();return n&&(this.user={...this.user,...n}),this.scheduleTokenRefresh(),this.emit("tokenRefreshed"),!0}return console.error("Token refresh failed:",s.data?.error||s.message),this.clearAuthState(),this.emit("tokenExpired"),!1}setAuthState(e){this.isAuthenticated=!0,this.user=e,this.app?.setState&&this.app.setState("auth",{isAuthenticated:!0,user:e})}clearAuthState(){this.isAuthenticated=!1,this.user=null,this.tokenManager.clearTokens(),this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),this.app?.setState&&this.app.setState("auth",{isAuthenticated:!1,user:null})}scheduleTokenRefresh(){if(this.refreshTimer&&clearTimeout(this.refreshTimer),!this.tokenManager.isValid())return;if(this.tokenManager.isExpiringSoon(this.config.refreshThreshold))return void this.refreshToken();const e=this.tokenManager.getToken(),s=this.tokenManager.decode(e);if(s?.exp){const e=Math.floor(Date.now()/1e3),t=1e3*(s.exp-e-60*this.config.refreshThreshold);t>0&&(this.refreshTimer=setTimeout(()=>{this.refreshToken()},t))}}registerPlugin(e,s){this.plugins.set(e,s),s.initialize(this,this.app)}getPlugin(e){return this.plugins.get(e)||null}async forgotPassword(e,s="code"){const t=await this.app.rest.POST("/api/auth/forgot",{email:e,method:s});if(t.success&&t.data.status)return this.emit("forgotPasswordSuccess",{email:e,method:s}),{success:!0,message:t.data.data?.message};const a=t.data?.error||t.message||"Failed to process request.";return this.emit("forgotPasswordError",{message:a}),{success:!1,message:a}}async resetPasswordWithToken(e,s){const t={token:e,new_password:s},a=await this.app.rest.POST("/api/auth/password/reset/token",t);if(a.success&&a.data.status){const{access_token:e,refresh_token:s,user:t}=a.data.data;this.tokenManager.setTokens(e,s,!0);const n=this.tokenManager.getUserInfo();return this.setAuthState({...t,...n}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("resetPasswordSuccess",this.user),{success:!0,user:this.user}}const n=a.data?.error||a.message||"Failed to reset password.";return this.emit("resetPasswordError",{message:n}),{success:!1,message:n}}async resetPasswordWithCode(e,s,t){const a={email:e,code:s,new_password:t},n=await this.app.rest.POST("/api/auth/password/reset/code",a);if(n.success&&n.data.status){const{access_token:e,refresh_token:s,user:t}=n.data.data;this.tokenManager.setTokens(e,s,!0);const a=this.tokenManager.getUserInfo();return this.setAuthState({...t,...a}),this.config.autoRefresh&&this.scheduleTokenRefresh(),this.emit("resetPasswordSuccess",this.user),{success:!0,user:this.user}}const i=n.data?.error||n.message||"Failed to reset password.";return this.emit("resetPasswordError",{message:i}),{success:!1,message:i}}getAuthHeader(){return this.tokenManager.getAuthHeader()}emit(e,s){this.app?.events?.emit&&this.app.events.emit(`auth:${e}`,s)}destroy(){this.refreshTimer&&clearTimeout(this.refreshTimer),this.plugins.forEach(e=>{e.destroy&&e.destroy()}),this.plugins.clear()}}class LoginPage extends t.Page{static pageName="login";static title="Login";static icon="bi-box-arrow-in-right";static route="/login";constructor(e={}){super({...e,pageName:LoginPage.pageName,route:e.route||LoginPage.route,pageIcon:LoginPage.icon,template:e.template}),this.authConfig=e.authConfig||{ui:{title:"My App",logoUrl:"/assets/logo.png",messages:{loginTitle:"Welcome Back",loginSubtitle:"Sign in to your account"}},features:{rememberMe:!1,forgotPassword:!0,registration:!1}}}async onInit(){await super.onInit(),this.data={username:"",password:"",rememberMe:!0,loginIcon:this.options.pageIcon,shaodw:"shadow-lg",isLoading:!1,error:null,showPassword:!1,version:this.getApp().version,passkeySupported:this.getApp().auth?.isPasskeySupported?.()||!1,...this.authConfig.ui,...this.authConfig.features}}async onEnter(){await super.onEnter(),document.title=`${LoginPage.title} - ${this.authConfig.ui.title}`;const e=this.getApp().auth;e?.isAuthenticated?this.getApp().navigate("/"):this.updateData({username:"",password:"",error:null,isLoading:!1})}async onAfterRender(){await super.onAfterRender();const e=this.element.querySelector("#loginUsername");e&&e.focus()}async onActionUpdateField(e,s){const t=s.dataset.field,a="checkbox"===s.type?s.checked:s.value;this.updateData({[t]:a,error:null})}async onActionTogglePassword(e){e.preventDefault(),this.updateData({showPassword:!this.data.showPassword});const s=this.element.querySelector("#loginPassword");s&&(s.type=this.data.showPassword?"text":"password")}async onActionLogin(e){e.preventDefault(),this.data.username=this.element.querySelector("#loginUsername")?.value||"",this.data.password=this.element.querySelector("#loginPassword")?.value||"",await this.updateData({error:null,isLoading:!0},!0);const s=this.getApp().auth;if(!s)return void(await this.updateData({error:"Authentication system not available",isLoading:!1},!0));if(!this.data.username||!this.data.password)return void(await this.updateData({error:"Please enter both username and password",isLoading:!1},!0));const t=await s.login(this.data.username,this.data.password,this.data.rememberMe);if(!t.success){await this.updateData({error:t.message,isLoading:!1},!0);const e=this.element.querySelector("#loginPassword");e&&(e.focus(),e.select())}}async onActionLoginWithPasskey(e){e.preventDefault();const s=this.getApp().auth;if(s?.isPasskeySupported?.()){this.updateData({error:null,isLoading:!0});try{(await s.loginWithPasskey()).success}catch(t){console.error("Passkey login error:",t),this.updateData({error:"Passkey authentication failed. Please try another method.",isLoading:!1})}}else this.getApp().showError("Passkey authentication is not supported")}async onActionRegister(e){e.preventDefault(),this.getApp().navigate("/register")}async onActionForgotPassword(e){e.preventDefault(),this.getApp().navigate("/forgot-password")}async onActionHandleKeyPress(e,s){if("Enter"===e.key)if(e.preventDefault(),"loginUsername"===s.id){const e=this.element.querySelector("#loginPassword");e&&e.focus()}else"loginPassword"===s.id&&await this.onActionLogin(e)}async getViewData(){return{...this.data}}}class RegisterPage extends t.Page{static pageName="auth-register";static title="Register";static icon="bi-person-plus";static route="register";constructor(e={}){super({...e,pageName:RegisterPage.pageName,route:e.route||RegisterPage.route,pageIcon:RegisterPage.icon,template:e.template}),this.authConfig=e.authConfig||{ui:{title:"My App",logoUrl:"/assets/logo.png",messages:{registerTitle:"Create Account",registerSubtitle:"Join us today"}},features:{registration:!0}}}async onInit(){await super.onInit(),this.data={...this.authConfig.ui,...this.authConfig.features,name:"",email:"",password:"",confirmPassword:"",acceptTerms:!1,isLoading:!1,error:null,showPassword:!1,showConfirmPassword:!1,passwordStrength:null,passwordMatch:!0}}async onEnter(){await super.onEnter(),document.title=`${RegisterPage.title} - ${this.authConfig.ui.title}`;const e=this.getApp().auth;e?.isAuthenticated?this.getApp().navigate("/"):this.updateData({name:"",email:"",password:"",confirmPassword:"",acceptTerms:!1,error:null,isLoading:!1,passwordStrength:null,passwordMatch:!0})}async onAfterRender(){await super.onAfterRender();const e=this.element.querySelector("#registerName");e&&e.focus()}async onActionUpdateField(e,s){const t=s.dataset.field,a="checkbox"===s.type?s.checked:s.value;this.updateData({[t]:a}),"password"===t&&this.checkPasswordStrength(a),"password"!==t&&"confirmPassword"!==t||this.checkPasswordMatch(),this.data.error&&this.updateData({error:null})}checkPasswordStrength(e){let s=null;s=0===e.length?null:e.length<6?"weak":e.length<8?"fair":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(e)?"strong":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(e)?"good":"fair",this.updateData({passwordStrength:s},!0)}checkPasswordMatch(){const e=!this.data.confirmPassword||this.data.password===this.data.confirmPassword;this.updateData({passwordMatch:e},!0)}async onActionTogglePassword(e,s){e.preventDefault();const t=s.dataset.passwordField;if("password"===t){this.updateData({showPassword:!this.data.showPassword});const e=this.element.querySelector("#registerPassword");e&&(e.type=this.data.showPassword?"text":"password")}else if("confirmPassword"===t){this.updateData({showConfirmPassword:!this.data.showConfirmPassword});const e=this.element.querySelector("#registerConfirmPassword");e&&(e.type=this.data.showConfirmPassword?"text":"password")}}async onActionRegister(e){if(e.preventDefault(),await this.updateData({error:null,isLoading:!0},!0),!(this.data.name&&this.data.email&&this.data.password&&this.data.confirmPassword))return void(await this.updateData({error:"Please fill in all required fields",isLoading:!1},!0));if(this.data.name.trim().length<2)return void(await this.updateData({error:"Name must be at least 2 characters long",isLoading:!1},!0));if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.data.email))return void(await this.updateData({error:"Please enter a valid email address",isLoading:!1},!0));if(this.data.password.length<6)return void(await this.updateData({error:"Password must be at least 6 characters long",isLoading:!1},!0));if(this.data.password!==this.data.confirmPassword)return void(await this.updateData({error:"Passwords do not match",isLoading:!1},!0));const s=this.getApp().auth;if(!s)return void(await this.updateData({error:"Authentication system not available",isLoading:!1},!0));const t={name:this.data.name.trim(),email:this.data.email.toLowerCase().trim(),password:this.data.password,acceptedTerms:this.data.acceptTerms},a=await s.register(t);a.success||await this.updateData({error:a.message||"Registration failed. Please try again.",isLoading:!1},!0)}async onActionLogin(e){e.preventDefault(),this.getApp().navigate("/login")}async onActionHandleKeyPress(e,s){if("Enter"===e.key){e.preventDefault();const t=["registerName","registerEmail","registerPassword","registerConfirmPassword"],a=t.indexOf(s.id);if(a>=0&&a<t.length-1){const e=this.element.querySelector(`#${t[a+1]}`);e&&e.focus()}else a===t.length-1&&await this.onActionRegister(e)}}async getViewData(){return{...this.data}}}class ForgotPasswordPage extends t.Page{static pageName="auth-forgot-password";static title="Forgot Password";static icon="bi-key";static route="forgot-password";constructor(e={}){super({...e,template:e.template}),this.authConfig=e.authConfig||{passwordResetMethod:"code",ui:{title:"My App"},features:{}}}async onInit(){this.data={...this.authConfig.ui,...this.authConfig.features,passwordResetMethod:this.authConfig.passwordResetMethod,step:"email",isLoading:!1,error:null,email:""}}async onEnter(){document.title=`${ForgotPasswordPage.title} - ${this.authConfig.ui.title}`,this.updateData({step:"email",isLoading:!1,error:null,email:""})}getFormData(e){const s=this.element.querySelector(e);if(!s)return{};const t=new FormData(s);return Object.fromEntries(t.entries())}async onActionRequestReset(){const{email:e}=this.getFormData("#form-request-reset");if(await this.updateData({isLoading:!0,error:null,email:e},!0),!e)return this.updateData({error:"Please enter your email address",isLoading:!1},!0);const s=this.getApp().auth,t=this.authConfig.passwordResetMethod||"code",a=await s.forgotPassword(e,t);"link"===t?(await this.updateData({step:"link_sent",isLoading:!1},!0),a.success||console.error("Forgot password (link) error:",a.message)):a.success?await this.updateData({step:"code",isLoading:!1},!0):await this.updateData({error:a.message,isLoading:!1},!0)}async onActionResetWithCode(){const{code:e,new_password:s,confirm_password:t}=this.getFormData("#form-reset-with-code");if(await this.updateData({isLoading:!0,error:null},!0),!e||!s)return this.updateData({error:"Please enter the code and your new password",isLoading:!1},!0);if(s!==t)return this.updateData({error:"Passwords do not match",isLoading:!1},!0);const a=this.getApp().auth,n=await a.resetPasswordWithCode(this.data.email,e,s);n.success?(await this.updateData({step:"success",isLoading:!1},!0),setTimeout(()=>{this.getApp().showSuccess("Password reset complete. Welcome back!"),this.getApp().navigate("/")},2e3)):await this.updateData({error:n.message,isLoading:!1},!0)}async onActionBackToLogin(){this.getApp().navigate("/login")}get isStepEmail(){return"email"===this.data.step}get isStepCode(){return"code"===this.data.step}get isStepLinkSent(){return"link_sent"===this.data.step}get isStepSuccess(){return"success"===this.data.step}}class ResetPasswordPage extends t.Page{static pageName="auth-reset-password";static title="Reset Password";static icon="bi-key-fill";static route="reset-password";constructor(e={}){super({...e,pageName:ResetPasswordPage.pageName,route:e.route||ResetPasswordPage.route,pageIcon:ResetPasswordPage.icon,template:"auth/pages/ResetPasswordPage.mst"}),this.authConfig=e.authConfig||{ui:{title:"My App",logoUrl:"/assets/logo.png",messages:{resetTitle:"Set New Password",resetSubtitle:"Choose a strong password"}},features:{registration:!0}},this.resetToken=null}async onInit(){await super.onInit(),this.data={...this.authConfig.ui,...this.authConfig.features,password:"",confirmPassword:"",resetToken:"",isLoading:!1,error:null,success:!1,successMessage:null,showPassword:!1,showConfirmPassword:!1,passwordStrength:null,passwordMatch:!0,tokenValid:!1}}async onEnter(){await super.onEnter(),document.title=`${ResetPasswordPage.title} - ${this.authConfig.ui.title}`;const e=new URLSearchParams(window.location.search);this.resetToken=e.get("token")||e.get("login_token")||"",this.resetToken?this.updateData({resetToken:this.resetToken,tokenValid:!0,error:null,success:!1}):this.updateData({error:"Invalid or missing reset token. Please request a new password reset.",tokenValid:!1})}async onAfterRender(){if(await super.onAfterRender(),this.data.tokenValid){const e=this.element.querySelector("#resetPassword");e&&e.focus()}}async onActionUpdateField(e,s){const t=s.dataset.field,a=s.value;this.updateData({[t]:a,error:null}),"password"===t&&this.checkPasswordStrength(a),"password"!==t&&"confirmPassword"!==t||this.checkPasswordMatch()}checkPasswordStrength(e){let s=null;s=0===e.length?null:e.length<6?"weak":e.length<8?"fair":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(e)?"strong":/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(e)?"good":"fair",this.updateData({passwordStrength:s},!0)}checkPasswordMatch(){const e=!this.data.confirmPassword||this.data.password===this.data.confirmPassword;this.updateData({passwordMatch:e},!0)}async onActionTogglePassword(e,s){e.preventDefault();const t=s.dataset.passwordField;if("password"===t){this.updateData({showPassword:!this.data.showPassword});const e=this.element.querySelector("#resetPassword");e&&(e.type=this.data.showPassword?"text":"password")}else if("confirmPassword"===t){this.updateData({showConfirmPassword:!this.data.showConfirmPassword});const e=this.element.querySelector("#resetConfirmPassword");e&&(e.type=this.data.showConfirmPassword?"text":"password")}}async onActionResetPassword(e){if(e.preventDefault(),await this.updateData({error:null,isLoading:!0},!0),!this.data.password||!this.data.confirmPassword)return void(await this.updateData({error:"Please enter and confirm your new password",isLoading:!1},!0));if(this.data.password.length<6)return void(await this.updateData({error:"Password must be at least 6 characters long",isLoading:!1},!0));if(this.data.password!==this.data.confirmPassword)return void(await this.updateData({error:"Passwords do not match",isLoading:!1},!0));const s=this.getApp().auth;if(!s)return void(await this.updateData({error:"Authentication system not available",isLoading:!1},!0));const t=await s.resetPasswordWithToken(this.resetToken,this.data.password);t.success?(await this.updateData({success:!0,successMessage:"Password reset successful! Logging you in...",isLoading:!1},!0),setTimeout(()=>{this.getApp().showSuccess("Password reset complete. Welcome back!"),this.getApp().navigate("/")},2e3)):await this.updateData({error:t.message||"Password reset failed. Please try again.",isLoading:!1},!0)}async onActionBackToLogin(e){e.preventDefault(),this.getApp().navigate("/login")}async onActionRegister(e){e.preventDefault(),this.getApp().navigate("/register")}async onActionRequestNew(e){e.preventDefault(),this.getApp().navigate("/forgot-password")}async onActionHandleKeyPress(e,s){if("Enter"===e.key){e.preventDefault();const t=["resetPassword","resetConfirmPassword"],a=t.indexOf(s.id);if(a>=0&&a<t.length-1){const e=this.element.querySelector(`#${t[a+1]}`);e&&e.focus()}else a===t.length-1&&await this.onActionResetPassword(e)}}async getViewData(){return{...this.data}}}const a={};function n(e){const s=e.replace(/^\//,"").replace(/^src\//,"").replace(/\\/g,"/");return a[s]||a[e]}a["extensions/auth/pages/ForgotPasswordPage.mst"]='<div class="auth-page forgot-password-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-8 col-md-8 col-lg-6 col-xl-5">\n <div class="card {{shadow}} border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Header --\x3e\n <div class="text-center mb-4">\n {{#logoUrl}}<img src="{{logoUrl}}" alt="{{title}}" class="mb-3" style="max-height: 60px;">{{/logoUrl}}\n <h2 class="h3 mb-2">{{forgotTitle}}</h2>\n <p class="text-muted">{{forgotSubtitle}}</p>\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/error}}\n\n \x3c!-- Step 1: Email Form --\x3e\n {{#isStepEmail}}\n <form id="form-request-reset" novalidate>\n <div class="mb-3">\n <label for="forgotEmail" class="form-label"><i class="bi bi-envelope me-1"></i>Email Address</label>\n <input type="email" class="form-control form-control-lg" id="forgotEmail" name="email" placeholder="Enter your registered email" required autofocus>\n <div class="form-text">We\'ll send you instructions to reset your password.</div>\n </div>\n <button type="button" class="btn btn-primary btn-lg w-100 mb-3" data-action="requestReset" {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}<span class="spinner-border spinner-border-sm me-2"></span>Sending...{{/isLoading}}\n {{^isLoading}}<i class="bi bi-send me-2"></i>Send Instructions{{/isLoading}}\n </button>\n <button type="button" class="btn btn-outline-secondary btn-lg w-100" data-action="backToLogin" {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-arrow-left me-2"></i>Back to Login\n </button>\n </form>\n <div class="text-center mt-4">\n <p class="text-muted">Remember your password? <a href="#" class="text-decoration-none fw-semibold" data-action="backToLogin">Sign in</a></p>\n </div>\n {{/isStepEmail}}\n\n \x3c!-- Step 2 (Link Method): Confirmation --\x3e\n {{#isStepLinkSent}}\n <div class="text-center">\n <div class="mb-4"><i class="bi bi-envelope-check text-success" style="font-size: 4rem;"></i></div>\n <h3 class="h4 mb-3">Check your email</h3>\n <p class="text-muted">If an account exists for <strong>{{data.email}}</strong>, we have sent instructions for resetting your password.</p>\n <div class="d-grid gap-2 mt-4">\n <button type="button" class="btn btn-primary btn-lg" data-action="backToLogin"><i class="bi bi-arrow-left me-2"></i>Back to Login</button>\n </div>\n </div>\n {{/isStepLinkSent}}\n\n \x3c!-- Step 2 (Code Method): Code Entry Form --\x3e\n {{#isStepCode}}\n <p class="text-muted text-center mb-3">A verification code has been sent to <strong>{{data.email}}</strong>. Please enter it below.</p>\n <form id="form-reset-with-code" novalidate>\n <div class="mb-3">\n <label for="resetCode" class="form-label"><i class="bi bi-shield-lock me-1"></i>Verification Code</label>\n <input type="text" class="form-control form-control-lg" id="resetCode" name="code" placeholder="Enter code" required>\n </div>\n <div class="mb-3">\n <label for="resetPassword" class="form-label"><i class="bi bi-lock me-1"></i>New Password</label>\n <input type="password" class="form-control form-control-lg" id="resetPassword" name="new_password" placeholder="Enter new password" required autocomplete="new-password">\n </div>\n <div class="mb-3">\n <label for="confirmPassword" class="form-label"><i class="bi bi-lock-fill me-1"></i>Confirm New Password</label>\n <input type="password" class="form-control form-control-lg" id="confirmPassword" name="confirm_password" placeholder="Confirm new password" required autocomplete="new-password">\n </div>\n <button type="button" class="btn btn-primary btn-lg w-100" data-action="resetWithCode" {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}<span class="spinner-border spinner-border-sm me-2"></span>Resetting...{{/isLoading}}\n {{^isLoading}}<i class="bi bi-key me-2"></i>Reset Password{{/isLoading}}\n </button>\n </form>\n {{/isStepCode}}\n\n \x3c!-- Step 3 (Code Method): Success --\x3e\n {{#isStepSuccess}}\n <div class="text-center">\n <div class="mb-4"><i class="bi bi-check-circle text-success" style="font-size: 4rem;"></i></div>\n <h3 class="h4 mb-3">Password Reset!</h3>\n <p class="text-muted">Your password has been changed successfully. You will be redirected to the login page shortly.</p>\n </div>\n {{/isStepSuccess}}\n\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n',a["extensions/auth/pages/LoginPage.mst"]='<div class="auth-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-10 col-md-8 col-lg-6 col-xl-5">\n <div class="card {{data.shadow}} border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Logo and Header --\x3e\n <div class="text-center mb-4">\n {{#data.logoUrl}}\n <img src="{{data.logoUrl}}" alt="{{data.title}}" class="mb-3" style="max-height: 60px;">\n {{/data.logoUrl}}\n {{#data.messages.loginTitle}}\n <h2 class="h3 mb-2">{{#data.loginIcon}}<i class="{{data.loginIcon}}"></i> {{/data.loginIcon}}{{data.messages.loginTitle}}</h2>\n {{/data.messages.loginTitle}}\n {{/data.messages.loginSubtitle}}\n <p class="text-muted">{{data.messages.loginSubtitle}}</p>\n {{/data.messages.loginSubtitle}}\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#data.error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{data.error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/data.error}}\n\n \x3c!-- Login Form --\x3e\n <form novalidate>\n \x3c!-- Username/Email Field --\x3e\n <div class="mb-3">\n <label for="loginUsername" class="form-label">\n <i class="bi bi-person me-1"></i>Username or Email\n </label>\n <input\n type="text"\n class="form-control form-control-lg"\n id="loginUsername"\n placeholder="Enter your username or email"\n value="{{username}}"\n data-field="username"\n data-action-keydown="handleKeyPress"\n autocomplete="username"\n required\n autofocus\n {{#isLoading}}disabled{{/isLoading}}>\n </div>\n\n \x3c!-- Password Field --\x3e\n <div class="mb-3">\n <label for="loginPassword" class="form-label">\n <i class="bi bi-lock me-1"></i>Password\n </label>\n <div class="input-group">\n <input\n type="{{#showPassword}}text{{/showPassword}}{{^showPassword}}password{{/showPassword}}"\n class="form-control form-control-lg"\n id="loginPassword"\n placeholder="Enter your password"\n value="{{password}}"\n data-field="password"\n data-action-keydown="handleKeyPress"\n autocomplete="current-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#data.showPassword}}-slash{{/data.showPassword}}"></i>\n </button>\n </div>\n </div>\n\n \x3c!-- Remember Me & Forgot Password --\x3e\n <div class="d-flex justify-content-between align-items-center mb-4">\n {{#data.rememberMe}}\n <div class="form-check">\n <input\n class="form-check-input"\n type="checkbox"\n id="rememberMe"\n data-field="rememberMe"\n data-change-action="updateField"\n autocomplete="off"\n {{#rememberMe}}checked{{/rememberMe}}\n {{#isLoading}}disabled{{/isLoading}}>\n <label class="form-check-label" for="rememberMe">\n Remember me\n </label>\n </div>\n {{/data.rememberMe}}\n {{^data.rememberMe}}<div></div>{{/data.rememberMe}}\n\n {{#data.forgotPassword}}\n <a href="?page=forgot-password" class="text-decoration-none" data-action="forgotPassword">\n Forgot password?\n </a>\n {{/data.forgotPassword}}\n </div>\n\n \x3c!-- Login Button --\x3e\n <button\n type="button"\n class="btn btn-primary btn-lg w-100 mb-3"\n data-action="login"\n {{#data.isLoading}}disabled{{/data.isLoading}}>\n {{#data.isLoading}}\n <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>\n Signing in...\n {{/data.isLoading}}\n {{^data.isLoading}}\n <i class="bi bi-box-arrow-in-right me-2"></i>Sign In\n {{/data.isLoading}}\n </button>\n\n \x3c!-- Alternative Login Methods --\x3e\n {{#data.passkeySupported}}\n <div class="position-relative my-3">\n <hr class="text-muted">\n <span class="position-absolute top-50 start-50 translate-middle bg-white px-3 text-muted small">\n OR\n </span>\n </div>\n\n <button\n type="button"\n class="btn btn-outline-primary btn-lg w-100 mb-2"\n data-action="loginWithPasskey"\n {{#data.isLoading}}disabled{{/data.isLoading}}>\n <i class="bi bi-fingerprint me-2"></i>Sign in with Passkey\n </button>\n {{/data.passkeySupported}}\n </form>\n\n \x3c!-- Register Link --\x3e\n {{#data.registration}}\n <div class="text-center mt-4">\n <p class="mb-0">\n Don\'t have an account?\n <a href="#" class="text-decoration-none fw-semibold" data-action="register">\n Sign up\n </a>\n </p>\n </div>\n {{/data.registration}}\n </div>\n </div>\n\n \x3c!-- Security Notice --\x3e\n \x3c!-- TOS and Privacy Links --\x3e\n <div class="text-center mt-3 auth-footer-links">\n {{#data.termsUrl}}\n <small><a href="{{data.termsUrl}}" target="_blank" rel="noopener noreferrer">Terms of Service</a></small>\n {{/data.termsUrl}}\n {{#data.termsUrl}}{{#data.privacyUrl}}\n <small class="mx-1 text-muted">&middot;</small>\n {{/data.privacyUrl}}{{/data.termsUrl}}\n {{#data.privacyUrl}}\n <small><a href="{{data.privacyUrl}}" target="_blank" rel="noopener noreferrer">Privacy Policy</a></small>\n {{/data.privacyUrl}}\n {{#data.showVersion}}\n <div class="text-muted text-center mt-3">\n <small>version {{data.version}}</small>\n </div>\n {{/data.showVersion}}\n </div>\n </div>\n </div>\n </div>\n</div>\n',a["extensions/auth/pages/RegisterPage.mst"]='<div class="auth-page register-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-10 col-md-8 col-lg-6 col-xl-5">\n <div class="card {{shadow}} border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Logo and Header --\x3e\n <div class="text-center mb-4">\n {{#logoUrl}}\n <img src="{{logoUrl}}" alt="{{title}}" class="mb-3" style="max-height: 60px;">\n {{/logoUrl}}\n <h2 class="h3 mb-2">{{messages.registerTitle}}</h2>\n <p class="text-muted">{{messages.registerSubtitle}}</p>\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/error}}\n\n \x3c!-- Registration Form --\x3e\n <form data-action="register" novalidate>\n \x3c!-- Name Field --\x3e\n <div class="mb-3">\n <label for="registerName" class="form-label">\n <i class="bi bi-person me-1"></i>Full Name\n </label>\n <input\n type="text"\n class="form-control form-control-lg"\n id="registerName"\n placeholder="Enter your full name"\n value="{{name}}"\n data-field="name"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="name"\n required\n autofocus\n {{#isLoading}}disabled{{/isLoading}}>\n </div>\n\n \x3c!-- Email Field --\x3e\n <div class="mb-3">\n <label for="registerEmail" class="form-label">\n <i class="bi bi-envelope me-1"></i>Email Address\n </label>\n <input\n type="email"\n class="form-control form-control-lg"\n id="registerEmail"\n placeholder="name@example.com"\n value="{{email}}"\n data-field="email"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="email"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n </div>\n\n \x3c!-- Password Field --\x3e\n <div class="mb-3">\n <label for="registerPassword" class="form-label">\n <i class="bi bi-lock me-1"></i>Password\n </label>\n <div class="input-group">\n <input\n type="{{#showPassword}}text{{/showPassword}}{{^showPassword}}password{{/showPassword}}"\n class="form-control form-control-lg"\n id="registerPassword"\n placeholder="Create a strong password"\n value="{{password}}"\n data-field="password"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="password"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showPassword}}-slash{{/showPassword}}"></i>\n </button>\n </div>\n\n \x3c!-- Password Strength Indicator --\x3e\n {{#passwordStrength}}\n <div class="mt-2">\n <div class="progress" style="height: 4px;">\n <div class="progress-bar\n {{#passwordStrength.weak}}bg-danger{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}bg-warning{{/passwordStrength.fair}}\n {{#passwordStrength.good}}bg-info{{/passwordStrength.good}}\n {{#passwordStrength.strong}}bg-success{{/passwordStrength.strong}}"\n role="progressbar"\n style="width:\n {{#passwordStrength.weak}}25%{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}50%{{/passwordStrength.fair}}\n {{#passwordStrength.good}}75%{{/passwordStrength.good}}\n {{#passwordStrength.strong}}100%{{/passwordStrength.strong}}">\n </div>\n </div>\n <small class="text-muted mt-1">\n Password strength: {{passwordStrength}}\n </small>\n </div>\n {{/passwordStrength}}\n </div>\n\n \x3c!-- Confirm Password Field --\x3e\n <div class="mb-3">\n <label for="registerConfirmPassword" class="form-label">\n <i class="bi bi-lock-fill me-1"></i>Confirm Password\n </label>\n <div class="input-group">\n <input\n type="{{#showConfirmPassword}}text{{/showConfirmPassword}}{{^showConfirmPassword}}password{{/showConfirmPassword}}"\n class="form-control form-control-lg {{^passwordMatch}}is-invalid{{/passwordMatch}}"\n id="registerConfirmPassword"\n placeholder="Re-enter your password"\n value="{{confirmPassword}}"\n data-field="confirmPassword"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="confirmPassword"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showConfirmPassword}}-slash{{/showConfirmPassword}}"></i>\n </button>\n </div>\n {{^passwordMatch}}\n <div class="invalid-feedback">\n Passwords do not match\n </div>\n {{/passwordMatch}}\n </div>\n\n \x3c!-- Register Button --\x3e\n <button\n type="submit"\n class="btn btn-primary btn-lg w-100 mb-3"\n {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}\n <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>\n Creating account...\n {{/isLoading}}\n {{^isLoading}}\n <i class="bi bi-person-plus me-2"></i>Create Account\n {{/isLoading}}\n </button>\n </form>\n\n \x3c!-- Login Link --\x3e\n <div class="text-center mt-4">\n <p class="mb-0">\n Already have an account?\n <a href="#" class="text-decoration-none fw-semibold" data-action="login">\n Sign in\n </a>\n </p>\n </div>\n </div>\n </div>\n\n \x3c!-- Security Notice --\x3e\n <div class="text-center mt-3">\n <small class="text-muted">\n <i class="bi bi-shield-check me-1"></i>Your information is secure and encrypted\n </small>\n </div>\n </div>\n </div>\n </div>\n</div>\n',a["extensions/auth/pages/ResetPasswordPage.mst"]='<div class="auth-page reset-password-page min-vh-100 d-flex align-items-center py-4">\n <div class="container">\n <div class="row justify-content-center">\n <div class="col-sm-10 col-md-8 col-lg-6 col-xl-5">\n <div class="card shadow-lg border-0">\n <div class="card-body p-4 p-md-5">\n \x3c!-- Logo and Header --\x3e\n <div class="text-center mb-4">\n {{#logoUrl}}\n <img src="{{logoUrl}}" alt="{{title}}" class="mb-3" style="max-height: 60px;">\n {{/logoUrl}}\n <h2 class="h3 mb-2">{{messages.resetTitle}}</h2>\n <p class="text-muted">{{messages.resetSubtitle}}</p>\n </div>\n\n \x3c!-- Error Alert --\x3e\n {{#error}}\n <div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">\n <i class="bi bi-exclamation-triangle-fill me-2"></i>\n <div>{{error}}</div>\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n </div>\n {{/error}}\n\n \x3c!-- Success State --\x3e\n {{#success}}\n <div class="alert alert-success d-flex align-items-center" role="alert">\n <i class="bi bi-check-circle-fill me-2"></i>\n <div>\n <strong>Password Reset Complete!</strong><br>\n {{#successMessage}}{{successMessage}}{{/successMessage}}\n {{^successMessage}}Your password has been reset successfully. You can now log in with your new password.{{/successMessage}}\n </div>\n </div>\n\n <div class="text-center">\n <p class="mb-3">\n <i class="bi bi-shield-check text-success" style="font-size: 3rem;"></i>\n </p>\n <p class="text-muted">\n Redirecting you to the login page...\n </p>\n <div class="d-grid gap-2 mt-4">\n <button\n class="btn btn-primary btn-lg"\n data-action="backToLogin">\n <i class="bi bi-box-arrow-in-right me-2"></i>Continue to Login\n </button>\n </div>\n </div>\n {{/success}}\n\n \x3c!-- Reset Form --\x3e\n {{^success}}\n {{#tokenValid}}\n <form data-action="resetPassword" novalidate>\n \x3c!-- New Password Field --\x3e\n <div class="mb-3">\n <label for="resetPassword" class="form-label">\n <i class="bi bi-lock me-1"></i>New Password\n </label>\n <div class="input-group">\n <input\n type="{{#showPassword}}text{{/showPassword}}{{^showPassword}}password{{/showPassword}}"\n class="form-control form-control-lg"\n id="resetPassword"\n placeholder="Enter your new password"\n value="{{password}}"\n data-field="password"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n autofocus\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="password"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showPassword}}-slash{{/showPassword}}"></i>\n </button>\n </div>\n\n \x3c!-- Password Strength Indicator --\x3e\n {{#passwordStrength}}\n <div class="mt-2">\n <div class="progress" style="height: 4px;">\n <div class="progress-bar\n {{#passwordStrength.weak}}bg-danger{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}bg-warning{{/passwordStrength.fair}}\n {{#passwordStrength.good}}bg-info{{/passwordStrength.good}}\n {{#passwordStrength.strong}}bg-success{{/passwordStrength.strong}}"\n role="progressbar"\n style="width:\n {{#passwordStrength.weak}}25%{{/passwordStrength.weak}}\n {{#passwordStrength.fair}}50%{{/passwordStrength.fair}}\n {{#passwordStrength.good}}75%{{/passwordStrength.good}}\n {{#passwordStrength.strong}}100%{{/passwordStrength.strong}}">\n </div>\n </div>\n <small class="text-muted mt-1">\n Password strength: {{passwordStrength}}\n </small>\n </div>\n {{/passwordStrength}}\n </div>\n\n \x3c!-- Confirm Password Field --\x3e\n <div class="mb-4">\n <label for="resetConfirmPassword" class="form-label">\n <i class="bi bi-lock-fill me-1"></i>Confirm New Password\n </label>\n <div class="input-group">\n <input\n type="{{#showConfirmPassword}}text{{/showConfirmPassword}}{{^showConfirmPassword}}password{{/showConfirmPassword}}"\n class="form-control form-control-lg {{^passwordMatch}}is-invalid{{/passwordMatch}}"\n id="resetConfirmPassword"\n placeholder="Re-enter your new password"\n value="{{confirmPassword}}"\n data-field="confirmPassword"\n data-change-action="updateField"\n data-filter="live-search"\n data-action-keydown="handleKeyPress"\n autocomplete="new-password"\n required\n {{#isLoading}}disabled{{/isLoading}}>\n <button\n class="btn btn-outline-secondary"\n type="button"\n data-password-field="confirmPassword"\n data-action="togglePassword"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-eye{{#showConfirmPassword}}-slash{{/showConfirmPassword}}"></i>\n </button>\n </div>\n {{^passwordMatch}}\n <div class="invalid-feedback">\n Passwords do not match\n </div>\n {{/passwordMatch}}\n </div>\n\n \x3c!-- Reset Button --\x3e\n <button\n type="submit"\n class="btn btn-primary btn-lg w-100 mb-3"\n {{#isLoading}}disabled{{/isLoading}}>\n {{#isLoading}}\n <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>\n Resetting password...\n {{/isLoading}}\n {{^isLoading}}\n <i class="bi bi-key me-2"></i>Reset Password\n {{/isLoading}}\n </button>\n\n \x3c!-- Back to Login --\x3e\n <button\n type="button"\n class="btn btn-outline-secondary btn-lg w-100"\n data-action="backToLogin"\n {{#isLoading}}disabled{{/isLoading}}>\n <i class="bi bi-arrow-left me-2"></i>Back to Login\n </button>\n </form>\n {{/tokenValid}}\n\n \x3c!-- Invalid Token State --\x3e\n {{^tokenValid}}\n <div class="text-center">\n <div class="mb-4">\n <i class="bi bi-exclamation-triangle text-warning" style="font-size: 4rem;"></i>\n </div>\n <h4 class="text-warning mb-3">Invalid Reset Link</h4>\n <p class="text-muted mb-4">\n This password reset link is invalid or has expired.\n Please request a new password reset.\n </p>\n <div class="d-grid gap-2">\n <button\n class="btn btn-primary btn-lg"\n data-action="requestNew">\n <i class="bi bi-envelope me-2"></i>Request New Reset\n </button>\n <button\n class="btn btn-outline-secondary btn-lg"\n data-action="backToLogin">\n <i class="bi bi-arrow-left me-2"></i>Back to Login\n </button>\n </div>\n </div>\n {{/tokenValid}}\n\n \x3c!-- Additional Links --\x3e\n {{#tokenValid}}{{^success}}\n <div class="text-center mt-4">\n <p class="text-muted mb-2">\n Remember your password?\n <a href="#" class="text-decoration-none fw-semibold" data-action="backToLogin">\n Sign in\n </a>\n </p>\n {{#registration}}\n <p class="text-muted mb-0">\n Don\'t have an account?\n <a href="#" class="text-decoration-none fw-semibold" data-action="register">\n Sign up\n </a>\n </p>\n {{/registration}}\n </div>\n {{/success}}{{/tokenValid}}\n {{/success}}\n </div>\n </div>\n\n \x3c!-- Security Notice --\x3e\n <div class="text-center mt-3">\n <small class="text-muted">\n <i class="bi bi-shield-lock me-1"></i>\n Secure password reset with email verification\n </small>\n </div>\n </div>\n </div>\n </div>\n</div>\n';class AuthApp extends e.WebApp{constructor(e={}){const s={title:e.name||"My App",logoUrl:null,termsUrl:null,privacyUrl:null,theme:{background:"auth-bg-light",panel:"auth-panel-light",...e.ui?.theme||{}},messages:{loginTitle:"Welcome Back",loginSubtitle:"Sign in to your account",registerTitle:"Create Account",registerSubtitle:"Join us today",forgotTitle:"Reset Password",forgotSubtitle:"We'll send you reset instructions",...e.ui?.messages||{}},...e.ui||{}},t={...e,routes:{login:"/login",register:"/register",forgot:"/forgot-password",reset:"/reset-password",...e.routes||{}},loginRedirect:e.loginRedirect||"/",logoutRedirect:e.logoutRedirect||"/login",features:{forgotPassword:!0,registration:!0,rememberMe:!0,...e.features||{}},passwordResetMethod:e.passwordResetMethod||"code",ui:s};super(t),this.auth=new AuthManager(this,t),this.authConfig=t,this.applyAuthTheme(),this.registerAuthPages(),this.setupAuthIntegration(),this.setupAuthGuards()}applyAuthTheme(){const e=this.authConfig.ui.theme;if(!e)return;const s=Array.from(document.body.classList).filter(e=>e.startsWith("auth-bg-")||e.startsWith("auth-panel-"));s.length&&document.body.classList.remove(...s),e.background&&document.body.classList.add(e.background),e.panel&&document.body.classList.add(e.panel)}registerAuthPages(){const e=this.authConfig;this.registerPage("login",LoginPage,{route:e.routes.login,title:"Login",authConfig:e,template:n("extensions/auth/pages/LoginPage.mst")}),e.features.registration&&this.registerPage("register",RegisterPage,{route:e.routes.register,title:"Register",authConfig:e,template:n("extensions/auth/pages/RegisterPage.mst")}),e.features.forgotPassword&&(this.registerPage("forgot-password",ForgotPasswordPage,{route:e.routes.forgot,title:"Reset Password",authConfig:e,template:n("extensions/auth/pages/ForgotPasswordPage.mst")}),this.registerPage("reset-password",ResetPasswordPage,{route:e.routes.reset,title:"Set New Password",authConfig:e,template:n("extensions/auth/pages/ResetPasswordPage.mst")}))}setupAuthIntegration(){this.events.on("auth:login",e=>{this.showSuccess(`Welcome back, ${e.name||e.email}!`),this.navigateAfterLogin()}),this.events.on("auth:logout",()=>{this.navigate(this.authConfig.logoutRedirect)}),this.events.on("auth:register",e=>{this.showSuccess(`Welcome, ${e.name||e.email}! Your account is ready.`),this.navigate(this.authConfig.loginRedirect)}),this.events.on("auth:tokenExpired",()=>{this.showWarning("Your session has expired. Please login again."),this.navigate(this.authConfig.logoutRedirect)})}setupAuthGuards(){this.events.on("route:changed",({pageName:e,path:s})=>{const t=this.getOrCreatePage(e);if(!t)return;const a=t.constructor,n=this.auth.isAuthenticated,i=["login","register","forgot-password","reset-password"].includes(e);if(a.requiresAuth&&!n)return sessionStorage.setItem("auth_redirect",s),this.navigate(this.authConfig.routes.login),void this.showWarning("Please login to access this page.");n&&i&&this.navigate(this.authConfig.loginRedirect)})}navigateAfterLogin(){const e=sessionStorage.getItem("auth_redirect");e?(sessionStorage.removeItem("auth_redirect"),this.navigate(e)):this.navigate(this.authConfig.loginRedirect)}static requireAuth(e){return e.requiresAuth=!0,e}}exports.BUILD_TIME=e.BUILD_TIME,exports.VERSION=e.VERSION,exports.VERSION_INFO=e.VERSION_INFO,exports.VERSION_MAJOR=e.VERSION_MAJOR,exports.VERSION_MINOR=e.VERSION_MINOR,exports.VERSION_REVISION=e.VERSION_REVISION,exports.WebApp=e.WebApp,exports.AuthApp=AuthApp,exports.AuthManager=AuthManager,exports.ForgotPasswordPage=ForgotPasswordPage,exports.LoginPage=LoginPage,exports.PasskeyPlugin=class{constructor(e={}){this.name="passkey",this.config={rpName:"MOJO App",rpId:window?.location?.hostname||"localhost",timeout:6e4,userVerification:"preferred",authenticatorAttachment:"platform",...e},this.authManager=null,this.app=null,this.authService=null}async initialize(e,s){this.authManager=e,this.app=s,this.isSupported()?(this.authManager.loginWithPasskey=this.loginWithPasskey.bind(this),this.authManager.setupPasskey=this.setupPasskey.bind(this),this.authManager.isPasskeySupported=this.isSupported.bind(this)):console.warn("Passkey authentication is not supported in this browser")}isSupported(){return void 0!==window.PublicKeyCredential&&void 0!==navigator.credentials&&"function"==typeof navigator.credentials.create&&"function"==typeof navigator.credentials.get}async loginWithPasskey(){if(!this.isSupported())throw new Error("Passkey authentication is not supported in this browser");try{const e=await this.app.rest.POST("/api/auth/passkey/challenge");if(!e.success||!e.data.data.challenge)throw new Error("No authentication challenge received from server");const s=e.data.data,t={publicKey:{challenge:this.base64ToArrayBuffer(s.challenge),timeout:this.config.timeout,userVerification:this.config.userVerification,rpId:this.config.rpId}},a=await navigator.credentials.get(t);if(!a)throw new Error("No credential received from authenticator");const n={id:a.id,rawId:this.arrayBufferToBase64(a.rawId),type:a.type,response:{authenticatorData:this.arrayBufferToBase64(a.response.authenticatorData),clientDataJSON:this.arrayBufferToBase64(a.response.clientDataJSON),signature:this.arrayBufferToBase64(a.response.signature),userHandle:a.response.userHandle?this.arrayBufferToBase64(a.response.userHandle):null}},i=await this.app.rest.POST("/api/auth/passkey/verify",{credential:n,challengeId:s.challengeId});if(!i.success||!i.data.status)throw new Error(i.data.error||"Passkey verification failed");const{token:r,refreshToken:o,user:d}=i.data.data;this.authManager.tokenManager.setTokens(r,o,!0);const l=this.authManager.tokenManager.getUserInfo();return this.authManager.setAuthState({...d,...l}),this.authManager.config.autoRefresh&&this.authManager.scheduleTokenRefresh(),this.authManager.emit("login",this.authManager.user),{success:!0,user:this.authManager.user}}catch(e){throw console.error("Passkey login error:",e),this.authManager.emit("loginError",e),new Error(e.message||"Passkey authentication failed")}}async setupPasskey(){if(!this.isSupported())throw new Error("Passkey authentication is not supported in this browser");if(!this.authManager.isAuthenticated)throw new Error("User must be authenticated to setup passkey");try{const e=await this.app.rest.POST("/api/auth/passkey/register-options");if(!e.success||!e.data.data.options)throw new Error("No registration options received from server");const s=e.data.data,t=s.options,a={publicKey:{challenge:this.base64ToArrayBuffer(t.challenge),rp:{name:this.config.rpName,id:this.config.rpId},user:{id:this.base64ToArrayBuffer(t.userId),name:t.userName,displayName:t.userDisplayName},pubKeyCredParams:[{alg:-7,type:"public-key"},{alg:-257,type:"public-key"}],authenticatorSelection:{userVerification:this.config.userVerification},timeout:this.config.timeout,attestation:"none"}};this.config.authenticatorAttachment&&(a.publicKey.authenticatorSelection.authenticatorAttachment=this.config.authenticatorAttachment);const n=await navigator.credentials.create(a);if(!n)throw new Error("Failed to create credential");const i={id:n.id,rawId:this.arrayBufferToBase64(n.rawId),type:n.type,response:{attestationObject:this.arrayBufferToBase64(n.response.attestationObject),clientDataJSON:this.arrayBufferToBase64(n.response.clientDataJSON)}},r=await this.app.rest.POST("/api/auth/passkey/register",{credential:i,optionsId:s.optionsId});if(!r.success||!r.data.status)throw new Error(r.data.error||"Failed to register passkey");return this.authManager.emit("passkeySetupSuccess",r.data.data),{success:!0,data:r.data.data}}catch(e){throw console.error("Passkey setup error:",e),this.authManager.emit("passkeySetupError",e),new Error(e.message||"Failed to setup passkey")}}async hasPasskeys(){if(!this.authManager.isAuthenticated)return{success:!1,hasPasskeys:!1};try{const e=await this.app.rest.GET("/api/auth/passkey/list");return{success:e.success,hasPasskeys:e.data.data?.passkeys&&e.data.data.passkeys.length>0,count:e.data.data?.passkeys?e.data.data.passkeys.length:0}}catch(e){return console.error("Error checking passkeys:",e),{success:!1,hasPasskeys:!1}}}async removePasskey(e){if(!this.authManager.isAuthenticated)throw new Error("User must be authenticated to remove passkey");try{const s=await this.app.rest.DELETE("/api/auth/passkey/remove",{credentialId:e});if(!s.success||!s.data.status)throw new Error(s.data.error||"Failed to remove passkey");return this.authManager.emit("passkeyRemoved",{credentialId:e}),{success:!0,data:s.data.data}}catch(s){throw console.error("Error removing passkey:",s),new Error(s.message||"Failed to remove passkey")}}base64ToArrayBuffer(e){const s=atob(e),t=new Uint8Array(s.length);for(let a=0;a<s.length;a++)t[a]=s.charCodeAt(a);return t.buffer}arrayBufferToBase64(e){const s=new Uint8Array(e);let t="";for(let a=0;a<s.byteLength;a++)t+=String.fromCharCode(s[a]);return btoa(t)}getBrowserCompatibility(){return{webAuthnSupported:!!window.PublicKeyCredential,credentialsSupported:!!navigator.credentials,platformSupported:"platform"!==this.config.authenticatorAttachment||window.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable?.(),conditionalMediationSupported:window.PublicKeyCredential?.isConditionalMediationAvailable?.()}}destroy(){this.authManager&&(delete this.authManager.loginWithPasskey,delete this.authManager.setupPasskey,delete this.authManager.isPasskeySupported),this.authManager=null,this.app=null,this.authService=null}},exports.RegisterPage=RegisterPage,exports.ResetPasswordPage=ResetPasswordPage;
2
2
  //# sourceMappingURL=auth.cjs.js.map