@crossauth/fastify 0.0.41 → 1.0.1
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.
- package/dist/index.cjs +1 -1
- package/dist/index.js +63 -63
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var ve=Object.defineProperty;var Se=(m,r,a)=>r in m?ve(m,r,{enumerable:!0,configurable:!0,writable:!0,value:a}):m[r]=a;var l=(m,r,a)=>Se(m,typeof r!="symbol"?r+"":r,a);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Pe=require("fastify"),ke=require("@fastify/view"),Te=require("@fastify/formbody"),ye=require("@fastify/cors"),be=require("@fastify/cookie"),ae=require("nunjucks"),c=require("@crossauth/backend"),e=require("@crossauth/common"),R=require("jwt-decode"),Ae=require("qrcode"),b=["Content-Type","application/json; charset=utf-8"];class he{constructor(r,a={}){l(this,"sessionServer");l(this,"enableEmailVerification",!0);l(this,"enablePasswordReset",!0);l(this,"prefix","/");l(this,"updateUserPage","updateuser.njk");l(this,"changeFactor2Page","changefactor2.njk");l(this,"configureFactor2Page","configurefactor2.njk");l(this,"changePasswordPage","changepassword.njk");l(this,"resetPasswordPage","resetpassword.njk");l(this,"requestPasswordResetPage","requestpasswordreset.njk");l(this,"emailVerifiedPage","emailverified.njk");l(this,"signupPage","signup.njk");l(this,"deleteUserPage","deleteuser.njk");this.sessionServer=r,c.setParameter("prefix",c.ParamType.String,this,a,"PREFIX"),c.setParameter("enableEmailVerification",c.ParamType.Boolean,this,a,"ENABLE_EMAIL_VERIFICATION"),c.setParameter("enablePasswordReset",c.ParamType.Boolean,this,a,"ENABLE_PASSWORD_RESET"),c.setParameter("updateUserPage",c.ParamType.String,this,a,"UPDATE_USER_PAGE"),c.setParameter("changeFactor2Page",c.ParamType.String,this,a,"CHANGE_FACTOR2_PAGE"),c.setParameter("configureFactor2Page",c.ParamType.String,this,a,"SIGNUP_FACTOR2_PAGE"),c.setParameter("changePasswordPage",c.ParamType.String,this,a,"CHANGE_PASSWORD_PAGE"),c.setParameter("resetPasswordPage",c.ParamType.String,this,a,"RESET_PASSWORD_PAGE"),c.setParameter("requestPasswordResetPage",c.ParamType.String,this,a,"REQUEST_PASSWORD_RESET_PAGE"),c.setParameter("emailVerifiedPage",c.ParamType.String,this,a,"EMAIL_VERIFIED_PAGE"),c.setParameter("signupPage",c.ParamType.String,this,a,"SIGNUP_PAGE"),c.setParameter("deleteUserPage",c.ParamType.String,this,a,"DELETE_USER_PAGE")}addUpdateUserEndpoints(){this.sessionServer.app.get(this.prefix+"updateuser",async(r,a)=>{var s;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"updateuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user||!this.sessionServer.canEditUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage);if(this.updateUserPage){let o={urlPrefix:this.prefix,csrfToken:r.csrfToken,user:r.user,allowedFactor2:this.sessionServer.allowedFactor2Details()};return a.view(this.updateUserPage,o)}}),this.sessionServer.app.post(this.prefix+"updateuser",async(r,a)=>{var s;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"updateuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.canEditUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage);for(let o in r.body)o.startsWith("user_")&&r.body[o];try{return await this.updateUser(r,a,(o,i,d)=>{const t=d?"Please click on the link in your email to verify your email address.":"Your details have been updated";return o.view(this.updateUserPage,{csrfToken:r.csrfToken,message:t,urlPrefix:this.prefix,allowedFactor2:this.sessionServer.allowedFactor2Details()})})}catch(o){const i=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:r.body.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o}));let d={};for(let t in r.body)t.startsWith("user_")&&(d[t]=r.body[t]);return this.sessionServer.handleError(o,r,a,(t,n)=>t.view(this.updateUserPage,{user:r.user,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,allowedFactor2:this.sessionServer.allowedFactor2Details(),...d}))}})}addApiUpdateUserEndpoints(){this.sessionServer.app.post(this.prefix+"api/updateuser",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/updateuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.canEditUser(r))return this.sessionServer.sendJsonError(a,401);try{return await this.updateUser(r,a,(i,d,t)=>i.header(...b).send({ok:!0,emailVerificationRequired:t}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})},!0)}})}addChangeFactor2Endpoints(){this.sessionServer.app.get(this.prefix+"changefactor2",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"changefactor2",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.sessionServer.isSessionUser(r)||!r.user){const d=await this.sessionServer.getSessionData(r,"factor2change");if(!(d!=null&&d.username)&&!this.sessionServer.isSessionUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage)}let s={urlPrefix:this.prefix,csrfToken:r.csrfToken,next:r.query.next??this.sessionServer.loginRedirect,allowedFactor2:this.sessionServer.allowedFactor2Details(),factor2:((i=r.user)==null?void 0:i.factor2)??"none",required:r.query.required??!1};return a.view(this.changeFactor2Page,s)}),this.sessionServer.app.post(this.prefix+"changefactor2",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"changefactor2",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.isSessionUser(r)||!r.user){const i=await this.sessionServer.getSessionData(r,"factor2change");if(!(i!=null&&i.username)&&!this.sessionServer.isSessionUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage)}try{return await this.changeFactor2(r,a,(i,d,t)=>d.factor2?i.view(this.configureFactor2Page,{csrfToken:d.csrfToken,next:r.body.next??this.sessionServer.loginRedirect,...d.userData}):i.view(this.configureFactor2Page,{message:"Two factor authentication has been updated",next:r.body.next??this.sessionServer.loginRedirect,csrfToken:d.csrfToken}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change two factor authentication failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{var h;return t.view(this.changeFactor2Page,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,allowedFactor2:this.sessionServer.allowedFactor2Details(),factor2:((h=r.user)==null?void 0:h.factor2)??"none",next:r.body.next??this.sessionServer.loginRedirect,required:r.body.required})})}})}addApiChangeFactor2Endpoints(){this.sessionServer.app.post(this.prefix+"api/changefactor2",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/changefactor2",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.isSessionUser(r))return this.sessionServer.sendJsonError(a,401);try{return await this.changeFactor2(r,a,(i,d,t)=>i.header(...b).send({ok:!0,...d.userData}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change factor2 failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]}),!0)}})}addChangePasswordEndpoints(){this.sessionServer.app.get(this.prefix+"changepassword",async(r,a)=>{var o;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"changepassword",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.sessionServer.isSessionUser(r)||!r.user){const i=await this.sessionServer.getSessionData(r,"passwordchange");if((i==null?void 0:i.username)==null&&!this.sessionServer.isSessionUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage)}let s={urlPrefix:this.prefix,csrfToken:r.csrfToken,next:r.query.next,required:r.query.required};return a.view(this.changePasswordPage,s)}),this.sessionServer.app.post(this.prefix+"changepassword",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"changepassword",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.changePassword(r,a,(i,d)=>r.body.next?i.redirect(r.body.next):i.view(this.changePasswordPage,{csrfToken:r.csrfToken,message:"Your password has been changed.",urlPrefix:this.prefix,next:r.body.next,required:r.body.required}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change password failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.view(this.changePasswordPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,next:r.body.next,required:r.body.required}))}})}addApiChangePasswordEndpoints(){this.sessionServer.app.post(this.prefix+"api/changepassword",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/changepassword",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.canEditUser(r))return this.sessionServer.sendJsonError(a,401);try{return await this.changePassword(r,a,(i,d)=>i.header(...b).send({ok:!0}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change password failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]}),!0)}})}addConfigureFactor2Endpoints(){this.sessionServer.app.get(this.prefix+"configurefactor2",async(r,a)=>{var s;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"configurefactor2",ip:r.ip}));try{return await this.reconfigureFactor2(r,a,(o,i,d)=>o.view(this.configureFactor2Page,{...i,next:r.query.next??this.sessionServer.loginRedirect}))}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({msg:"Configure factor2 failure",user:(s=r.user)==null?void 0:s.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.sessionServer.handleError(o,r,a,(d,t)=>d.view(this.configureFactor2Page,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:r.query.next??this.sessionServer.loginRedirect,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}}),this.sessionServer.app.post(this.prefix+"configurefactor2",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"configurefactor2",ip:r.ip}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.sessionServer.loginRedirect;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.configureFactor2(r,a,(o,i)=>{const d=i!=null&&i.factor2?this.sessionServer.authenticators[i.factor2]:void 0;return!this.sessionServer.isSessionUser(r)&&this.enableEmailVerification&&(d==null||d.skipEmailVerificationOnSignup()!=!0)?o.view(this.signupPage,{next:s,csrfToken:r.csrfToken,urlPrefix:this.prefix,message:"Please check your email to finish signing up."}):this.sessionServer.isSessionUser(r)?o.view(this.configureFactor2Page,{message:"Two-factor authentication updated",urlPrefix:this.prefix,next:s,required:r.body.required,csrfToken:r.csrfToken}):o.redirect(r.body.next??this.sessionServer.loginRedirect)})}catch(o){e.CrossauthLogger.logger.debug(e.j({err:o}));try{if(!r.sessionId){const n=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({msg:"Signup second factor failure",errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.error(e.j({msg:"Session not defined during two factor process"})),a.status(500).view(this.sessionServer.errorPage,{status:500,errorMessage:"An unknown error occurred",errorCode:e.ErrorCode.UnknownError,errorCodeName:"UnknownError"})}let i=(await this.sessionServer.sessionManager.dataForSessionId(r.sessionId))["2fa"];const d=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.error(e.j({msg:"Signup two factor failure",user:i==null?void 0:i.username,errorCodeName:d.codeName,errorCode:d.code}));const{userData:t}=await this.sessionServer.sessionManager.repeatTwoFactorSignup(r.sessionId);return this.sessionServer.handleError(o,r,a,(n,h)=>n.view(this.configureFactor2Page,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],urlPrefix:this.prefix,next:s,...t,csrfToken:this.sessionServer.csrfToken(r,n)}))}catch(i){return e.CrossauthLogger.logger.error(e.j({err:i})),a.status(500).view(this.sessionServer.errorPage,{status:500,errorMessage:"An unknown error occurred",errorCode:e.ErrorCode.UnknownError,errorCodeName:"UnknownError"})}}})}addApiConfigureFactor2Endpoints(r){this.sessionServer.app.get(r+"api/configurefactor2",async(a,s)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"GET",url:r+"api/configurefactor2",ip:a.ip,hashOfSessionId:this.sessionServer.getHashOfSessionId(a)}));try{return await this.reconfigureFactor2(a,s,(i,d,t)=>i.header(...b).send({ok:!0,...d}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Configure 2FA configuration failure",user:(o=a.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,a,s,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}}),this.sessionServer.app.post(r+"api/configurefactor2",async(a,s)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:r+"api/configurefactor2",ip:a.ip,hashOfSessionId:this.sessionServer.getHashOfSessionId(a)}));try{return await this.configureFactor2(a,s,(i,d)=>{const t={ok:!0,user:d};return this.sessionServer.isSessionUser(a)||(t.emailVerificationNeeded=this.enableEmailVerification),i.header(...b).send(t)})}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Configure 2FA configuration failure",user:(o=a.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,a,s,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addRequestPasswordResetEndpoints(){this.sessionServer.app.get(this.prefix+"requestpasswordreset",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"requestpasswordreset",ip:r.ip}));let s={csrfToken:r.csrfToken,next:r.query.next,required:r.query.required};return a.view(this.requestPasswordResetPage,s)}),this.sessionServer.app.post(this.prefix+"requestpasswordreset",async(r,a)=>{const s="If a user with exists with the email you entered, a message with a link to reset your password has been sent.";e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"requestpasswordreset",ip:r.ip}));try{return await this.requestPasswordReset(r,a,(o,i)=>o.view(this.requestPasswordResetPage,{csrfToken:r.csrfToken,message:s,urlPrefix:this.prefix}))}catch(o){return e.CrossauthLogger.logger.error(e.j({msg:"Request password reset faiulure user failure",email:r.body.email})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.sessionServer.handleError(o,r,a,(i,d)=>d.code==e.ErrorCode.EmailNotExist?i.view(this.requestPasswordResetPage,{csrfToken:r.csrfToken,message:s,urlPrefix:this.prefix,required:r.body.required,next:r.body.next}):r.body.next?i.redirect(r.body.next):i.view(this.requestPasswordResetPage,{errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code],email:r.body.email,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}})}addApiRequestPasswordResetEndpoints(){this.sessionServer.app.post(this.prefix+"api/requestpasswordreset",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/resetpasswordrequest",ip:r.ip}));try{return await this.requestPasswordReset(r,a,(s,o)=>s.header(...b).send({ok:!0}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Reset password failure failure",email:r.body.email,errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>{i.status(this.sessionServer.errorStatus(s)).header(...b).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})},!0)}})}addResetPasswordEndpoints(){this.sessionServer.app.get(this.prefix+"resetpassword/:token",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"resetpassword",ip:r.ip}));try{await this.sessionServer.sessionManager.userForPasswordResetToken(r.params.token)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return a.view(this.sessionServer.errorPage,{errorMessage:o.message,errorCode:o.code,errorCodeName:o.codeName})}return a.view(this.resetPasswordPage,{token:r.params.token,csrfToken:r.csrfToken})}),this.sessionServer.app.post(this.prefix+"resetpassword",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"resetpassword",ip:r.ip}));try{return await this.resetPassword(r,a,(s,o)=>s.view(this.resetPasswordPage,{csrfToken:r.csrfToken,message:"Your password has been changed.",urlPrefix:this.prefix}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Reset password failure",hashedToken:c.Crypto.hash(r.body.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>i.view(this.resetPasswordPage,{errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,token:r.body.token}))}})}addApiResetPasswordEndpoints(){this.sessionServer.app.post(this.prefix+"api/resetpassword",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/resetpassword",ip:r.ip}));try{return await this.resetPassword(r,a,(s,o)=>s.header(...b).send({ok:!0}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Reset password failure",hashedToken:c.Crypto.hash(r.body.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>{i.status(this.sessionServer.errorStatus(s)).header(...b).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})},!0)}})}addVerifyEmailEndpoints(){this.sessionServer.app.get(this.prefix+"verifyemail/:token",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"verifyemail",ip:r.ip}));try{return await this.verifyEmail(r,a,(s,o)=>s.view(this.emailVerifiedPage,{urlPrefix:this.prefix,user:o}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Verify email failed",hashedToken:c.Crypto.hash(r.params.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>i.view(this.sessionServer.errorPage,{errorCode:d.code,errorCodeName:e.ErrorCode[d.code],errorMessage:d.message,errorMessages:d.messages,urlPrefix:this.prefix}))}})}addApiVerifyEmailEndpoints(){this.sessionServer.app.get(this.prefix+"api/verifyemail/:token",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/verifyemail",ip:r.ip}));try{return await this.verifyEmail(r,a,(s,o)=>s.header(...b).send({ok:!0,user:o}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Verify email failure",hashedToken:c.Crypto.hash(r.params.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>{i.status(this.sessionServer.errorStatus(s)).header(...b).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})})}})}addDeleteUserEndpoints(){this.sessionServer.app.get(this.prefix+"deleteuser",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"deleteuser",ip:r.ip}));let s;if(!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteuser");try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call delete user unless a user storage is provided");s=(await this.sessionServer.userStorage.getUserById(r.user.id)).user}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??this.prefix;let i={urlPrefix:this.prefix,csrfToken:r.csrfToken,next:o,isAdmin:!1,user:s};return a.view(this.deleteUserPage,i)}),this.sessionServer.app.post(this.prefix+"deleteuser",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"deleteuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteuser");const s=r.body.next??this.prefix;try{return await this.deleteUser(r,a,d=>{var t;return d.view(this.deleteUserPage,{message:"User deleted",csrfToken:r.csrfToken,urlPrefix:this.prefix,userid:(t=r.user)==null?void 0:t.id,isAdmin:!1,next:s})})}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting user",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{var C;const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteUserPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,userid:(C=r.user)==null?void 0:C.id,isAdmin:!1,next:s})})}})}addApiDeleteUserEndpoints(){this.sessionServer.app.post(this.prefix+"api/deleteuser",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/deleteuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user)return a.status(401).header(...b).send({ok:!1});try{return await this.deleteUser(r,a,i=>{var d;return i.header(...b).send({ok:!0,userid:(d=r.user)==null?void 0:d.id})})}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async updateUser(r,a,s){if(!this.sessionServer.canEditUser(r)||!r.user)throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);let o={id:r.user.id,username:r.user.username,state:"active"};if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateUser unless a user storage is provided");o=this.sessionServer.updateUserFn(o,r,this.sessionServer.userStorage.userEditableFields);let i=this.sessionServer.validateUserFn(o);if(i.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,i);let d=await this.sessionServer.sessionManager.updateUser(r.user,o);return s(a,r.user,d.emailVerificationTokenSent)}async changeFactor2(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changeFactor2 unless a user storage is provided");let o;if(!this.sessionServer.isSessionUser(r)||!r.user){const n=await this.sessionServer.getSessionData(r,"factor2change");if(n!=null&&n.username)o=(await this.sessionServer.userStorage.getUserByUsername(n==null?void 0:n.username,{skipActiveCheck:!0,skipEmailVerifiedCheck:!0})).user;else throw new e.CrossauthError(e.ErrorCode.Unauthorized)}else if(this.sessionServer.canEditUser(r))o=r.user;else throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);let i=r.body.factor2;if(r.body.factor2&&!this.sessionServer.allowedFactor2.includes(r.body.factor2))throw new e.CrossauthError(e.ErrorCode.Forbidden,"Illegal second factor "+r.body.factor2+" requested");(r.body.factor2=="none"||r.body.factor2=="")&&(i=void 0);const d=await this.sessionServer.sessionManager.initiateTwoFactorSetup(o,i,r.sessionId);let t={factor2:i,userData:d,username:d.username,next:r.body.next??this.sessionServer.loginRedirect,csrfToken:r.csrfToken};return s(a,t)}async changePassword(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changePassword unless a user storage is provided");let o,i=!1;if(!this.sessionServer.isSessionUser(r)||!r.user){const f=await this.sessionServer.getSessionData(r,"passwordchange");if(f!=null&&f.username){if(o=(await this.sessionServer.userStorage.getUserByUsername(f==null?void 0:f.username,{skipActiveCheck:!0,skipEmailVerifiedCheck:!0})).user,i=!0,!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf)}else throw new e.CrossauthError(e.ErrorCode.Unauthorized)}else if(this.sessionServer.canEditUser(r)){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);o=r.user}else throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const d=this.sessionServer.authenticators[o.factor1],t=d.secretNames();let n={},h={},g={};for(let f in r.body)if(f.startsWith("new_")){const p=f.replace(/^new_/,"");t.includes(p)&&(h[p]=r.body[f])}else if(f.startsWith("old_")){const p=f.replace(/^old_/,"");t.includes(p)&&(n[p]=r.body[f])}else if(f.startsWith("repeat_")){const p=f.replace(/^repeat_/,"");t.includes(p)&&(g[p]=r.body[f])}if(Object.keys(g).length===0&&(g=void 0),d.validateSecrets(h).length>0)throw new e.CrossauthError(e.ErrorCode.PasswordFormat);const C=o.state;try{i&&(o.state="active",await this.sessionServer.userStorage.updateUser({id:o.id,state:o.state})),await this.sessionServer.sessionManager.changeSecrets(o.username,1,h,g,n)}catch(f){const p=e.CrossauthError.asCrossauthError(f);if(e.CrossauthLogger.logger.debug(e.j({err:f})),i)try{await this.sessionServer.userStorage.updateUser({id:o.id,state:C})}catch(E){e.CrossauthLogger.logger.debug(e.j({err:E}))}throw p}return i?await this.sessionServer.loginWithUser(o,!1,r,a,s):s(a,void 0)}async configureFactor2(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"No session active while enabling 2FA. Please enable cookies");let o=await this.sessionServer.sessionManager.completeTwoFactorSetup(r.body,r.sessionId);return!this.sessionServer.isSessionUser(r)&&!this.enableEmailVerification?this.sessionServer.loginWithUser(o,!0,r,a,(i,d)=>s(i,d)):s(a,o)}async reconfigureFactor2(r,a,s){if(!r.user||!r.sessionId||!this.sessionServer.isSessionUser(r))throw new e.CrossauthError(e.ErrorCode.Unauthorized);let o=r.user.factor2;const i=this.sessionServer.authenticators[o];if(!i||i.secretNames().length==0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"Selected second factor does not have configuration");let t={...await this.sessionServer.sessionManager.initiateTwoFactorSetup(r.user,o,r.sessionId),csrfToken:r.csrfToken};return s(a,t)}async requestPasswordReset(r,a,s){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"password reset not enabled");if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=r.body.email;try{await this.sessionServer.sessionManager.requestPasswordReset(o)}catch(i){e.CrossauthError.asCrossauthError(i).code==e.ErrorCode.UserNotExist?e.CrossauthLogger.logger.warn(e.j({msg:"Password reset requested for invalid email",email:r.body.email})):e.CrossauthLogger.logger.debug(e.j({err:i,msg:"Couldn't send password reset email"}))}return s(a,void 0)}async resetPassword(r,a,s){if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=r.body.token,i=await this.sessionServer.sessionManager.userForPasswordResetToken(o),d=this.sessionServer.authenticators[i.factor1],t=d.secretNames();let n={},h={};for(let C in r.body)if(C.startsWith("new_")){const f=C.replace(/^new_/,"");t.includes(f)&&(n[f]=r.body[C])}else if(C.startsWith("repeat_")){const f=C.replace(/^repeat_/,"");t.includes(f)&&(h[f]=r.body[C])}if(Object.keys(h).length===0&&(h=void 0),d.validateSecrets(n).length>0)throw new e.CrossauthError(e.ErrorCode.PasswordFormat);const u=await this.sessionServer.sessionManager.resetSecret(o,1,n,h);return u.state!=e.UserState.factor2ResetNeeded?this.sessionServer.loginWithUser(u,!0,r,a,s):s(a)}async verifyEmail(r,a,s){if(!this.enableEmailVerification)throw new e.CrossauthError(e.ErrorCode.Configuration,"Email verification reset not enabled");const o=r.params.token,i=await this.sessionServer.sessionManager.applyEmailVerificationToken(o);return await this.sessionServer.loginWithUser(i,!0,r,a,s)}async deleteUser(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call deleteUser unless a user storage is provided");if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return await this.sessionServer.userStorage.deleteUserById(r.user.id),s(a)}}async function _e(m,r){let a=[];try{const{user:s}=await r.getUserByUsername(m);a.push(s)}catch(s){const o=e.CrossauthError.asCrossauthError(s);if(o.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:o})),o;try{const{user:i}=await r.getUserByEmail(m);a.push(i)}catch(i){const d=e.CrossauthError.asCrossauthError(i);if(d.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:d})),o}}return a}const x=["Content-Type","application/json; charset=utf-8"];class ge{constructor(r,a={}){l(this,"sessionServer");l(this,"adminPrefix","/admin/");l(this,"userSearchFn",_e);l(this,"enableOAuthClientManagement",!1);l(this,"adminCreateUserPage","admin/createuser.njk");l(this,"adminSelectUserPage","admin/selectuser.njk");l(this,"adminUpdateUserPage","admin/updateuser.njk");l(this,"adminChangePasswordPage","admin/changepassword.njk");l(this,"deleteUserPage","deleteuser.njk");this.sessionServer=r,c.setParameter("adminPrefix",c.ParamType.String,this,a,"ADMIN_PREFIX"),c.setParameter("adminCreateUserPage",c.ParamType.String,this,a,"ADMIN_CREATE_USER_PAGE"),c.setParameter("adminSelectUserPage",c.ParamType.String,this,a,"ADMIN_SELECT_USER_PAGE"),c.setParameter("adminUpdateUserPage",c.ParamType.String,this,a,"ADMIN_UPDATE_USER_PAGE"),c.setParameter("adminChangePasswordPage",c.ParamType.String,this,a,"ADMIN_CHANGE_PASSWORD_PAGE"),c.setParameter("enableOAuthClientManagement",c.ParamType.Boolean,this,a,"ENABLE_OAUTH_CLIENT_MANAGEMENT"),c.setParameter("deleteUserPage",c.ParamType.String,this,a,"DELETE_USER_PAGE"),this.adminPrefix.endsWith("/")||(this.adminPrefix+="/"),this.adminPrefix.startsWith("/")||""+this.adminPrefix,a.userSearchFn&&(this.userSearchFn=a.userSearchFn)}addCreateUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"createuser",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"createuser",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);let s={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,allowedFactor2:this.sessionServer.allowedFactor2Details()};return r.query.next&&(s.next=r.query.next),a.view(this.adminCreateUserPage,s)}),this.sessionServer.app.post(this.adminPrefix+"createuser",async(r,a)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"createuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.adminPrefix;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.createUser(r,a,(i,d,t)=>i.redirect(302,s))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Signup failure",user:r.body.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{const g=e.CrossauthError.asCrossauthError(i).httpStatus;return t.status(g).view(this.adminCreateUserPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,csrfToken:r.csrfToken,factor2:r.body.factor2,allowedFactor2:this.sessionServer.allowedFactor2Details(),urlPrefix:this.adminPrefix,...r.body})})}})}addApiCreateUserEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/createuser",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/createuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.createUser(r,a,(i,d,t)=>i.header(...x).send({ok:!0,user:t,...d.userData}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Create user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...x).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addSelectUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"selectuser",async(r,a)=>{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call selectuser unless a user storage is provided");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"selectuser",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{let s=[],o=Number(r.query.skip),i=Number(r.query.take);o<0&&(i=-o,o=0),o||(o=0),i||(i=10),r.query.search?s=await this.userSearchFn(r.query.search,this.sessionServer.userStorage):s=await this.sessionServer.userStorage.getUsers(o,i);let d={urlPrefix:this.adminPrefix,skip:o,take:i,users:s,havePrevious:o>0,haveNext:i!=null&&s.length==i};return r.query.next&&(d.next=r.query.next),a.view(this.adminSelectUserPage,d)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({err:s})),v.sendPageError(a,o.httpStatus,this.sessionServer.errorPage,o.message,o)}})}addUpdateUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"updateuser/:id",async(r,a)=>{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateuser unless a user storage is provided");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"updateuser",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{const{user:s}=await this.sessionServer.userStorage.getUserById(r.params.id);let o={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,user:s,allowedFactor2:this.sessionServer.allowedFactor2Details(),enableOAuthClientManagement:this.enableOAuthClientManagement};return a.view(this.adminUpdateUserPage,o)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({err:s})),v.sendPageError(a,o.httpStatus,this.sessionServer.errorPage,o.message,o)}}),this.sessionServer.app.post(this.adminPrefix+"updateuser/:id",async(r,a)=>{var o;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"updateuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.sessionServer.canEditUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage);let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateuser unless a user storage is provided");const{user:i}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=i,await this.updateUser(s,r,a,(d,t,n,h)=>{let g="User's details have been updated.";return n?g="User's details have been updated and sent and an email verification link.":h&&(g="User's details have been updated and sent and a password reset token sent."),d.view(this.adminUpdateUserPage,{csrfToken:r.csrfToken,message:g,urlPrefix:this.adminPrefix,allowedFactor2:this.sessionServer.allowedFactor2Details()})})}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:r.body.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>s?t.view(this.adminUpdateUserPage,{user:s,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,allowedFactor2:this.sessionServer.allowedFactor2Details(),...r.body}):v.sendPageError(t,d.httpStatus,this.sessionServer.errorPage,d.message,d))}})}addDeleteUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"deleteuser/:id",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"deleteclient",ip:r.ip}));let s;if(!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call deleteuser unless a user storage is provided");s=(await this.sessionServer.userStorage.getUserById(r.params.id)).user}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??this.adminPrefix+"selectuser";let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,next:o,isAdmin:!0,user:s};return a.view(this.deleteUserPage,i)}),this.sessionServer.app.post(this.adminPrefix+"deleteuser/:id",async(r,a)=>{var o,i;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"deleteuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));const s=r.body.next??this.adminPrefix+"selectuser";try{return await this.deleteUser(r,a,d=>d.view(this.deleteUserPage,{message:"User deleted",csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,userid:r.params.id,isAdmin:!0,next:s}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting user",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteUserPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,userid:r.params.id,isAdmin:!0,next:s})})}})}addApiUpdateUserEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/updateuser/:id",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/updateuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user||!v.isAdmin(r.user))return this.sessionServer.sendJsonError(a,401);let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateuser unless a user storage is provided");const{user:d}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=d,await this.updateUser(s,r,a,(t,n,h)=>t.header(...x).send({ok:!0,emailVerificationRequired:h}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{n.status(this.sessionServer.errorStatus(d)).header(...x).send({ok:!1,errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})},!0)}})}addChangePasswordEndpoints(){this.sessionServer.app.get(this.adminPrefix+"changepassword/:id",async(r,a)=>{var s;if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changepassword unless a user storage is provided");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"changepassword",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{const{user:o}=await this.sessionServer.userStorage.getUserById(r.params.id);let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,user:o};return a.view(this.adminChangePasswordPage,i)}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({err:o})),v.sendPageError(a,i.httpStatus,this.sessionServer.errorPage,i.message,i)}}),this.sessionServer.app.post(this.adminPrefix+"changepassword/:id",async(r,a)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"changepassword",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changepassword unless a user storage is provided");const{user:i}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=i,await this.changePassword(s,r,a,(d,t)=>r.body.next?d.redirect(r.body.next):d.view(this.adminChangePasswordPage,{csrfToken:r.csrfToken,message:"User's password has been changed.",urlPrefix:this.adminPrefix,next:r.body.next,required:r.body.required,user:s}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change password failure",userid:r.params.id,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.view(this.adminChangePasswordPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix}))}})}addApiChangePasswordEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/changepassword/:id",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/changepassword",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user||!v.isAdmin(r.user))return this.sessionServer.sendJsonError(a,401);let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changepassword unless a user storage is provided");const{user:d}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=d,await this.changePassword(s,r,a,(t,n)=>t.header(...x).send({ok:!0}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{n.status(this.sessionServer.errorStatus(d)).header(...x).send({ok:!1,errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})},!0)}})}addApiDeleteUserEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/deleteuser/:id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/deleteuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.deleteUser(r,a,i=>i.header(...x).send({ok:!0,client_id:r.params.id}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...x).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async createUser(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createUser unless a user storage is provided");if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);if(r.body.factor2||(r.body.factor2=this.sessionServer.allowedFactor2[0]),r.body.factor2&&!this.sessionServer.allowedFactor2.includes(r.body.factor2??"none"))throw new e.CrossauthError(e.ErrorCode.Forbidden,"Illegal second factor "+r.body.factor2+" requested");(r.body.factor2=="none"||r.body.factor2=="")&&(r.body.factor2=void 0);let o=this.sessionServer.createUserFn(r,{...this.sessionServer.userStorage.userEditableFields,...this.sessionServer.userStorage.adminEditableFields},this.sessionServer.adminAllowedFactor1);const i=this.sessionServer.authenticators[o.factor1].secretNames();let d=!0;for(let C of i)!r.body[C]&&!r.body["repeat_"+C]&&(d=!1);let t=[],n={};if(d){t=this.sessionServer.authenticators[o.factor1].validateSecrets(r.body);for(let C in r.body)if(C.startsWith("repeat_")){const f=C.replace(/^repeat_/,"");i.includes(f)&&(n[f]=r.body[C])}Object.keys(n).length===0&&(n=void 0)}d?o.factor2&&o.factor2!="none"&&(o.state=e.UserState.factor2ResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.factor2ResetNeeded}`,username:o.username}))):o.factor2&&o.factor2!="none"?(o.state=e.UserState.passwordAndFactor2ResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.passwordAndFactor2ResetNeeded}`,username:o.username}))):(o.state=e.UserState.passwordResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.passwordResetNeeded}`,username:o.username})));let g=[...this.sessionServer.validateUserFn(o),...t];if(g.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,g);const u=await this.sessionServer.sessionManager.createUser(o,r.body,n,!0,!d);if(!d){let C=r.body.username;if("user_email"in r.body){const f=r.body.user_email;typeof f=="string"&&(C=f)}if(c.TokenEmailer.validateEmail(C),!C)throw new e.CrossauthError(e.ErrorCode.FormEntry,"No password given but no email address found either");await this.sessionServer.sessionManager.requestPasswordReset(C)}return s(a,{},u)}async accessDeniedPage(r,a){const s=new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return this.sessionServer.handleError(s,r,a,(o,i)=>o.status(s.httpStatus).view(this.sessionServer.errorPage,{errorMessage:i.message,errorMessages:i.messages,errorCode:i.code,errorCodeName:e.ErrorCode[i.code]}))}async updateUser(r,a,s,o){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateUser unless a user storage is provided");if(!a.user||!v.isAdmin(a.user))throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(this.sessionServer.isSessionUser(a)&&!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const i=r.factor2,d=r.state;r.state=a.body.state,r=this.sessionServer.updateUserFn(r,a,{...this.sessionServer.userStorage.userEditableFields,...this.sessionServer.userStorage.adminEditableFields});const t=r.factor2&&r.factor2!="none"&&r.factor2!=i;if(t&&!(r.state==d||r.state=="factor2ResetNeeded"))throw new e.CrossauthError(e.ErrorCode.BadRequest,"Cannot change both factor2 and state at the same time");t&&(r.state=e.UserState.factor2ResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.factor2ResetNeeded}`,username:r.username})));let n=this.sessionServer.validateUserFn(r);if(n.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,n);let h=await this.sessionServer.sessionManager.updateUser(r,r,!0);return o(s,a.user,h.emailVerificationTokenSent,h.passwordResetTokenSent)}async changePassword(r,a,s,o){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateUser unless a user storage is provided");if(!a.user||!v.isAdmin(a.user))throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(this.sessionServer.isSessionUser(a)&&!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const i=this.sessionServer.authenticators[r.factor1],d=i.secretNames();let t={},n={};for(let g in a.body)if(g.startsWith("new_")){const u=g.replace(/^new_/,"");d.includes(u)&&(t[u]=a.body[g])}else if(g.startsWith("repeat_")){const u=g.replace(/^repeat_/,"");d.includes(u)&&(n[u]=a.body[g])}if(Object.keys(n).length===0&&(n=void 0),i.validateSecrets(t).length>0)throw new e.CrossauthError(e.ErrorCode.PasswordFormat);return r.state="active",await this.sessionServer.userStorage.updateUser({id:r.id,state:r.state}),await this.sessionServer.sessionManager.changeSecrets(r.username,1,t,n),o(s,void 0)}async deleteUser(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call deleteUser unless a user storage is provided");if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return await this.sessionServer.userStorage.deleteUserById(r.params.id),s(a)}}async function ce(m,r,a){let s=[];try{const o=await r.getClientById(m);s.push(o)}catch(o){const i=e.CrossauthError.asCrossauthError(o);if(i.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:i})),i;try{s=await r.getClientByName(m,a)}catch(d){const t=e.CrossauthError.asCrossauthError(d);if(t.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:t})),i}}return s}const j=["Content-Type","application/json; charset=utf-8"];class le{constructor(r,a={}){l(this,"sessionServer");l(this,"clientStorage");l(this,"clientManager");l(this,"adminPrefix","/admin/");l(this,"clientSearchFn",ce);l(this,"validFlows",["all"]);l(this,"selectClientPage","selectclient.njk");l(this,"createClientPage","createclient.njk");l(this,"updateClientPage","updateclient.njk");l(this,"deleteClientPage","deleteclient.njk");if(this.sessionServer=r,!a.clientStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Must specify clientStorage if adding OAuth client endpoints");this.clientManager=new c.OAuthClientManager(a),this.clientStorage=a.clientStorage,c.setParameter("adminPrefix",c.ParamType.String,this,a,"ADMIN_PREFIX"),c.setParameter("createClientPage",c.ParamType.String,this,a,"CREATE_CLIENT_PAGE"),c.setParameter("updateClientPage",c.ParamType.String,this,a,"UPDATE_CLIENT_PAGE"),c.setParameter("selectClientPage",c.ParamType.String,this,a,"SELECT_CLIENT_PAGE"),c.setParameter("deleteClientPage",c.ParamType.String,this,a,"DELETE_CLIENT_PAGE"),c.setParameter("validFlows",c.ParamType.JsonArray,this,a,"OAUTH_validFlows"),this.validFlows.length==1&&this.validFlows[0]==e.OAuthFlows.All&&(this.validFlows=e.OAuthFlows.allFlows()),a.clientSearchFn&&(this.clientSearchFn=a.clientSearchFn)}addSelectClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"selectclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"selectclient",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);const s=r.query.next??encodeURIComponent(r.url);try{let o=[],i=Number(r.query.skip),d=Number(r.query.take);i||(i=0),d||(d=10);let t=null,n;if(r.query.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call selectclient with user unless a user storage is provided");n=(await this.sessionServer.userStorage.getUserById(r.query.userid)).user,t=n.id}r.query.search?o=await this.clientSearchFn(r.query.search,this.clientStorage,t):o=await this.clientStorage.getClients(i,d,t);let h={urlPrefix:this.adminPrefix,user:n,skip:i,take:d,clients:o,havePrevious:i>0,haveNext:d!=null&&o.length==d,isAdmin:!0,next:s};return r.query.next&&(h.next=r.query.next),a.view(this.selectClientPage,h)}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({err:o})),v.sendPageError(a,i.httpStatus,this.sessionServer.errorPage,i.message,i)}})}addCreateClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"createclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"createclient",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);let s=r.query.next;s||(r.query.userid?s=this.adminPrefix+"selectuser":s=this.adminPrefix+"selectclient");let o;try{if(r.query.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createclient unless a user storage is provided");o=(await this.sessionServer.userStorage.getUserById(r.query.userid)).user}}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s};return a.view(this.createClientPage,i)}),this.sessionServer.app.post(this.adminPrefix+"createclient",async(r,a)=>{var i,d;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"createclient",ip:r.ip,user:(i=r.user)==null?void 0:i.username}));let s=r.body.next;s||(r.body.userid?s=this.adminPrefix+"selectuser":s=this.adminPrefix+"selectclient");let o;try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createclient unless a user storage is provided");o=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user}return await this.createClient(r,a,(t,n)=>t.view(this.createClientPage,{message:"Created client",client:n,csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s,...r.body}),o)}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.error(e.j({msg:"Failed creating OAuth client",user:(d=r.user)==null?void 0:d.username,errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.debug(e.j({err:t})),this.sessionServer.handleError(t,r,a,(h,g)=>{const C=e.CrossauthError.asCrossauthError(t).httpStatus;return h.status(C).view(this.createClientPage,{errorMessage:g.message,errorMessages:g.messages,errorCode:g.code,errorCodeName:e.ErrorCode[g.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),isAdmin:!0,next:s,...r.body})})}})}addUpdateClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"updateclient/:client_id",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"updateclient",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);let s;try{s=await this.clientStorage.getClientById(r.params.client_id)}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let o=r.query.next;o||(r.query.userid?o=this.adminPrefix+"selectuser":o=this.adminPrefix+"selectclient");let i;try{if(s.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient with user unless a user storage is provided");i=(await this.sessionServer.userStorage.getUserById(s.userid)).user}}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let d={};for(let n of this.validFlows)s.valid_flow.includes(n)&&(d[n]=!0);let t={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),selectedFlows:d,user:i,client_id:s.client_id,client_name:s.client_name,confidential:s.confidential,redirect_uris:s.redirect_uri.join(" "),isAdmin:!0,next:o};return a.view(this.updateClientPage,t)}),this.sessionServer.app.post(this.adminPrefix+"updateclient/:client_id",async(r,a)=>{var i,d;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"updateclient",ip:r.ip,user:(i=r.user)==null?void 0:i.username}));let s=r.body.next;s||(r.body.userid?s=this.adminPrefix+"selectuser":s=this.adminPrefix+"selectclient");let o;try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient with user unless a user storage is provided");o=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user}return await this.updateClient(r,a,(t,n,h)=>t.view(this.updateClientPage,{message:"Updated client",client:n,csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s,newSecret:h,...r.body}))}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(d=r.user)==null?void 0:d.username,errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.debug(e.j({err:t})),this.sessionServer.handleError(t,r,a,(h,g)=>{const C=e.CrossauthError.asCrossauthError(t).httpStatus;let f={};for(let p of this.validFlows)p in r.body&&(f[p]=!0);return h.status(C).view(this.updateClientPage,{errorMessage:g.message,errorMessages:g.messages,errorCode:g.code,errorCodeName:e.ErrorCode[g.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,isAdmin:!0,next:s,validFlows:this.validFlows,selectedFlows:f,flowNames:e.OAuthFlows.flowNames(this.validFlows),...r.body})})}})}addDeleteClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"deleteclient/:client_id",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"deleteclient",ip:r.ip}));let s;if(!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{s=await this.clientStorage.getClientById(r.params.client_id)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??this.adminPrefix+"selectclient";let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,next:o,client:s};return a.view(this.deleteClientPage,i)}),this.sessionServer.app.post(this.adminPrefix+"deleteclient/:client_id",async(r,a)=>{var o,i;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"deleteclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));const s=r.body.next??this.adminPrefix+"selectclient";try{return await this.deleteClient(r,a,d=>d.view(this.deleteClientPage,{message:"Client deleted",csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,client_id:r.params.client_id,next:s}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting OAuth client",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteClientPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,client_id:r.params.client_id,validFlows:this.validFlows,next:s})})}})}addApiCreateClientEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/createclient",async(r,a)=>{var o,i;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/createclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));let s;try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createclient with user unless a user storage is provided");s=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user}return await this.createClient(r,a,(d,t)=>d.header(...j).send({ok:!0,client:t}),s)}catch(d){const t=e.CrossauthError.asCrossauthError(d);e.CrossauthLogger.logger.error(e.j({msg:"Create client failure",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{n.status(this.sessionServer.errorStatus(d)).header(...j).send({ok:!1,errorMessage:h.message,errorMessages:h.messages,errorCode:e.ErrorCode[h.code]})})}})}addApiUpdateClientEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/updateclient/:client_id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"api/updateclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient with user unless a user storage is provided");await this.sessionServer.userStorage.getUserById(r.body.userid)}return await this.updateClient(r,a,(i,d,t)=>i.header(...j).send({ok:!0,client:d,csrfToken:r.csrfToken,newSecret:t}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...j).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})})}})}addApiDeleteClientEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/deleteclient/:client_id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/deleteclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.deleteClient(r,a,i=>i.header(...j).send({ok:!0,client_id:r.params.client_id}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete client failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...j).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async accessDeniedPage(r,a){const s=new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return this.sessionServer.handleError(s,r,a,(o,i)=>o.status(s.httpStatus).view(this.sessionServer.errorPage,{errorMessage:i.message,errorMessages:i.messages,errorCode:i.code,errorCodeName:e.ErrorCode[i.code]}))}async createClient(r,a,s,o){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const i=r.body.confidential=="true",d=r.body.client_name,t=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let n=[];for(let u of t)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),n.push("["+u+"]")}if(n.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+n.join(" "));let h=[];for(let u of this.validFlows)u in r.body&&h.push(u);const g=await this.clientManager.createClient(d,t,h,i,o==null?void 0:o.id);return s(a,g)}async updateClient(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const o=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let i=[];for(let u of o)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),i.push("["+u+"]")}if(i.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+i.join(" "));let d=[];for(let u of this.validFlows)u in r.body&&d.push(u);const t={};t.client_name=r.body.client_name,t.confidential=r.body.confidential=="true",t.valid_flow=d,t.redirect_uri=o,t.userid=r.body.userid,t.userid==null&&(t.userid=null);const n=r.body.resetSecret=="true",{client:h,newSecret:g}=await this.clientManager.updateClient(r.params.client_id,t,n);return s(a,h,g)}async deleteClient(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return await this.clientStorage.deleteClient(r.params.client_id),s(a)}}const F=["Content-Type","application/json; charset=utf-8"];class ue{constructor(r,a={}){l(this,"sessionServer");l(this,"clientStorage");l(this,"clientManager");l(this,"prefix","/");l(this,"clientSearchFn",ce);l(this,"validFlows",["all"]);l(this,"selectClientPage","selectclient.njk");l(this,"createClientPage","createclient.njk");l(this,"updateClientPage","updateclient.njk");l(this,"deleteClientPage","deleteclient.njk");if(this.sessionServer=r,!a.clientStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Must specify clientStorage if adding OAuth client endpoints");this.clientManager=new c.OAuthClientManager(a),this.clientStorage=a.clientStorage,c.setParameter("prefix",c.ParamType.String,this,a,"PREFIX"),c.setParameter("createClientPage",c.ParamType.String,this,a,"CREATE_CLIENT_PAGE"),c.setParameter("updateClientPage",c.ParamType.String,this,a,"UPDATE_CLIENT_PAGE"),c.setParameter("selectClientPage",c.ParamType.String,this,a,"SELECT_CLIENT_PAGE"),c.setParameter("deleteClientPage",c.ParamType.String,this,a,"DELETE_CLIENT_PAGE"),c.setParameter("validFlows",c.ParamType.JsonArray,this,a,"OAUTH_validFlows"),this.validFlows.length==1&&this.validFlows[0]==e.OAuthFlows.All&&(this.validFlows=e.OAuthFlows.allFlows()),a.clientSearchFn&&(this.clientSearchFn=a.clientSearchFn)}addSelectClientEndpoints(){this.sessionServer.app.get(this.prefix+"selectclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"selectclient",ip:r.ip})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"selectclient");try{let s=[],o=Number(r.query.skip),i=Number(r.query.take);o||(o=0),i||(i=10),r.query.search?s=await this.clientSearchFn(r.query.search,this.clientStorage,r.user.id):s=await this.clientStorage.getClients(o,i,r.user.id);const d=r.query.next??encodeURIComponent(r.url);let t={urlPrefix:this.prefix,user:r.user,skip:o,take:i,clients:s,havePrevious:o>0,haveNext:i!=null&&s.length==i,isAdmin:!1,next:d};return r.query.next&&(t.next=r.query.next),a.view(this.selectClientPage,t)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({err:s})),v.sendPageError(a,o.httpStatus,this.sessionServer.errorPage,o.message,o)}})}addCreateClientEndpoints(){this.sessionServer.app.get(this.prefix+"createclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"createclient",ip:r.ip})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"createclient");const s=r.query.next??"/";let o={urlPrefix:this.prefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:r.user,isAdmin:!1,next:s};return a.view(this.createClientPage,o)}),this.sessionServer.app.post(this.prefix+"createclient",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"createclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+encodeURIComponent(r.url));const s=r.body.next??"/";try{return await this.createClient(r,a,(d,t)=>d.view(this.createClientPage,{message:"Created client",client:t,csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:r.user,isAdmin:!0,next:s,...r.body}),r.user)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed creating OAuth client",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.createClientPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),isAdmin:!1,next:s,...r.body})})}})}addApiCreateClientEndpoints(){this.sessionServer.app.post(this.prefix+"api/createclient",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/createclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user)return a.status(401).header(...F).send({ok:!1});try{return await this.createClient(r,a,(i,d)=>i.header(...F).send({ok:!0,client:d}),r.user)}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Create client failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...F).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addUpdateClientEndpoints(){this.sessionServer.app.get(this.prefix+"updateclient/:client_id",async(r,a)=>{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"updateclient",ip:r.ip})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"createclient");let s;try{s=await this.clientStorage.getClientById(r.params.client_id)}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let o=r.query.next;o||(r.query.userid?o=this.prefix+"selectuser":o=this.prefix+"selectclient");let i;try{r.query.userid&&(i=(await this.sessionServer.userStorage.getUserById(r.query.userid)).user)}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let d={};for(let n of this.validFlows)s.valid_flow.includes(n)&&(d[n]=!0);let t={urlPrefix:this.prefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),selectedFlows:d,user:i,client_id:s.client_id,client_name:s.client_name,confidential:s.confidential,redirect_uris:s.redirect_uri.join(" "),isAdmin:!0,next:o};return a.view(this.updateClientPage,t)}),this.sessionServer.app.post(this.prefix+"updateclient/:client_id",async(r,a)=>{var i,d;if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"updateclient",ip:r.ip,user:(i=r.user)==null?void 0:i.username}));let s=r.body.next;s||(s=this.prefix+"selectuser");let o;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");return r.body.userid&&(o=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user),await this.updateClient(r,a,(t,n,h)=>t.view(this.updateClientPage,{message:"Updated client",client:n,csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s,newSecret:h,...r.body}))}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(d=r.user)==null?void 0:d.username,errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.debug(e.j({err:t})),this.sessionServer.handleError(t,r,a,(h,g)=>{const C=e.CrossauthError.asCrossauthError(t).httpStatus;let f={};for(let p of this.validFlows)p in r.body&&(f[p]=!0);return h.status(C).view(this.updateClientPage,{errorMessage:g.message,errorMessages:g.messages,errorCode:g.code,errorCodeName:e.ErrorCode[g.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,selectedFlows:f,flowNames:e.OAuthFlows.flowNames(this.validFlows),isAdmin:!0,next:s,...r.body})})}})}addApiUpdateClientEndpoints(){this.sessionServer.app.post(this.prefix+"api/updateclient/:client_id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"api/updateclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");return r.body.userid&&await this.sessionServer.userStorage.getUserById(r.body.userid),await this.updateClient(r,a,(i,d,t)=>i.header(...F).send({ok:!0,client:d,csrfToken:r.csrfToken,newSecret:t}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...F).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})})}})}addDeleteClientEndpoints(){this.sessionServer.app.get(this.prefix+"deleteclient/:client_id",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"deleteclient",ip:r.ip}));let s;if(!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteclient/"+r.params.client_id);try{if(s=await this.clientStorage.getClientById(r.params.client_id),s.userid!=r.user.id)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges,"You may not delete this client")}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??"/";let i={urlPrefix:this.prefix,csrfToken:r.csrfToken,backUrl:this.prefix+"selectclient",client:s,next:o};return a.view(this.deleteClientPage,i)}),this.sessionServer.app.post(this.prefix+"deleteclient/:client_id",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"deleteclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteclient/"+r.params.client_id);const s=this.prefix+"selectclient";try{return await this.deleteClient(r,a,d=>d.view(this.deleteClientPage,{message:"Client deleted",csrfToken:r.csrfToken,urlPrefix:this.prefix,client_id:r.params.client_id,next:s}),r.user)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting OAuth client",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteClientPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,client_id:r.params.client_id,validFlows:this.validFlows,next:s})})}})}addApiDeleteClientEndpoints(){this.sessionServer.app.post(this.prefix+"api/deleteclient/:client_id",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/deleteclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user)return a.status(401).header(...F).send({ok:!1});try{return await this.deleteClient(r,a,i=>i.header(...F).send({ok:!0,client_id:r.params.client_id}),r.user)}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete client failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...F).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async createClient(r,a,s,o){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const i=r.body.confidential=="true",d=r.body.client_name,t=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let n=[];for(let u of t)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),n.push("["+u+"]")}if(n.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+n.join(" "));let h=[];r.body[e.OAuthFlows.AuthorizationCode]&&h.push(e.OAuthFlows.AuthorizationCode),r.body[e.OAuthFlows.AuthorizationCodeWithPKCE]&&h.push(e.OAuthFlows.AuthorizationCodeWithPKCE),r.body[e.OAuthFlows.ClientCredentials]&&h.push(e.OAuthFlows.ClientCredentials),r.body[e.OAuthFlows.RefreshToken]&&h.push(e.OAuthFlows.RefreshToken),r.body[e.OAuthFlows.DeviceCode]&&h.push(e.OAuthFlows.DeviceCode),r.body[e.OAuthFlows.Password]&&h.push(e.OAuthFlows.Password),r.body[e.OAuthFlows.PasswordMfa]&&h.push(e.OAuthFlows.PasswordMfa),r.body[e.OAuthFlows.OidcAuthorizationCode]&&h.push(e.OAuthFlows.OidcAuthorizationCode);const g=await this.clientManager.createClient(d,t,h,i,o==null?void 0:o.id);return s(a,g)}async updateClient(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const o=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let i=[];for(let u of o)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),i.push("["+u+"]")}if(i.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+i.join(" "));let d=[];for(let u of this.validFlows)u in r.body&&d.push(u);const t={};t.client_name=r.body.client_name,t.confidential=r.body.confidential=="true",t.valid_flow=d,t.redirect_uri=o,t.userid=r.user.id;const n=r.body.resetSecret=="true",{client:h,newSecret:g}=await this.clientManager.updateClient(r.params.client_id,t,n);return s(a,h,g)}async deleteClient(r,a,s,o){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!o)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);if((await this.clientStorage.getClientById(r.params.client_id)).userid!=o.id)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges,"You may not delete this client");return await this.clientStorage.deleteClient(r.params.client_id),s(a)}}const T=["Content-Type","application/json; charset=utf-8"],K=["login","logout","changepassword","updateuser","deleteuser"],Y=["admin/createuser","admin/changepassword","admin/selectuser","admin/updateuser","admin/changepassword","admin/deleteuser"],G=["admin/selectclient","admin/createclient","admin/deleteclient","admin/updateclient"],B=["selectclient","createclient","updateclient","deleteclient"],$=["api/login","api/logout","api/changepassword","api/userforsessionkey","api/getcsrftoken","api/updateuser","api/deleteuser"],X=["admin/api/createuser","admin/api/changepassword","admin/api/updateuser","admin/api/changepassword","admin/api/deleteuser"],V=["admin/api/createclient","admin/api/deleteclient","admin/api/updateclient"],W=["api/deleteclient","api/updateclient","api/createclient"],Q=["api/configurefactor2","api/loginfactor2","api/changefactor2","api/factor2","api/cancelfactor2"],Z=["verifyemail","emailverified"],q=["api/verifyemail"],ee=["requestpasswordreset","resetpassword"],re=["api/requestpasswordreset","api/resetpassword"],se=["signup"],oe=["api/signup"],te=["configurefactor2","loginfactor2","changefactor2","factor2"],xe=[...se,...oe,...K,...$,...Y,...X,...Z,...q,...ee,...re,...te,...Q],Fe=[...se,...oe,...K,...B,...$,...W,...Y,...G,...X,...V,...Z,...q,...ee,...re,...te,...Q];function Ue(m){let r=[];return m.username==null?r.push("Username must be given"):m.username.length<2?r.push("Username must be at least 2 characters"):m.username.length>254&&r.push("Username must be no longer than 254 characters"),r}function Ne(m,r,a){let o={username:m.body.username,state:"active"};const i=m.user&&v.isAdmin(m.user);for(let d in m.body){let t=d.replace(/^user_/,"");d.startsWith("user_")&&(i||r.includes(t))&&(o[t]=m.body[d])}return o.factor1="localpassword",a.includes(o.factor1)&&(o.factor1=m.body.factor1),o.factor2=m.body.factor2,o}function Le(m,r,a){const s=r.user&&v.isAdmin(r.user);for(let o in r.body){let i=o.replace(/^user_/,"");o.startsWith("user_")&&(s||a.includes(i))&&(m[i]=r.body[o])}return m}class fe{constructor(r,a,s,o={}){l(this,"app");l(this,"prefix","/");l(this,"loginUrl");l(this,"loginRedirect","/");l(this,"logoutRedirect","/");l(this,"errorPage","error.njk");l(this,"validateUserFn",Ue);l(this,"createUserFn",Ne);l(this,"updateUserFn",Le);l(this,"userStorage");l(this,"authenticators");l(this,"allowedFactor2",[]);l(this,"sessionManager");l(this,"endpoints",[]);l(this,"signupPage","signup.njk");l(this,"loginPage","login.njk");l(this,"factor2Page","factor2.njk");l(this,"configureFactor2Page","configurefactor2.njk");l(this,"addToSession");l(this,"validateSession");l(this,"userEndpoints");l(this,"adminEndpoints");l(this,"adminClientEndpoints");l(this,"userClientEndpoints");l(this,"enableEmailVerification",!0);l(this,"enablePasswordReset",!0);l(this,"enableAdminEndpoints",!1);l(this,"enableOAuthClientManagement",!1);l(this,"factor2ProtectedPageEndpoints",["/requestpasswordreset","/updateuser","/changepassword","/resetpassword","/changefactor2"]);l(this,"factor2ProtectedApiEndpoints",["/api/requestpasswordreset","/api/updateuser","/api/changepassword","/api/resetpassword","/api/changefactor2"]);l(this,"editUserScope");l(this,"enableCsrfProtection",!0);l(this,"userAllowedFactor1",["localpassword"]);l(this,"adminAllowedFactor1",["localpassword"]);this.app=r,this.userEndpoints=new he(this,o),this.adminEndpoints=new ge(this,o),c.setParameter("prefix",c.ParamType.String,this,o,"PREFIX"),this.prefix.endsWith("/")||(this.prefix+="/"),this.prefix.startsWith("/")||""+this.prefix,this.loginUrl=this.prefix+"login",c.setParameter("signupPage",c.ParamType.String,this,o,"SIGNUP_PAGE"),c.setParameter("loginPage",c.ParamType.String,this,o,"LOGIN_PAGE"),c.setParameter("factor2Page",c.ParamType.String,this,o,"FACTOR2_PAGE"),c.setParameter("configureFactor2Page",c.ParamType.String,this,o,"SIGNUP_FACTOR2_PAGE"),c.setParameter("errorPage",c.ParamType.String,this,o,"ERROR_PAGE"),c.setParameter("emailFrom",c.ParamType.String,this,o,"EMAIL_FROM"),c.setParameter("allowedFactor2",c.ParamType.JsonArray,this,o,"ALLOWED_FACTOR2"),c.setParameter("enableEmailVerification",c.ParamType.Boolean,this,o,"ENABLE_EMAIL_VERIFICATION"),c.setParameter("enablePasswordReset",c.ParamType.Boolean,this,o,"ENABLE_PASSWORD_RESET"),c.setParameter("factor2ProtectedPageEndpoints",c.ParamType.JsonArray,this,o,"FACTOR2_PROTECTED_PAGE_ENDPOINTS"),c.setParameter("factor2ProtectedApiEndpoints",c.ParamType.JsonArray,this,o,"FACTOR2_PROTECTED_API_ENDPOINTS"),c.setParameter("enableAdminEndpoints",c.ParamType.Boolean,this,o,"ENABLE_ADMIN_ENDPOINTS"),c.setParameter("enableOAuthClientManagement",c.ParamType.Boolean,this,o,"ENABLE_OAUTH_CLIENT_MANAGEMENT"),c.setParameter("editUserScope",c.ParamType.String,this,o,"EDIT_USER_SCOPE"),c.setParameter("userAllowedFactor1",c.ParamType.JsonArray,this,o,"USER_ALLOWED_FACTOR1"),c.setParameter("adminAllowedFactor1",c.ParamType.JsonArray,this,o,"ADMIN_ALLOWED_FACTOR1"),o.validateUserFn&&(this.validateUserFn=o.validateUserFn),o.createUserFn&&(this.createUserFn=o.createUserFn),o.updateUserFn&&(this.updateUserFn=o.updateUserFn),o.addToSession&&(this.addToSession=o.addToSession),o.validateSession&&(this.validateSession=o.validateSession),this.endpoints=[...se,...oe],this.endpoints=[...this.endpoints,...K,...$],this.enableAdminEndpoints&&(this.endpoints=[...this.endpoints,...Y,...X]),this.enableOAuthClientManagement&&(this.endpoints=[...this.endpoints,...B,...W,...G,...V]),this.enableEmailVerification&&(this.endpoints=[...this.endpoints,...Z,...q]),this.enablePasswordReset&&(this.endpoints=[...this.endpoints,...ee,...re]),o.endpoints&&(c.setParameter("endpoints",c.ParamType.JsonArray,this,o,"SESSION_ENDPOINTS"),this.endpoints.length==1&&this.endpoints[0]=="all"&&(this.endpoints=Fe),this.endpoints.length==1&&this.endpoints[0]=="allMinusOAuth"&&(this.endpoints=xe)),this.allowedFactor2.length>0&&(this.endpoints=[...this.endpoints,...te,...Q]);let i=!1;for(let t of this.endpoints)if(V.includes(t)||G.includes(t)){i=!0;break}i&&(this.adminClientEndpoints=new le(this,o));let d=!1;for(let t of this.endpoints)if(W.includes(t)||B.includes(t)){d=!0;break}d&&(this.userClientEndpoints=new ue(this,o)),this.addEndpoints(),c.setParameter("endpoints",c.ParamType.JsonArray,this,o,"ENDPOINTS"),o.userStorage&&(this.userStorage=o.userStorage),this.authenticators=s,this.sessionManager=new c.SessionManager(a,s,o),r.addHook("preHandler",async(t,n)=>{var C,f;e.CrossauthLogger.logger.debug(e.j({msg:"Getting session cookie"}));let h=this.getSessionCookieValue(t),g={};if(h)try{g.hashedSessionId=c.Crypto.hash(this.sessionManager.getSessionId(h))}catch{g.hashedSessionCookie=c.Crypto.hash(h)}e.CrossauthLogger.logger.debug(e.j({msg:"Getting csrf cookie"}));let u;try{u=this.getCsrfCookieValue(t),u&&this.sessionManager.validateCsrfCookie(u)}catch(p){e.CrossauthLogger.logger.warn(e.j({msg:"Invalid csrf cookie received",cerr:p,hashedCsrfCookie:this.getHashOfCsrfCookie(t)})),n.clearCookie(this.sessionManager.csrfCookieName),u=void 0}if(["GET","OPTIONS","HEAD"].includes(t.method))try{if(u){e.CrossauthLogger.logger.debug(e.j({msg:"Valid CSRF cookie - creating token"}));const p=await this.sessionManager.createCsrfFormOrHeaderValue(u);t.csrfToken=p}else{e.CrossauthLogger.logger.debug(e.j({msg:"Invalid CSRF cookie - recreating"}));const{csrfCookie:p,csrfFormOrHeaderValue:E}=await this.sessionManager.createCsrfToken();n.setCookie(p.name,p.value,p.options),t.csrfToken=E}n.header(this.sessionManager.csrfHeaderName,t.csrfToken)}catch(p){e.CrossauthLogger.logger.error(e.j({msg:"Couldn't create CSRF token",cerr:p,user:(C=t.user)==null?void 0:C.username,...g})),e.CrossauthLogger.logger.debug(e.j({err:p})),n.clearCookie(this.sessionManager.csrfCookieName)}else if(u)try{this.csrfToken(t,n)}catch(p){e.CrossauthLogger.logger.error(e.j({msg:"Couldn't create CSRF token",cerr:p,user:(f=t.user)==null?void 0:f.username,...g})),e.CrossauthLogger.logger.debug(e.j({err:p}))}if(h=this.getSessionCookieValue(t),h)try{const p=this.sessionManager.getSessionId(h);let{key:E,user:w}=await this.sessionManager.userForSessionId(p);this.validateSession&&this.validateSession(E,w,t),t.sessionId=p,t.user=w,t.authType="cookie",e.CrossauthLogger.logger.debug(e.j({msg:"Valid session id",user:w==null?void 0:w.username}))}catch{e.CrossauthLogger.logger.warn(e.j({msg:"Invalid session cookie received",hashOfSessionId:this.getHashOfSessionId(t)})),n.clearCookie(this.sessionManager.sessionCookieName)}}),r.addHook("preHandler",async(t,n)=>{var g,u,C;const h=this.getSessionCookieValue(t);if(h&&((g=t.user)!=null&&g.factor2)&&(this.factor2ProtectedPageEndpoints.includes(t.url)||this.factor2ProtectedApiEndpoints.includes(t.url))){const f=this.sessionManager.getSessionId(h);if(["GET","OPTIONS","HEAD"].includes(t.method)){const p=this.getSessionCookieValue(t);if(p){const E=this.sessionManager.getSessionId(p);if("pre2fa"in await this.sessionManager.dataForSessionId(E)){e.CrossauthLogger.logger.debug("Cancelling 2FA");try{await this.sessionManager.cancelTwoFactorPageVisit(E)}catch(y){e.CrossauthLogger.logger.debug(e.j({err:y})),e.CrossauthLogger.logger.error(e.j({msg:"Failed cancelling 2FA",cerr:y,user:(C=t.user)==null?void 0:C.username,hashOfSessionId:this.getHashOfSessionId(t)}))}}}}else{const p=await this.sessionManager.dataForSessionId(f);if("pre2fa"in p){e.CrossauthLogger.logger.debug("Completing 2FA");const w=[...this.authenticators[p.pre2fa.factor2].transientSecretNames()];let y={};for(let _ in t.body)w.includes(_)&&(y[_]=t.body[_]);let S;try{await this.sessionManager.completeTwoFactorPageVisit(y,f)}catch(_){S=e.CrossauthError.asCrossauthError(_),e.CrossauthLogger.logger.debug(e.j({err:_}));const A=e.CrossauthError.asCrossauthError(_);e.CrossauthLogger.logger.error(e.j({msg:S.message,cerr:_,user:t.body.username,errorCode:A.code,errorCodeName:A.codeName}))}if(t.body=p.pre2fa.body,S)if(S.code==e.ErrorCode.Expired){e.CrossauthLogger.logger.debug("Error - cancelling 2FA");try{await this.sessionManager.cancelTwoFactorPageVisit(f)}catch(_){e.CrossauthLogger.logger.error(e.j({msg:"Failed cancelling 2FA",cerr:_,user:(u=t.user)==null?void 0:u.username,hashOfSessionId:this.getHashOfSessionId(t)})),e.CrossauthLogger.logger.debug(e.j({err:_}))}t.body={...t.body,errorMessage:S.message,errorMessages:S.message,errorCode:""+S.code,errorCodeName:e.ErrorCode[S.code]}}else return this.factor2ProtectedPageEndpoints.includes(t.url)?n.redirect(this.prefix+"factor2?error="+e.ErrorCode[S.code]):n.status(S.httpStatus).send(JSON.stringify({ok:!1,errorMessage:S.message,errorMessages:S.messages,errorCode:S.code,errorCodeName:e.ErrorCode[S.code]}))}else return this.validateCsrfToken(t),e.CrossauthLogger.logger.debug("Starting 2FA"),this.sessionManager.initiateTwoFactorPageVisit(t.user,f,t.body,t.url.replace(/\?.*$/,"")),this.factor2ProtectedPageEndpoints.includes(t.url)?n.redirect(this.prefix+"factor2"):n.send(JSON.stringify({ok:!0,factor2Required:!0}))}}})}addEndpoints(){if(this.endpoints.includes("login")&&this.addLoginEndpoints(),this.endpoints.includes("loginfactor2")&&this.addLoginFactor2Endpoints(),this.endpoints.includes("factor2")&&this.addFactor2Endpoints(),this.endpoints.includes("signup")&&this.addSignupEndpoints(),this.endpoints.includes("configurefactor2")&&this.userEndpoints.addConfigureFactor2Endpoints(),this.endpoints.includes("changefactor2")&&this.userEndpoints.addChangeFactor2Endpoints(),this.endpoints.includes("changepassword")&&this.userEndpoints.addChangePasswordEndpoints(),this.endpoints.includes("updateuser")&&this.userEndpoints.addUpdateUserEndpoints(),this.endpoints.includes("requestpasswordreset")&&this.userEndpoints.addRequestPasswordResetEndpoints(),this.endpoints.includes("deleteuser")&&this.userEndpoints.addDeleteUserEndpoints(),this.endpoints.includes("resetpassword")){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"Password reset must be enabled for /resetpassword");this.userEndpoints.addResetPasswordEndpoints()}if(this.endpoints.includes("verifyemail")){if(!this.enableEmailVerification)throw new e.CrossauthError(e.ErrorCode.Configuration,"Email verification must be enabled for /verifyemail");this.userEndpoints.addVerifyEmailEndpoints()}if(this.endpoints.includes("logout")&&this.addLogoutEndpoints(),this.endpoints.includes("api/login")&&this.addApiLoginEndpoints(),this.endpoints.includes("api/loginfactor2")&&this.addApiLoginFactor2Endpoints(),this.endpoints.includes("api/cancelfactor2")&&this.addApiCancelFactor2Endpoints(),this.endpoints.includes("api/logout")&&this.addApiLogoutEndpoints(),this.endpoints.includes("api/signup")&&this.addApiSignupEndpoints(),this.endpoints.includes("api/configurefactor2")&&this.userEndpoints.addApiConfigureFactor2Endpoints(this.prefix),this.endpoints.includes("api/changepassword")&&this.userEndpoints.addApiChangePasswordEndpoints(),this.endpoints.includes("api/changefactor2")&&this.userEndpoints.addApiChangeFactor2Endpoints(),this.endpoints.includes("api/updateuser")&&this.userEndpoints.addApiUpdateUserEndpoints(),this.endpoints.includes("api/resetpassword")){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"Password reset must be enabled for /api/resetpassword");this.userEndpoints.addApiResetPasswordEndpoints()}if(this.endpoints.includes("api/requestpasswordreset")){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"Password reset must be enabled for /api/requestpasswordreset");this.userEndpoints.addApiRequestPasswordResetEndpoints()}if(this.endpoints.includes("api/verifyemail")){if(!this.enableEmailVerification)throw new e.CrossauthError(e.ErrorCode.Configuration,"Email verification must be enabled for /api/verifyemail");this.userEndpoints.addApiVerifyEmailEndpoints()}this.endpoints.includes("api/userforsessionkey")&&this.addApiUserForSessionKeyEndpoints(),this.endpoints.includes("api/getcsrftoken")&&this.addApiGetCsrfTokenEndpoints(),this.endpoints.includes("api/deleteuser")&&this.userEndpoints.addApiDeleteUserEndpoints(),this.userClientEndpoints&&(this.endpoints.includes("selectclient")&&this.userClientEndpoints.addSelectClientEndpoints(),this.endpoints.includes("createclient")&&this.userClientEndpoints.addCreateClientEndpoints(),this.endpoints.includes("deleteclient")&&this.userClientEndpoints.addDeleteClientEndpoints(),this.endpoints.includes("api/createclient")&&this.userClientEndpoints.addApiCreateClientEndpoints(),this.endpoints.includes("api/deleteclient")&&this.userClientEndpoints.addApiDeleteClientEndpoints(),this.endpoints.includes("updateclient")&&this.userClientEndpoints.addUpdateClientEndpoints(),this.endpoints.includes("api/updateclient")&&this.userClientEndpoints.addApiUpdateClientEndpoints()),this.endpoints.includes("admin/createuser")&&this.adminEndpoints.addCreateUserEndpoints(),this.endpoints.includes("admin/api/createuser")&&this.adminEndpoints.addApiCreateUserEndpoints(),this.endpoints.includes("admin/selectuser")&&this.adminEndpoints.addSelectUserEndpoints(),this.endpoints.includes("admin/updateuser")&&this.adminEndpoints.addUpdateUserEndpoints(),this.endpoints.includes("admin/api/updateuser")&&this.adminEndpoints.addApiUpdateUserEndpoints(),this.endpoints.includes("admin/changepassword")&&this.adminEndpoints.addChangePasswordEndpoints(),this.endpoints.includes("admin/api/changepassword")&&this.adminEndpoints.addApiChangePasswordEndpoints(),this.endpoints.includes("admin/deleteuser")&&this.adminEndpoints.addDeleteUserEndpoints(),this.endpoints.includes("admin/api/deleteuser")&&this.adminEndpoints.addApiDeleteUserEndpoints(),this.adminClientEndpoints&&(this.endpoints.includes("admin/selectclient")&&this.adminClientEndpoints.addSelectClientEndpoints(),this.endpoints.includes("admin/createclient")&&this.adminClientEndpoints.addCreateClientEndpoints(),this.endpoints.includes("admin/deleteclient")&&this.adminClientEndpoints.addDeleteClientEndpoints(),this.endpoints.includes("admin/updateclient")&&this.adminClientEndpoints.addUpdateClientEndpoints(),this.endpoints.includes("admin/api/createclient")&&this.adminClientEndpoints.addApiCreateClientEndpoints(),this.endpoints.includes("admin/api/deleteclient")&&this.adminClientEndpoints.addApiDeleteClientEndpoints(),this.endpoints.includes("admin/api/updateclient")&&this.adminClientEndpoints.addApiUpdateClientEndpoints())}addLoginEndpoints(){this.app.get(this.prefix+"login",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"login",ip:r.ip})),r.user)return a.redirect(r.query.next??this.loginRedirect);let s={urlPrefix:this.prefix,csrfToken:r.csrfToken};return r.query.next&&(s.next=r.query.next),a.view(this.loginPage,s)}),this.app.post(this.prefix+"login",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"login",ip:r.ip}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.loginRedirect;try{return await this.login(r,a,(o,i)=>{if(i.state==e.UserState.passwordChangeNeeded){if(this.endpoints.includes("changepassword"))return e.CrossauthLogger.logger.debug(e.j({msg:"Password change needed - sending redirect"})),o.redirect("/changepassword?required=true&next="+encodeURIComponent("login?next="+s));{const d=new e.CrossauthError(e.ErrorCode.PasswordChangeNeeded);return this.handleError(d,r,o,(t,n)=>t.view(this.loginPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}}else if(i.state==e.UserState.passwordResetNeeded||i.state==e.UserState.passwordAndFactor2ResetNeeded){e.CrossauthLogger.logger.debug(e.j({msg:"Password reset needed - sending error"}));const d=new e.CrossauthError(e.ErrorCode.PasswordResetNeeded);return this.handleError(d,r,o,(t,n)=>t.view(this.loginPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}else if(this.allowedFactor2.length>0&&(i.state==e.UserState.factor2ResetNeeded||!this.allowedFactor2.includes(i.factor2?i.factor2:"none"))){if(e.CrossauthLogger.logger.debug(e.j({msg:`Factor2 reset needed. Factor2 is ${i.factor2}, state is ${i.state}, allowed factor2 is [${this.allowedFactor2.join(", ")}]`,username:i.username})),this.endpoints.includes("changefactor2"))return e.CrossauthLogger.logger.debug(e.j({msg:"Factor 2 reset needed - sending redirect"})),o.redirect("/changefactor2?required=true&next="+encodeURIComponent("login?next="+s));{const d=new e.CrossauthError(e.ErrorCode.Factor2ResetNeeded);return this.handleError(d,r,o,(t,n)=>t.view(this.loginPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}}else{if(!i.factor2||i.factor2.length==0)return e.CrossauthLogger.logger.debug(e.j({msg:"Successful login - sending redirect"})),o.redirect(s);{let d={csrfToken:r.csrfToken,next:r.body.next??this.loginRedirect,persist:r.body.persist?"on":"",urlPrefix:this.prefix,factor2:i.factor2,action:"loginfactor2"};return o.view(this.factor2Page,d)}}})}catch(o){return e.CrossauthLogger.logger.debug(e.j({err:o})),this.handleError(o,r,a,(i,d)=>i.view(this.loginPage,{errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}})}addLoginFactor2Endpoints(){this.app.post(this.prefix+"loginfactor2",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"loginfactor2",ip:r.ip}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.loginRedirect;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.loginFactor2(r,a,(o,i)=>(e.CrossauthLogger.logger.debug(e.j({msg:"Successful login - sending redirect to"})),o.redirect(s)))}catch(o){e.CrossauthLogger.logger.debug(e.j({err:o}));let i;try{const d=r.sessionId?await this.sessionManager.dataForSessionId(r.sessionId):void 0;i=d==null?void 0:d.factor2}catch(d){e.CrossauthLogger.logger.error(e.j({err:d}))}return i&&i in this.authenticators?this.handleError(o,r,a,(d,t)=>d.view(this.factor2Page,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:r.body.next,persist:r.body.persist?"on":"",csrfToken:r.csrfToken,urlPrefix:this.prefix,factor2:i,action:"loginfactor2"})):this.handleError(o,r,a,(d,t)=>d.view(this.loginPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:r.body.next,persist:r.body.persist?"on":"",csrfToken:r.csrfToken,urlPrefix:this.prefix}))}})}addFactor2Endpoints(){this.app.get(this.prefix+"factor2",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"factor2",ip:r.ip})),!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"No session cookie present");const s=this.getSessionCookieValue(r),o=this.sessionManager.getSessionId(s??""),i=await this.sessionManager.dataForSessionId(o);if(!(i!=null&&i.pre2fa))throw new e.CrossauthError(e.ErrorCode.Unauthorized,"2FA not initiated");let d={urlPrefix:this.prefix,csrfToken:r.csrfToken,action:i.pre2fa.url,errorCodeName:r.query.error,factor2:i.pre2fa.factor2};return a.view(this.factor2Page,d)})}addSignupEndpoints(){this.app.get(this.prefix+"signup",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"signup",ip:r.ip}));let s={urlPrefix:this.prefix,csrfToken:r.csrfToken,allowedFactor2:this.allowedFactor2Details()};return r.query.next&&(s.next=r.query.next),a.view(this.signupPage,s)}),this.app.post(this.prefix+"signup",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"signup",ip:r.ip,user:r.body.username}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.loginRedirect;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.signup(r,a,(o,i,d)=>{var n,h;const t=(n=i==null?void 0:i.userData)!=null&&n.factor2?this.authenticators[i.userData.factor2]:void 0;return(h=i.userData)!=null&&h.factor2?o.view(this.configureFactor2Page,{csrfToken:i.csrfToken,...i.userData}):this.enableEmailVerification&&(t==null||t.skipEmailVerificationOnSignup()!=!0)?o.view(this.signupPage,{next:s,csrfToken:r.csrfToken,message:"Please check your email to finish signing up.",allowedFactor2:this.allowedFactor2Details(),urlPrefix:this.prefix,factor2:r.body.factor2,...i.userData}):o.redirect(this.loginRedirect)})}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({msg:"Signup failure",user:r.body.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.handleError(o,r,a,(d,t)=>{let n={};for(let h in r.body)h.startsWith("user_")&&(n[h]=r.body[h]);return d.view(this.signupPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,factor2:r.body.factor2,allowedFactor2:this.allowedFactor2Details(),urlPrefix:this.prefix,...n})})}})}addLogoutEndpoints(){this.app.post(this.prefix+"logout",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"logout",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.logout(r,a,i=>i.redirect(r.body.next?r.body.next:this.logoutRedirect))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Logout failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.handleError(i,r,a,(t,n)=>t.view(this.errorPage,{urlPrefix:this.prefix,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]}))}})}addApiLoginEndpoints(){this.app.post(this.prefix+"api/login",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/login",ip:r.ip})),r.user)return a.header(...T).send({ok:!1,user:r.user});try{return await this.login(r,a,(s,o)=>{if(o.state==e.UserState.passwordChangeNeeded){const i=new e.CrossauthError(e.ErrorCode.PasswordChangeNeeded);return this.handleError(i,r,s,(d,t)=>{d.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})})}else if(o.state==e.UserState.passwordResetNeeded||o.state==e.UserState.passwordAndFactor2ResetNeeded){const i=new e.CrossauthError(e.ErrorCode.PasswordResetNeeded);return this.handleError(i,r,s,(d,t)=>{d.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})})}else if(this.allowedFactor2.length>0&&(o.state==e.UserState.factor2ResetNeeded||!this.allowedFactor2.includes(o.factor2?o.factor2:"none"))){const i=new e.CrossauthError(e.ErrorCode.Factor2ResetNeeded);return this.handleError(i,r,s,(d,t)=>{d.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})})}else return o.twoFactorRequired?s.header(...T).send({ok:!0,twoFactorRequired:!0}):s.header(...T).send({ok:!0,user:o})})}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Login failure",user:r.body.username,errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.handleError(s,r,a,(i,d)=>{i.status(this.errorStatus(s)).header(...T).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})})}})}addApiCancelFactor2Endpoints(){this.app.post(this.prefix+"api/cancelfactor2",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/cancelfactor2",ip:r.ip})),r.user)return a.header(...T).send({ok:!1,user:r.user});try{return await this.cancelFactor2(r,a,s=>s.header(...T).send({ok:!0}))}catch(s){const i=r.user||"",d=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Login failure",user:i,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.handleError(s,r,a,(t,n)=>{t.status(this.errorStatus(s)).header(...T).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})})}})}addApiLoginFactor2Endpoints(){this.app.post(this.prefix+"api/loginfactor2",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/loginfactor2",ip:r.ip})),r.user)return a.header(...T).send({ok:!1,user:r.user});try{return await this.loginFactor2(r,a,(s,o)=>s.header(...T).send({ok:!0,user:o}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Login failure",hashOfSessionId:this.getHashOfSessionId(r),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.handleError(s,r,a,(i,d)=>i.status(this.errorStatus(s)).header(...T).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]}))}})}addApiLogoutEndpoints(){this.app.post(this.prefix+"api/logout",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/logout",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.canEditUser(r))return this.sendJsonError(a,401,"You are not authorized to access this url");try{return await this.logout(r,a,i=>i.header(...T).send({ok:!0}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Logout failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.handleError(i,r,a,(t,n)=>{t.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addApiSignupEndpoints(){this.app.post(this.prefix+"api/signup",async(r,a)=>{var s;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/signup",ip:r.ip,user:r.body.username}));try{return await this.signup(r,a,(o,i,d)=>o.header(...T).send({ok:!0,user:d,emailVerificationNeeded:this.enableEmailVerification??!1,...i.userData}))}catch(o){const i=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.error(e.j({msg:"Signup failure",user:(s=r.user)==null?void 0:s.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.handleError(o,r,a,(d,t)=>{d.status(this.errorStatus(o)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:e.ErrorCode[t.code]})})}})}addApiUserForSessionKeyEndpoints(){this.app.post(this.prefix+"api/userforsessionkey",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/userforsessionkey",ip:r.ip,user:(s=r.user)==null?void 0:s.username,hashOfSessionId:this.getHashOfSessionId(r)})),!this.canEditUser(r))return this.sendJsonError(a,401,"User not logged in");if(this.isSessionUser(r)&&!r.csrfToken)return this.sendJsonError(a,403,"No CSRF token present");try{let i;return r.sessionId&&(i=(await this.sessionManager.userForSessionId(r.sessionId)).user),a.header(...T).send({ok:!0,user:i})}catch(i){const d=e.CrossauthError.asCrossauthError(i);let t=d.message,n=d.code,h=d.codeName;switch(d.code){case e.ErrorCode.UserNotExist:case e.ErrorCode.PasswordInvalid:t="Invalid username or password",n=e.ErrorCode.UsernameOrPasswordInvalid,h=e.ErrorCode[n];break}return e.CrossauthLogger.logger.error(e.j({msg:t,user:(o=r.user)==null?void 0:o.username,hashOfSessionId:this.getHashOfSessionId(r),errorCodeName:h,errorCode:n})),e.CrossauthLogger.logger.debug(e.j({err:i})),a.status(this.errorStatus(i)).header(...T).send({ok:!1,errorCode:n,errorCodeName:h})}})}addApiGetCsrfTokenEndpoints(){this.app.get(this.prefix+"api/getcsrftoken",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/getcsrftoken",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return a.header(...T).send({ok:!0,csrfToken:r.csrfToken})}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"getcsrftoken failure",user:(o=r.user)==null?void 0:o.username,hashedCsrfCookie:this.getHashOfCsrfCookie(r),errorCode:d.code,errorCodeName:d.codeName})),e.CrossauthLogger.logger.debug(e.j({err:i})),a.status(this.errorStatus(i)).header(...T).send({ok:!1,errorCode:d.code,errorCodeName:d.codeName,error:d.message})}})}async login(r,a,s){if(r.user)return s(a,r.user);const o=r.body.username,i=r.body.persist;if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const d=this.getSessionCookieValue(r);let t=this.addToSession?this.addToSession(r):{},{sessionCookie:n,csrfCookie:h,user:g}=await this.sessionManager.login(o,r.body,t,i);if(e.CrossauthLogger.logger.debug(e.j({msg:"Login: set session cookie "+n.name+" opts "+JSON.stringify(n.options),user:r.body.username})),a.cookie(n.name,n.value,n.options),e.CrossauthLogger.logger.debug(e.j({msg:"Login: set csrf cookie "+h.name+" opts "+JSON.stringify(n.options),user:r.body.username})),a.cookie(h.name,h.value,h.options),r.csrfToken=await this.sessionManager.createCsrfFormOrHeaderValue(h.value),d)try{await this.sessionManager.deleteSession(d)}catch(u){e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't delete session ID from database",hashOfSessionId:this.getHashOfSessionId(r)})),e.CrossauthLogger.logger.debug(e.j({err:u}))}return s(a,g)}async loginFactor2(r,a,s){if(r.user)return s(a,r.user);const o=r.sessionId;if(!o)throw new e.CrossauthError(e.ErrorCode.Unauthorized);const i=r.body.persist;if(this.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);let d=this.addToSession?this.addToSession(r):{};const{sessionCookie:t,csrfCookie:n,user:h}=await this.sessionManager.completeTwoFactorLogin(r.body,o,d,i);return e.CrossauthLogger.logger.debug(e.j({msg:"Login: set session cookie "+t.name+" opts "+JSON.stringify(t.options),user:h==null?void 0:h.username})),a.cookie(t.name,t.value,t.options),e.CrossauthLogger.logger.debug(e.j({msg:"Login: set csrf cookie "+n.name+" opts "+JSON.stringify(t.options),user:h==null?void 0:h.username})),a.cookie(n.name,n.value,n.options),r.csrfToken=await this.sessionManager.createCsrfFormOrHeaderValue(n.value),s(a,h)}async cancelFactor2(r,a,s){if(this.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=this.getSessionCookieValue(r);return o&&this.sessionManager.cancelTwoFactorPageVisit(o),s(a)}async loginWithUser(r,a,s,o,i){const d=this.getSessionCookieValue(s);let t=this.addToSession?this.addToSession(s):{},{sessionCookie:n,csrfCookie:h}=await this.sessionManager.login("",{},t,void 0,r,a);if(e.CrossauthLogger.logger.debug(e.j({msg:"Login: set session cookie "+n.name+" opts "+JSON.stringify(n.options),user:r.username})),o.cookie(n.name,n.value,n.options),e.CrossauthLogger.logger.debug(e.j({msg:"Login: set csrf cookie "+h.name+" opts "+JSON.stringify(n.options),user:r.username})),o.cookie(h.name,h.value,h.options),d)try{await this.sessionManager.deleteSession(d)}catch(g){e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't delete session ID from database",hashOfSessionId:this.getHashOfSessionId(s)})),e.CrossauthLogger.logger.debug(e.j({err:g}))}return i(o,r)}async signup(r,a,s){if(!this.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call signup unless you provide a user stotage");if(this.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=r.body.username,i=r.body.next;if(r.body.factor2||(r.body.factor2=this.allowedFactor2[0]),r.body.factor2&&!this.allowedFactor2.includes(r.body.factor2??"none"))throw new e.CrossauthError(e.ErrorCode.Forbidden,"Illegal second factor "+r.body.factor2+" requested");(r.body.factor2=="none"||r.body.factor2=="")&&(r.body.factor2=void 0);let d=this.createUserFn(r,this.userStorage.userEditableFields,this.userAllowedFactor1),t=this.authenticators[d.factor1].validateSecrets(r.body);const n=this.authenticators[d.factor1].secretNames();let h={};for(let f in r.body)if(f.startsWith("repeat_")){const p=f.replace(/^repeat_/,"");n.includes(p)&&(h[p]=r.body[f])}Object.keys(h).length===0&&(h=void 0),d.state="active",r.body.factor2&&r.body.factor2!="none"?d.state="awaitingtwofactor":this.enableEmailVerification&&(d.state="awaitingemailverification");let u=[...this.validateUserFn(d),...t];if(u.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,u);let C=!1;try{const{user:f,secrets:p}=await this.userStorage.getUserByUsername(o);await this.sessionManager.authenticators[d.factor1].authenticateUser(f,p,r.body)}catch(f){e.CrossauthError.asCrossauthError(f).code==e.ErrorCode.TwoFactorIncomplete&&(C=!0)}if(!r.body.factor2&&!C)return await this.sessionManager.createUser(d,r.body,h),this.enableEmailVerification?s(a,{},void 0):this.login(r,a,(f,p)=>s(f,{},p));{let f;if(C){if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized);f=(await this.sessionManager.repeatTwoFactorSignup(r.sessionId)).userData}else{const p=await this.createAnonymousSession(r,a),E=this.sessionManager.getSessionId(p);f=(await this.sessionManager.initiateTwoFactorSignup(d,r.body,E,h)).userData}try{let p={userData:f,username:o,next:i??this.loginRedirect,csrfToken:r.csrfToken};return s(a,p)}catch(p){e.CrossauthLogger.logger.error(e.j({err:p}));try{this.sessionManager.deleteUserByUsername(o)}catch(E){e.CrossauthLogger.logger.error(e.j({err:E}))}}}}async logout(r,a,s){if(r.sessionId&&await this.sessionManager.logout(r.sessionId),e.CrossauthLogger.logger.debug(e.j({msg:"Logout: clear cookie "+this.sessionManager.sessionCookieName})),a.clearCookie(this.sessionManager.sessionCookieName),a.clearCookie(this.sessionManager.csrfCookieName),r.sessionId)try{await this.sessionManager.deleteSession(r.sessionId)}catch(o){e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't delete session ID from database",hashOfSessionId:this.getHashOfSessionId(r)})),e.CrossauthLogger.logger.debug(e.j({err:o}))}return s(a)}async createAnonymousSession(r,a,s){e.CrossauthLogger.logger.debug(e.j({msg:"Creating session ID"}));let o=this.addToSession?this.addToSession(r):{};s&&(o.data=JSON.stringify(s));let{sessionCookie:i,csrfCookie:d,csrfFormOrHeaderValue:t}=await this.sessionManager.createAnonymousSession(o);a.cookie(i.name,i.value,i.options),r.csrfToken=t,a.setCookie(d.name,d.value,d.options),r.user=void 0;const n=this.sessionManager.getSessionId(i.value);return r.sessionId=n,i.value}handleError(r,a,s,o,i=!1){var d;try{let t=e.CrossauthError.asCrossauthError(r);if(!i)switch(t.code){case e.ErrorCode.UserNotExist:case e.ErrorCode.PasswordInvalid:t=new e.CrossauthError(e.ErrorCode.UsernameOrPasswordInvalid,"Invalid username or password");break}return e.CrossauthLogger.logger.debug(e.j({err:t})),e.CrossauthLogger.logger.error(e.j({cerr:t,hashOfSessionId:this.getHashOfSessionId(a),user:(d=a.user)==null?void 0:d.username})),o(s,t)}catch(t){return e.CrossauthLogger.logger.error(e.j({err:t})),o(s,new e.CrossauthError(e.ErrorCode.UnknownError))}}getSessionCookieValue(r){if(r.cookies&&this.sessionManager.sessionCookieName in r.cookies)return r.cookies[this.sessionManager.sessionCookieName]}getCsrfCookieValue(r){if(r.cookies&&this.sessionManager.csrfCookieName in r.cookies)return r.cookies[this.sessionManager.csrfCookieName]}getHashOfSessionId(r){if(!r.sessionId)return"";try{return c.Crypto.hash(r.sessionId)}catch{}return""}getHashOfCsrfCookie(r){const a=this.getCsrfCookieValue(r);if(!a)return"";try{return c.Crypto.hash(a.split(".")[0])}catch{}return""}validateCsrfToken(r){return this.sessionManager.validateDoubleSubmitCsrfToken(this.getCsrfCookieValue(r),r.csrfToken),this.getCsrfCookieValue(r)}csrfToken(r,a){var i;let s;const o=this.sessionManager.csrfHeaderName;if(r.headers&&o.toLowerCase()in r.headers){const d=r.headers[o.toLowerCase()];Array.isArray(d)?s=d[0]:s=d}if(!s&&((i=r.body)!=null&&i.csrfToken)&&(s=r.body.csrfToken),s)try{this.sessionManager.validateDoubleSubmitCsrfToken(this.getCsrfCookieValue(r),s),r.csrfToken=s,a.header(this.sessionManager.csrfHeaderName,s)}catch{e.CrossauthLogger.logger.warn(e.j({msg:"Invalid CSRF token",hashedCsrfCookie:this.getHashOfCsrfCookie(r)})),a.clearCookie(this.sessionManager.csrfCookieName),r.csrfToken=void 0}else r.csrfToken=void 0;return s}sendJsonError(r,a,s,o){(!s||!o)&&(s="Unknown error");const i=o?e.CrossauthError.asCrossauthError(o):void 0;return e.CrossauthLogger.logger.warn(e.j({msg:s,errorCode:i==null?void 0:i.code,errorCodeName:i==null?void 0:i.codeName,httpStatus:a})),r.header(...T).status(a).send({ok:!1,status:a,errorMessage:s,errorCode:i==null?void 0:i.code,errorCodeName:i==null?void 0:i.codeName})}errorStatus(r){return typeof r=="object"&&"httpStatus"in r?r.httpStatus??500:500}allowedFactor2Details(){let r=[];return this.allowedFactor2.forEach(a=>{if(a in this.authenticators){const s=this.authenticators[a].secretNames();r.push({name:a,friendlyName:this.authenticators[a].friendlyName,hasSecrets:s&&s.length>0})}else a=="none"&&r.push({name:"none",friendlyName:"None",hasSecrets:!1})}),r}isSessionUser(r){return r.user!=null&&r.authType=="cookie"}canEditUser(r){return this.isSessionUser(r)||this.editUserScope&&r.scope&&r.scope.includes(this.editUserScope)}csrfProtectionEnabled(){return this.enableCsrfProtection}getCsrfToken(r){return r.csrfToken}getUser(r){return r.user}async updateSessionData(r,a,s){if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"User is not logged in");await this.sessionManager.updateSessionData(r.sessionId,a,s)}async updateManySessionData(r,a){if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"No session present");await this.sessionManager.updateManySessionData(r.sessionId,a)}async deleteSessionData(r,a){r.sessionId?await this.sessionManager.deleteSessionData(r.sessionId,a):e.CrossauthLogger.logger.warn(e.j({msg:"Attempt to delete session data when there is no session"}))}async getSessionData(r,a){try{const s=r.sessionId?await this.sessionManager.dataForSessionId(r.sessionId):void 0;if(s&&a in s)return s[a]}catch(s){e.CrossauthLogger.logger.error(e.j({msg:"Couldn't get "+a+"from session",cerr:s})),e.CrossauthLogger.logger.debug(e.j({err:s}))}}}class pe{constructor(r,a,s,o={}){l(this,"app");l(this,"userStorage");l(this,"apiKeyManager");this.app=r,this.userStorage=a,this.apiKeyManager=new c.ApiKeyManager(s,o),this.app.addHook("preHandler",async(i,d)=>{if(i.headers.authorization)try{e.CrossauthLogger.logger.debug(e.j({msg:"Received authorization header"}));const t=await this.apiKeyManager.validateToken(i.headers.authorization);e.CrossauthLogger.logger.debug(e.j({msg:"Valid API key",hahedApiKey:c.ApiKeyManager.hashSignedApiKeyValue(t.value)}));const n=c.KeyStorage.decodeData(t.data);if(i.apiKey={...t,...n},"scope"in n&&Array.isArray(n.scope)){let h=[];for(let g of n.scope)typeof g=="string"&&h.push(g);i.scope=h}if(t.userid)try{const{user:h}=await this.userStorage.getUserById(t.userid);i.user=h,i.authType="apiKey",e.CrossauthLogger.logger.debug(e.j({msg:"API key is for user",userid:h.id,user:h.username,hahedApiKey:c.ApiKeyManager.hashSignedApiKeyValue(t.value)}))}catch(h){e.CrossauthLogger.logger.error(e.j({msg:"API key has invalid user",userid:t.userid,hashedApiKey:c.ApiKeyManager.hashSignedApiKeyValue(t.value)})),e.CrossauthLogger.logger.debug(e.j({err:h}))}}catch(t){e.CrossauthLogger.logger.error(e.j({msg:"Invalid authorization header received",header:i.headers.authorization})),e.CrossauthLogger.logger.debug(e.j({err:t}))}})}}const k=["Content-Type","application/json; charset=utf-8"];class Ce{constructor(r,a,s,o,i,d={}){l(this,"app");l(this,"authServer");l(this,"fastifyServer");l(this,"prefix","/");l(this,"loginUrl","/login");l(this,"oauthAuthorizePage","userauthorize.njk");l(this,"errorPage","error.njk");l(this,"devicePage","device.njk");l(this,"clientStorage");l(this,"refreshTokenType","json");l(this,"refreshTokenCookieName","CROSSAUTH_REFRESH_TOKEN");l(this,"refreshTokenCookieDomain");l(this,"refreshTokenCookieHttpOnly",!1);l(this,"refreshTokenCookiePath","/");l(this,"refreshTokenCookieSecure",!0);l(this,"refreshTokenCookieSameSite","strict");l(this,"csrfTokens");l(this,"createGetCsrfTokenEndpoint",!1);this.app=r,this.fastifyServer=a,this.clientStorage=s,this.authServer=new c.OAuthAuthorizationServer(this.clientStorage,o,i,d),c.setParameter("prefix",c.ParamType.String,this,d,"PREFIX"),this.prefix.endsWith("/")||(this.prefix+="/"),c.setParameter("errorPage",c.ParamType.String,this,d,"ERROR_PAGE"),c.setParameter("devicePage",c.ParamType.String,this,d,"OAUTH_DEVICE_PAGE"),c.setParameter("loginUrl",c.ParamType.String,this,d,"LOGIN_URL"),c.setParameter("oauthAuthorizePage",c.ParamType.String,this,d,"OAUTH_AUTHORIZE_PAGE"),c.setParameter("refreshTokenType",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_TYPE"),c.setParameter("refreshTokenCookieName",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_NAME"),c.setParameter("refreshTokenCookieDomain",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_DOMAIN"),c.setParameter("refreshTokenCookieHttpOnly",c.ParamType.Boolean,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_HTTPONLY"),c.setParameter("refreshTokenCookiePath",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_PATH"),c.setParameter("refreshTokenCookieSecure",c.ParamType.Boolean,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_SECURE"),c.setParameter("refreshTokenCookieSameSite",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_SAMESITE"),c.setParameter("createGetCsrfTokenEndpoint",c.ParamType.String,this,d,"OAUTH_CREATE_GET_CSRF_TOKEN_ENDPOINT"),this.refreshTokenType!="json"&&(this.createGetCsrfTokenEndpoint?this.csrfTokens=new c.DoubleSubmitCsrfToken(d.doubleSubmitCookieOptions):this.fastifyServer.sessionServer&&(this.csrfTokens=this.fastifyServer.sessionServer.sessionManager.csrfTokens)),this.createGetCsrfTokenEndpoint&&this.addApiGetCsrfTokenEndpoints(),r.get(this.prefix+".well-known/openid-configuration",async(t,n)=>n.header(...k).status(200).send(this.authServer.oidcConfiguration({authorizeEndpoint:this.prefix+"authorize",tokenEndpoint:this.prefix+"token",jwksUri:this.prefix+"jwks",additionalClaims:[]}))),r.get(this.prefix+"jwks",async(t,n)=>n.header(...k).status(200).send(this.authServer.jwks())),(this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCode)||this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.authServer.validFlows.includes(e.OAuthFlows.OidcAuthorizationCode))&&(r.get(this.prefix+"authorize",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authorize",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.authorizeEndpoint(t,n,t.query)}),r.post(this.prefix+"authorize",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"authorize",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.authorizeEndpoint(t,n,t.body)}),this.app.post(this.prefix+"userauthorize",async(t,n)=>{var u,C;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"authorize",ip:t.ip,user:(u=t.user)==null?void 0:u.username})),!t.user)return v.sendPageError(n,401,this.errorPage);let h,g;try{h=await this.fastifyServer.validateCsrfToken(t)}catch(f){g=e.CrossauthError.asCrossauthError(f),g.message="Invalid csrf cookie received",e.CrossauthLogger.logger.error(e.j({msg:g.message,hashedCsrfCookie:h?c.Crypto.hash(h):void 0,user:(C=t.user)==null?void 0:C.username,cerr:g}))}if(g){if(this.errorPage)return n.status(g.httpStatus).view(this.errorPage,{status:g.httpStatus,errorMessage:g.message,errorCode:g.code,errorCodeName:g.codeName});{let f="500";switch(g.httpStatus){case 401:f="401";break;case 400:f="400";break}return n.status(g.httpStatus).send(z[f]??L)}}if(!g){const f=t.body.authorized=="true";return await this.authorize(t,n,f,{responseType:t.body.response_type,client_id:t.body.client_id,redirect_uri:t.body.redirect_uri,scope:t.body.scope,state:t.body.state,codeChallenge:t.body.code_challenge,codeChallengeMethod:t.body.code_challenge_method})}})),(this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCode)||this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.authServer.validFlows.includes(e.OAuthFlows.OidcAuthorizationCode)||this.authServer.validFlows.includes(e.OAuthFlows.ClientCredentials)||this.authServer.validFlows.includes(e.OAuthFlows.RefreshToken)||this.authServer.validFlows.includes(e.OAuthFlows.Password)||this.authServer.validFlows.includes(e.OAuthFlows.PasswordMfa)||this.authServer.validFlows.includes(e.OAuthFlows.DeviceCode))&&this.app.post(this.prefix+"token",async(t,n)=>{var f;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"token",ip:t.ip,user:(f=t.user)==null?void 0:f.username}));let h=t.body.client_id,g=t.body.client_secret;if(t.headers.authorization){let p,E;const w=t.headers.authorization.split(" ");if(w.length==2&&w[0].toLocaleLowerCase()=="basic"){const S=c.Crypto.base64Decode(w[1]).split(":",2);S.length==2&&(p=S[0],E=S[1])}p==null||E==null?e.CrossauthLogger.logger.warn(e.j({msg:"Ignoring malform authenization header "+t.headers.authorization})):(h=p,g=E)}let u=t.body.refresh_token;if((this.refreshTokenType=="cookie"&&t.cookies&&this.refreshTokenCookieName in t.cookies||this.refreshTokenType=="both"&&t.cookies&&this.refreshTokenCookieName in t.cookies&&u==null)&&this.csrfTokens){const p=t.cookies[this.csrfTokens.cookieName];let E=t.headers[this.csrfTokens.headerName.toLowerCase()];if(Array.isArray(E)&&(E=E[0]),!p||!E)return{error:"access_denied",error_description:"Invalid csrf token"};try{this.csrfTokens.validateDoubleSubmitCsrfToken(p,E)}catch(w){return e.CrossauthLogger.logger.debug(e.j({err:w})),e.CrossauthLogger.logger.warn(e.j({cerr:w,msg:"Invalid csrf token",client_id:t.body.client_id})),{error:"access_denied",error_description:"Invalid csrf token"}}u=t.cookies[this.refreshTokenCookieName]}const C=await this.authServer.tokenEndpoint({grantType:t.body.grant_type,client_id:h,client_secret:g,scope:t.body.scope,codeVerifier:t.body.code_verifier,code:t.body.code,username:t.body.username,password:t.body.password,mfaToken:t.body.mfa_token,oobCode:t.body.oob_code,bindingCode:t.body.binding_code,otp:t.body.otp,refreshToken:u,deviceCode:t.body.device_code});if(C.error=="authorization_pending")return n.header(...k).status(200).send(C);if(C.refresh_token&&this.refreshTokenType!="json"&&this.setRefreshTokenCookie(n,C.refresh_token,C.expires_in),C.error||!C.access_token){let p="server_error",E="Neither code nor error received when requesting authorization";C.error&&(p=C.error),C.error_description&&(E=C.error_description);const w=e.CrossauthError.fromOAuthError(p,E);return e.CrossauthLogger.logger.error(e.j({cerr:w})),n.header(...k).status(w.httpStatus).send(C)}return n.header(...k).send(C)}),this.authServer.validFlows.includes(e.OAuthFlows.PasswordMfa)&&(r.get(this.prefix+"mfa/authenticators",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"mfa/authenticators",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.mfaAuthenticatorsEndpoint(t,n)}),r.post(this.prefix+"mfa/authenticators",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"mfa/authenticators",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.mfaAuthenticatorsEndpoint(t,n)}),r.post(this.prefix+"mfa/challenge",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"mfa/challenge",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.mfaChallengeEndpoint(t,n,t.body)})),this.authServer.validFlows.includes(e.OAuthFlows.DeviceCode)&&(this.app.post(this.prefix+"device_authorization",async(t,n)=>{var C;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"device_authorization",ip:t.ip,user:(C=t.user)==null?void 0:C.username}));let h=t.body.client_id,g=t.body.client_secret;if(t.headers.authorization){let f,p;const E=t.headers.authorization.split(" ");if(E.length==2&&E[0].toLocaleLowerCase()=="basic"){const y=c.Crypto.base64Decode(E[1]).split(":",2);y.length==2&&(f=y[0],p=y[1])}f==null||p==null?e.CrossauthLogger.logger.warn(e.j({msg:"Ignoring malform authenization header "+t.headers.authorization})):(h=f,g=p)}const u=await this.authServer.deviceAuthorizationEndpoint({client_id:h,client_secret:g,scope:t.body.scope});if(u.error||!u.device_code||!u.user_code){let f="server_error",p="Neither code nor error received when requesting authorization";u.error&&(f=u.error),u.error_description&&(p=u.error_description);const E=e.CrossauthError.fromOAuthError(f,p);return e.CrossauthLogger.logger.error(e.j({cerr:E})),n.header(...k).status(E.httpStatus).send(u)}return n.header(...k).send(u)}),r.get(this.prefix+"device",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),t.user?await this.deviceGet(!1,t,n,t.user):n.redirect(this.loginUrl+"?next="+encodeURIComponent(t.url),302)}),r.get(this.prefix+"api/device",async(t,n)=>{var h;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),!t.user){const g=new e.CrossauthError(e.ErrorCode.Unauthorized,"Not logged in");return n.header(...k).status(401).send({errorMessage:g.message,errorCode:g.code,errorCodeName:g.codeName})}return await this.deviceGet(!0,t,n,t.user)}),this.app.post(this.prefix+"device",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),t.user?await this.deviceCodePost(!1,t,n):n.redirect(this.loginUrl+"?next="+encodeURIComponent(t.url),302)}),this.app.post(this.prefix+"api/device",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.deviceCodePost(!0,t,n)}))}async createCsrfToken(){if(!this.csrfTokens)throw new e.CrossauthError(e.ErrorCode.Configuration,"CSRF tokens not enabled");this.csrfTokens.makeCsrfCookie(await this.csrfTokens.createCsrfToken());const r=this.csrfTokens.createCsrfToken(),a=this.csrfTokens.makeCsrfFormOrHeaderToken(r);return{csrfCookie:this.csrfTokens.makeCsrfCookie(r),csrfFormOrHeaderValue:a}}addApiGetCsrfTokenEndpoints(){this.csrfTokens&&this.app.get(this.prefix+"getcsrftoken",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"getcsrftoken",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.csrfTokens)return;let s="";try{const{csrfCookie:d,csrfFormOrHeaderValue:t}=await this.createCsrfToken();return s=d.value,a.setCookie(d.name,d.value,d.options),a.header(...k).send({ok:!0,csrfToken:t})}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"getcsrftoken failure",user:(i=r.user)==null?void 0:i.username,hashedCsrfCookie:c.Crypto.hash(s.split(".")[0]),errorCode:t.code,errorCodeName:t.codeName})),e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).header(...k).send({ok:!1,errorCode:t.code,errorCodeName:t.codeName,error:t.message})}})}async authorizeEndpoint(r,a,s){var t,n,h;if(!r.user)return a.redirect(this.loginUrl+"?next="+encodeURIComponent(r.url),302);e.CrossauthLogger.logger.debug(e.j({msg:"validating authorize parameters"}));let{error_description:o}=this.authServer.validateAuthorizeParameters(s),i;if(o?(i=new e.CrossauthError(e.ErrorCode.BadRequest,o),e.CrossauthLogger.logger.error(e.j({msg:"authorize parameter invalid",cerr:i,user:(t=r.user)==null?void 0:t.username}))):e.CrossauthLogger.logger.error(e.j({msg:"authorize parameter valid",user:(n=r.user)==null?void 0:n.username})),i){if(this.errorPage)return a.status(i.httpStatus).view(this.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCode:i.code,errorCodeName:i.codeName});{let g="500";switch(i.httpStatus){case 401:g="401";break;case 400:g="400";break}return a.status(i.httpStatus).send(z[g]??L)}}let d=!1;if(e.CrossauthLogger.logger.debug(e.j({msg:"Checking scopes have been authorized",scope:s.scope})),s.scope?d=await this.authServer.hasAllScopes(s.client_id,r.user,s.scope.split(" ")):d=await this.authServer.hasAllScopes(s.client_id,r.user,[null]),d)return e.CrossauthLogger.logger.debug(e.j({msg:"All scopes authorized",scope:s.scope})),this.authorize(r,a,!0,{responseType:s.response_type,client_id:s.client_id,redirect_uri:s.redirect_uri,scope:s.scope,state:s.state,codeChallenge:s.code_challenge,codeChallengeMethod:s.code_challenge_method});e.CrossauthLogger.logger.debug(e.j({msg:"Not all scopes authorized",scope:s.scope}));try{const g=await this.clientStorage.getClientById(s.client_id);return a.view(this.oauthAuthorizePage,{user:r.user,response_type:s.response_type,client_id:s.client_id,client_name:g.client_name,redirect_uri:s.redirect_uri,scope:s.scope,scopes:s.scope?s.scope.split(" "):void 0,state:s.state,code_challenge:s.code_challenge,code_challenge_method:s.code_challenge_method,csrfToken:r.csrfToken})}catch(g){const u=g;return e.CrossauthLogger.logger.debug(e.j({err:u})),this.errorPage?a.status(u.httpStatus).view(this.errorPage,{status:u.httpStatus,errorMessage:"Invalid client given",client_id:s.client_id,user:(h=r.user)==null?void 0:h.username,httpStatus:u.httpStatus,errorCode:e.ErrorCode.UnauthorizedClient,errorCodeName:e.ErrorCode[e.ErrorCode.UnauthorizedClient]}):a.status(u.httpStatus).send(z[401])}}async authorize(r,a,s,{responseType:o,client_id:i,redirect_uri:d,scope:t,state:n,codeChallenge:h,codeChallengeMethod:g}){let u,C,f;if(s){const p=await this.authServer.authorizeGetEndpoint({responseType:o,client_id:i,redirect_uri:d,scope:t,state:n,codeChallenge:h,codeChallengeMethod:g,user:r.user});if(f=p.code,u=p.error,C=p.error_description,u||!f){const E=e.CrossauthError.fromOAuthError(u??"server_error",C??"Neither code nor error received");if(e.CrossauthLogger.logger.error(e.j({cerr:E})),this.errorPage)return a.status(E.httpStatus).view(this.errorPage,{status:E.httpStatus,errorMessage:E.message,errorCode:E.code,errorCodeName:E.codeName});{let w="500";switch(E.httpStatus){case 401:w="401";break;case 400:w="400";break}return a.status(E.httpStatus).send(z[w]??L)}}return a.redirect(this.authServer.redirect_uri(d,f,n))}else{const p=new e.CrossauthError(e.ErrorCode.Unauthorized,"You have not granted access");e.CrossauthLogger.logger.error(e.j({msg:C,errorCode:p.code,errorCodeName:p.codeName}));try{return c.OAuthClientManager.validateUri(d),a.redirect(d)}catch{e.CrossauthLogger.logger.error(e.j({msg:`Couldn't send error message ${p.codeName} to ${d}}`}))}}}async mfaAuthenticatorsEndpoint(r,a){var t;const s=(t=r.headers.authorization)==null?void 0:t.split(" ");if(!s||s.length!=2)return{error:"access_denied",error_desciption:"Invalid authorization header"};const o=s[1],i=await this.authServer.mfaAuthenticatorsEndpoint(o);if(i.authenticators)return a.header(...k).status(200).send(i.authenticators);const d=e.CrossauthError.fromOAuthError(i.error??"server_error");return a.header(...k).status(d.httpStatus).send(i)}async mfaChallengeEndpoint(r,a,s){const o=await this.authServer.mfaChallengeEndpoint(s.mfa_token,s.client_id,s.client_secret,s.challenge_type,s.authenticator_id);if(o.error){const i=e.CrossauthError.fromOAuthError(o.error);return a.header(...k).status(i.httpStatus).send(o)}return a.header(...k).status(200).send(o)}setRefreshTokenCookie(r,a,s){if(!this.refreshTokenCookieName)return;let o=s?new Date(Date.now()+s*1e3).toUTCString():void 0,i=this.refreshTokenCookieName+"="+a;o&&(i+="; expires="+new Date(o).toUTCString()),this.refreshTokenCookieSameSite&&(i+="; SameSite="+this.refreshTokenCookieSameSite),this.refreshTokenCookieDomain&&(i+="; domain="+this.refreshTokenCookieDomain),this.refreshTokenCookiePath&&(i+="; path="+this.refreshTokenCookiePath),this.refreshTokenCookieHttpOnly==!0&&(i+="; httpOnly"),this.refreshTokenCookieSecure==!0&&(i+="; secure"),r.setCookie(this.refreshTokenCookieName,i)}oidcConfiguration(){return this.authServer.oidcConfiguration({authorizeEndpoint:this.prefix+"authorize",tokenEndpoint:this.prefix+"token",jwksUri:this.prefix+"jwks",additionalClaims:[]})}async applyUserCode(r,a,s){var o,i,d;try{const t=await this.authServer.deviceEndpoint({userCode:r,user:s});if(t.error)return{ok:!1,completed:!1,retryAllowed:!1,error:t.error,error_description:t.error_description};if(!t.client_id)return e.CrossauthLogger.logger.error(e.j({msg:"No client id found for user code",userCodeHash:c.Crypto.hash(r),ip:a.ip,username:(o=a.user)==null?void 0:o.username})),{ok:!1,completed:!1,retryAllowed:!1,error:"server_error",error_description:"No client id found for user code"};if(t.error=="access_denied")return e.CrossauthLogger.logger.error(e.j({msg:"Incorrect user code given",userCodeHash:c.Crypto.hash(r),ip:a.ip,username:(i=a.user)==null?void 0:i.username})),this.authServer.userCodeThrottle>0&&await(g=>new Promise(u=>setTimeout(u,g)))(this.authServer.userCodeThrottle),{ok:!1,completed:!1,retryAllowed:!0,error:t.error,error_description:t.error_description};if(t.error=="expired_token")return e.CrossauthLogger.logger.error(e.j({msg:"Expired user code",userCodeHash:c.Crypto.hash(r),ip:a.ip,username:(d=a.user)==null?void 0:d.username})),{ok:!1,completed:!1,retryAllowed:!1,error:t.error,error_description:t.error_description};const n=await this.clientStorage.getClientById(t.client_id);return t.scopeAuthorizationNeeded?{ok:!0,completed:!1,retryAllowed:!0,authorizationNeeded:{user:s,client_id:t.client_id,client_name:n.client_name,scope:t.scope,scopes:t.scope?t.scope.split(" "):[],csrfToken:a.csrfToken},user:a.user,csrfToken:a.csrfToken,user_code:r}:{ok:!0,completed:!0,retryAllowed:!1,user:a.user,csrfToken:a.csrfToken}}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.debug(e.j({err:n})),e.CrossauthLogger.logger.error(e.j({msg:n.message,cerr:n})),{ok:!1,completed:!1,retryAllowed:!0,error:n.oauthErrorCode,error_description:n.message}}}async deviceGet(r,a,s,o){if(a.query.user_code){let i=await this.applyUserCode(a.query.user_code,a,o);if(i.error){const t=e.CrossauthError.fromOAuthError(i.error,i.error_description);e.CrossauthLogger.logger.debug({err:t}),e.CrossauthLogger.logger.error({cerr:t});const n={ok:!1,completed:!1,status:t.httpStatus,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,retryAllowed:i.retryAllowed};return r?s.header(...k).status(t.httpStatus).send(n):s.status(t.httpStatus).view(this.devicePage,{csrfToken:a.csrfToken,...n})}else if(i.authorizationNeeded){const t={ok:!0,completed:!1,retryAllowed:i.retryAllowed,authorizationNeeded:i.authorizationNeeded,user_code:i.user_code};return r?s.header(...k).status(200).send(t):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...t})}const d={ok:!0,completed:!0};return r?s.header(...k).status(401).send(d):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...d})}else{const i={ok:!1,completed:!1,user_code:a.query.user_code,csrfToken:a.csrfToken};return r?s.header(...k).status(200).send(i):s.status(200).view(this.devicePage,i)}}async deviceCodePost(r,a,s){try{if(!a.user)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"You are not logged in");if(!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"CSRF token missing or invalid");if(!a.body.authorized||a.body.authorized=="")if(a.body.user_code){let o=await this.applyUserCode(a.body.user_code,a,a.user);if(o.error){const d=e.CrossauthError.fromOAuthError(o.error,o.error_description);e.CrossauthLogger.logger.debug({err:d}),e.CrossauthLogger.logger.error({cerr:d});const t={ok:!1,completed:!1,status:d.httpStatus,errorMessage:d.message,errorCode:d.code,errorCodeName:d.codeName,retryAllowed:o.retryAllowed};return r?s.header(...k).status(200).send(t):s.status(d.httpStatus).view(this.devicePage,{csrfToken:a.csrfToken,...t})}else if(o.authorizationNeeded){const d={ok:!0,completed:!1,retryAllowed:o.retryAllowed,authorizationNeeded:o.authorizationNeeded,user_code:o.user_code};return r?s.header(...k).status(200).send(d):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...d})}const i={ok:!0,completed:!0,csrfToken:a.csrfToken};return r?s.header(...k).status(200).send(i):s.status(200).view(this.devicePage,i)}else{const o=e.CrossauthError.fromOAuthError("unauthorized","Please enter the code"),i={ok:!1,completed:!1,user_code:a.body.user_code,retryAllowed:!0,error:"unauthorized",error_description:"Please enter the code",errorMessage:o.message,errorCode:o.code,errorCodeName:o.codeName};return r?s.header(...k).status(401).send(i):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...i})}else if(a.body.authorized=="true"){let o=a.body.user_code,i=a.body.scope;i=="";const d=a.body.client_id;if(!o)throw new e.CrossauthError(e.ErrorCode.BadRequest,"user_code missing");if(!d)throw new e.CrossauthError(e.ErrorCode.BadRequest,"client_id missing");let t=await this.authServer.validateAndPersistScope(d,i,a.user);if(t.error||(t=await this.applyUserCode(o,a,a.user),t.error))throw e.CrossauthError.fromOAuthError(t.error,t.error_description);const n={ok:!0,completed:!0,csrfToken:a.csrfToken};return r?s.header(...k).status(401).send(n):s.status(200).view(this.devicePage,n)}else throw new e.CrossauthError(e.ErrorCode.Unauthorized,"You did not authorize the client")}catch(o){const i=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.debug({err:i}),e.CrossauthLogger.logger.error({cerr:i});const d={ok:!1,status:i.httpStatus,errorMessage:i.message,errorCode:i.code,errorCodeName:i.codeName};return r?s.header(...k).status(401).send(d):s.status(i.httpStatus).view(this.devicePage,{csrfToken:a.csrfToken,...d})}}}const P=["Content-Type","application/json; charset=utf-8"];async function ne(m,r,a,s){return e.CrossauthLogger.logger.debug(e.j({err:s})),a.header(...P).status(s.httpStatus).send({ok:!1,status:s.httpStatus,errorMessage:s.message,errorMessages:s.messages,errorCode:s.code,errorCodeName:s.codeName})}async function je(m,r,a,s){var o;return e.CrossauthLogger.logger.debug(e.j({err:s})),a.status(s.httpStatus).view(((o=m.oAuthClient)==null?void 0:o.errorPage)??"error.njk",{status:s.httpStatus,errorMessage:s.message,errorMessages:s.messages,errorCodeName:s.codeName})}function O(m){let r;if(m)try{r=JSON.parse(c.Crypto.base64Decode(m.split(".")[1]))}catch(a){const s=e.CrossauthError.asCrossauthError(a);e.CrossauthLogger.logger.debug(e.j({err:s})),e.CrossauthLogger.logger.error(e.j({msg:"Couldn't decode id token",cerr:s}))}return r}async function de(m,r,a,s){if(s){let o={ok:!0,...m};return r.jwtTokens.includes("id")&&(o.id_payload=m.id_payload??O(m.id_token)),s.header(...P).status(200).send(o)}}function ie(m,r){var a;if(m.access_token)try{if(m.access_token&&r.includes("access")){const s=R.jwtDecode(m.access_token),o=s.jti?s.jti:s.sid?s.sid:"",i=o?c.Crypto.hash(o):void 0;e.CrossauthLogger.logger.debug(e.j({msg:"Got access token",accessTokenHash:i}))}}catch(s){e.CrossauthLogger.logger.debug(e.j({err:s}))}if(m.id_token)try{if(m.id_token&&r.includes("id")){let s=m.id_payload??R.jwtDecode(m.id_token);if(s){const o=s.jti?s.jti:s.sid?s.sid:"",i=o?c.Crypto.hash(o):void 0;e.CrossauthLogger.logger.debug(e.j({msg:"Got id token",idTokenHash:i}))}}}catch(s){e.CrossauthLogger.logger.debug(e.j({err:s}))}if(m.refresh_token&&r.includes("refresh"))try{if(m.refresh_token){const s=(a=R.jwtDecode(m.refresh_token))==null?void 0:a.jti,o=s?c.Crypto.hash(s):void 0;e.CrossauthLogger.logger.debug(e.j({msg:"Got refresh token",refreshTokenHash:o}))}}catch(s){e.CrossauthLogger.logger.debug(e.j({err:s}))}}async function Oe(m,r,a,s){if(m.error){const o=e.CrossauthError.fromOAuthError(m.error,m.error_description);if(s)return s.status(o.httpStatus).view(r.errorPage,{status:o.httpStatus,errorMessage:o.message,errorCodeName:o.codeName,errorCode:o.code})}if(ie(m,r.jwtTokens),s)try{let o={...m};return r.jwtTokens.includes("id")&&m.id_token&&(o.id_payload=m.id_payload??O(m.id_token)),s.status(200).view(r.authorizedPage,o)}catch(o){const i=e.CrossauthError.asCrossauthError(o);return s.status(i.httpStatus).view(r.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCodeName:i.codeName})}}async function Ie(m,r,a,s){if(m.error){const o=e.CrossauthError.fromOAuthError(m.error,m.error_description);if(s)return s.status(o.httpStatus).view(r.errorPage,{status:o.httpStatus,errorMessage:o.message,errorCodeName:o.codeName,errorCode:o.code})}ie(m,r.jwtTokens);try{if((m.access_token||m.id_token||m.refresh_token)&&await me(m,r,a,s),s){if(!r.authorizedPage)return s.status(500).view(r.errorPage,{status:500,errorMessage:"Authorized url not configured",errorCodeName:e.ErrorCode[e.ErrorCode.Configuration],errorCode:e.ErrorCode.Configuration});let o={...m};return r.jwtTokens.includes("id")&&(o.id_payload=m.id_payload??O(m.id_token)),s.status(200).view(r.authorizedPage,o)}}catch(o){const i=e.CrossauthError.asCrossauthError(o);if(e.CrossauthLogger.logger.debug(e.j({err:i})),e.CrossauthLogger.logger.debug(e.j({cerr:i,msg:"Error receiving tokens"})),s)return s.status(i.httpStatus).view(r.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCodeName:i.codeName})}}async function me(m,r,a,s){if(!r.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot update session data if sessions not enabled");let o=m.expires_in;if(!o&&m.access_token&&r.jwtTokens.includes("access")){const t=R.jwtDecode(m.access_token);t.exp&&(o=t.exp)}if(!o)throw new e.CrossauthError(e.ErrorCode.BadRequest,"OAuth server did not return an expiry for the access token");const i=Date.now()+o*1e3;let d={...m,expires_at:i};if("id_token"in m){let t=m.id_payload??O(m.id_token);t&&(d.id_token=t)}await r.storeSessionData(d,a,s)}async function Me(m,r,a,s){if(m.error){const o=e.CrossauthError.fromOAuthError(m.error,m.error_description);if(s)return s.status(o.httpStatus).view(r.errorPage,{status:o.httpStatus,errorMessage:o.message,errorCodeName:o.codeName,errorCode:o.code})}ie(m,r.jwtTokens);try{if((m.access_token||m.id_token||m.refresh_token)&&await me(m,r,a,s),s)return r.authorizedUrl?s.redirect(r.authorizedUrl):s.status(500).view(r.errorPage,{status:500,errorMessage:"Authorized url not configured",errorCodeName:e.ErrorCode[e.ErrorCode.Configuration],errorCode:e.ErrorCode.Configuration})}catch(o){const i=e.CrossauthError.asCrossauthError(o);if(e.CrossauthLogger.logger.debug(e.j({err:i})),e.CrossauthLogger.logger.debug(e.j({cerr:i,msg:"Error receiving tokens"})),s)return s.status(i.httpStatus).view(r.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCodeName:i.codeName})}}class J extends c.OAuthClientBackend{constructor(a,s,o){var i,d,t;super(s,o);l(this,"server");l(this,"siteUrl","/");l(this,"prefix","/");l(this,"errorPage","error.njk");l(this,"passwordFlowPage","passwordflow.njk");l(this,"deviceCodeFlowPage","devicecodeflow.njk");l(this,"deleteTokensPage","deletetokens.njk");l(this,"deleteTokensGetUrl");l(this,"deleteTokensPostUrl");l(this,"apiDeleteTokensPostUrl");l(this,"mfaOtpPage","mfaotp.njk");l(this,"mfaOobPage","mfaoob.njk");l(this,"authorizedPage","authorized.njk");l(this,"authorizedUrl","authorized");l(this,"sessionDataName","oauth");l(this,"receiveTokenFn",de);l(this,"errorFn",ne);l(this,"loginUrl","/login");l(this,"validFlows",[]);l(this,"jwtTokens",["access","id","refresh"]);l(this,"testMiddleware",!1);l(this,"requestObj");l(this,"loginProtectedFlows",[]);l(this,"tokenResponseType","sendJson");l(this,"errorResponseType","jsonError");l(this,"passwordFlowUrl","passwordflow");l(this,"passwordOtpUrl","passwordotp");l(this,"passwordOobUrl","passwordoob");l(this,"deviceCodeFlowUrl","devicecodeflow");l(this,"deviceCodePollUrl","devicecodepoll");l(this,"bffEndpoints",[]);l(this,"bffEndpointName","bff");l(this,"bffBaseUrl");l(this,"tokenEndpoints",[]);if(this.server=a,c.setParameter("sessionDataName",c.ParamType.String,this,o,"OAUTH_SESSION_DATA_NAME"),c.setParameter("siteUrl",c.ParamType.String,this,o,"SITE_URL",!0),c.setParameter("tokenResponseType",c.ParamType.String,this,o,"OAUTH_TOKEN_RESPONSE_TYPE"),c.setParameter("errorResponseType",c.ParamType.String,this,o,"OAUTH_ERROR_RESPONSE_TYPE"),c.setParameter("prefix",c.ParamType.String,this,o,"PREFIX"),this.prefix.endsWith("/")||(this.prefix+="/"),c.setParameter("loginUrl",c.ParamType.String,this,o,"LOGIN_URL"),c.setParameter("errorPage",c.ParamType.String,this,o,"ERROR_PAGE"),c.setParameter("authorizedPage",c.ParamType.String,this,o,"AUTHORIZED_PAGE"),c.setParameter("authorizedUrl",c.ParamType.String,this,o,"AUTHORIZED_URL"),c.setParameter("loginProtectedFlows",c.ParamType.JsonArray,this,o,"OAUTH_LOGIN_PROTECTED_FLOWS"),c.setParameter("passwordFlowUrl",c.ParamType.String,this,o,"OAUTH_PASSWORD_FLOW_URL"),c.setParameter("passwordOtpUrl",c.ParamType.String,this,o,"OAUTH_PASSWORD_OTP_URL"),c.setParameter("passwordOobUrl",c.ParamType.String,this,o,"OAUTH_PASSWORD_OOB_URL"),c.setParameter("passwordFlowPage",c.ParamType.String,this,o,"OAUTH_PASSWORD_FLOW_PAGE"),c.setParameter("deviceCodeFlowPage",c.ParamType.String,this,o,"OAUTH_DEVICECODE_FLOW_PAGE"),c.setParameter("deleteTokensPage",c.ParamType.String,this,o,"OAUTH_DELETE_TOKENS_PAGE"),c.setParameter("deleteTokensGetUrl",c.ParamType.String,this,o,"OAUTH_DELETE_TOKENS_GET_URL"),c.setParameter("deleteTokensPostUrl",c.ParamType.String,this,o,"OAUTH_DELETE_TOKENS_POST_URL"),c.setParameter("apiDeleteTokensPostUrl",c.ParamType.String,this,o,"OAUTHAPI__DELETE_TOKENS_POST_URL"),c.setParameter("mfaOtpPage",c.ParamType.String,this,o,"OAUTH_MFA_OTP_PAGE"),c.setParameter("mfaOobPage",c.ParamType.String,this,o,"OAUTH_MFA_OOB_PAGE"),c.setParameter("deviceCodeFlowUrl",c.ParamType.String,this,o,"OAUTH_DEVICECODE_FLOW_URL"),c.setParameter("deviceCodePollUrl",c.ParamType.String,this,o,"OAUTH_DEVICECODE_POLL_URL"),c.setParameter("bffEndpointName",c.ParamType.String,this,o,"OAUTH_BFF_ENDPOINT_NAME"),c.setParameter("bffBaseUrl",c.ParamType.String,this,o,"OAUTH_BFF_BASEURL"),c.setParameter("validFlows",c.ParamType.JsonArray,this,o,"OAUTH_VALIDFLOWS"),c.setParameter("jwtTokens",c.ParamType.JsonArray,this,o,"OAUTH_JWT_TOKENS"),(i=this.deleteTokensGetUrl)!=null&&i.startsWith("/")&&(this.deleteTokensGetUrl=this.deleteTokensGetUrl.substring(1)),(d=this.deleteTokensPostUrl)!=null&&d.startsWith("/")&&(this.deleteTokensPostUrl=this.deleteTokensPostUrl.substring(1)),(t=this.deleteTokensPostUrl)!=null&&t.startsWith("/")&&(this.deleteTokensPostUrl=this.deleteTokensPostUrl.substring(1)),this.validFlows.length==1&&this.validFlows[0]==e.OAuthFlows.All)this.validFlows=e.OAuthFlows.allFlows();else if(!e.OAuthFlows.areAllValidFlows(this.validFlows))throw new e.CrossauthError(e.ErrorCode.Configuration,"Invalid flows specificied in "+this.validFlows.join(","));if(o.tokenEndpoints&&(this.tokenEndpoints=o.tokenEndpoints),this.bffEndpointName.endsWith("/")&&(this.bffEndpointName=this.bffEndpointName.substring(0,this.bffEndpointName.length-1)),o.bffEndpoints&&(this.bffEndpoints=o.bffEndpoints),this.loginProtectedFlows.length==1&&this.loginProtectedFlows[0]==e.OAuthFlows.All)this.loginProtectedFlows=this.validFlows;else if(!e.OAuthFlows.areAllValidFlows(this.loginProtectedFlows))throw new e.CrossauthError(e.ErrorCode.Configuration,"Invalid flows specificied in "+this.loginProtectedFlows.join(","));if(this.tokenResponseType=="custom"&&!o.receiveTokenFn)throw new e.CrossauthError(e.ErrorCode.Configuration,"Token response type of custom selected but receiveTokenFn not defined");if(this.tokenResponseType=="custom"&&o.receiveTokenFn?this.receiveTokenFn=o.receiveTokenFn:this.tokenResponseType=="sendJson"?this.receiveTokenFn=de:this.tokenResponseType=="sendInPage"?this.receiveTokenFn=Oe:this.tokenResponseType=="saveInSessionAndLoad"?this.receiveTokenFn=Ie:this.tokenResponseType=="saveInSessionAndRedirect"&&(this.receiveTokenFn=Me),this.errorResponseType=="custom"&&!o.errorFn)throw new e.CrossauthError(e.ErrorCode.Configuration,"Error response type of custom selected but errorFn not defined");if(this.errorResponseType=="custom"&&o.errorFn?this.errorFn=o.errorFn:this.errorResponseType=="jsonError"?this.errorFn=ne:this.errorResponseType=="pageError"&&(this.errorFn=je),this.loginProtectedFlows.length>0&&this.loginUrl=="")throw new e.CrossauthError(e.ErrorCode.Configuration,"loginUrl must be set if protecting oauth endpoints");this.prefix.endsWith("/")||(this.prefix+="/"),this.redirect_uri=this.siteUrl+this.prefix+"authzcode",this.validFlows.includes(e.OAuthFlows.AuthorizationCode)&&this.server.app.get(this.prefix+"authzcodeflow",async(n,h)=>{var E;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authzcodeflow",ip:n.ip,user:(E=n.user)==null?void 0:E.username})),!this.server.sessionAdapter){const w=new e.CrossauthError(e.ErrorCode.Configuration,"Need a session server or adapter for authorization code flow");return await this.errorFn(this.server,n,h,w)}if(!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCode))return h.redirect(this.loginUrl+"?next="+encodeURIComponent(n.url),302);if(!this.server.sessionAdapter){const w=new e.CrossauthError(e.ErrorCode.Configuration,"Need a session server or adapter for authorization code flow");return await this.errorFn(this.server,n,h,w)}const g=this.randomValue(this.stateLength),u={scope:n.query.scope,state:g};await this.storeSessionData(u,n,h);const{url:C,error:f,error_description:p}=await this.startAuthorizationCodeFlow(g,n.query.scope);if(f||!C){const w=e.CrossauthError.fromOAuthError(f??"server_error",p);return await this.errorFn(this.server,n,h,w)}return this.oauthLogFetch?e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect",url:C})):e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect"})),h.redirect(C)}),a.app.addHook("preHandler",async(n,h)=>{if(n.user||!a.sessionAdapter)return;let g=await a.sessionAdapter.getSessionData(n,this.sessionDataName);if(g&&g.id_payload){let u=g.expires_at;if(u&&u>Date.now()&&g.id_payload.sub){n.user={id:g.id_payload.userid??g.id_payload.sub,username:g.id_payload.sub,state:g.id_payload.state??"active"},n.idTokenPayload=g.id_payload;let C;try{C=await this.userCreationFn(g.id_payload,this.userStorage,this.userMatchField,this.idTokenMatchField),n.user=C,n.authType=C?"oidc":void 0}catch(f){e.CrossauthLogger.logger.error(e.j({cerr:f})),n.user=void 0,n.authType=void 0}}}this.testMiddleware&&(this.requestObj=n)}),this.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)&&this.server.app.get(this.prefix+"authzcodeflowpkce",async(n,h)=>{var y;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authzcodeflowpkce",ip:n.ip,user:(y=n.user)==null?void 0:y.username})),!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE))return h.redirect(this.loginUrl+"?next="+encodeURIComponent(n.url),302);const g=this.randomValue(this.stateLength),{codeChallenge:u,codeVerifier:C}=await this.codeChallengeAndVerifier(),f={scope:n.query.scope,state:g,codeChallenge:u,codeVerifier:C};await this.storeSessionData(f,n,h);const{url:p,error:E,error_description:w}=await this.startAuthorizationCodeFlow(g,n.query.scope,u,!0);if(E||!p){const S=e.CrossauthError.fromOAuthError(E??"server_error",w);return await this.errorFn(this.server,n,h,S)}return this.oauthLogFetch?e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect",url:p})):e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect"})),h.redirect(p)}),(this.validFlows.includes(e.OAuthFlows.AuthorizationCode)||this.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.validFlows.includes(e.OAuthFlows.OidcAuthorizationCode))&&this.server.app.get(this.prefix+"authzcode",async(n,h)=>{var C,f,p;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authzcode",ip:n.ip,user:(C=n.user)==null?void 0:C.username})),this.oauthLogFetch&&e.CrossauthLogger.logger.debug(e.j({msg:"Received OAuth redirect",url:n.url})),!n.user&&(this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCode)))return h.redirect(this.loginUrl+"?next="+encodeURIComponent(n.url),302);const g=await((f=this.server.sessionAdapter)==null?void 0:f.getSessionData(n,this.sessionDataName));if(!(g!=null&&g.state)||(g==null?void 0:g.state)!=n.query.state)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"State does not match");let u=await this.redirectEndpoint(n.query.code,g==null?void 0:g.scope,g==null?void 0:g.codeVerifier,n.query.error,n.query.error_description);try{if(u.error){const E=e.CrossauthError.fromOAuthError(u.error,u.error_description);return await this.errorFn(this.server,n,h,E)}return await this.receiveTokenFn(u,this,n,h)}catch(E){const w=e.CrossauthError.asCrossauthError(E);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:w,user:(p=n.user)==null?void 0:p.user})),e.CrossauthLogger.logger.debug(e.j({err:E})),await this.errorFn(this.server,n,h,w)}}),this.validFlows.includes(e.OAuthFlows.ClientCredentials)&&this.server.app.post(this.prefix+"clientcredflow",async(n,h)=>{var g,u,C;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"clientcredflow",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.server.sessionAdapter){const{error:f,reply:p}=await a.errorIfCsrfInvalid(n,h,this.errorFn);if(f)return p}if(!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.ClientCredentials))return h.status(401).header(...P).send({ok:!1,msg:"Access denied"});try{const f=await this.clientCredentialsFlow((u=n.body)==null?void 0:u.scope);if(f.error){const p=e.CrossauthError.fromOAuthError(f.error,f.error_description);return await this.errorFn(this.server,n,h,p)}return await this.receiveTokenFn(f,this,n,h)}catch(f){const p=e.CrossauthError.asCrossauthError(f);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:p,user:(C=n.user)==null?void 0:C.user})),e.CrossauthLogger.logger.debug(e.j({err:f})),await this.errorFn(this.server,n,h,p)}}),this.validFlows.includes(e.OAuthFlows.RefreshToken)&&(this.server.app.post(this.prefix+"refreshtokenflow",async(n,h)=>{var f,p;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokenflow",ip:n.ip,user:(f=n.user)==null?void 0:f.username}));const{error:g,reply:u}=await a.errorIfCsrfInvalid(n,h,this.errorFn);if(g)return u;let C=n.body.refreshToken;if(!C&&this.server.sessionAdapter){if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const E=await this.server.sessionAdapter.getSessionData(n,this.sessionDataName);if(!(E!=null&&E.refresh_token)){const w=new e.CrossauthError(e.ErrorCode.BadRequest,"No refresh token in session or in parameters");return await this.errorFn(this.server,n,h,w)}C=E.refresh_token}if(!C){const E=new e.CrossauthError(e.ErrorCode.BadRequest,"No refresh token supplied");return await this.errorFn(this.server,n,h,E)}if(!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.RefreshToken))return h.status(401).header(...P).send({ok:!1,msg:"Access denied"});try{const E=await this.refreshTokenFlow(C);if(E.error){const w=e.CrossauthError.fromOAuthError(E.error,E.error_description);return await this.errorFn(this.server,n,h,w)}return await this.receiveTokenFn(E,this,n,h)}catch(E){const w=e.CrossauthError.asCrossauthError(E);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:w,user:(p=n.user)==null?void 0:p.user})),e.CrossauthLogger.logger.debug(e.j({err:E})),await this.errorFn(this.server,n,h,w)}}),this.server.app.post(this.prefix+"refreshtokensifexpired",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokensifexpired",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!1,!0)}),this.server.app.post(this.prefix+"api/refreshtokensifexpired",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokens",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!0,!0)}),this.server.app.post(this.prefix+"refreshtokens",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokens",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!1,!1)}),this.server.app.post(this.prefix+"api/refreshtokens",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokens",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!0,!1)})),(this.validFlows.includes(e.OAuthFlows.Password)||this.validFlows.includes(e.OAuthFlows.PasswordMfa))&&(this.server.app.get(this.prefix+this.passwordFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+this.passwordFlowUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.Password)?h.redirect(302,this.loginUrl+"?next="+encodeURIComponent(n.url)):h.view(this.passwordFlowPage,{user:n.user,scope:n.query.scope,csrfToken:n.csrfToken})}),this.server.app.post(this.prefix+this.passwordFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.passwordFlowUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.passwordPost(!1,n,h)})),this.validFlows.includes(e.OAuthFlows.PasswordMfa)&&(this.server.app.post(this.prefix+this.passwordOtpUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.passwordOtpUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.passwordOtp(!1,n,h)}),this.server.app.post(this.prefix+this.passwordOobUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.passwordOobUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.passwordOob(!1,n,h)})),this.validFlows.includes(e.OAuthFlows.DeviceCode)&&(this.server.app.post(this.prefix+this.deviceCodeFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deviceCodeFlowPage,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePost(!1,n,h)}),this.server.app.post(this.prefix+"api/"+this.deviceCodeFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"api/"+this.deviceCodeFlowPage,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePost(!0,n,h)}),this.server.app.post(this.prefix+this.deviceCodePollUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deviceCodePollUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePoll(!1,n,h)}),this.server.app.post(this.prefix+"api/"+this.deviceCodePollUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deviceCodePollUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePoll(!0,n,h)})),this.deleteTokensGetUrl&&this.server.app.get(this.prefix+this.deleteTokensGetUrl,async(n,h)=>{var g,u;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+this.deleteTokensGetUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),h.view(this.deleteTokensPage,{user:(u=n.user)==null?void 0:u.username,csrfToken:n.csrfToken})}),this.deleteTokensPostUrl&&this.server.app.post(this.prefix+this.deleteTokensPostUrl,async(n,h)=>{var g,u,C;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deleteTokensPostUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username}));try{return await this.deleteTokens(n),h.view(this.deleteTokensPage,{ok:!0,user:(u=n.user)==null?void 0:u.username,csrfToken:n.csrfToken})}catch(f){const p=e.CrossauthError.asCrossauthError(f);return e.CrossauthLogger.logger.debug(e.j({err:p})),e.CrossauthLogger.logger.error(e.j({msg:"Couldn't delete oauth tokens",cerr:p})),h.view(this.deleteTokensPage,{ok:!1,user:(C=n.user)==null?void 0:C.username,csrfToken:n.csrfToken,errorMessage:p.message,errorCode:p.code,errorCodeName:p.codeName})}}),this.apiDeleteTokensPostUrl&&this.server.app.post(this.prefix+this.apiDeleteTokensPostUrl,async(n,h)=>{var g;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.apiDeleteTokensPostUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username}));try{return await this.deleteTokens(n),h.header(...P).send('{"ok": true}')}catch(u){const C=e.CrossauthError.asCrossauthError(u);return e.CrossauthLogger.logger.debug(e.j({err:C})),e.CrossauthLogger.logger.error(e.j({msg:"Couldn't delete oauth tokens",cerr:C})),h.header(...P).status(C.httpStatus).send(JSON.stringify({ok:!1,errorMessage:C.message,errorCode:C.code,errorCodeName:C.codeName,csrfToken:n.csrfToken}))}});for(let n of this.tokenEndpoints)this.server.app.post(this.prefix+n,async(h,g)=>{var y;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+n,ip:h.ip,user:(y=h.user)==null?void 0:y.username})),!h.csrfToken)return g.header(...P).status(401).send({ok:!1,msg:"No csrf token given"});let u=!1,C=n;n.startsWith("have_")&&(C=n.replace("have_",""),u=!0);let f=C.replace("_token",""),p=!1;if(this.jwtTokens.includes(f)&&(p=h.body.decode??!0),!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const E=await this.server.sessionAdapter.getSessionData(h,this.sessionDataName);if(!E)return u?g.header(...P).status(200).send({ok:!1}):g.header(...P).status(204).send();let w=E[C];return p&&(w=O(E[C])),w?u?g.header(...P).status(200).send({ok:!0}):g.header(...P).status(200).send({...w}):u?g.header(...P).status(200).send({ok:!1}):g.header(...P).status(204).send()});if(this.server.app.post(this.prefix+"tokens",async(n,h)=>{var C;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"tokens",ip:n.ip,user:(C=n.user)==null?void 0:C.username})),!n.csrfToken)return h.header(...P).status(401).send({ok:!1,msg:"No csrf token given"});if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const g=await this.server.sessionAdapter.getSessionData(n,this.sessionDataName);if(!g)return h.header(...P).status(204).send();let u={};for(let f of this.tokenEndpoints){let p=!1,E=f;f.startsWith("have_")&&(E=f.replace("have_",""),p=!0);let w=E.replace("_token",""),y=!1;if(this.jwtTokens.includes(w)&&(y=n.body.decode??!0),E in g){let S=g[E];y&&(S=O(g[E])),S&&(u[f]=p?!0:S)}else p&&(u[f]=!1)}return h.header(...P).status(200).send({...u})}),this.bffEndpoints.length>0&&!this.bffBaseUrl)throw new e.CrossauthError(e.ErrorCode.Configuration,"If enabling BFF endpoints, must also define bffBaseUrl");this.bffBaseUrl==null&&(this.bffBaseUrl=""),this.bffBaseUrl.endsWith("/")&&(this.bffBaseUrl=this.bffBaseUrl.substring(0,this.bffBaseUrl.length-1));for(let n=0;n<this.bffEndpoints.length;++n){const h=this.bffEndpoints[n].url;if(h.includes("?")||h.includes("#"))throw new e.CrossauthError(e.ErrorCode.Configuration,"BFF urls may not contain query parameters or page fragments");if(!h.startsWith("/"))throw new e.CrossauthError(e.ErrorCode.Configuration,"BFF urls must be absolute and without the HTTP method, hostname or port");const g=this.bffEndpoints[n].methods,u=this.bffEndpoints[n].matchSubUrls??!1;let C=h;u&&(C.endsWith("/")||(C+="/"),C+="*");for(let f in g)this.server.app.route({method:g[f],url:this.prefix+this.bffEndpointName+C,handler:async(p,E)=>{var S,_;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:p.method,url:p.url,ip:p.ip,user:(S=p.user)==null?void 0:S.username}));const w=p.url.substring(this.prefix.length+this.bffEndpointName.length);e.CrossauthLogger.logger.debug(e.j({msg:"Resource server URL "+w}));const y=g[f]!="GET"&&g[f]!="HEAD"&&g[f]!="OPTIONS";if(this.server.sessionAdapter&&y){const{error:A,reply:I}=await a.errorIfCsrfInvalid(p,E,this.errorFn);if(A)return I}try{if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const A=await this.server.sessionAdapter.getSessionData(p,this.sessionDataName);if(!A)return E.header(...P).status(401).send({ok:!1});let I=A==null?void 0:A.access_token;if(A&&A.access_token){const N=await((_=a.oAuthClient)==null?void 0:_.refresh(p,E,!0,!0,A.refresh_token,A.expires_at));N!=null&&N.access_token&&(I=N.access_token)}let H={Accept:"application/json","Content-Type":"application/json"};I&&(H.Authorization="Bearer "+I);let M;p.body?M=await fetch(this.bffBaseUrl+w,{headers:H,method:p.method,body:JSON.stringify(p.body??"{}")}):M=await fetch(this.bffBaseUrl+w,{headers:H,method:p.method});const we=await M.json();for(const N of M.headers.entries())E=E.header(N[0],N[1]);return E.header(...P).status(M.status).send(we)}catch(A){return e.CrossauthLogger.logger.error(e.j({err:A})),E.header(...P).status(500).send({})}}})}}async passwordPost(a,s,o){var i;if(this.server.sessionAdapter){const{error:d,reply:t}=await this.server.errorIfCsrfInvalid(s,o,this.errorFn);if(d)return t}try{let d=await this.passwordFlow(s.body.username,s.body.password,s.body.scope);if(d.error=="mfa_required"&&d.mfa_token&&this.validFlows.includes(e.OAuthFlows.PasswordMfa)){const t=d.mfa_token;if(d=await this.passwordMfa(a,t,s.body.scope,s,o),d.error){const n=e.CrossauthError.fromOAuthError(d.error,d.error_description);return a?await this.errorFn(this.server,s,o,n):o.view(this.passwordFlowPage,{user:s.user,username:s.body.username,password:s.body.password,scope:s.body.scope,errorMessage:n.message,errorCode:n.code,errorCodeName:n.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(d,this,s,o)}else if(d.error){const t=e.CrossauthError.fromOAuthError(d.error,d.error_description);return a?await this.errorFn(this.server,s,o,t):o.view(this.passwordFlowPage,{user:s.user,username:s.body.username,scope:s.body.scope,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(d,this,s,o)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:t,user:(i=s.user)==null?void 0:i.user})),e.CrossauthLogger.logger.debug(e.j({err:d})),a?await this.errorFn(this.server,s,o,t):o.view(this.passwordFlowPage,{user:s.user,username:s.body.username,password:s.body.password,scope:s.body.scope,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}}async passwordMfa(a,s,o,i,d){const t=await this.mfaAuthenticators(s);if(t.error||!t.authenticators||!Array.isArray(t.authenticators)||t.authenticators.length==0||t.authenticators.length>1&&!t.authenticators[0].active)return t.error?t:{error:"access_denied",error_description:"No MFA authenticators available"};const n=t.authenticators[0];if(n.authenticator_type=="otp"){const g=await this.mfaOtpRequest(s,n.id);return g.error||g.challenge_type!="otp"?{error:g.error??"server_error",error_description:g.error_description??"Invalid response from MFA OTP challenge"}:{scope:o,mfa_token:s}}else if(n.authenticator_type=="oob"){const g=await this.mfaOobRequest(s,n.id);return g.error||g.challenge_type!="oob"||!g.oob_code||g.binding_method!="prompt"?{error:g.error??"server_error",error_description:g.error_description??"Invalid response from MFA OOB challenge"}:{scope:o,mfa_token:s,oob_channel:n.oob_channel,challenge_type:g.challenge_type,binding_method:g.binding_method,oob_code:g.oob_code,name:n.name}}const h=new e.CrossauthError(e.ErrorCode.UnknownError,"Unsupported MFA type "+n.authenticator_type+" returned");return{error:h.oauthErrorCode,error_description:h.message}}async passwordOtp(a,s,o){var d;const i=await this.mfaOtpComplete(s.body.mfa_token,s.body.otp);if(i.error){const t=e.CrossauthError.fromOAuthError(i.error,i.error_description??"Error completing MFA");return e.CrossauthLogger.logger.warn(e.j({msg:"Error completing MFA",cerr:t,user:(d=s.user)==null?void 0:d.user,hashedMfaToken:c.Crypto.hash(s.body.mfa_token)})),e.CrossauthLogger.logger.debug(e.j({err:t})),a?await this.errorFn(this.server,s,o,t):o.view(this.mfaOtpPage,{user:s.user,scope:s.body.scope,mfa_token:s.body.mfa_token,challenge_tpye:s.body.challenge_type,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(i,this,s,o)??o}async passwordOob(a,s,o){var d;const i=await this.mfaOobComplete(s.body.mfa_token,s.body.oob_code,s.body.binding_code);if(i.error){const t=e.CrossauthError.fromOAuthError(i.error,i.error_description??"Error completing MFA");return e.CrossauthLogger.logger.warn(e.j({msg:"Error completing MFA",cerr:t,user:(d=s.user)==null?void 0:d.user,hashedMfaToken:c.Crypto.hash(s.body.mfa_token)})),e.CrossauthLogger.logger.debug(e.j({err:t})),a?await this.errorFn(this.server,s,o,t):o.view(this.mfaOobPage,{user:s.user,scope:s.body.scope,oob_code:s.body.mfa_token,name:s.body.name,challenge_tpye:s.body.challenge_type,mfa_token:s.body.mfa_token,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(i,this,s,o)??o}async deviceCodePost(a,s,o){var i;if(this.server.sessionAdapter){const{error:d,reply:t}=await this.server.errorIfCsrfInvalid(s,o,this.errorFn);if(d)return t}try{if(!s.csrfToken)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"CSRF token missing or invalid");let d=this.authServerBaseUrl;d.endsWith("/")||(d+="/"),d+=this.deviceAuthorizationUrl;const t=await this.startDeviceCodeFlow(d,s.body.scope);if(t.error){const h=e.CrossauthError.fromOAuthError(t.error,t.error_description),g={user:s.user,scope:s.body.scope,errorMessage:h.message,errorCode:h.code,errorCodeName:h.codeName,csrfToken:s.csrfToken,error:t.error,error_description:t.error_description};return a?o.header(...P).status(h.httpStatus).send(t):o.view(this.deviceCodeFlowPage,g)}let n;return t.verification_uri_complete&&await Ae.toDataURL(t.verification_uri_complete).then(h=>{n=h}).catch(h=>{e.CrossauthLogger.logger.debug(e.j({err:h})),e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't generate verification URL QR Code"}))}),a?o.header(...P).send(t):o.view(this.deviceCodeFlowPage,{user:s.user,scope:s.body.scope,verification_uri_qrdata:n,...t})}catch(d){const t=e.CrossauthError.asCrossauthError(d);e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:t,user:(i=s.user)==null?void 0:i.user})),e.CrossauthLogger.logger.debug(e.j({err:d}));const n={errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName};return a?o.header(...P).status(t.httpStatus).send(n):o.view(this.deviceCodeFlowPage,{user:s.user,csrfToken:s.csrfToken,scope:s.body.scope,...n})}}async deviceCodePoll(a,s,o){var i;try{const d=await this.pollDeviceCodeFlow(s.body.device_code);return d.error?o.header(...P).send(d):await this.receiveTokenFn(d,this,s,a?void 0:o)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:t,user:(i=s.user)==null?void 0:i.user})),e.CrossauthLogger.logger.debug(e.j({err:d})),await this.errorFn(this.server,s,o,t)}}async refresh(a,s,o,i,d,t){if(!t||!d)return o?void 0:await this.receiveTokenFn({},this,a,o?void 0:s);if(!i||t<=Date.now())try{const n=await this.refreshTokenFlow(d);if(!n.error&&!n.access_token&&(n.error="server_error",n.error_description="Unexpectedly did not receive error or access token"),!n.error){const u=await this.receiveTokenFn(n,this,a,o?void 0:s);if(!o)return u}if(!o){const u=e.CrossauthError.fromOAuthError(n.error??"server_error",n.error_description);return await this.errorFn(this.server,a,s,u)}let h=n.expires_in;if(!h&&n.access_token){const u=R.jwtDecode(n.access_token);u.exp&&(h=u.exp)}if(!h)throw new e.CrossauthError(e.ErrorCode.BadRequest,"OAuth server did not return an expiry for the access token");const g=new Date().getTime()+h*1e3;return{access_token:n.access_token,refresh_token:n.refresh_token,expires_in:n.expires_in,expires_at:g,error:n.error,error_description:n.error_description}}catch(n){if(e.CrossauthLogger.logger.debug(e.j({err:n})),e.CrossauthLogger.logger.error(e.j({cerr:n,msg:"Failed refreshing access token"})),!o){const h=e.CrossauthError.asCrossauthError(n);return await this.errorFn(this.server,a,s,h)}return{error:"server_error",error_description:"Failed refreshing access token"}}}async refreshTokens(a,s,o,i){if(!a.csrfToken)return s.header(...P).status(401).send({ok:!1,msg:"No csrf token given"});if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const d=await this.server.sessionAdapter.getSessionData(a,this.sessionDataName);if(!(d!=null&&d.refresh_token)){if(o)return s.header(...P).status(204).send();{const n=new e.CrossauthError(e.ErrorCode.InvalidSession,"No tokens found in session");return await this.errorFn(this.server,a,s,n)}}const t=await this.refresh(a,s,o,i,d.refresh_token,d.expires_at);if(!o){if(t==null)return this.receiveTokenFn({},this,a,s);if(t!=null)return t}return s.header(...P).status(200).send({ok:!0,expires_at:t==null?void 0:t.expires_at})}async deleteTokens(a){if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot delete tokens if sessions not enabled");if(!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidSession,"Missing or incorrec CSRF token");if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot delete session data if sessions not enabled");await this.server.sessionAdapter.deleteSessionData(a,this.sessionDataName)}async storeSessionData(a,s,o){var i;if(this.server.sessionServer){let d=this.server.sessionServer.getSessionCookieValue(s);!d&&o?d=await this.server.createAnonymousSession(s,o,{[this.sessionDataName]:a}):await((i=this.server.sessionAdapter)==null?void 0:i.updateSessionData(s,this.sessionDataName,a))}else{if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");await this.server.sessionAdapter.updateSessionData(s,this.sessionDataName,a)}}}class Ee extends c.OAuthResourceServer{constructor(a,s,o={}){super(s,o);l(this,"userStorage");l(this,"protectedEndpoints",{});l(this,"protectedEndpointPrefixes",[]);l(this,"errorBody",{});l(this,"sessionDataName","oauth");l(this,"tokenLocations",["header"]);l(this,"sessionAdapter");if(c.setParameter("errorBody",c.ParamType.Json,this,o,"OAUTH_RESSERVER_ACCESS_DENIED_BODY"),c.setParameter("tokenLocations",c.ParamType.JsonArray,this,o,"OAUTH_TOKEN_LOCATIONS"),c.setParameter("sessionDataName",c.ParamType.String,this,o,"OAUTH_SESSION_DATA_NAME"),this.userStorage=o.userStorage,this.sessionAdapter=o.sessionAdapter,o.protectedEndpoints){const i=/^[!#\$%&'\(\)\*\+,\.\/a-zA-Z\[\]\^_`-]+/;for(const[d,t]of Object.entries(o.protectedEndpoints)){if(!d.startsWith("/"))throw new e.CrossauthError(e.ErrorCode.Configuration,"protected endpoints must be absolute paths without the protocol and hostname");t.scope&&t.scope.forEach(n=>{if(!i.test(n))throw new e.CrossauthError(e.ErrorCode.Configuration,"Illegal characters in scope "+n)})}this.protectedEndpoints={...o.protectedEndpoints};for(let d in o.protectedEndpoints){let t=this.protectedEndpoints[d];t.suburls==!0&&(d.endsWith("/")||(d+="/",this.protectedEndpoints[d]=t),this.protectedEndpointPrefixes.push(d))}}o.protectedEndpoints&&a.addHook("preHandler",async(i,d)=>{var u,C;const t=i.url.split("?",2)[0];let n=!1,h="";if(t in this.protectedEndpoints)n=!0,h=t;else for(let f of this.protectedEndpointPrefixes)t.startsWith(f)&&(n=!0),h=f;if(!n)return;const g=await this.authorized(i);if(!(i.user&&i.authType=="cookie"&&this.protectedEndpoints[h].acceptSessionAuthorization!=!0)){if(!g){i.authError="access_denied",i.authErrorDescription="No access token";const f=this.authenticateHeader(i);return d.header("WWW-Authenticate",f).status(401).send(this.errorBody)}if(!g.authorized){const f=this.authenticateHeader(i);return d.header("WWW-Authenticate",f).status(401).send(this.errorBody)}}if(g){if(i.accessTokenPayload=g.tokenPayload,i.user=g.user,(u=g.tokenPayload)!=null&&u.scope)if(Array.isArray(g.tokenPayload.scope)){let f=[];for(let p of g.tokenPayload.scope)typeof p=="string"&&f.push(p);i.scope=f}else typeof g.tokenPayload.scope=="string"&&(i.scope=g.tokenPayload.scope.split(" "));if(this.protectedEndpoints[h].scope){for(let f of this.protectedEndpoints[h].scope??[])if(!i.scope||!i.scope.includes(f)&&this.protectedEndpoints[h].acceptSessionAuthorization!=!0)return e.CrossauthLogger.logger.warn(e.j({msg:"Access token does not have sufficient scope",username:(C=i.user)==null?void 0:C.username,url:i.url})),i.scope=void 0,i.accessTokenPayload=void 0,i.user=void 0,i.authError="access_denied",i.authErrorDescription="Access token does not have sufficient scope",d.status(401).send(this.errorBody)}if(i.authType="oauth",i.authError=g==null?void 0:g.error,(g==null?void 0:g.error)=="access_denied"){const f=this.authenticateHeader(i);return d.header("WWW-Authenticate",f).status(401).send(this.errorBody)}else if(g!=null&&g.error)return d.status(500).send(this.errorBody);i.authErrorDescription=g==null?void 0:g.error_description,e.CrossauthLogger.logger.debug(e.j({msg:"Resource server url",url:i.url,authorized:i.accessTokenPayload!=null}))}})}authenticateHeader(a){const s=a.url.split("?",2)[0];if(s in this.protectedEndpoints){let o="Bearer";return this.protectedEndpoints[s].scope&&(o+=' scope="'+(this.protectedEndpoints[s].scope??[]).join(" ")),o}return""}async authorized(a){try{let s;for(let i of this.tokenLocations)if(i=="header"){const d=await this.tokenFromHeader(a);if(d){s=d;break}}else{const d=await this.tokenFromSession(a);if(d){s=d;break}}let o;if(s){if(s.sub&&this.userStorage){const i=await this.userStorage.getUserByUsername(s.sub);i&&(o=i.user),a.user=o}else s.sub&&(a.user={id:s.userid??s.sub,username:s.sub,state:s.state??"active"});return{authorized:!0,tokenPayload:s,user:o}}else return{authorized:!1}}catch(s){const o=s;return e.CrossauthLogger.logger.debug(e.j({err:s})),e.CrossauthLogger.logger.error(e.j({cerr:o})),{authorized:!1,error:"server_error",error_description:o.message}}}async tokenFromHeader(a){const s=a.headers.authorization;if(s&&s.startsWith("Bearer ")){const o=s.split(" ");if(o.length==2)return await this.accessTokenAuthorized(o[1])}}async tokenFromSession(a){if(!this.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const s=await this.sessionAdapter.getSessionData(a,this.sessionDataName);if(s!=null&&s.session_token)return s.expires_at&&s.expires_at<Date.now()?void 0:await this.accessTokenAuthorized(s.session_token)}}const Re=`<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
|
1
|
+
"use strict";var ve=Object.defineProperty;var Se=(m,r,a)=>r in m?ve(m,r,{enumerable:!0,configurable:!0,writable:!0,value:a}):m[r]=a;var l=(m,r,a)=>Se(m,typeof r!="symbol"?r+"":r,a);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Pe=require("fastify"),ke=require("@fastify/view"),Te=require("@fastify/formbody"),ye=require("@fastify/cors"),be=require("@fastify/cookie"),ae=require("nunjucks"),c=require("@crossauth/backend"),e=require("@crossauth/common"),R=require("jwt-decode"),Ae=require("qrcode"),b=["Content-Type","application/json; charset=utf-8"];class he{constructor(r,a={}){l(this,"sessionServer");l(this,"enableEmailVerification",!0);l(this,"enablePasswordReset",!0);l(this,"prefix","/");l(this,"updateUserPage","updateuser.njk");l(this,"changeFactor2Page","changefactor2.njk");l(this,"configureFactor2Page","configurefactor2.njk");l(this,"changePasswordPage","changepassword.njk");l(this,"resetPasswordPage","resetpassword.njk");l(this,"requestPasswordResetPage","requestpasswordreset.njk");l(this,"emailVerifiedPage","emailverified.njk");l(this,"signupPage","signup.njk");l(this,"deleteUserPage","deleteuser.njk");this.sessionServer=r,c.setParameter("prefix",c.ParamType.String,this,a,"PREFIX"),c.setParameter("enableEmailVerification",c.ParamType.Boolean,this,a,"ENABLE_EMAIL_VERIFICATION"),c.setParameter("enablePasswordReset",c.ParamType.Boolean,this,a,"ENABLE_PASSWORD_RESET"),c.setParameter("updateUserPage",c.ParamType.String,this,a,"UPDATE_USER_PAGE"),c.setParameter("changeFactor2Page",c.ParamType.String,this,a,"CHANGE_FACTOR2_PAGE"),c.setParameter("configureFactor2Page",c.ParamType.String,this,a,"SIGNUP_FACTOR2_PAGE"),c.setParameter("changePasswordPage",c.ParamType.String,this,a,"CHANGE_PASSWORD_PAGE"),c.setParameter("resetPasswordPage",c.ParamType.String,this,a,"RESET_PASSWORD_PAGE"),c.setParameter("requestPasswordResetPage",c.ParamType.String,this,a,"REQUEST_PASSWORD_RESET_PAGE"),c.setParameter("emailVerifiedPage",c.ParamType.String,this,a,"EMAIL_VERIFIED_PAGE"),c.setParameter("signupPage",c.ParamType.String,this,a,"SIGNUP_PAGE"),c.setParameter("deleteUserPage",c.ParamType.String,this,a,"DELETE_USER_PAGE")}addUpdateUserEndpoints(){this.sessionServer.app.get(this.prefix+"updateuser",async(r,a)=>{var s;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"updateuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user||!this.sessionServer.canEditUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage);if(this.updateUserPage){let o={urlPrefix:this.prefix,csrfToken:r.csrfToken,user:r.user,allowedFactor2:this.sessionServer.allowedFactor2Details()};return a.view(this.updateUserPage,o)}}),this.sessionServer.app.post(this.prefix+"updateuser",async(r,a)=>{var s;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"updateuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.canEditUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage);for(let o in r.body)o.startsWith("user_")&&r.body[o];try{return await this.updateUser(r,a,(o,i,d)=>{const t=d?"Please click on the link in your email to verify your email address.":"Your details have been updated";return o.view(this.updateUserPage,{csrfToken:r.csrfToken,message:t,urlPrefix:this.prefix,allowedFactor2:this.sessionServer.allowedFactor2Details()})})}catch(o){const i=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:r.body.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o}));let d={};for(let t in r.body)t.startsWith("user_")&&(d[t]=r.body[t]);return this.sessionServer.handleError(o,r,a,(t,n)=>t.view(this.updateUserPage,{user:r.user,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,allowedFactor2:this.sessionServer.allowedFactor2Details(),...d}))}})}addApiUpdateUserEndpoints(){this.sessionServer.app.post(this.prefix+"api/updateuser",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/updateuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.canEditUser(r))return this.sessionServer.sendJsonError(a,401);try{return await this.updateUser(r,a,(i,d,t)=>i.header(...b).send({ok:!0,emailVerificationRequired:t}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})},!0)}})}addChangeFactor2Endpoints(){this.sessionServer.app.get(this.prefix+"changefactor2",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"changefactor2",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.sessionServer.isSessionUser(r)||!r.user){const d=await this.sessionServer.getSessionData(r,"factor2change");if(!(d!=null&&d.username)&&!this.sessionServer.isSessionUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage)}let s={urlPrefix:this.prefix,csrfToken:r.csrfToken,next:r.query.next??this.sessionServer.loginRedirect,allowedFactor2:this.sessionServer.allowedFactor2Details(),factor2:((i=r.user)==null?void 0:i.factor2)??"none",required:r.query.required??!1};return a.view(this.changeFactor2Page,s)}),this.sessionServer.app.post(this.prefix+"changefactor2",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"changefactor2",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.isSessionUser(r)||!r.user){const i=await this.sessionServer.getSessionData(r,"factor2change");if(!(i!=null&&i.username)&&!this.sessionServer.isSessionUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage)}try{return await this.changeFactor2(r,a,(i,d,t)=>d.factor2?i.view(this.configureFactor2Page,{csrfToken:d.csrfToken,next:r.body.next??this.sessionServer.loginRedirect,...d.userData}):i.view(this.configureFactor2Page,{message:"Two factor authentication has been updated",next:r.body.next??this.sessionServer.loginRedirect,csrfToken:d.csrfToken}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change two factor authentication failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{var h;return t.view(this.changeFactor2Page,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,allowedFactor2:this.sessionServer.allowedFactor2Details(),factor2:((h=r.user)==null?void 0:h.factor2)??"none",next:r.body.next??this.sessionServer.loginRedirect,required:r.body.required})})}})}addApiChangeFactor2Endpoints(){this.sessionServer.app.post(this.prefix+"api/changefactor2",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/changefactor2",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.isSessionUser(r))return this.sessionServer.sendJsonError(a,401);try{return await this.changeFactor2(r,a,(i,d,t)=>i.header(...b).send({ok:!0,...d.userData}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change factor2 failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]}),!0)}})}addChangePasswordEndpoints(){this.sessionServer.app.get(this.prefix+"changepassword",async(r,a)=>{var o;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"changepassword",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.sessionServer.isSessionUser(r)||!r.user){const i=await this.sessionServer.getSessionData(r,"passwordchange");if((i==null?void 0:i.username)==null&&!this.sessionServer.isSessionUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage)}let s={urlPrefix:this.prefix,csrfToken:r.csrfToken,next:r.query.next,required:r.query.required};return a.view(this.changePasswordPage,s)}),this.sessionServer.app.post(this.prefix+"changepassword",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"changepassword",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.changePassword(r,a,(i,d)=>r.body.next?i.redirect(r.body.next):i.view(this.changePasswordPage,{csrfToken:r.csrfToken,message:"Your password has been changed.",urlPrefix:this.prefix,next:r.body.next,required:r.body.required}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change password failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.view(this.changePasswordPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,next:r.body.next,required:r.body.required}))}})}addApiChangePasswordEndpoints(){this.sessionServer.app.post(this.prefix+"api/changepassword",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/changepassword",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.sessionServer.canEditUser(r))return this.sessionServer.sendJsonError(a,401);try{return await this.changePassword(r,a,(i,d)=>i.header(...b).send({ok:!0}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change password failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]}),!0)}})}addConfigureFactor2Endpoints(){this.sessionServer.app.get(this.prefix+"configurefactor2",async(r,a)=>{var s;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"configurefactor2",ip:r.ip}));try{return await this.reconfigureFactor2(r,a,(o,i,d)=>o.view(this.configureFactor2Page,{...i,next:r.query.next??this.sessionServer.loginRedirect}))}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({msg:"Configure factor2 failure",user:(s=r.user)==null?void 0:s.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.sessionServer.handleError(o,r,a,(d,t)=>d.view(this.configureFactor2Page,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:r.query.next??this.sessionServer.loginRedirect,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}}),this.sessionServer.app.post(this.prefix+"configurefactor2",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"configurefactor2",ip:r.ip}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.sessionServer.loginRedirect;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.configureFactor2(r,a,(o,i)=>{const d=i!=null&&i.factor2?this.sessionServer.authenticators[i.factor2]:void 0;return!this.sessionServer.isSessionUser(r)&&this.enableEmailVerification&&(d==null||d.skipEmailVerificationOnSignup()!=!0)?o.view(this.signupPage,{next:s,csrfToken:r.csrfToken,urlPrefix:this.prefix,message:"Please check your email to finish signing up."}):this.sessionServer.isSessionUser(r)?o.view(this.configureFactor2Page,{message:"Two-factor authentication updated",urlPrefix:this.prefix,next:s,required:r.body.required,csrfToken:r.csrfToken}):o.redirect(r.body.next??this.sessionServer.loginRedirect)})}catch(o){e.CrossauthLogger.logger.debug(e.j({err:o}));try{if(!r.sessionId){const n=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({msg:"Signup second factor failure",errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.error(e.j({msg:"Session not defined during two factor process"})),a.status(500).view(this.sessionServer.errorPage,{status:500,errorMessage:"An unknown error occurred",errorCode:e.ErrorCode.UnknownError,errorCodeName:"UnknownError"})}let i=(await this.sessionServer.sessionManager.dataForSessionId(r.sessionId))["2fa"];const d=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.error(e.j({msg:"Signup two factor failure",user:i==null?void 0:i.username,errorCodeName:d.codeName,errorCode:d.code}));const{userData:t}=await this.sessionServer.sessionManager.repeatTwoFactorSignup(r.sessionId);return this.sessionServer.handleError(o,r,a,(n,h)=>n.view(this.configureFactor2Page,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],urlPrefix:this.prefix,next:s,...t,csrfToken:this.sessionServer.csrfToken(r,n)}))}catch(i){return e.CrossauthLogger.logger.error(e.j({err:i})),a.status(500).view(this.sessionServer.errorPage,{status:500,errorMessage:"An unknown error occurred",errorCode:e.ErrorCode.UnknownError,errorCodeName:"UnknownError"})}}})}addApiConfigureFactor2Endpoints(r){this.sessionServer.app.get(r+"api/configurefactor2",async(a,s)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"GET",url:r+"api/configurefactor2",ip:a.ip,hashOfSessionId:this.sessionServer.getHashOfSessionId(a)}));try{return await this.reconfigureFactor2(a,s,(i,d,t)=>i.header(...b).send({ok:!0,...d}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Configure 2FA configuration failure",user:(o=a.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,a,s,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}}),this.sessionServer.app.post(r+"api/configurefactor2",async(a,s)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:r+"api/configurefactor2",ip:a.ip,hashOfSessionId:this.sessionServer.getHashOfSessionId(a)}));try{return await this.configureFactor2(a,s,(i,d)=>{const t={ok:!0,user:d};return this.sessionServer.isSessionUser(a)||(t.emailVerificationNeeded=this.enableEmailVerification),i.header(...b).send(t)})}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Configure 2FA configuration failure",user:(o=a.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,a,s,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addRequestPasswordResetEndpoints(){this.sessionServer.app.get(this.prefix+"requestpasswordreset",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"requestpasswordreset",ip:r.ip}));let s={csrfToken:r.csrfToken,next:r.query.next,required:r.query.required};return a.view(this.requestPasswordResetPage,s)}),this.sessionServer.app.post(this.prefix+"requestpasswordreset",async(r,a)=>{const s="If a user with exists with the email you entered, a message with a link to reset your password has been sent.";e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"requestpasswordreset",ip:r.ip}));try{return await this.requestPasswordReset(r,a,(o,i)=>o.view(this.requestPasswordResetPage,{csrfToken:r.csrfToken,message:s,urlPrefix:this.prefix}))}catch(o){return e.CrossauthLogger.logger.error(e.j({msg:"Request password reset faiulure user failure",email:r.body.email})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.sessionServer.handleError(o,r,a,(i,d)=>d.code==e.ErrorCode.EmailNotExist?i.view(this.requestPasswordResetPage,{csrfToken:r.csrfToken,message:s,urlPrefix:this.prefix,required:r.body.required,next:r.body.next}):r.body.next?i.redirect(r.body.next):i.view(this.requestPasswordResetPage,{errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code],email:r.body.email,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}})}addApiRequestPasswordResetEndpoints(){this.sessionServer.app.post(this.prefix+"api/requestpasswordreset",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/resetpasswordrequest",ip:r.ip}));try{return await this.requestPasswordReset(r,a,(s,o)=>s.header(...b).send({ok:!0}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Reset password failure failure",email:r.body.email,errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>{i.status(this.sessionServer.errorStatus(s)).header(...b).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})},!0)}})}addResetPasswordEndpoints(){this.sessionServer.app.get(this.prefix+"resetpassword/:token",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"resetpassword",ip:r.ip}));try{await this.sessionServer.sessionManager.userForPasswordResetToken(r.params.token)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return a.view(this.sessionServer.errorPage,{errorMessage:o.message,errorCode:o.code,errorCodeName:o.codeName})}return a.view(this.resetPasswordPage,{token:r.params.token,csrfToken:r.csrfToken})}),this.sessionServer.app.post(this.prefix+"resetpassword",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"resetpassword",ip:r.ip}));try{return await this.resetPassword(r,a,(s,o)=>s.view(this.resetPasswordPage,{csrfToken:r.csrfToken,message:"Your password has been changed.",urlPrefix:this.prefix}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Reset password failure",hashedToken:c.Crypto.hash(r.body.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>i.view(this.resetPasswordPage,{errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,token:r.body.token}))}})}addApiResetPasswordEndpoints(){this.sessionServer.app.post(this.prefix+"api/resetpassword",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/resetpassword",ip:r.ip}));try{return await this.resetPassword(r,a,(s,o)=>s.header(...b).send({ok:!0}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Reset password failure",hashedToken:c.Crypto.hash(r.body.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>{i.status(this.sessionServer.errorStatus(s)).header(...b).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})},!0)}})}addVerifyEmailEndpoints(){this.sessionServer.app.get(this.prefix+"verifyemail/:token",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"verifyemail",ip:r.ip}));try{return await this.verifyEmail(r,a,(s,o)=>s.view(this.emailVerifiedPage,{urlPrefix:this.prefix,user:o}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Verify email failed",hashedToken:c.Crypto.hash(r.params.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>i.view(this.sessionServer.errorPage,{errorCode:d.code,errorCodeName:e.ErrorCode[d.code],errorMessage:d.message,errorMessages:d.messages,urlPrefix:this.prefix}))}})}addApiVerifyEmailEndpoints(){this.sessionServer.app.get(this.prefix+"api/verifyemail/:token",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/verifyemail",ip:r.ip}));try{return await this.verifyEmail(r,a,(s,o)=>s.header(...b).send({ok:!0,user:o}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Verify email failure",hashedToken:c.Crypto.hash(r.params.token),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.sessionServer.handleError(s,r,a,(i,d)=>{i.status(this.sessionServer.errorStatus(s)).header(...b).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})})}})}addDeleteUserEndpoints(){this.sessionServer.app.get(this.prefix+"deleteuser",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"deleteuser",ip:r.ip}));let s;if(!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteuser");try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call delete user unless a user storage is provided");s=(await this.sessionServer.userStorage.getUserById(r.user.id)).user}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??this.prefix;let i={urlPrefix:this.prefix,csrfToken:r.csrfToken,next:o,isAdmin:!1,user:s};return a.view(this.deleteUserPage,i)}),this.sessionServer.app.post(this.prefix+"deleteuser",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"deleteuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteuser");const s=r.body.next??this.prefix;try{return await this.deleteUser(r,a,d=>{var t;return d.view(this.deleteUserPage,{message:"User deleted",csrfToken:r.csrfToken,urlPrefix:this.prefix,userid:(t=r.user)==null?void 0:t.id,isAdmin:!1,next:s})})}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting user",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{var C;const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteUserPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,userid:(C=r.user)==null?void 0:C.id,isAdmin:!1,next:s})})}})}addApiDeleteUserEndpoints(){this.sessionServer.app.post(this.prefix+"api/deleteuser",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/deleteuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user)return a.status(401).header(...b).send({ok:!1});try{return await this.deleteUser(r,a,i=>{var d;return i.header(...b).send({ok:!0,userid:(d=r.user)==null?void 0:d.id})})}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...b).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async updateUser(r,a,s){if(!this.sessionServer.canEditUser(r)||!r.user)throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);let o={id:r.user.id,username:r.user.username,state:"active"};if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateUser unless a user storage is provided");o=this.sessionServer.updateUserFn(o,r,this.sessionServer.userStorage.userEditableFields);let i=this.sessionServer.validateUserFn(o);if(i.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,i);let d=await this.sessionServer.sessionManager.updateUser(r.user,o);return s(a,r.user,d.emailVerificationTokenSent)}async changeFactor2(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changeFactor2 unless a user storage is provided");let o;if(!this.sessionServer.isSessionUser(r)||!r.user){const n=await this.sessionServer.getSessionData(r,"factor2change");if(n!=null&&n.username)o=(await this.sessionServer.userStorage.getUserByUsername(n==null?void 0:n.username,{skipActiveCheck:!0,skipEmailVerifiedCheck:!0})).user;else throw new e.CrossauthError(e.ErrorCode.Unauthorized)}else if(this.sessionServer.canEditUser(r))o=r.user;else throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);let i=r.body.factor2;if(r.body.factor2&&!this.sessionServer.allowedFactor2.includes(r.body.factor2))throw new e.CrossauthError(e.ErrorCode.Forbidden,"Illegal second factor "+r.body.factor2+" requested");(r.body.factor2=="none"||r.body.factor2=="")&&(i=void 0);const d=await this.sessionServer.sessionManager.initiateTwoFactorSetup(o,i,r.sessionId);let t={factor2:i,userData:d,username:d.username,next:r.body.next??this.sessionServer.loginRedirect,csrfToken:r.csrfToken};return s(a,t)}async changePassword(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changePassword unless a user storage is provided");let o,i=!1;if(!this.sessionServer.isSessionUser(r)||!r.user){const f=await this.sessionServer.getSessionData(r,"passwordchange");if(f!=null&&f.username){if(o=(await this.sessionServer.userStorage.getUserByUsername(f==null?void 0:f.username,{skipActiveCheck:!0,skipEmailVerifiedCheck:!0})).user,i=!0,!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf)}else throw new e.CrossauthError(e.ErrorCode.Unauthorized)}else if(this.sessionServer.canEditUser(r)){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);o=r.user}else throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const d=this.sessionServer.authenticators[o.factor1],t=d.secretNames();let n={},h={},g={};for(let f in r.body)if(f.startsWith("new_")){const p=f.replace(/^new_/,"");t.includes(p)&&(h[p]=r.body[f])}else if(f.startsWith("old_")){const p=f.replace(/^old_/,"");t.includes(p)&&(n[p]=r.body[f])}else if(f.startsWith("repeat_")){const p=f.replace(/^repeat_/,"");t.includes(p)&&(g[p]=r.body[f])}if(Object.keys(g).length===0&&(g=void 0),d.validateSecrets(h).length>0)throw new e.CrossauthError(e.ErrorCode.PasswordFormat);const C=o.state;try{i&&(o.state="active",await this.sessionServer.userStorage.updateUser({id:o.id,state:o.state})),await this.sessionServer.sessionManager.changeSecrets(o.username,1,h,g,n)}catch(f){const p=e.CrossauthError.asCrossauthError(f);if(e.CrossauthLogger.logger.debug(e.j({err:f})),i)try{await this.sessionServer.userStorage.updateUser({id:o.id,state:C})}catch(E){e.CrossauthLogger.logger.debug(e.j({err:E}))}throw p}return i?await this.sessionServer.loginWithUser(o,!1,r,a,s):s(a,void 0)}async configureFactor2(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"No session active while enabling 2FA. Please enable cookies");let o=await this.sessionServer.sessionManager.completeTwoFactorSetup(r.body,r.sessionId);return!this.sessionServer.isSessionUser(r)&&!this.enableEmailVerification?this.sessionServer.loginWithUser(o,!0,r,a,(i,d)=>s(i,d)):s(a,o)}async reconfigureFactor2(r,a,s){if(!r.user||!r.sessionId||!this.sessionServer.isSessionUser(r))throw new e.CrossauthError(e.ErrorCode.Unauthorized);let o=r.user.factor2;const i=this.sessionServer.authenticators[o];if(!i||i.secretNames().length==0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"Selected second factor does not have configuration");let t={...await this.sessionServer.sessionManager.initiateTwoFactorSetup(r.user,o,r.sessionId),csrfToken:r.csrfToken};return s(a,t)}async requestPasswordReset(r,a,s){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"password reset not enabled");if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=r.body.email;try{await this.sessionServer.sessionManager.requestPasswordReset(o)}catch(i){e.CrossauthError.asCrossauthError(i).code==e.ErrorCode.UserNotExist?e.CrossauthLogger.logger.warn(e.j({msg:"Password reset requested for invalid email",email:r.body.email})):e.CrossauthLogger.logger.debug(e.j({err:i,msg:"Couldn't send password reset email"}))}return s(a,void 0)}async resetPassword(r,a,s){if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=r.body.token,i=await this.sessionServer.sessionManager.userForPasswordResetToken(o),d=this.sessionServer.authenticators[i.factor1],t=d.secretNames();let n={},h={};for(let C in r.body)if(C.startsWith("new_")){const f=C.replace(/^new_/,"");t.includes(f)&&(n[f]=r.body[C])}else if(C.startsWith("repeat_")){const f=C.replace(/^repeat_/,"");t.includes(f)&&(h[f]=r.body[C])}if(Object.keys(h).length===0&&(h=void 0),d.validateSecrets(n).length>0)throw new e.CrossauthError(e.ErrorCode.PasswordFormat);const u=await this.sessionServer.sessionManager.resetSecret(o,1,n,h);return u.state!=e.UserState.factor2ResetNeeded?this.sessionServer.loginWithUser(u,!0,r,a,s):s(a)}async verifyEmail(r,a,s){if(!this.enableEmailVerification)throw new e.CrossauthError(e.ErrorCode.Configuration,"Email verification reset not enabled");const o=r.params.token,i=await this.sessionServer.sessionManager.applyEmailVerificationToken(o);return await this.sessionServer.loginWithUser(i,!0,r,a,s)}async deleteUser(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call deleteUser unless a user storage is provided");if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return await this.sessionServer.userStorage.deleteUserById(r.user.id),s(a)}}async function _e(m,r){let a=[];try{const{user:s}=await r.getUserByUsername(m);a.push(s)}catch(s){const o=e.CrossauthError.asCrossauthError(s);if(o.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:o})),o;try{const{user:i}=await r.getUserByEmail(m);a.push(i)}catch(i){const d=e.CrossauthError.asCrossauthError(i);if(d.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:d})),o}}return a}const x=["Content-Type","application/json; charset=utf-8"];class ge{constructor(r,a={}){l(this,"sessionServer");l(this,"adminPrefix","/admin/");l(this,"userSearchFn",_e);l(this,"enableOAuthClientManagement",!1);l(this,"adminCreateUserPage","admin/createuser.njk");l(this,"adminSelectUserPage","admin/selectuser.njk");l(this,"adminUpdateUserPage","admin/updateuser.njk");l(this,"adminChangePasswordPage","admin/changepassword.njk");l(this,"deleteUserPage","deleteuser.njk");this.sessionServer=r,c.setParameter("adminPrefix",c.ParamType.String,this,a,"ADMIN_PREFIX"),c.setParameter("adminCreateUserPage",c.ParamType.String,this,a,"ADMIN_CREATE_USER_PAGE"),c.setParameter("adminSelectUserPage",c.ParamType.String,this,a,"ADMIN_SELECT_USER_PAGE"),c.setParameter("adminUpdateUserPage",c.ParamType.String,this,a,"ADMIN_UPDATE_USER_PAGE"),c.setParameter("adminChangePasswordPage",c.ParamType.String,this,a,"ADMIN_CHANGE_PASSWORD_PAGE"),c.setParameter("enableOAuthClientManagement",c.ParamType.Boolean,this,a,"ENABLE_OAUTH_CLIENT_MANAGEMENT"),c.setParameter("deleteUserPage",c.ParamType.String,this,a,"DELETE_USER_PAGE"),this.adminPrefix.endsWith("/")||(this.adminPrefix+="/"),this.adminPrefix.startsWith("/")||""+this.adminPrefix,a.userSearchFn&&(this.userSearchFn=a.userSearchFn)}addCreateUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"createuser",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"createuser",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);let s={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,allowedFactor2:this.sessionServer.allowedFactor2Details()};return r.query.next&&(s.next=r.query.next),a.view(this.adminCreateUserPage,s)}),this.sessionServer.app.post(this.adminPrefix+"createuser",async(r,a)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"createuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.adminPrefix;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.createUser(r,a,(i,d,t)=>i.redirect(302,s))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Signup failure",user:r.body.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{const g=e.CrossauthError.asCrossauthError(i).httpStatus;return t.status(g).view(this.adminCreateUserPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,csrfToken:r.csrfToken,factor2:r.body.factor2,allowedFactor2:this.sessionServer.allowedFactor2Details(),urlPrefix:this.adminPrefix,...r.body})})}})}addApiCreateUserEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/createuser",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/createuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.createUser(r,a,(i,d,t)=>i.header(...x).send({ok:!0,user:t,...d.userData}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Create user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...x).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addSelectUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"selectuser",async(r,a)=>{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call selectuser unless a user storage is provided");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"selectuser",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{let s=[],o=Number(r.query.skip),i=Number(r.query.take);o<0&&(i=-o,o=0),o||(o=0),i||(i=10),r.query.search?s=await this.userSearchFn(r.query.search,this.sessionServer.userStorage):s=await this.sessionServer.userStorage.getUsers(o,i);let d={urlPrefix:this.adminPrefix,skip:o,take:i,users:s,havePrevious:o>0,haveNext:i!=null&&s.length==i};return r.query.next&&(d.next=r.query.next),a.view(this.adminSelectUserPage,d)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({err:s})),v.sendPageError(a,o.httpStatus,this.sessionServer.errorPage,o.message,o)}})}addUpdateUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"updateuser/:id",async(r,a)=>{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateuser unless a user storage is provided");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"updateuser",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{const{user:s}=await this.sessionServer.userStorage.getUserById(r.params.id);let o={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,user:s,allowedFactor2:this.sessionServer.allowedFactor2Details(),enableOAuthClientManagement:this.enableOAuthClientManagement};return a.view(this.adminUpdateUserPage,o)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({err:s})),v.sendPageError(a,o.httpStatus,this.sessionServer.errorPage,o.message,o)}}),this.sessionServer.app.post(this.adminPrefix+"updateuser/:id",async(r,a)=>{var o;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"updateuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.sessionServer.canEditUser(r))return v.sendPageError(a,401,this.sessionServer.errorPage);let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateuser unless a user storage is provided");const{user:i}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=i,await this.updateUser(s,r,a,(d,t,n,h)=>{let g="User's details have been updated.";return n?g="User's details have been updated and sent and an email verification link.":h&&(g="User's details have been updated and sent and a password reset token sent."),d.view(this.adminUpdateUserPage,{csrfToken:r.csrfToken,message:g,urlPrefix:this.adminPrefix,allowedFactor2:this.sessionServer.allowedFactor2Details()})})}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:r.body.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>s?t.view(this.adminUpdateUserPage,{user:s,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,allowedFactor2:this.sessionServer.allowedFactor2Details(),...r.body}):v.sendPageError(t,d.httpStatus,this.sessionServer.errorPage,d.message,d))}})}addDeleteUserEndpoints(){this.sessionServer.app.get(this.adminPrefix+"deleteuser/:id",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"deleteclient",ip:r.ip}));let s;if(!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call deleteuser unless a user storage is provided");s=(await this.sessionServer.userStorage.getUserById(r.params.id)).user}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??this.adminPrefix+"selectuser";let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,next:o,isAdmin:!0,user:s};return a.view(this.deleteUserPage,i)}),this.sessionServer.app.post(this.adminPrefix+"deleteuser/:id",async(r,a)=>{var o,i;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"deleteuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));const s=r.body.next??this.adminPrefix+"selectuser";try{return await this.deleteUser(r,a,d=>d.view(this.deleteUserPage,{message:"User deleted",csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,userid:r.params.id,isAdmin:!0,next:s}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting user",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteUserPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,userid:r.params.id,isAdmin:!0,next:s})})}})}addApiUpdateUserEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/updateuser/:id",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/updateuser",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user||!v.isAdmin(r.user))return this.sessionServer.sendJsonError(a,401);let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateuser unless a user storage is provided");const{user:d}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=d,await this.updateUser(s,r,a,(t,n,h)=>t.header(...x).send({ok:!0,emailVerificationRequired:h}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{n.status(this.sessionServer.errorStatus(d)).header(...x).send({ok:!1,errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})},!0)}})}addChangePasswordEndpoints(){this.sessionServer.app.get(this.adminPrefix+"changepassword/:id",async(r,a)=>{var s;if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changepassword unless a user storage is provided");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"changepassword",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{const{user:o}=await this.sessionServer.userStorage.getUserById(r.params.id);let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,user:o};return a.view(this.adminChangePasswordPage,i)}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({err:o})),v.sendPageError(a,i.httpStatus,this.sessionServer.errorPage,i.message,i)}}),this.sessionServer.app.post(this.adminPrefix+"changepassword/:id",async(r,a)=>{var o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"changepassword",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changepassword unless a user storage is provided");const{user:i}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=i,await this.changePassword(s,r,a,(d,t)=>r.body.next?d.redirect(r.body.next):d.view(this.adminChangePasswordPage,{csrfToken:r.csrfToken,message:"User's password has been changed.",urlPrefix:this.adminPrefix,next:r.body.next,required:r.body.required,user:s}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Change password failure",userid:r.params.id,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>t.view(this.adminChangePasswordPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix}))}})}addApiChangePasswordEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/changepassword/:id",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/changepassword",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user||!v.isAdmin(r.user))return this.sessionServer.sendJsonError(a,401);let s;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call changepassword unless a user storage is provided");const{user:d}=await this.sessionServer.userStorage.getUserById(r.params.id);return s=d,await this.changePassword(s,r,a,(t,n)=>t.header(...x).send({ok:!0}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Update user failure",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{n.status(this.sessionServer.errorStatus(d)).header(...x).send({ok:!1,errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})},!0)}})}addApiDeleteUserEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/deleteuser/:id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/deleteuser",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.deleteUser(r,a,i=>i.header(...x).send({ok:!0,client_id:r.params.id}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete user failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...x).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async createUser(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createUser unless a user storage is provided");if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);if(r.body.factor2||(r.body.factor2=this.sessionServer.allowedFactor2[0]),r.body.factor2&&!this.sessionServer.allowedFactor2.includes(r.body.factor2??"none"))throw new e.CrossauthError(e.ErrorCode.Forbidden,"Illegal second factor "+r.body.factor2+" requested");(r.body.factor2=="none"||r.body.factor2=="")&&(r.body.factor2=void 0);let o=this.sessionServer.createUserFn(r,{...this.sessionServer.userStorage.userEditableFields,...this.sessionServer.userStorage.adminEditableFields},this.sessionServer.adminAllowedFactor1);const i=this.sessionServer.authenticators[o.factor1].secretNames();let d=!0;for(let C of i)!r.body[C]&&!r.body["repeat_"+C]&&(d=!1);let t=[],n={};if(d){t=this.sessionServer.authenticators[o.factor1].validateSecrets(r.body);for(let C in r.body)if(C.startsWith("repeat_")){const f=C.replace(/^repeat_/,"");i.includes(f)&&(n[f]=r.body[C])}Object.keys(n).length===0&&(n=void 0)}d?o.factor2&&o.factor2!="none"&&(o.state=e.UserState.factor2ResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.factor2ResetNeeded}`,username:o.username}))):o.factor2&&o.factor2!="none"?(o.state=e.UserState.passwordAndFactor2ResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.passwordAndFactor2ResetNeeded}`,username:o.username}))):(o.state=e.UserState.passwordResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.passwordResetNeeded}`,username:o.username})));let g=[...this.sessionServer.validateUserFn(o),...t];if(g.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,g);const u=await this.sessionServer.sessionManager.createUser(o,r.body,n,!0,!d);if(!d){let C=r.body.username;if("user_email"in r.body){const f=r.body.user_email;typeof f=="string"&&(C=f)}if(c.TokenEmailer.validateEmail(C),!C)throw new e.CrossauthError(e.ErrorCode.FormEntry,"No password given but no email address found either");await this.sessionServer.sessionManager.requestPasswordReset(C)}return s(a,{},u)}async accessDeniedPage(r,a){const s=new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return this.sessionServer.handleError(s,r,a,(o,i)=>o.status(s.httpStatus).view(this.sessionServer.errorPage,{errorMessage:i.message,errorMessages:i.messages,errorCode:i.code,errorCodeName:e.ErrorCode[i.code]}))}async updateUser(r,a,s,o){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateUser unless a user storage is provided");if(!a.user||!v.isAdmin(a.user))throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(this.sessionServer.isSessionUser(a)&&!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const i=r.factor2,d=r.state;r.state=a.body.state,r=this.sessionServer.updateUserFn(r,a,{...this.sessionServer.userStorage.userEditableFields,...this.sessionServer.userStorage.adminEditableFields});const t=r.factor2&&r.factor2!="none"&&r.factor2!=i;if(t&&!(r.state==d||r.state=="factor2ResetNeeded"))throw new e.CrossauthError(e.ErrorCode.BadRequest,"Cannot change both factor2 and state at the same time");t&&(r.state=e.UserState.factor2ResetNeeded,e.CrossauthLogger.logger.warn(e.j({msg:`Setting state for user to ${e.UserState.factor2ResetNeeded}`,username:r.username})));let n=this.sessionServer.validateUserFn(r);if(n.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,n);let h=await this.sessionServer.sessionManager.updateUser(r,r,!0);return o(s,a.user,h.emailVerificationTokenSent,h.passwordResetTokenSent)}async changePassword(r,a,s,o){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateUser unless a user storage is provided");if(!a.user||!v.isAdmin(a.user))throw new e.CrossauthError(e.ErrorCode.Unauthorized);if(this.sessionServer.isSessionUser(a)&&!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const i=this.sessionServer.authenticators[r.factor1],d=i.secretNames();let t={},n={};for(let g in a.body)if(g.startsWith("new_")){const u=g.replace(/^new_/,"");d.includes(u)&&(t[u]=a.body[g])}else if(g.startsWith("repeat_")){const u=g.replace(/^repeat_/,"");d.includes(u)&&(n[u]=a.body[g])}if(Object.keys(n).length===0&&(n=void 0),i.validateSecrets(t).length>0)throw new e.CrossauthError(e.ErrorCode.PasswordFormat);return r.state="active",await this.sessionServer.userStorage.updateUser({id:r.id,state:r.state}),await this.sessionServer.sessionManager.changeSecrets(r.username,1,t,n),o(s,void 0)}async deleteUser(r,a,s){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call deleteUser unless a user storage is provided");if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return await this.sessionServer.userStorage.deleteUserById(r.params.id),s(a)}}async function ce(m,r,a){let s=[];try{const o=await r.getClientById(m);s.push(o)}catch(o){const i=e.CrossauthError.asCrossauthError(o);if(i.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:i})),i;try{s=await r.getClientByName(m,a)}catch(d){const t=e.CrossauthError.asCrossauthError(d);if(t.code!=e.ErrorCode.UserNotExist)throw e.CrossauthLogger.logger.debug(e.j({err:t})),i}}return s}const j=["Content-Type","application/json; charset=utf-8"];class le{constructor(r,a={}){l(this,"sessionServer");l(this,"clientStorage");l(this,"clientManager");l(this,"adminPrefix","/admin/");l(this,"clientSearchFn",ce);l(this,"validFlows",["all"]);l(this,"selectClientPage","selectclient.njk");l(this,"createClientPage","createclient.njk");l(this,"updateClientPage","updateclient.njk");l(this,"deleteClientPage","deleteclient.njk");if(this.sessionServer=r,!a.clientStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Must specify clientStorage if adding OAuth client endpoints");this.clientManager=new c.OAuthClientManager(a),this.clientStorage=a.clientStorage,c.setParameter("adminPrefix",c.ParamType.String,this,a,"ADMIN_PREFIX"),c.setParameter("createClientPage",c.ParamType.String,this,a,"CREATE_CLIENT_PAGE"),c.setParameter("updateClientPage",c.ParamType.String,this,a,"UPDATE_CLIENT_PAGE"),c.setParameter("selectClientPage",c.ParamType.String,this,a,"SELECT_CLIENT_PAGE"),c.setParameter("deleteClientPage",c.ParamType.String,this,a,"DELETE_CLIENT_PAGE"),c.setParameter("validFlows",c.ParamType.JsonArray,this,a,"OAUTH_validFlows"),this.validFlows.length==1&&this.validFlows[0]==e.OAuthFlows.All&&(this.validFlows=e.OAuthFlows.allFlows()),a.clientSearchFn&&(this.clientSearchFn=a.clientSearchFn)}addSelectClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"selectclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"selectclient",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);const s=r.query.next??encodeURIComponent(r.url);try{let o=[],i=Number(r.query.skip),d=Number(r.query.take);i||(i=0),d||(d=10);let t=null,n;if(r.query.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call selectclient with user unless a user storage is provided");n=(await this.sessionServer.userStorage.getUserById(r.query.userid)).user,t=n.id}r.query.search?o=await this.clientSearchFn(r.query.search,this.clientStorage,t):o=await this.clientStorage.getClients(i,d,t);let h={urlPrefix:this.adminPrefix,user:n,skip:i,take:d,clients:o,havePrevious:i>0,haveNext:d!=null&&o.length==d,isAdmin:!0,next:s};return r.query.next&&(h.next=r.query.next),a.view(this.selectClientPage,h)}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({err:o})),v.sendPageError(a,i.httpStatus,this.sessionServer.errorPage,i.message,i)}})}addCreateClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"createclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"createclient",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);let s=r.query.next;s||(r.query.userid?s=this.adminPrefix+"selectuser":s=this.adminPrefix+"selectclient");let o;try{if(r.query.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createclient unless a user storage is provided");o=(await this.sessionServer.userStorage.getUserById(r.query.userid)).user}}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s};return a.view(this.createClientPage,i)}),this.sessionServer.app.post(this.adminPrefix+"createclient",async(r,a)=>{var i,d;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"createclient",ip:r.ip,user:(i=r.user)==null?void 0:i.username}));let s=r.body.next;s||(r.body.userid?s=this.adminPrefix+"selectuser":s=this.adminPrefix+"selectclient");let o;try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createclient unless a user storage is provided");o=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user}return await this.createClient(r,a,(t,n)=>t.view(this.createClientPage,{message:"Created client",client:n,csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s,...r.body}),o)}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.error(e.j({msg:"Failed creating OAuth client",user:(d=r.user)==null?void 0:d.username,errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.debug(e.j({err:t})),this.sessionServer.handleError(t,r,a,(h,g)=>{const C=e.CrossauthError.asCrossauthError(t).httpStatus;return h.status(C).view(this.createClientPage,{errorMessage:g.message,errorMessages:g.messages,errorCode:g.code,errorCodeName:e.ErrorCode[g.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),isAdmin:!0,next:s,...r.body})})}})}addUpdateClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"updateclient/:client_id",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"updateclient",ip:r.ip})),!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);let s;try{s=await this.clientStorage.getClientById(r.params.client_id)}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let o=r.query.next;o||(r.query.userid?o=this.adminPrefix+"selectuser":o=this.adminPrefix+"selectclient");let i;try{if(s.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient with user unless a user storage is provided");i=(await this.sessionServer.userStorage.getUserById(s.userid)).user}}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let d={};for(let n of this.validFlows)s.valid_flow.includes(n)&&(d[n]=!0);let t={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),selectedFlows:d,user:i,client_id:s.client_id,client_name:s.client_name,confidential:s.confidential,redirect_uris:s.redirect_uri.join(" "),isAdmin:!0,next:o};return a.view(this.updateClientPage,t)}),this.sessionServer.app.post(this.adminPrefix+"updateclient/:client_id",async(r,a)=>{var i,d;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"updateclient",ip:r.ip,user:(i=r.user)==null?void 0:i.username}));let s=r.body.next;s||(r.body.userid?s=this.adminPrefix+"selectuser":s=this.adminPrefix+"selectclient");let o;try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient with user unless a user storage is provided");o=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user}return await this.updateClient(r,a,(t,n,h)=>t.view(this.updateClientPage,{message:"Updated client",client:n,csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s,newSecret:h,...r.body}))}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(d=r.user)==null?void 0:d.username,errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.debug(e.j({err:t})),this.sessionServer.handleError(t,r,a,(h,g)=>{const C=e.CrossauthError.asCrossauthError(t).httpStatus;let f={};for(let p of this.validFlows)p in r.body&&(f[p]=!0);return h.status(C).view(this.updateClientPage,{errorMessage:g.message,errorMessages:g.messages,errorCode:g.code,errorCodeName:e.ErrorCode[g.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,isAdmin:!0,next:s,validFlows:this.validFlows,selectedFlows:f,flowNames:e.OAuthFlows.flowNames(this.validFlows),...r.body})})}})}addDeleteClientEndpoints(){this.sessionServer.app.get(this.adminPrefix+"deleteclient/:client_id",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.adminPrefix+"deleteclient",ip:r.ip}));let s;if(!(r!=null&&r.user)||!v.isAdmin(r.user))return this.accessDeniedPage(r,a);try{s=await this.clientStorage.getClientById(r.params.client_id)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??this.adminPrefix+"selectclient";let i={urlPrefix:this.adminPrefix,csrfToken:r.csrfToken,next:o,client:s};return a.view(this.deleteClientPage,i)}),this.sessionServer.app.post(this.adminPrefix+"deleteclient/:client_id",async(r,a)=>{var o,i;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"deleteclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));const s=r.body.next??this.adminPrefix+"selectclient";try{return await this.deleteClient(r,a,d=>d.view(this.deleteClientPage,{message:"Client deleted",csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,validFlows:this.validFlows,client_id:r.params.client_id,next:s}))}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting OAuth client",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteClientPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.adminPrefix,client_id:r.params.client_id,validFlows:this.validFlows,next:s})})}})}addApiCreateClientEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/createclient",async(r,a)=>{var o,i;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/createclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username}));let s;try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call createclient with user unless a user storage is provided");s=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user}return await this.createClient(r,a,(d,t)=>d.header(...j).send({ok:!0,client:t}),s)}catch(d){const t=e.CrossauthError.asCrossauthError(d);e.CrossauthLogger.logger.error(e.j({msg:"Create client failure",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{n.status(this.sessionServer.errorStatus(d)).header(...j).send({ok:!1,errorMessage:h.message,errorMessages:h.messages,errorCode:e.ErrorCode[h.code]})})}})}addApiUpdateClientEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/updateclient/:client_id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.adminPrefix+"api/updateclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{if(r.body.userid){if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient with user unless a user storage is provided");await this.sessionServer.userStorage.getUserById(r.body.userid)}return await this.updateClient(r,a,(i,d,t)=>i.header(...j).send({ok:!0,client:d,csrfToken:r.csrfToken,newSecret:t}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...j).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})})}})}addApiDeleteClientEndpoints(){this.sessionServer.app.post(this.adminPrefix+"api/deleteclient/:client_id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.adminPrefix+"api/deleteclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.deleteClient(r,a,i=>i.header(...j).send({ok:!0,client_id:r.params.client_id}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete client failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...j).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async accessDeniedPage(r,a){const s=new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return this.sessionServer.handleError(s,r,a,(o,i)=>o.status(s.httpStatus).view(this.sessionServer.errorPage,{errorMessage:i.message,errorMessages:i.messages,errorCode:i.code,errorCodeName:e.ErrorCode[i.code]}))}async createClient(r,a,s,o){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const i=r.body.confidential=="true",d=r.body.client_name,t=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let n=[];for(let u of t)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),n.push("["+u+"]")}if(n.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+n.join(" "));let h=[];for(let u of this.validFlows)u in r.body&&h.push(u);const g=await this.clientManager.createClient(d,t,h,i,o==null?void 0:o.id);return s(a,g)}async updateClient(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const o=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let i=[];for(let u of o)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),i.push("["+u+"]")}if(i.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+i.join(" "));let d=[];for(let u of this.validFlows)u in r.body&&d.push(u);const t={};t.client_name=r.body.client_name,t.confidential=r.body.confidential=="true",t.valid_flow=d,t.redirect_uri=o,t.userid=r.body.userid,t.userid==null&&(t.userid=null);const n=r.body.resetSecret=="true",{client:h,newSecret:g}=await this.clientManager.updateClient(r.params.client_id,t,n);return s(a,h,g)}async deleteClient(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user||!v.isAdmin(r.user))throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);return await this.clientStorage.deleteClient(r.params.client_id),s(a)}}const F=["Content-Type","application/json; charset=utf-8"];class ue{constructor(r,a={}){l(this,"sessionServer");l(this,"clientStorage");l(this,"clientManager");l(this,"prefix","/");l(this,"clientSearchFn",ce);l(this,"validFlows",["all"]);l(this,"selectClientPage","selectclient.njk");l(this,"createClientPage","createclient.njk");l(this,"updateClientPage","updateclient.njk");l(this,"deleteClientPage","deleteclient.njk");if(this.sessionServer=r,!a.clientStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Must specify clientStorage if adding OAuth client endpoints");this.clientManager=new c.OAuthClientManager(a),this.clientStorage=a.clientStorage,c.setParameter("prefix",c.ParamType.String,this,a,"PREFIX"),c.setParameter("createClientPage",c.ParamType.String,this,a,"CREATE_CLIENT_PAGE"),c.setParameter("updateClientPage",c.ParamType.String,this,a,"UPDATE_CLIENT_PAGE"),c.setParameter("selectClientPage",c.ParamType.String,this,a,"SELECT_CLIENT_PAGE"),c.setParameter("deleteClientPage",c.ParamType.String,this,a,"DELETE_CLIENT_PAGE"),c.setParameter("validFlows",c.ParamType.JsonArray,this,a,"OAUTH_validFlows"),this.validFlows.length==1&&this.validFlows[0]==e.OAuthFlows.All&&(this.validFlows=e.OAuthFlows.allFlows()),a.clientSearchFn&&(this.clientSearchFn=a.clientSearchFn)}addSelectClientEndpoints(){this.sessionServer.app.get(this.prefix+"selectclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"selectclient",ip:r.ip})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"selectclient");try{let s=[],o=Number(r.query.skip),i=Number(r.query.take);o||(o=0),i||(i=10),r.query.search?s=await this.clientSearchFn(r.query.search,this.clientStorage,r.user.id):s=await this.clientStorage.getClients(o,i,r.user.id);const d=r.query.next??encodeURIComponent(r.url);let t={urlPrefix:this.prefix,user:r.user,skip:o,take:i,clients:s,havePrevious:o>0,haveNext:i!=null&&s.length==i,isAdmin:!1,next:d};return r.query.next&&(t.next=r.query.next),a.view(this.selectClientPage,t)}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({err:s})),v.sendPageError(a,o.httpStatus,this.sessionServer.errorPage,o.message,o)}})}addCreateClientEndpoints(){this.sessionServer.app.get(this.prefix+"createclient",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"createclient",ip:r.ip})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"createclient");const s=r.query.next??"/";let o={urlPrefix:this.prefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:r.user,isAdmin:!1,next:s};return a.view(this.createClientPage,o)}),this.sessionServer.app.post(this.prefix+"createclient",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"createclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+encodeURIComponent(r.url));const s=r.body.next??"/";try{return await this.createClient(r,a,(d,t)=>d.view(this.createClientPage,{message:"Created client",client:t,csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:r.user,isAdmin:!0,next:s,...r.body}),r.user)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed creating OAuth client",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.createClientPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),isAdmin:!1,next:s,...r.body})})}})}addApiCreateClientEndpoints(){this.sessionServer.app.post(this.prefix+"api/createclient",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/createclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user)return a.status(401).header(...F).send({ok:!1});try{return await this.createClient(r,a,(i,d)=>i.header(...F).send({ok:!0,client:d}),r.user)}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Create client failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...F).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addUpdateClientEndpoints(){this.sessionServer.app.get(this.prefix+"updateclient/:client_id",async(r,a)=>{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"updateclient",ip:r.ip})),!(r!=null&&r.user))return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"createclient");let s;try{s=await this.clientStorage.getClientById(r.params.client_id)}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let o=r.query.next;o||(r.query.userid?o=this.prefix+"selectuser":o=this.prefix+"selectclient");let i;try{r.query.userid&&(i=(await this.sessionServer.userStorage.getUserById(r.query.userid)).user)}catch(n){const h=e.CrossauthError.asCrossauthError(n);return e.CrossauthLogger.logger.debug(e.j({err:n})),a.status(h.httpStatus).view(this.sessionServer.errorPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code]})}let d={};for(let n of this.validFlows)s.valid_flow.includes(n)&&(d[n]=!0);let t={urlPrefix:this.prefix,csrfToken:r.csrfToken,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),selectedFlows:d,user:i,client_id:s.client_id,client_name:s.client_name,confidential:s.confidential,redirect_uris:s.redirect_uri.join(" "),isAdmin:!0,next:o};return a.view(this.updateClientPage,t)}),this.sessionServer.app.post(this.prefix+"updateclient/:client_id",async(r,a)=>{var i,d;if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"updateclient",ip:r.ip,user:(i=r.user)==null?void 0:i.username}));let s=r.body.next;s||(s=this.prefix+"selectuser");let o;try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");return r.body.userid&&(o=(await this.sessionServer.userStorage.getUserById(r.body.userid)).user),await this.updateClient(r,a,(t,n,h)=>t.view(this.updateClientPage,{message:"Updated client",client:n,csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,flowNames:e.OAuthFlows.flowNames(this.validFlows),user:o,isAdmin:!0,next:s,newSecret:h,...r.body}))}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(d=r.user)==null?void 0:d.username,errorCodeName:n.codeName,errorCode:n.code})),e.CrossauthLogger.logger.debug(e.j({err:t})),this.sessionServer.handleError(t,r,a,(h,g)=>{const C=e.CrossauthError.asCrossauthError(t).httpStatus;let f={};for(let p of this.validFlows)p in r.body&&(f[p]=!0);return h.status(C).view(this.updateClientPage,{errorMessage:g.message,errorMessages:g.messages,errorCode:g.code,errorCodeName:e.ErrorCode[g.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,validFlows:this.validFlows,selectedFlows:f,flowNames:e.OAuthFlows.flowNames(this.validFlows),isAdmin:!0,next:s,...r.body})})}})}addApiUpdateClientEndpoints(){this.sessionServer.app.post(this.prefix+"api/updateclient/:client_id",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"api/updateclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{if(!this.sessionServer.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call updateclient unless a user storage is provided ");return r.body.userid&&await this.sessionServer.userStorage.getUserById(r.body.userid),await this.updateClient(r,a,(i,d,t)=>i.header(...F).send({ok:!0,client:d,csrfToken:r.csrfToken,newSecret:t}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Failed updating OAuth client",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...F).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})})}})}addDeleteClientEndpoints(){this.sessionServer.app.get(this.prefix+"deleteclient/:client_id",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"deleteclient",ip:r.ip}));let s;if(!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteclient/"+r.params.client_id);try{if(s=await this.clientStorage.getClientById(r.params.client_id),s.userid!=r.user.id)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges,"You may not delete this client")}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).view(this.sessionServer.errorPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})}const o=r.query.next??"/";let i={urlPrefix:this.prefix,csrfToken:r.csrfToken,backUrl:this.prefix+"selectclient",client:s,next:o};return a.view(this.deleteClientPage,i)}),this.sessionServer.app.post(this.prefix+"deleteclient/:client_id",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"deleteclient",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!r.user)return a.redirect(this.sessionServer.loginUrl+"?next="+this.prefix+"deleteclient/"+r.params.client_id);const s=this.prefix+"selectclient";try{return await this.deleteClient(r,a,d=>d.view(this.deleteClientPage,{message:"Client deleted",csrfToken:r.csrfToken,urlPrefix:this.prefix,client_id:r.params.client_id,next:s}),r.user)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Failed deleting OAuth client",user:(i=r.user)==null?void 0:i.username,errorCodeName:t.codeName,errorCode:t.code})),e.CrossauthLogger.logger.debug(e.j({err:d})),this.sessionServer.handleError(d,r,a,(n,h)=>{const u=e.CrossauthError.asCrossauthError(d).httpStatus;return n.status(u).view(this.deleteClientPage,{errorMessage:h.message,errorMessages:h.messages,errorCode:h.code,errorCodeName:e.ErrorCode[h.code],csrfToken:r.csrfToken,urlPrefix:this.prefix,client_id:r.params.client_id,validFlows:this.validFlows,next:s})})}})}addApiDeleteClientEndpoints(){this.sessionServer.app.post(this.prefix+"api/deleteclient/:client_id",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/deleteclient",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!r.user)return a.status(401).header(...F).send({ok:!1});try{return await this.deleteClient(r,a,i=>i.header(...F).send({ok:!0,client_id:r.params.client_id}),r.user)}catch(i){const d=e.CrossauthError.asCrossauthError(i);e.CrossauthLogger.logger.error(e.j({msg:"Delete client failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.sessionServer.handleError(i,r,a,(t,n)=>{t.status(this.sessionServer.errorStatus(i)).header(...F).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}async createClient(r,a,s,o){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const i=r.body.confidential=="true",d=r.body.client_name,t=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let n=[];for(let u of t)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),n.push("["+u+"]")}if(n.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+n.join(" "));let h=[];r.body[e.OAuthFlows.AuthorizationCode]&&h.push(e.OAuthFlows.AuthorizationCode),r.body[e.OAuthFlows.AuthorizationCodeWithPKCE]&&h.push(e.OAuthFlows.AuthorizationCodeWithPKCE),r.body[e.OAuthFlows.ClientCredentials]&&h.push(e.OAuthFlows.ClientCredentials),r.body[e.OAuthFlows.RefreshToken]&&h.push(e.OAuthFlows.RefreshToken),r.body[e.OAuthFlows.DeviceCode]&&h.push(e.OAuthFlows.DeviceCode),r.body[e.OAuthFlows.Password]&&h.push(e.OAuthFlows.Password),r.body[e.OAuthFlows.PasswordMfa]&&h.push(e.OAuthFlows.PasswordMfa),r.body[e.OAuthFlows.OidcAuthorizationCode]&&h.push(e.OAuthFlows.OidcAuthorizationCode);const g=await this.clientManager.createClient(d,t,h,i,o==null?void 0:o.id);return s(a,g)}async updateClient(r,a,s){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!r.user)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);const o=r.body.redirect_uris.trim().length==0?[]:r.body.redirect_uris.trim().split(/[, ][ \t\n]*/);let i=[];for(let u of o)try{c.OAuthClientManager.validateUri(u)}catch(C){e.CrossauthLogger.logger.error(e.j({err:C})),i.push("["+u+"]")}if(i.length>0)throw new e.CrossauthError(e.ErrorCode.BadRequest,"The following redirect URIs are invalid: "+i.join(" "));let d=[];for(let u of this.validFlows)u in r.body&&d.push(u);const t={};t.client_name=r.body.client_name,t.confidential=r.body.confidential=="true",t.valid_flow=d,t.redirect_uri=o,t.userid=r.user.id;const n=r.body.resetSecret=="true",{client:h,newSecret:g}=await this.clientManager.updateClient(r.params.client_id,t,n);return s(a,h,g)}async deleteClient(r,a,s,o){if(this.sessionServer.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);if(!o)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges);if((await this.clientStorage.getClientById(r.params.client_id)).userid!=o.id)throw new e.CrossauthError(e.ErrorCode.InsufficientPriviledges,"You may not delete this client");return await this.clientStorage.deleteClient(r.params.client_id),s(a)}}const T=["Content-Type","application/json; charset=utf-8"],K=["login","logout","changepassword","updateuser","deleteuser"],Y=["admin/createuser","admin/changepassword","admin/selectuser","admin/updateuser","admin/changepassword","admin/deleteuser"],G=["admin/selectclient","admin/createclient","admin/deleteclient","admin/updateclient"],B=["selectclient","createclient","updateclient","deleteclient"],$=["api/login","api/logout","api/changepassword","api/userforsessionkey","api/getcsrftoken","api/updateuser","api/deleteuser"],X=["admin/api/createuser","admin/api/changepassword","admin/api/updateuser","admin/api/changepassword","admin/api/deleteuser"],V=["admin/api/createclient","admin/api/deleteclient","admin/api/updateclient"],W=["api/deleteclient","api/updateclient","api/createclient"],Q=["api/configurefactor2","api/loginfactor2","api/changefactor2","api/factor2","api/cancelfactor2"],Z=["verifyemail","emailverified"],q=["api/verifyemail"],ee=["requestpasswordreset","resetpassword"],re=["api/requestpasswordreset","api/resetpassword"],se=["signup"],oe=["api/signup"],te=["configurefactor2","loginfactor2","changefactor2","factor2"],xe=[...se,...oe,...K,...$,...Y,...X,...Z,...q,...ee,...re,...te,...Q],Fe=[...se,...oe,...K,...B,...$,...W,...Y,...G,...X,...V,...Z,...q,...ee,...re,...te,...Q];function Ue(m){let r=[];return m.username==null?r.push("Username must be given"):m.username.length<2?r.push("Username must be at least 2 characters"):m.username.length>254&&r.push("Username must be no longer than 254 characters"),r}function Ne(m,r,a){let o={username:m.body.username,state:"active"};const i=m.user&&v.isAdmin(m.user);for(let d in m.body){let t=d.replace(/^user_/,"");d.startsWith("user_")&&(i||r.includes(t))&&(o[t]=m.body[d])}return o.factor1="localpassword",a.includes(o.factor1)&&(o.factor1=m.body.factor1),o.factor2=m.body.factor2,o}function Le(m,r,a){const s=r.user&&v.isAdmin(r.user);for(let o in r.body){let i=o.replace(/^user_/,"");o.startsWith("user_")&&(s||a.includes(i))&&(m[i]=r.body[o])}return m}class fe{constructor(r,a,s,o={}){l(this,"app");l(this,"prefix","/");l(this,"loginUrl");l(this,"loginRedirect","/");l(this,"logoutRedirect","/");l(this,"errorPage","error.njk");l(this,"validateUserFn",Ue);l(this,"createUserFn",Ne);l(this,"updateUserFn",Le);l(this,"userStorage");l(this,"authenticators");l(this,"allowedFactor2",[]);l(this,"sessionManager");l(this,"endpoints",[]);l(this,"signupPage","signup.njk");l(this,"loginPage","login.njk");l(this,"factor2Page","factor2.njk");l(this,"configureFactor2Page","configurefactor2.njk");l(this,"addToSession");l(this,"validateSession");l(this,"userEndpoints");l(this,"adminEndpoints");l(this,"adminClientEndpoints");l(this,"userClientEndpoints");l(this,"enableEmailVerification",!0);l(this,"enablePasswordReset",!0);l(this,"enableAdminEndpoints",!1);l(this,"enableOAuthClientManagement",!1);l(this,"factor2ProtectedPageEndpoints",["/requestpasswordreset","/updateuser","/changepassword","/resetpassword","/changefactor2"]);l(this,"factor2ProtectedApiEndpoints",["/api/requestpasswordreset","/api/updateuser","/api/changepassword","/api/resetpassword","/api/changefactor2"]);l(this,"editUserScope");l(this,"enableCsrfProtection",!0);l(this,"userAllowedFactor1",["localpassword"]);l(this,"adminAllowedFactor1",["localpassword"]);this.app=r,this.userEndpoints=new he(this,o),this.adminEndpoints=new ge(this,o),c.setParameter("prefix",c.ParamType.String,this,o,"PREFIX"),this.prefix.endsWith("/")||(this.prefix+="/"),this.prefix.startsWith("/")||""+this.prefix,this.loginUrl=this.prefix+"login",c.setParameter("signupPage",c.ParamType.String,this,o,"SIGNUP_PAGE"),c.setParameter("loginPage",c.ParamType.String,this,o,"LOGIN_PAGE"),c.setParameter("factor2Page",c.ParamType.String,this,o,"FACTOR2_PAGE"),c.setParameter("configureFactor2Page",c.ParamType.String,this,o,"SIGNUP_FACTOR2_PAGE"),c.setParameter("errorPage",c.ParamType.String,this,o,"ERROR_PAGE"),c.setParameter("allowedFactor2",c.ParamType.JsonArray,this,o,"ALLOWED_FACTOR2"),c.setParameter("enableEmailVerification",c.ParamType.Boolean,this,o,"ENABLE_EMAIL_VERIFICATION"),c.setParameter("enablePasswordReset",c.ParamType.Boolean,this,o,"ENABLE_PASSWORD_RESET"),c.setParameter("factor2ProtectedPageEndpoints",c.ParamType.JsonArray,this,o,"FACTOR2_PROTECTED_PAGE_ENDPOINTS"),c.setParameter("factor2ProtectedApiEndpoints",c.ParamType.JsonArray,this,o,"FACTOR2_PROTECTED_API_ENDPOINTS"),c.setParameter("enableAdminEndpoints",c.ParamType.Boolean,this,o,"ENABLE_ADMIN_ENDPOINTS"),c.setParameter("enableOAuthClientManagement",c.ParamType.Boolean,this,o,"ENABLE_OAUTH_CLIENT_MANAGEMENT"),c.setParameter("editUserScope",c.ParamType.String,this,o,"EDIT_USER_SCOPE"),c.setParameter("userAllowedFactor1",c.ParamType.JsonArray,this,o,"USER_ALLOWED_FACTOR1"),c.setParameter("adminAllowedFactor1",c.ParamType.JsonArray,this,o,"ADMIN_ALLOWED_FACTOR1"),c.setParameter("loginRedirect",c.ParamType.JsonArray,this,o,"LOGIN_REDIRECT"),c.setParameter("logoutRedirect",c.ParamType.JsonArray,this,o,"LOGOUT_REDIRECT"),o.validateUserFn&&(this.validateUserFn=o.validateUserFn),o.createUserFn&&(this.createUserFn=o.createUserFn),o.updateUserFn&&(this.updateUserFn=o.updateUserFn),o.addToSession&&(this.addToSession=o.addToSession),o.validateSession&&(this.validateSession=o.validateSession),this.endpoints=[...se,...oe],this.endpoints=[...this.endpoints,...K,...$],this.enableAdminEndpoints&&(this.endpoints=[...this.endpoints,...Y,...X]),this.enableOAuthClientManagement&&(this.endpoints=[...this.endpoints,...B,...W,...G,...V]),this.enableEmailVerification&&(this.endpoints=[...this.endpoints,...Z,...q]),this.enablePasswordReset&&(this.endpoints=[...this.endpoints,...ee,...re]),o.endpoints&&(c.setParameter("endpoints",c.ParamType.JsonArray,this,o,"SESSION_ENDPOINTS"),this.endpoints.length==1&&this.endpoints[0]=="all"&&(this.endpoints=Fe),this.endpoints.length==1&&this.endpoints[0]=="allMinusOAuth"&&(this.endpoints=xe)),this.allowedFactor2.length>0&&(this.endpoints=[...this.endpoints,...te,...Q]);let i=!1;for(let t of this.endpoints)if(V.includes(t)||G.includes(t)){i=!0;break}i&&(this.adminClientEndpoints=new le(this,o));let d=!1;for(let t of this.endpoints)if(W.includes(t)||B.includes(t)){d=!0;break}d&&(this.userClientEndpoints=new ue(this,o)),this.addEndpoints(),c.setParameter("endpoints",c.ParamType.JsonArray,this,o,"ENDPOINTS"),o.userStorage&&(this.userStorage=o.userStorage),this.authenticators=s,this.sessionManager=new c.SessionManager(a,s,o),r.addHook("preHandler",async(t,n)=>{var C,f;e.CrossauthLogger.logger.debug(e.j({msg:"Getting session cookie"}));let h=this.getSessionCookieValue(t),g={};if(h)try{g.hashedSessionId=c.Crypto.hash(this.sessionManager.getSessionId(h))}catch{g.hashedSessionCookie=c.Crypto.hash(h)}e.CrossauthLogger.logger.debug(e.j({msg:"Getting csrf cookie"}));let u;try{u=this.getCsrfCookieValue(t),u&&this.sessionManager.validateCsrfCookie(u)}catch(p){e.CrossauthLogger.logger.warn(e.j({msg:"Invalid csrf cookie received",cerr:p,hashedCsrfCookie:this.getHashOfCsrfCookie(t)})),n.clearCookie(this.sessionManager.csrfCookieName),u=void 0}if(["GET","OPTIONS","HEAD"].includes(t.method))try{if(u){e.CrossauthLogger.logger.debug(e.j({msg:"Valid CSRF cookie - creating token"}));const p=await this.sessionManager.createCsrfFormOrHeaderValue(u);t.csrfToken=p}else{e.CrossauthLogger.logger.debug(e.j({msg:"Invalid CSRF cookie - recreating"}));const{csrfCookie:p,csrfFormOrHeaderValue:E}=await this.sessionManager.createCsrfToken();n.setCookie(p.name,p.value,p.options),t.csrfToken=E}n.header(this.sessionManager.csrfHeaderName,t.csrfToken)}catch(p){e.CrossauthLogger.logger.error(e.j({msg:"Couldn't create CSRF token",cerr:p,user:(C=t.user)==null?void 0:C.username,...g})),e.CrossauthLogger.logger.debug(e.j({err:p})),n.clearCookie(this.sessionManager.csrfCookieName)}else if(u)try{this.csrfToken(t,n)}catch(p){e.CrossauthLogger.logger.error(e.j({msg:"Couldn't create CSRF token",cerr:p,user:(f=t.user)==null?void 0:f.username,...g})),e.CrossauthLogger.logger.debug(e.j({err:p}))}if(h=this.getSessionCookieValue(t),h)try{const p=this.sessionManager.getSessionId(h);let{key:E,user:w}=await this.sessionManager.userForSessionId(p);this.validateSession&&this.validateSession(E,w,t),t.sessionId=p,t.user=w,t.authType="cookie",e.CrossauthLogger.logger.debug(e.j({msg:"Valid session id",user:w==null?void 0:w.username}))}catch{e.CrossauthLogger.logger.warn(e.j({msg:"Invalid session cookie received",hashOfSessionId:this.getHashOfSessionId(t)})),n.clearCookie(this.sessionManager.sessionCookieName)}}),r.addHook("preHandler",async(t,n)=>{var g,u,C;const h=this.getSessionCookieValue(t);if(h&&((g=t.user)!=null&&g.factor2)&&(this.factor2ProtectedPageEndpoints.includes(t.url)||this.factor2ProtectedApiEndpoints.includes(t.url))){const f=this.sessionManager.getSessionId(h);if(["GET","OPTIONS","HEAD"].includes(t.method)){const p=this.getSessionCookieValue(t);if(p){const E=this.sessionManager.getSessionId(p);if("pre2fa"in await this.sessionManager.dataForSessionId(E)){e.CrossauthLogger.logger.debug("Cancelling 2FA");try{await this.sessionManager.cancelTwoFactorPageVisit(E)}catch(y){e.CrossauthLogger.logger.debug(e.j({err:y})),e.CrossauthLogger.logger.error(e.j({msg:"Failed cancelling 2FA",cerr:y,user:(C=t.user)==null?void 0:C.username,hashOfSessionId:this.getHashOfSessionId(t)}))}}}}else{const p=await this.sessionManager.dataForSessionId(f);if("pre2fa"in p){e.CrossauthLogger.logger.debug("Completing 2FA");const w=[...this.authenticators[p.pre2fa.factor2].transientSecretNames()];let y={};for(let _ in t.body)w.includes(_)&&(y[_]=t.body[_]);let S;try{await this.sessionManager.completeTwoFactorPageVisit(y,f)}catch(_){S=e.CrossauthError.asCrossauthError(_),e.CrossauthLogger.logger.debug(e.j({err:_}));const A=e.CrossauthError.asCrossauthError(_);e.CrossauthLogger.logger.error(e.j({msg:S.message,cerr:_,user:t.body.username,errorCode:A.code,errorCodeName:A.codeName}))}if(t.body=p.pre2fa.body,S)if(S.code==e.ErrorCode.Expired){e.CrossauthLogger.logger.debug("Error - cancelling 2FA");try{await this.sessionManager.cancelTwoFactorPageVisit(f)}catch(_){e.CrossauthLogger.logger.error(e.j({msg:"Failed cancelling 2FA",cerr:_,user:(u=t.user)==null?void 0:u.username,hashOfSessionId:this.getHashOfSessionId(t)})),e.CrossauthLogger.logger.debug(e.j({err:_}))}t.body={...t.body,errorMessage:S.message,errorMessages:S.message,errorCode:""+S.code,errorCodeName:e.ErrorCode[S.code]}}else return this.factor2ProtectedPageEndpoints.includes(t.url)?n.redirect(this.prefix+"factor2?error="+e.ErrorCode[S.code]):n.status(S.httpStatus).send(JSON.stringify({ok:!1,errorMessage:S.message,errorMessages:S.messages,errorCode:S.code,errorCodeName:e.ErrorCode[S.code]}))}else return this.validateCsrfToken(t),e.CrossauthLogger.logger.debug("Starting 2FA"),await this.sessionManager.initiateTwoFactorPageVisit(t.user,f,t.body,t.url.replace(/\?.*$/,"")),this.factor2ProtectedPageEndpoints.includes(t.url)?n.redirect(this.prefix+"factor2"):n.send(JSON.stringify({ok:!0,factor2Required:!0}))}}})}addEndpoints(){if(this.endpoints.includes("login")&&this.addLoginEndpoints(),this.endpoints.includes("loginfactor2")&&this.addLoginFactor2Endpoints(),this.endpoints.includes("factor2")&&this.addFactor2Endpoints(),this.endpoints.includes("signup")&&this.addSignupEndpoints(),this.endpoints.includes("configurefactor2")&&this.userEndpoints.addConfigureFactor2Endpoints(),this.endpoints.includes("changefactor2")&&this.userEndpoints.addChangeFactor2Endpoints(),this.endpoints.includes("changepassword")&&this.userEndpoints.addChangePasswordEndpoints(),this.endpoints.includes("updateuser")&&this.userEndpoints.addUpdateUserEndpoints(),this.endpoints.includes("requestpasswordreset")&&this.userEndpoints.addRequestPasswordResetEndpoints(),this.endpoints.includes("deleteuser")&&this.userEndpoints.addDeleteUserEndpoints(),this.endpoints.includes("resetpassword")){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"Password reset must be enabled for /resetpassword");this.userEndpoints.addResetPasswordEndpoints()}if(this.endpoints.includes("verifyemail")){if(!this.enableEmailVerification)throw new e.CrossauthError(e.ErrorCode.Configuration,"Email verification must be enabled for /verifyemail");this.userEndpoints.addVerifyEmailEndpoints()}if(this.endpoints.includes("logout")&&this.addLogoutEndpoints(),this.endpoints.includes("api/login")&&this.addApiLoginEndpoints(),this.endpoints.includes("api/loginfactor2")&&this.addApiLoginFactor2Endpoints(),this.endpoints.includes("api/cancelfactor2")&&this.addApiCancelFactor2Endpoints(),this.endpoints.includes("api/logout")&&this.addApiLogoutEndpoints(),this.endpoints.includes("api/signup")&&this.addApiSignupEndpoints(),this.endpoints.includes("api/configurefactor2")&&this.userEndpoints.addApiConfigureFactor2Endpoints(this.prefix),this.endpoints.includes("api/changepassword")&&this.userEndpoints.addApiChangePasswordEndpoints(),this.endpoints.includes("api/changefactor2")&&this.userEndpoints.addApiChangeFactor2Endpoints(),this.endpoints.includes("api/updateuser")&&this.userEndpoints.addApiUpdateUserEndpoints(),this.endpoints.includes("api/resetpassword")){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"Password reset must be enabled for /api/resetpassword");this.userEndpoints.addApiResetPasswordEndpoints()}if(this.endpoints.includes("api/requestpasswordreset")){if(!this.enablePasswordReset)throw new e.CrossauthError(e.ErrorCode.Configuration,"Password reset must be enabled for /api/requestpasswordreset");this.userEndpoints.addApiRequestPasswordResetEndpoints()}if(this.endpoints.includes("api/verifyemail")){if(!this.enableEmailVerification)throw new e.CrossauthError(e.ErrorCode.Configuration,"Email verification must be enabled for /api/verifyemail");this.userEndpoints.addApiVerifyEmailEndpoints()}this.endpoints.includes("api/userforsessionkey")&&this.addApiUserForSessionKeyEndpoints(),this.endpoints.includes("api/getcsrftoken")&&this.addApiGetCsrfTokenEndpoints(),this.endpoints.includes("api/deleteuser")&&this.userEndpoints.addApiDeleteUserEndpoints(),this.userClientEndpoints&&(this.endpoints.includes("selectclient")&&this.userClientEndpoints.addSelectClientEndpoints(),this.endpoints.includes("createclient")&&this.userClientEndpoints.addCreateClientEndpoints(),this.endpoints.includes("deleteclient")&&this.userClientEndpoints.addDeleteClientEndpoints(),this.endpoints.includes("api/createclient")&&this.userClientEndpoints.addApiCreateClientEndpoints(),this.endpoints.includes("api/deleteclient")&&this.userClientEndpoints.addApiDeleteClientEndpoints(),this.endpoints.includes("updateclient")&&this.userClientEndpoints.addUpdateClientEndpoints(),this.endpoints.includes("api/updateclient")&&this.userClientEndpoints.addApiUpdateClientEndpoints()),this.endpoints.includes("admin/createuser")&&this.adminEndpoints.addCreateUserEndpoints(),this.endpoints.includes("admin/api/createuser")&&this.adminEndpoints.addApiCreateUserEndpoints(),this.endpoints.includes("admin/selectuser")&&this.adminEndpoints.addSelectUserEndpoints(),this.endpoints.includes("admin/updateuser")&&this.adminEndpoints.addUpdateUserEndpoints(),this.endpoints.includes("admin/api/updateuser")&&this.adminEndpoints.addApiUpdateUserEndpoints(),this.endpoints.includes("admin/changepassword")&&this.adminEndpoints.addChangePasswordEndpoints(),this.endpoints.includes("admin/api/changepassword")&&this.adminEndpoints.addApiChangePasswordEndpoints(),this.endpoints.includes("admin/deleteuser")&&this.adminEndpoints.addDeleteUserEndpoints(),this.endpoints.includes("admin/api/deleteuser")&&this.adminEndpoints.addApiDeleteUserEndpoints(),this.adminClientEndpoints&&(this.endpoints.includes("admin/selectclient")&&this.adminClientEndpoints.addSelectClientEndpoints(),this.endpoints.includes("admin/createclient")&&this.adminClientEndpoints.addCreateClientEndpoints(),this.endpoints.includes("admin/deleteclient")&&this.adminClientEndpoints.addDeleteClientEndpoints(),this.endpoints.includes("admin/updateclient")&&this.adminClientEndpoints.addUpdateClientEndpoints(),this.endpoints.includes("admin/api/createclient")&&this.adminClientEndpoints.addApiCreateClientEndpoints(),this.endpoints.includes("admin/api/deleteclient")&&this.adminClientEndpoints.addApiDeleteClientEndpoints(),this.endpoints.includes("admin/api/updateclient")&&this.adminClientEndpoints.addApiUpdateClientEndpoints())}addLoginEndpoints(){this.app.get(this.prefix+"login",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"login",ip:r.ip})),r.user)return a.redirect(r.query.next??this.loginRedirect);let s={urlPrefix:this.prefix,csrfToken:r.csrfToken};return r.query.next&&(s.next=r.query.next),a.view(this.loginPage,s)}),this.app.post(this.prefix+"login",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"login",ip:r.ip}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.loginRedirect;try{return await this.login(r,a,(o,i)=>{if(i.state==e.UserState.passwordChangeNeeded){if(this.endpoints.includes("changepassword"))return e.CrossauthLogger.logger.debug(e.j({msg:"Password change needed - sending redirect"})),o.redirect("/changepassword?required=true&next="+encodeURIComponent("login?next="+s));{const d=new e.CrossauthError(e.ErrorCode.PasswordChangeNeeded);return this.handleError(d,r,o,(t,n)=>t.view(this.loginPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}}else if(i.state==e.UserState.passwordResetNeeded||i.state==e.UserState.passwordAndFactor2ResetNeeded){e.CrossauthLogger.logger.debug(e.j({msg:"Password reset needed - sending error"}));const d=new e.CrossauthError(e.ErrorCode.PasswordResetNeeded);return this.handleError(d,r,o,(t,n)=>t.view(this.loginPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}else if(this.allowedFactor2.length>0&&(i.state==e.UserState.factor2ResetNeeded||!this.allowedFactor2.includes(i.factor2?i.factor2:"none"))){if(e.CrossauthLogger.logger.debug(e.j({msg:`Factor2 reset needed. Factor2 is ${i.factor2}, state is ${i.state}, allowed factor2 is [${this.allowedFactor2.join(", ")}]`,username:i.username})),this.endpoints.includes("changefactor2"))return e.CrossauthLogger.logger.debug(e.j({msg:"Factor 2 reset needed - sending redirect"})),o.redirect("/changefactor2?required=true&next="+encodeURIComponent("login?next="+s));{const d=new e.CrossauthError(e.ErrorCode.Factor2ResetNeeded);return this.handleError(d,r,o,(t,n)=>t.view(this.loginPage,{errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}}else{if(!i.factor2||i.factor2.length==0)return e.CrossauthLogger.logger.debug(e.j({msg:"Successful login - sending redirect"})),o.redirect(s);{let d={csrfToken:r.csrfToken,next:r.body.next??this.loginRedirect,persist:r.body.persist?"on":"",urlPrefix:this.prefix,factor2:i.factor2,action:"loginfactor2"};return o.view(this.factor2Page,d)}}})}catch(o){return e.CrossauthLogger.logger.debug(e.j({err:o})),this.handleError(o,r,a,(i,d)=>i.view(this.loginPage,{errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,urlPrefix:this.prefix}))}})}addLoginFactor2Endpoints(){this.app.post(this.prefix+"loginfactor2",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"loginfactor2",ip:r.ip}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.loginRedirect;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.loginFactor2(r,a,(o,i)=>(e.CrossauthLogger.logger.debug(e.j({msg:"Successful login - sending redirect to"})),o.redirect(s)))}catch(o){e.CrossauthLogger.logger.debug(e.j({err:o}));let i;try{const d=r.sessionId?await this.sessionManager.dataForSessionId(r.sessionId):void 0;i=d==null?void 0:d.factor2}catch(d){e.CrossauthLogger.logger.error(e.j({err:d}))}return i&&i in this.authenticators?this.handleError(o,r,a,(d,t)=>d.view(this.factor2Page,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:r.body.next,persist:r.body.persist?"on":"",csrfToken:r.csrfToken,urlPrefix:this.prefix,factor2:i,action:"loginfactor2"})):this.handleError(o,r,a,(d,t)=>d.view(this.loginPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:r.body.next,persist:r.body.persist?"on":"",csrfToken:r.csrfToken,urlPrefix:this.prefix}))}})}addFactor2Endpoints(){this.app.get(this.prefix+"factor2",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"factor2",ip:r.ip})),!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"No session cookie present");const s=this.getSessionCookieValue(r),o=this.sessionManager.getSessionId(s??""),i=await this.sessionManager.dataForSessionId(o);if(!(i!=null&&i.pre2fa))throw new e.CrossauthError(e.ErrorCode.Unauthorized,"2FA not initiated");let d={urlPrefix:this.prefix,csrfToken:r.csrfToken,action:i.pre2fa.url,errorCodeName:r.query.error,factor2:i.pre2fa.factor2};return a.view(this.factor2Page,d)})}addSignupEndpoints(){this.app.get(this.prefix+"signup",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"signup",ip:r.ip}));let s={urlPrefix:this.prefix,csrfToken:r.csrfToken,allowedFactor2:this.allowedFactor2Details()};return r.query.next&&(s.next=r.query.next),a.view(this.signupPage,s)}),this.app.post(this.prefix+"signup",async(r,a)=>{e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"signup",ip:r.ip,user:r.body.username}));let s=r.body.next&&r.body.next.length>0?r.body.next:this.loginRedirect;try{return e.CrossauthLogger.logger.debug(e.j({msg:"Next page "+s})),await this.signup(r,a,(o,i,d)=>{var n,h;const t=(n=i==null?void 0:i.userData)!=null&&n.factor2?this.authenticators[i.userData.factor2]:void 0;return(h=i.userData)!=null&&h.factor2?o.view(this.configureFactor2Page,{csrfToken:i.csrfToken,...i.userData}):this.enableEmailVerification&&(t==null||t.skipEmailVerificationOnSignup()!=!0)?o.view(this.signupPage,{next:s,csrfToken:r.csrfToken,message:"Please check your email to finish signing up.",allowedFactor2:this.allowedFactor2Details(),urlPrefix:this.prefix,factor2:r.body.factor2,...i.userData}):o.redirect(this.loginRedirect)})}catch(o){const i=e.CrossauthError.asCrossauthError(o);return e.CrossauthLogger.logger.error(e.j({msg:"Signup failure",user:r.body.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.handleError(o,r,a,(d,t)=>{let n={};for(let h in r.body)h.startsWith("user_")&&(n[h]=r.body[h]);return d.view(this.signupPage,{errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code],next:s,persist:r.body.persist,username:r.body.username,csrfToken:r.csrfToken,factor2:r.body.factor2,allowedFactor2:this.allowedFactor2Details(),urlPrefix:this.prefix,...n})})}})}addLogoutEndpoints(){this.app.post(this.prefix+"logout",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"logout",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return await this.logout(r,a,i=>i.redirect(r.body.next?r.body.next:this.logoutRedirect))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Logout failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.handleError(i,r,a,(t,n)=>t.view(this.errorPage,{urlPrefix:this.prefix,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]}))}})}addApiLoginEndpoints(){this.app.post(this.prefix+"api/login",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/login",ip:r.ip})),r.user)return a.header(...T).send({ok:!1,user:r.user});try{return await this.login(r,a,(s,o)=>{if(o.state==e.UserState.passwordChangeNeeded){const i=new e.CrossauthError(e.ErrorCode.PasswordChangeNeeded);return this.handleError(i,r,s,(d,t)=>{d.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})})}else if(o.state==e.UserState.passwordResetNeeded||o.state==e.UserState.passwordAndFactor2ResetNeeded){const i=new e.CrossauthError(e.ErrorCode.PasswordResetNeeded);return this.handleError(i,r,s,(d,t)=>{d.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})})}else if(this.allowedFactor2.length>0&&(o.state==e.UserState.factor2ResetNeeded||!this.allowedFactor2.includes(o.factor2?o.factor2:"none"))){const i=new e.CrossauthError(e.ErrorCode.Factor2ResetNeeded);return this.handleError(i,r,s,(d,t)=>{d.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:t.code,errorCodeName:e.ErrorCode[t.code]})})}else return o.twoFactorRequired?s.header(...T).send({ok:!0,twoFactorRequired:!0}):s.header(...T).send({ok:!0,user:o})})}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Login failure",user:r.body.username,errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.handleError(s,r,a,(i,d)=>{i.status(this.errorStatus(s)).header(...T).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]})})}})}addApiCancelFactor2Endpoints(){this.app.post(this.prefix+"api/cancelfactor2",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/cancelfactor2",ip:r.ip})),r.user)return a.header(...T).send({ok:!1,user:r.user});try{return await this.cancelFactor2(r,a,s=>s.header(...T).send({ok:!0}))}catch(s){const i=r.user||"",d=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Login failure",user:i,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.handleError(s,r,a,(t,n)=>{t.status(this.errorStatus(s)).header(...T).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:n.code,errorCodeName:e.ErrorCode[n.code]})})}})}addApiLoginFactor2Endpoints(){this.app.post(this.prefix+"api/loginfactor2",async(r,a)=>{if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/loginfactor2",ip:r.ip})),r.user)return a.header(...T).send({ok:!1,user:r.user});try{return await this.loginFactor2(r,a,(s,o)=>s.header(...T).send({ok:!0,user:o}))}catch(s){const o=e.CrossauthError.asCrossauthError(s);return e.CrossauthLogger.logger.error(e.j({msg:"Login failure",hashOfSessionId:this.getHashOfSessionId(r),errorCodeName:o.codeName,errorCode:o.code})),e.CrossauthLogger.logger.debug(e.j({err:s})),this.handleError(s,r,a,(i,d)=>i.status(this.errorStatus(s)).header(...T).send({ok:!1,errorMessage:d.message,errorMessages:d.messages,errorCode:d.code,errorCodeName:e.ErrorCode[d.code]}))}})}addApiLogoutEndpoints(){this.app.post(this.prefix+"api/logout",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/logout",ip:r.ip,user:(s=r.user)==null?void 0:s.username})),!this.canEditUser(r))return this.sendJsonError(a,401,"You are not authorized to access this url");try{return await this.logout(r,a,i=>i.header(...T).send({ok:!0}))}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"Logout failure",user:(o=r.user)==null?void 0:o.username,errorCodeName:d.codeName,errorCode:d.code})),e.CrossauthLogger.logger.debug(e.j({err:i})),this.handleError(i,r,a,(t,n)=>{t.status(this.errorStatus(i)).header(...T).send({ok:!1,errorMessage:n.message,errorMessages:n.messages,errorCode:e.ErrorCode[n.code]})})}})}addApiSignupEndpoints(){this.app.post(this.prefix+"api/signup",async(r,a)=>{var s;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/signup",ip:r.ip,user:r.body.username}));try{return await this.signup(r,a,(o,i,d)=>o.header(...T).send({ok:!0,user:d,emailVerificationNeeded:this.enableEmailVerification??!1,...i.userData}))}catch(o){const i=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.error(e.j({msg:"Signup failure",user:(s=r.user)==null?void 0:s.username,errorCodeName:i.codeName,errorCode:i.code})),e.CrossauthLogger.logger.debug(e.j({err:o})),this.handleError(o,r,a,(d,t)=>{d.status(this.errorStatus(o)).header(...T).send({ok:!1,errorMessage:t.message,errorMessages:t.messages,errorCode:e.ErrorCode[t.code]})})}})}addApiUserForSessionKeyEndpoints(){this.app.post(this.prefix+"api/userforsessionkey",async(r,a)=>{var s,o;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/userforsessionkey",ip:r.ip,user:(s=r.user)==null?void 0:s.username,hashOfSessionId:this.getHashOfSessionId(r)})),!this.canEditUser(r))return this.sendJsonError(a,401,"User not logged in");if(this.isSessionUser(r)&&!r.csrfToken)return this.sendJsonError(a,403,"No CSRF token present");try{let i;return r.sessionId&&(i=(await this.sessionManager.userForSessionId(r.sessionId)).user),a.header(...T).send({ok:!0,user:i})}catch(i){const d=e.CrossauthError.asCrossauthError(i);let t=d.message,n=d.code,h=d.codeName;switch(d.code){case e.ErrorCode.UserNotExist:case e.ErrorCode.PasswordInvalid:t="Invalid username or password",n=e.ErrorCode.UsernameOrPasswordInvalid,h=e.ErrorCode[n];break}return e.CrossauthLogger.logger.error(e.j({msg:t,user:(o=r.user)==null?void 0:o.username,hashOfSessionId:this.getHashOfSessionId(r),errorCodeName:h,errorCode:n})),e.CrossauthLogger.logger.debug(e.j({err:i})),a.status(this.errorStatus(i)).header(...T).send({ok:!1,errorCode:n,errorCodeName:h})}})}addApiGetCsrfTokenEndpoints(){this.app.get(this.prefix+"api/getcsrftoken",async(r,a)=>{var s,o;e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"api/getcsrftoken",ip:r.ip,user:(s=r.user)==null?void 0:s.username}));try{return a.header(...T).send({ok:!0,csrfToken:r.csrfToken})}catch(i){const d=e.CrossauthError.asCrossauthError(i);return e.CrossauthLogger.logger.error(e.j({msg:"getcsrftoken failure",user:(o=r.user)==null?void 0:o.username,hashedCsrfCookie:this.getHashOfCsrfCookie(r),errorCode:d.code,errorCodeName:d.codeName})),e.CrossauthLogger.logger.debug(e.j({err:i})),a.status(this.errorStatus(i)).header(...T).send({ok:!1,errorCode:d.code,errorCodeName:d.codeName,error:d.message})}})}async login(r,a,s){if(r.user)return s(a,r.user);const o=r.body.username,i=r.body.persist;if(!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const d=this.getSessionCookieValue(r);let t=this.addToSession?this.addToSession(r):{},{sessionCookie:n,csrfCookie:h,user:g}=await this.sessionManager.login(o,r.body,t,i);if(e.CrossauthLogger.logger.debug(e.j({msg:"Login: set session cookie "+n.name+" opts "+JSON.stringify(n.options),user:r.body.username})),a.cookie(n.name,n.value,n.options),e.CrossauthLogger.logger.debug(e.j({msg:"Login: set csrf cookie "+h.name+" opts "+JSON.stringify(n.options),user:r.body.username})),a.cookie(h.name,h.value,h.options),r.csrfToken=await this.sessionManager.createCsrfFormOrHeaderValue(h.value),d)try{await this.sessionManager.deleteSession(d)}catch(u){e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't delete session ID from database",hashOfSessionId:this.getHashOfSessionId(r)})),e.CrossauthLogger.logger.debug(e.j({err:u}))}return s(a,g)}async loginFactor2(r,a,s){if(r.user)return s(a,r.user);const o=r.sessionId;if(!o)throw new e.CrossauthError(e.ErrorCode.Unauthorized);const i=r.body.persist;if(this.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);let d=this.addToSession?this.addToSession(r):{};const{sessionCookie:t,csrfCookie:n,user:h}=await this.sessionManager.completeTwoFactorLogin(r.body,o,d,i);return e.CrossauthLogger.logger.debug(e.j({msg:"Login: set session cookie "+t.name+" opts "+JSON.stringify(t.options),user:h==null?void 0:h.username})),a.cookie(t.name,t.value,t.options),e.CrossauthLogger.logger.debug(e.j({msg:"Login: set csrf cookie "+n.name+" opts "+JSON.stringify(t.options),user:h==null?void 0:h.username})),a.cookie(n.name,n.value,n.options),r.csrfToken=await this.sessionManager.createCsrfFormOrHeaderValue(n.value),s(a,h)}async cancelFactor2(r,a,s){if(this.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=this.getSessionCookieValue(r);return o&&this.sessionManager.cancelTwoFactorPageVisit(o),s(a)}async loginWithUser(r,a,s,o,i){const d=this.getSessionCookieValue(s);let t=this.addToSession?this.addToSession(s):{},{sessionCookie:n,csrfCookie:h}=await this.sessionManager.login("",{},t,void 0,r,a);if(e.CrossauthLogger.logger.debug(e.j({msg:"Login: set session cookie "+n.name+" opts "+JSON.stringify(n.options),user:r.username})),o.cookie(n.name,n.value,n.options),e.CrossauthLogger.logger.debug(e.j({msg:"Login: set csrf cookie "+h.name+" opts "+JSON.stringify(n.options),user:r.username})),o.cookie(h.name,h.value,h.options),d)try{await this.sessionManager.deleteSession(d)}catch(g){e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't delete session ID from database",hashOfSessionId:this.getHashOfSessionId(s)})),e.CrossauthLogger.logger.debug(e.j({err:g}))}return i(o,r)}async signup(r,a,s){if(!this.userStorage)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot call signup unless you provide a user stotage");if(this.isSessionUser(r)&&!r.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidCsrf);const o=r.body.username,i=r.body.next;if(r.body.factor2||(r.body.factor2=this.allowedFactor2[0]),r.body.factor2&&!this.allowedFactor2.includes(r.body.factor2??"none"))throw new e.CrossauthError(e.ErrorCode.Forbidden,"Illegal second factor "+r.body.factor2+" requested");(r.body.factor2=="none"||r.body.factor2=="")&&(r.body.factor2=void 0);let d=this.createUserFn(r,this.userStorage.userEditableFields,this.userAllowedFactor1),t=this.authenticators[d.factor1].validateSecrets(r.body);const n=this.authenticators[d.factor1].secretNames();let h={};for(let f in r.body)if(f.startsWith("repeat_")){const p=f.replace(/^repeat_/,"");n.includes(p)&&(h[p]=r.body[f])}Object.keys(h).length===0&&(h=void 0),d.state="active",r.body.factor2&&r.body.factor2!="none"?d.state="awaitingtwofactor":this.enableEmailVerification&&(d.state="awaitingemailverification");let u=[...this.validateUserFn(d),...t];if(u.length>0)throw new e.CrossauthError(e.ErrorCode.FormEntry,u);let C=!1;try{const{user:f,secrets:p}=await this.userStorage.getUserByUsername(o);await this.sessionManager.authenticators[d.factor1].authenticateUser(f,p,r.body)}catch(f){e.CrossauthError.asCrossauthError(f).code==e.ErrorCode.TwoFactorIncomplete&&(C=!0)}if(!r.body.factor2&&!C)return await this.sessionManager.createUser(d,r.body,h),this.enableEmailVerification?s(a,{},void 0):this.login(r,a,(f,p)=>s(f,{},p));{let f;if(C){if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized);f=(await this.sessionManager.repeatTwoFactorSignup(r.sessionId)).userData}else{const p=await this.createAnonymousSession(r,a),E=this.sessionManager.getSessionId(p);f=(await this.sessionManager.initiateTwoFactorSignup(d,r.body,E,h)).userData}try{let p={userData:f,username:o,next:i??this.loginRedirect,csrfToken:r.csrfToken};return s(a,p)}catch(p){e.CrossauthLogger.logger.error(e.j({err:p}));try{this.sessionManager.deleteUserByUsername(o)}catch(E){e.CrossauthLogger.logger.error(e.j({err:E}))}}}}async logout(r,a,s){if(r.sessionId&&await this.sessionManager.logout(r.sessionId),e.CrossauthLogger.logger.debug(e.j({msg:"Logout: clear cookie "+this.sessionManager.sessionCookieName})),a.clearCookie(this.sessionManager.sessionCookieName),a.clearCookie(this.sessionManager.csrfCookieName),r.sessionId)try{await this.sessionManager.deleteSession(r.sessionId)}catch(o){e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't delete session ID from database",hashOfSessionId:this.getHashOfSessionId(r)})),e.CrossauthLogger.logger.debug(e.j({err:o}))}return s(a)}async createAnonymousSession(r,a,s){e.CrossauthLogger.logger.debug(e.j({msg:"Creating session ID"}));let o=this.addToSession?this.addToSession(r):{};s&&(o.data=JSON.stringify(s));let{sessionCookie:i,csrfCookie:d,csrfFormOrHeaderValue:t}=await this.sessionManager.createAnonymousSession(o);a.cookie(i.name,i.value,i.options),r.csrfToken=t,a.setCookie(d.name,d.value,d.options),r.user=void 0;const n=this.sessionManager.getSessionId(i.value);return r.sessionId=n,i.value}handleError(r,a,s,o,i=!1){var d;try{let t=e.CrossauthError.asCrossauthError(r);if(!i)switch(t.code){case e.ErrorCode.UserNotExist:case e.ErrorCode.PasswordInvalid:t=new e.CrossauthError(e.ErrorCode.UsernameOrPasswordInvalid,"Invalid username or password");break}return e.CrossauthLogger.logger.debug(e.j({err:t})),e.CrossauthLogger.logger.error(e.j({cerr:t,hashOfSessionId:this.getHashOfSessionId(a),user:(d=a.user)==null?void 0:d.username})),o(s,t)}catch(t){return e.CrossauthLogger.logger.error(e.j({err:t})),o(s,new e.CrossauthError(e.ErrorCode.UnknownError))}}getSessionCookieValue(r){if(r.cookies&&this.sessionManager.sessionCookieName in r.cookies)return r.cookies[this.sessionManager.sessionCookieName]}getCsrfCookieValue(r){if(r.cookies&&this.sessionManager.csrfCookieName in r.cookies)return r.cookies[this.sessionManager.csrfCookieName]}getHashOfSessionId(r){if(!r.sessionId)return"";try{return c.Crypto.hash(r.sessionId)}catch{}return""}getHashOfCsrfCookie(r){const a=this.getCsrfCookieValue(r);if(!a)return"";try{return c.Crypto.hash(a.split(".")[0])}catch{}return""}validateCsrfToken(r){return this.sessionManager.validateDoubleSubmitCsrfToken(this.getCsrfCookieValue(r),r.csrfToken),this.getCsrfCookieValue(r)}csrfToken(r,a){var i;let s;const o=this.sessionManager.csrfHeaderName;if(r.headers&&o.toLowerCase()in r.headers){const d=r.headers[o.toLowerCase()];Array.isArray(d)?s=d[0]:s=d}if(!s&&((i=r.body)!=null&&i.csrfToken)&&(s=r.body.csrfToken),s)try{this.sessionManager.validateDoubleSubmitCsrfToken(this.getCsrfCookieValue(r),s),r.csrfToken=s,a.header(this.sessionManager.csrfHeaderName,s)}catch{e.CrossauthLogger.logger.warn(e.j({msg:"Invalid CSRF token",hashedCsrfCookie:this.getHashOfCsrfCookie(r)})),a.clearCookie(this.sessionManager.csrfCookieName),r.csrfToken=void 0}else r.csrfToken=void 0;return s}sendJsonError(r,a,s,o){(!s||!o)&&(s="Unknown error");const i=o?e.CrossauthError.asCrossauthError(o):void 0;return e.CrossauthLogger.logger.warn(e.j({msg:s,errorCode:i==null?void 0:i.code,errorCodeName:i==null?void 0:i.codeName,httpStatus:a})),r.header(...T).status(a).send({ok:!1,status:a,errorMessage:s,errorCode:i==null?void 0:i.code,errorCodeName:i==null?void 0:i.codeName})}errorStatus(r){return typeof r=="object"&&"httpStatus"in r?r.httpStatus??500:500}allowedFactor2Details(){let r=[];return this.allowedFactor2.forEach(a=>{if(a in this.authenticators){const s=this.authenticators[a].secretNames();r.push({name:a,friendlyName:this.authenticators[a].friendlyName,hasSecrets:s&&s.length>0})}else a=="none"&&r.push({name:"none",friendlyName:"None",hasSecrets:!1})}),r}isSessionUser(r){return r.user!=null&&r.authType=="cookie"}canEditUser(r){return this.isSessionUser(r)||this.editUserScope&&r.scope&&r.scope.includes(this.editUserScope)}csrfProtectionEnabled(){return this.enableCsrfProtection}getCsrfToken(r){return r.csrfToken}getUser(r){return r.user}async updateSessionData(r,a,s){if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"User is not logged in");await this.sessionManager.updateSessionData(r.sessionId,a,s)}async updateManySessionData(r,a){if(!r.sessionId)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"No session present");await this.sessionManager.updateManySessionData(r.sessionId,a)}async deleteSessionData(r,a){r.sessionId?await this.sessionManager.deleteSessionData(r.sessionId,a):e.CrossauthLogger.logger.warn(e.j({msg:"Attempt to delete session data when there is no session"}))}async getSessionData(r,a){try{const s=r.sessionId?await this.sessionManager.dataForSessionId(r.sessionId):void 0;if(s&&a in s)return s[a]}catch(s){e.CrossauthLogger.logger.error(e.j({msg:"Couldn't get "+a+"from session",cerr:s})),e.CrossauthLogger.logger.debug(e.j({err:s}))}}}class pe{constructor(r,a,s,o={}){l(this,"app");l(this,"userStorage");l(this,"apiKeyManager");this.app=r,this.userStorage=a,this.apiKeyManager=new c.ApiKeyManager(s,o),this.app.addHook("preHandler",async(i,d)=>{if(i.headers.authorization)try{e.CrossauthLogger.logger.debug(e.j({msg:"Received authorization header"}));const t=await this.apiKeyManager.validateToken(i.headers.authorization);e.CrossauthLogger.logger.debug(e.j({msg:"Valid API key",hahedApiKey:c.ApiKeyManager.hashSignedApiKeyValue(t.value)}));const n=c.KeyStorage.decodeData(t.data);if(i.apiKey={...t,...n},"scope"in n&&Array.isArray(n.scope)){let h=[];for(let g of n.scope)typeof g=="string"&&h.push(g);i.scope=h}if(t.userid)try{const{user:h}=await this.userStorage.getUserById(t.userid);i.user=h,i.authType="apiKey",e.CrossauthLogger.logger.debug(e.j({msg:"API key is for user",userid:h.id,user:h.username,hahedApiKey:c.ApiKeyManager.hashSignedApiKeyValue(t.value)}))}catch(h){e.CrossauthLogger.logger.error(e.j({msg:"API key has invalid user",userid:t.userid,hashedApiKey:c.ApiKeyManager.hashSignedApiKeyValue(t.value)})),e.CrossauthLogger.logger.debug(e.j({err:h}))}}catch(t){e.CrossauthLogger.logger.error(e.j({msg:"Invalid authorization header received",header:i.headers.authorization})),e.CrossauthLogger.logger.debug(e.j({err:t}))}})}}const k=["Content-Type","application/json; charset=utf-8"];class Ce{constructor(r,a,s,o,i,d={}){l(this,"app");l(this,"authServer");l(this,"fastifyServer");l(this,"prefix","/");l(this,"loginUrl","/login");l(this,"oauthAuthorizePage","userauthorize.njk");l(this,"errorPage","error.njk");l(this,"devicePage","device.njk");l(this,"clientStorage");l(this,"refreshTokenType","json");l(this,"refreshTokenCookieName","CROSSAUTH_REFRESH_TOKEN");l(this,"refreshTokenCookieDomain");l(this,"refreshTokenCookieHttpOnly",!1);l(this,"refreshTokenCookiePath","/");l(this,"refreshTokenCookieSecure",!0);l(this,"refreshTokenCookieSameSite","strict");l(this,"csrfTokens");l(this,"createGetCsrfTokenEndpoint",!1);this.app=r,this.fastifyServer=a,this.clientStorage=s,this.authServer=new c.OAuthAuthorizationServer(this.clientStorage,o,i,d),c.setParameter("prefix",c.ParamType.String,this,d,"PREFIX"),this.prefix.endsWith("/")||(this.prefix+="/"),c.setParameter("errorPage",c.ParamType.String,this,d,"ERROR_PAGE"),c.setParameter("devicePage",c.ParamType.String,this,d,"OAUTH_DEVICE_PAGE"),c.setParameter("loginUrl",c.ParamType.String,this,d,"LOGIN_URL"),c.setParameter("oauthAuthorizePage",c.ParamType.String,this,d,"OAUTH_AUTHORIZE_PAGE"),c.setParameter("refreshTokenType",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_TYPE"),c.setParameter("refreshTokenCookieName",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_NAME"),c.setParameter("refreshTokenCookieDomain",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_DOMAIN"),c.setParameter("refreshTokenCookieHttpOnly",c.ParamType.Boolean,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_HTTPONLY"),c.setParameter("refreshTokenCookiePath",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_PATH"),c.setParameter("refreshTokenCookieSecure",c.ParamType.Boolean,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_SECURE"),c.setParameter("refreshTokenCookieSameSite",c.ParamType.String,this,d,"OAUTH_REFRESH_TOKEN_COOKIE_SAMESITE"),c.setParameter("createGetCsrfTokenEndpoint",c.ParamType.String,this,d,"OAUTH_CREATE_GET_CSRF_TOKEN_ENDPOINT"),this.refreshTokenType!="json"&&(this.createGetCsrfTokenEndpoint?this.csrfTokens=new c.DoubleSubmitCsrfToken(d.doubleSubmitCookieOptions):this.fastifyServer.sessionServer&&(this.csrfTokens=this.fastifyServer.sessionServer.sessionManager.csrfTokens)),this.createGetCsrfTokenEndpoint&&this.addApiGetCsrfTokenEndpoints(),r.get(this.prefix+".well-known/openid-configuration",async(t,n)=>n.header(...k).status(200).send(this.authServer.oidcConfiguration({authorizeEndpoint:this.prefix+"authorize",tokenEndpoint:this.prefix+"token",jwksUri:this.prefix+"jwks",additionalClaims:[]}))),r.get(this.prefix+"jwks",async(t,n)=>n.header(...k).status(200).send(this.authServer.jwks())),(this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCode)||this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.authServer.validFlows.includes(e.OAuthFlows.OidcAuthorizationCode))&&(r.get(this.prefix+"authorize",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authorize",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.authorizeEndpoint(t,n,t.query)}),r.post(this.prefix+"authorize",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"authorize",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.authorizeEndpoint(t,n,t.body)}),this.app.post(this.prefix+"userauthorize",async(t,n)=>{var u,C;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"authorize",ip:t.ip,user:(u=t.user)==null?void 0:u.username})),!t.user)return v.sendPageError(n,401,this.errorPage);let h,g;try{h=await this.fastifyServer.validateCsrfToken(t)}catch(f){g=e.CrossauthError.asCrossauthError(f),g.message="Invalid csrf cookie received",e.CrossauthLogger.logger.error(e.j({msg:g.message,hashedCsrfCookie:h?c.Crypto.hash(h):void 0,user:(C=t.user)==null?void 0:C.username,cerr:g}))}if(g){if(this.errorPage)return n.status(g.httpStatus).view(this.errorPage,{status:g.httpStatus,errorMessage:g.message,errorCode:g.code,errorCodeName:g.codeName});{let f="500";switch(g.httpStatus){case 401:f="401";break;case 400:f="400";break}return n.status(g.httpStatus).send(z[f]??L)}}if(!g){const f=t.body.authorized=="true";return await this.authorize(t,n,f,{responseType:t.body.response_type,client_id:t.body.client_id,redirect_uri:t.body.redirect_uri,scope:t.body.scope,state:t.body.state,codeChallenge:t.body.code_challenge,codeChallengeMethod:t.body.code_challenge_method})}})),(this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCode)||this.authServer.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.authServer.validFlows.includes(e.OAuthFlows.OidcAuthorizationCode)||this.authServer.validFlows.includes(e.OAuthFlows.ClientCredentials)||this.authServer.validFlows.includes(e.OAuthFlows.RefreshToken)||this.authServer.validFlows.includes(e.OAuthFlows.Password)||this.authServer.validFlows.includes(e.OAuthFlows.PasswordMfa)||this.authServer.validFlows.includes(e.OAuthFlows.DeviceCode))&&this.app.post(this.prefix+"token",async(t,n)=>{var f;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"token",ip:t.ip,user:(f=t.user)==null?void 0:f.username}));let h=t.body.client_id,g=t.body.client_secret;if(t.headers.authorization){let p,E;const w=t.headers.authorization.split(" ");if(w.length==2&&w[0].toLocaleLowerCase()=="basic"){const S=c.Crypto.base64Decode(w[1]).split(":",2);S.length==2&&(p=S[0],E=S[1])}p==null||E==null?e.CrossauthLogger.logger.warn(e.j({msg:"Ignoring malform authenization header "+t.headers.authorization})):(h=p,g=E)}let u=t.body.refresh_token;if((this.refreshTokenType=="cookie"&&t.cookies&&this.refreshTokenCookieName in t.cookies||this.refreshTokenType=="both"&&t.cookies&&this.refreshTokenCookieName in t.cookies&&u==null)&&this.csrfTokens){const p=t.cookies[this.csrfTokens.cookieName];let E=t.headers[this.csrfTokens.headerName.toLowerCase()];if(Array.isArray(E)&&(E=E[0]),!p||!E)return{error:"access_denied",error_description:"Invalid csrf token"};try{this.csrfTokens.validateDoubleSubmitCsrfToken(p,E)}catch(w){return e.CrossauthLogger.logger.debug(e.j({err:w})),e.CrossauthLogger.logger.warn(e.j({cerr:w,msg:"Invalid csrf token",client_id:t.body.client_id})),{error:"access_denied",error_description:"Invalid csrf token"}}u=t.cookies[this.refreshTokenCookieName]}const C=await this.authServer.tokenEndpoint({grantType:t.body.grant_type,client_id:h,client_secret:g,scope:t.body.scope,codeVerifier:t.body.code_verifier,code:t.body.code,username:t.body.username,password:t.body.password,mfaToken:t.body.mfa_token,oobCode:t.body.oob_code,bindingCode:t.body.binding_code,otp:t.body.otp,refreshToken:u,deviceCode:t.body.device_code});if(C.error=="authorization_pending")return n.header(...k).status(200).send(C);if(C.refresh_token&&this.refreshTokenType!="json"&&this.setRefreshTokenCookie(n,C.refresh_token,C.expires_in),C.error||!C.access_token){let p="server_error",E="Neither code nor error received when requesting authorization";C.error&&(p=C.error),C.error_description&&(E=C.error_description);const w=e.CrossauthError.fromOAuthError(p,E);return e.CrossauthLogger.logger.error(e.j({cerr:w})),n.header(...k).status(w.httpStatus).send(C)}return n.header(...k).send(C)}),this.authServer.validFlows.includes(e.OAuthFlows.PasswordMfa)&&(r.get(this.prefix+"mfa/authenticators",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"mfa/authenticators",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.mfaAuthenticatorsEndpoint(t,n)}),r.post(this.prefix+"mfa/authenticators",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"mfa/authenticators",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.mfaAuthenticatorsEndpoint(t,n)}),r.post(this.prefix+"mfa/challenge",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"mfa/challenge",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.mfaChallengeEndpoint(t,n,t.body)})),this.authServer.validFlows.includes(e.OAuthFlows.DeviceCode)&&(this.app.post(this.prefix+"device_authorization",async(t,n)=>{var C;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"device_authorization",ip:t.ip,user:(C=t.user)==null?void 0:C.username}));let h=t.body.client_id,g=t.body.client_secret;if(t.headers.authorization){let f,p;const E=t.headers.authorization.split(" ");if(E.length==2&&E[0].toLocaleLowerCase()=="basic"){const y=c.Crypto.base64Decode(E[1]).split(":",2);y.length==2&&(f=y[0],p=y[1])}f==null||p==null?e.CrossauthLogger.logger.warn(e.j({msg:"Ignoring malform authenization header "+t.headers.authorization})):(h=f,g=p)}const u=await this.authServer.deviceAuthorizationEndpoint({client_id:h,client_secret:g,scope:t.body.scope});if(u.error||!u.device_code||!u.user_code){let f="server_error",p="Neither code nor error received when requesting authorization";u.error&&(f=u.error),u.error_description&&(p=u.error_description);const E=e.CrossauthError.fromOAuthError(f,p);return e.CrossauthLogger.logger.error(e.j({cerr:E})),n.header(...k).status(E.httpStatus).send(u)}return n.header(...k).send(u)}),r.get(this.prefix+"device",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),t.user?await this.deviceGet(!1,t,n,t.user):n.redirect(this.loginUrl+"?next="+encodeURIComponent(t.url),302)}),r.get(this.prefix+"api/device",async(t,n)=>{var h;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),!t.user){const g=new e.CrossauthError(e.ErrorCode.Unauthorized,"Not logged in");return n.header(...k).status(401).send({errorMessage:g.message,errorCode:g.code,errorCodeName:g.codeName})}return await this.deviceGet(!0,t,n,t.user)}),this.app.post(this.prefix+"device",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),t.user?await this.deviceCodePost(!1,t,n):n.redirect(this.loginUrl+"?next="+encodeURIComponent(t.url),302)}),this.app.post(this.prefix+"api/device",async(t,n)=>{var h;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"device",ip:t.ip,user:(h=t.user)==null?void 0:h.username})),await this.deviceCodePost(!0,t,n)}))}async createCsrfToken(){if(!this.csrfTokens)throw new e.CrossauthError(e.ErrorCode.Configuration,"CSRF tokens not enabled");this.csrfTokens.makeCsrfCookie(await this.csrfTokens.createCsrfToken());const r=this.csrfTokens.createCsrfToken(),a=this.csrfTokens.makeCsrfFormOrHeaderToken(r);return{csrfCookie:this.csrfTokens.makeCsrfCookie(r),csrfFormOrHeaderValue:a}}addApiGetCsrfTokenEndpoints(){this.csrfTokens&&this.app.get(this.prefix+"getcsrftoken",async(r,a)=>{var o,i;if(e.CrossauthLogger.logger.info(e.j({msg:"API visit",method:"POST",url:this.prefix+"getcsrftoken",ip:r.ip,user:(o=r.user)==null?void 0:o.username})),!this.csrfTokens)return;let s="";try{const{csrfCookie:d,csrfFormOrHeaderValue:t}=await this.createCsrfToken();return s=d.value,a.setCookie(d.name,d.value,d.options),a.header(...k).send({ok:!0,csrfToken:t})}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"getcsrftoken failure",user:(i=r.user)==null?void 0:i.username,hashedCsrfCookie:c.Crypto.hash(s.split(".")[0]),errorCode:t.code,errorCodeName:t.codeName})),e.CrossauthLogger.logger.debug(e.j({err:d})),a.status(t.httpStatus).header(...k).send({ok:!1,errorCode:t.code,errorCodeName:t.codeName,error:t.message})}})}async authorizeEndpoint(r,a,s){var t,n,h;if(!r.user)return a.redirect(this.loginUrl+"?next="+encodeURIComponent(r.url),302);e.CrossauthLogger.logger.debug(e.j({msg:"validating authorize parameters"}));let{error_description:o}=this.authServer.validateAuthorizeParameters(s),i;if(o?(i=new e.CrossauthError(e.ErrorCode.BadRequest,o),e.CrossauthLogger.logger.error(e.j({msg:"authorize parameter invalid",cerr:i,user:(t=r.user)==null?void 0:t.username}))):e.CrossauthLogger.logger.error(e.j({msg:"authorize parameter valid",user:(n=r.user)==null?void 0:n.username})),i){if(this.errorPage)return a.status(i.httpStatus).view(this.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCode:i.code,errorCodeName:i.codeName});{let g="500";switch(i.httpStatus){case 401:g="401";break;case 400:g="400";break}return a.status(i.httpStatus).send(z[g]??L)}}let d=!1;if(e.CrossauthLogger.logger.debug(e.j({msg:"Checking scopes have been authorized",scope:s.scope})),s.scope?d=await this.authServer.hasAllScopes(s.client_id,r.user,s.scope.split(" ")):d=await this.authServer.hasAllScopes(s.client_id,r.user,[null]),d)return e.CrossauthLogger.logger.debug(e.j({msg:"All scopes authorized",scope:s.scope})),this.authorize(r,a,!0,{responseType:s.response_type,client_id:s.client_id,redirect_uri:s.redirect_uri,scope:s.scope,state:s.state,codeChallenge:s.code_challenge,codeChallengeMethod:s.code_challenge_method});e.CrossauthLogger.logger.debug(e.j({msg:"Not all scopes authorized",scope:s.scope}));try{const g=await this.clientStorage.getClientById(s.client_id);return a.view(this.oauthAuthorizePage,{user:r.user,response_type:s.response_type,client_id:s.client_id,client_name:g.client_name,redirect_uri:s.redirect_uri,scope:s.scope,scopes:s.scope?s.scope.split(" "):void 0,state:s.state,code_challenge:s.code_challenge,code_challenge_method:s.code_challenge_method,csrfToken:r.csrfToken})}catch(g){const u=g;return e.CrossauthLogger.logger.debug(e.j({err:u})),this.errorPage?a.status(u.httpStatus).view(this.errorPage,{status:u.httpStatus,errorMessage:"Invalid client given",client_id:s.client_id,user:(h=r.user)==null?void 0:h.username,httpStatus:u.httpStatus,errorCode:e.ErrorCode.UnauthorizedClient,errorCodeName:e.ErrorCode[e.ErrorCode.UnauthorizedClient]}):a.status(u.httpStatus).send(z[401])}}async authorize(r,a,s,{responseType:o,client_id:i,redirect_uri:d,scope:t,state:n,codeChallenge:h,codeChallengeMethod:g}){let u,C,f;if(s){const p=await this.authServer.authorizeGetEndpoint({responseType:o,client_id:i,redirect_uri:d,scope:t,state:n,codeChallenge:h,codeChallengeMethod:g,user:r.user});if(f=p.code,u=p.error,C=p.error_description,u||!f){const E=e.CrossauthError.fromOAuthError(u??"server_error",C??"Neither code nor error received");if(e.CrossauthLogger.logger.error(e.j({cerr:E})),this.errorPage)return a.status(E.httpStatus).view(this.errorPage,{status:E.httpStatus,errorMessage:E.message,errorCode:E.code,errorCodeName:E.codeName});{let w="500";switch(E.httpStatus){case 401:w="401";break;case 400:w="400";break}return a.status(E.httpStatus).send(z[w]??L)}}return a.redirect(this.authServer.redirect_uri(d,f,n))}else{const p=new e.CrossauthError(e.ErrorCode.Unauthorized,"You have not granted access");e.CrossauthLogger.logger.error(e.j({msg:C,errorCode:p.code,errorCodeName:p.codeName}));try{return c.OAuthClientManager.validateUri(d),a.redirect(d)}catch{e.CrossauthLogger.logger.error(e.j({msg:`Couldn't send error message ${p.codeName} to ${d}}`}))}}}async mfaAuthenticatorsEndpoint(r,a){var t;const s=(t=r.headers.authorization)==null?void 0:t.split(" ");if(!s||s.length!=2)return{error:"access_denied",error_desciption:"Invalid authorization header"};const o=s[1],i=await this.authServer.mfaAuthenticatorsEndpoint(o);if(i.authenticators)return a.header(...k).status(200).send(i.authenticators);const d=e.CrossauthError.fromOAuthError(i.error??"server_error");return a.header(...k).status(d.httpStatus).send(i)}async mfaChallengeEndpoint(r,a,s){const o=await this.authServer.mfaChallengeEndpoint(s.mfa_token,s.client_id,s.client_secret,s.challenge_type,s.authenticator_id);if(o.error){const i=e.CrossauthError.fromOAuthError(o.error);return a.header(...k).status(i.httpStatus).send(o)}return a.header(...k).status(200).send(o)}setRefreshTokenCookie(r,a,s){if(!this.refreshTokenCookieName)return;let o=s?new Date(Date.now()+s*1e3).toUTCString():void 0,i=this.refreshTokenCookieName+"="+a;o&&(i+="; expires="+new Date(o).toUTCString()),this.refreshTokenCookieSameSite&&(i+="; SameSite="+this.refreshTokenCookieSameSite),this.refreshTokenCookieDomain&&(i+="; domain="+this.refreshTokenCookieDomain),this.refreshTokenCookiePath&&(i+="; path="+this.refreshTokenCookiePath),this.refreshTokenCookieHttpOnly==!0&&(i+="; httpOnly"),this.refreshTokenCookieSecure==!0&&(i+="; secure"),r.setCookie(this.refreshTokenCookieName,i)}oidcConfiguration(){return this.authServer.oidcConfiguration({authorizeEndpoint:this.prefix+"authorize",tokenEndpoint:this.prefix+"token",jwksUri:this.prefix+"jwks",additionalClaims:[]})}async applyUserCode(r,a,s){var o,i,d;try{const t=await this.authServer.deviceEndpoint({userCode:r,user:s});if(t.error)return{ok:!1,completed:!1,retryAllowed:!1,error:t.error,error_description:t.error_description};if(!t.client_id)return e.CrossauthLogger.logger.error(e.j({msg:"No client id found for user code",userCodeHash:c.Crypto.hash(r),ip:a.ip,username:(o=a.user)==null?void 0:o.username})),{ok:!1,completed:!1,retryAllowed:!1,error:"server_error",error_description:"No client id found for user code"};if(t.error=="access_denied")return e.CrossauthLogger.logger.error(e.j({msg:"Incorrect user code given",userCodeHash:c.Crypto.hash(r),ip:a.ip,username:(i=a.user)==null?void 0:i.username})),this.authServer.userCodeThrottle>0&&await(g=>new Promise(u=>setTimeout(u,g)))(this.authServer.userCodeThrottle),{ok:!1,completed:!1,retryAllowed:!0,error:t.error,error_description:t.error_description};if(t.error=="expired_token")return e.CrossauthLogger.logger.error(e.j({msg:"Expired user code",userCodeHash:c.Crypto.hash(r),ip:a.ip,username:(d=a.user)==null?void 0:d.username})),{ok:!1,completed:!1,retryAllowed:!1,error:t.error,error_description:t.error_description};const n=await this.clientStorage.getClientById(t.client_id);return t.scopeAuthorizationNeeded?{ok:!0,completed:!1,retryAllowed:!0,authorizationNeeded:{user:s,client_id:t.client_id,client_name:n.client_name,scope:t.scope,scopes:t.scope?t.scope.split(" "):[],csrfToken:a.csrfToken},user:a.user,csrfToken:a.csrfToken,user_code:r}:{ok:!0,completed:!0,retryAllowed:!1,user:a.user,csrfToken:a.csrfToken}}catch(t){const n=e.CrossauthError.asCrossauthError(t);return e.CrossauthLogger.logger.debug(e.j({err:n})),e.CrossauthLogger.logger.error(e.j({msg:n.message,cerr:n})),{ok:!1,completed:!1,retryAllowed:!0,error:n.oauthErrorCode,error_description:n.message}}}async deviceGet(r,a,s,o){if(a.query.user_code){let i=await this.applyUserCode(a.query.user_code,a,o);if(i.error){const t=e.CrossauthError.fromOAuthError(i.error,i.error_description);e.CrossauthLogger.logger.debug({err:t}),e.CrossauthLogger.logger.error({cerr:t});const n={ok:!1,completed:!1,status:t.httpStatus,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,retryAllowed:i.retryAllowed};return r?s.header(...k).status(t.httpStatus).send(n):s.status(t.httpStatus).view(this.devicePage,{csrfToken:a.csrfToken,...n})}else if(i.authorizationNeeded){const t={ok:!0,completed:!1,retryAllowed:i.retryAllowed,authorizationNeeded:i.authorizationNeeded,user_code:i.user_code};return r?s.header(...k).status(200).send(t):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...t})}const d={ok:!0,completed:!0};return r?s.header(...k).status(401).send(d):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...d})}else{const i={ok:!1,completed:!1,user_code:a.query.user_code,csrfToken:a.csrfToken};return r?s.header(...k).status(200).send(i):s.status(200).view(this.devicePage,i)}}async deviceCodePost(r,a,s){try{if(!a.user)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"You are not logged in");if(!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"CSRF token missing or invalid");if(!a.body.authorized||a.body.authorized=="")if(a.body.user_code){let o=await this.applyUserCode(a.body.user_code,a,a.user);if(o.error){const d=e.CrossauthError.fromOAuthError(o.error,o.error_description);e.CrossauthLogger.logger.debug({err:d}),e.CrossauthLogger.logger.error({cerr:d});const t={ok:!1,completed:!1,status:d.httpStatus,errorMessage:d.message,errorCode:d.code,errorCodeName:d.codeName,retryAllowed:o.retryAllowed};return r?s.header(...k).status(200).send(t):s.status(d.httpStatus).view(this.devicePage,{csrfToken:a.csrfToken,...t})}else if(o.authorizationNeeded){const d={ok:!0,completed:!1,retryAllowed:o.retryAllowed,authorizationNeeded:o.authorizationNeeded,user_code:o.user_code};return r?s.header(...k).status(200).send(d):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...d})}const i={ok:!0,completed:!0,csrfToken:a.csrfToken};return r?s.header(...k).status(200).send(i):s.status(200).view(this.devicePage,i)}else{const o=e.CrossauthError.fromOAuthError("unauthorized","Please enter the code"),i={ok:!1,completed:!1,user_code:a.body.user_code,retryAllowed:!0,error:"unauthorized",error_description:"Please enter the code",errorMessage:o.message,errorCode:o.code,errorCodeName:o.codeName};return r?s.header(...k).status(401).send(i):s.status(200).view(this.devicePage,{csrfToken:a.csrfToken,...i})}else if(a.body.authorized=="true"){let o=a.body.user_code,i=a.body.scope;i=="";const d=a.body.client_id;if(!o)throw new e.CrossauthError(e.ErrorCode.BadRequest,"user_code missing");if(!d)throw new e.CrossauthError(e.ErrorCode.BadRequest,"client_id missing");let t=await this.authServer.validateAndPersistScope(d,i,a.user);if(t.error||(t=await this.applyUserCode(o,a,a.user),t.error))throw e.CrossauthError.fromOAuthError(t.error,t.error_description);const n={ok:!0,completed:!0,csrfToken:a.csrfToken};return r?s.header(...k).status(401).send(n):s.status(200).view(this.devicePage,n)}else throw new e.CrossauthError(e.ErrorCode.Unauthorized,"You did not authorize the client")}catch(o){const i=e.CrossauthError.asCrossauthError(o);e.CrossauthLogger.logger.debug({err:i}),e.CrossauthLogger.logger.error({cerr:i});const d={ok:!1,status:i.httpStatus,errorMessage:i.message,errorCode:i.code,errorCodeName:i.codeName};return r?s.header(...k).status(401).send(d):s.status(i.httpStatus).view(this.devicePage,{csrfToken:a.csrfToken,...d})}}}const P=["Content-Type","application/json; charset=utf-8"];async function ne(m,r,a,s){return e.CrossauthLogger.logger.debug(e.j({err:s})),a.header(...P).status(s.httpStatus).send({ok:!1,status:s.httpStatus,errorMessage:s.message,errorMessages:s.messages,errorCode:s.code,errorCodeName:s.codeName})}async function je(m,r,a,s){var o;return e.CrossauthLogger.logger.debug(e.j({err:s})),a.status(s.httpStatus).view(((o=m.oAuthClient)==null?void 0:o.errorPage)??"error.njk",{status:s.httpStatus,errorMessage:s.message,errorMessages:s.messages,errorCodeName:s.codeName})}function O(m){let r;if(m)try{r=JSON.parse(c.Crypto.base64Decode(m.split(".")[1]))}catch(a){const s=e.CrossauthError.asCrossauthError(a);e.CrossauthLogger.logger.debug(e.j({err:s})),e.CrossauthLogger.logger.error(e.j({msg:"Couldn't decode id token",cerr:s}))}return r}async function de(m,r,a,s){if(s){let o={ok:!0,...m};return r.jwtTokens.includes("id")&&(o.id_payload=m.id_payload??O(m.id_token)),s.header(...P).status(200).send(o)}}function ie(m,r){var a;if(m.access_token)try{if(m.access_token&&r.includes("access")){const s=R.jwtDecode(m.access_token),o=s.jti?s.jti:s.sid?s.sid:"",i=o?c.Crypto.hash(o):void 0;e.CrossauthLogger.logger.debug(e.j({msg:"Got access token",accessTokenHash:i}))}}catch(s){e.CrossauthLogger.logger.debug(e.j({err:s}))}if(m.id_token)try{if(m.id_token&&r.includes("id")){let s=m.id_payload??R.jwtDecode(m.id_token);if(s){const o=s.jti?s.jti:s.sid?s.sid:"",i=o?c.Crypto.hash(o):void 0;e.CrossauthLogger.logger.debug(e.j({msg:"Got id token",idTokenHash:i}))}}}catch(s){e.CrossauthLogger.logger.debug(e.j({err:s}))}if(m.refresh_token&&r.includes("refresh"))try{if(m.refresh_token){const s=(a=R.jwtDecode(m.refresh_token))==null?void 0:a.jti,o=s?c.Crypto.hash(s):void 0;e.CrossauthLogger.logger.debug(e.j({msg:"Got refresh token",refreshTokenHash:o}))}}catch(s){e.CrossauthLogger.logger.debug(e.j({err:s}))}}async function Oe(m,r,a,s){if(m.error){const o=e.CrossauthError.fromOAuthError(m.error,m.error_description);if(s)return s.status(o.httpStatus).view(r.errorPage,{status:o.httpStatus,errorMessage:o.message,errorCodeName:o.codeName,errorCode:o.code})}if(ie(m,r.jwtTokens),s)try{let o={...m};return r.jwtTokens.includes("id")&&m.id_token&&(o.id_payload=m.id_payload??O(m.id_token)),s.status(200).view(r.authorizedPage,o)}catch(o){const i=e.CrossauthError.asCrossauthError(o);return s.status(i.httpStatus).view(r.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCodeName:i.codeName})}}async function Ie(m,r,a,s){if(m.error){const o=e.CrossauthError.fromOAuthError(m.error,m.error_description);if(s)return s.status(o.httpStatus).view(r.errorPage,{status:o.httpStatus,errorMessage:o.message,errorCodeName:o.codeName,errorCode:o.code})}ie(m,r.jwtTokens);try{if((m.access_token||m.id_token||m.refresh_token)&&await me(m,r,a,s),s){if(!r.authorizedPage)return s.status(500).view(r.errorPage,{status:500,errorMessage:"Authorized url not configured",errorCodeName:e.ErrorCode[e.ErrorCode.Configuration],errorCode:e.ErrorCode.Configuration});let o={...m};return r.jwtTokens.includes("id")&&(o.id_payload=m.id_payload??O(m.id_token)),s.status(200).view(r.authorizedPage,o)}}catch(o){const i=e.CrossauthError.asCrossauthError(o);if(e.CrossauthLogger.logger.debug(e.j({err:i})),e.CrossauthLogger.logger.debug(e.j({cerr:i,msg:"Error receiving tokens"})),s)return s.status(i.httpStatus).view(r.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCodeName:i.codeName})}}async function me(m,r,a,s){if(!r.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot update session data if sessions not enabled");let o=m.expires_in;if(!o&&m.access_token&&r.jwtTokens.includes("access")){const t=R.jwtDecode(m.access_token);t.exp&&(o=t.exp)}if(!o)throw new e.CrossauthError(e.ErrorCode.BadRequest,"OAuth server did not return an expiry for the access token");const i=Date.now()+o*1e3;let d={...m,expires_at:i};if("id_token"in m){let t=m.id_payload??O(m.id_token);t&&(d.id_token=t)}await r.storeSessionData(d,a,s)}async function Me(m,r,a,s){if(m.error){const o=e.CrossauthError.fromOAuthError(m.error,m.error_description);if(s)return s.status(o.httpStatus).view(r.errorPage,{status:o.httpStatus,errorMessage:o.message,errorCodeName:o.codeName,errorCode:o.code})}ie(m,r.jwtTokens);try{if((m.access_token||m.id_token||m.refresh_token)&&await me(m,r,a,s),s)return r.authorizedUrl?s.redirect(r.authorizedUrl):s.status(500).view(r.errorPage,{status:500,errorMessage:"Authorized url not configured",errorCodeName:e.ErrorCode[e.ErrorCode.Configuration],errorCode:e.ErrorCode.Configuration})}catch(o){const i=e.CrossauthError.asCrossauthError(o);if(e.CrossauthLogger.logger.debug(e.j({err:i})),e.CrossauthLogger.logger.debug(e.j({cerr:i,msg:"Error receiving tokens"})),s)return s.status(i.httpStatus).view(r.errorPage,{status:i.httpStatus,errorMessage:i.message,errorCodeName:i.codeName})}}class J extends c.OAuthClientBackend{constructor(a,s,o){var i,d,t;super(s,o);l(this,"server");l(this,"siteUrl","/");l(this,"prefix","/");l(this,"errorPage","error.njk");l(this,"passwordFlowPage","passwordflow.njk");l(this,"deviceCodeFlowPage","devicecodeflow.njk");l(this,"deleteTokensPage","deletetokens.njk");l(this,"deleteTokensGetUrl");l(this,"deleteTokensPostUrl");l(this,"apiDeleteTokensPostUrl");l(this,"mfaOtpPage","mfaotp.njk");l(this,"mfaOobPage","mfaoob.njk");l(this,"authorizedPage","authorized.njk");l(this,"authorizedUrl","authorized");l(this,"sessionDataName","oauth");l(this,"receiveTokenFn",de);l(this,"errorFn",ne);l(this,"loginUrl","/login");l(this,"validFlows",[]);l(this,"jwtTokens",["access","id","refresh"]);l(this,"testMiddleware",!1);l(this,"requestObj");l(this,"loginProtectedFlows",[]);l(this,"tokenResponseType","sendJson");l(this,"errorResponseType","jsonError");l(this,"passwordFlowUrl","passwordflow");l(this,"passwordOtpUrl","passwordotp");l(this,"passwordOobUrl","passwordoob");l(this,"deviceCodeFlowUrl","devicecodeflow");l(this,"deviceCodePollUrl","devicecodepoll");l(this,"bffEndpoints",[]);l(this,"bffEndpointName","bff");l(this,"bffBaseUrl");l(this,"tokenEndpoints",[]);if(this.server=a,c.setParameter("sessionDataName",c.ParamType.String,this,o,"OAUTH_SESSION_DATA_NAME"),c.setParameter("siteUrl",c.ParamType.String,this,o,"SITE_URL",!0),c.setParameter("tokenResponseType",c.ParamType.String,this,o,"OAUTH_TOKEN_RESPONSE_TYPE"),c.setParameter("errorResponseType",c.ParamType.String,this,o,"OAUTH_ERROR_RESPONSE_TYPE"),c.setParameter("prefix",c.ParamType.String,this,o,"PREFIX"),this.prefix.endsWith("/")||(this.prefix+="/"),c.setParameter("loginUrl",c.ParamType.String,this,o,"LOGIN_URL"),c.setParameter("errorPage",c.ParamType.String,this,o,"ERROR_PAGE"),c.setParameter("authorizedPage",c.ParamType.String,this,o,"AUTHORIZED_PAGE"),c.setParameter("authorizedUrl",c.ParamType.String,this,o,"AUTHORIZED_URL"),c.setParameter("loginProtectedFlows",c.ParamType.JsonArray,this,o,"OAUTH_LOGIN_PROTECTED_FLOWS"),c.setParameter("passwordFlowUrl",c.ParamType.String,this,o,"OAUTH_PASSWORD_FLOW_URL"),c.setParameter("passwordOtpUrl",c.ParamType.String,this,o,"OAUTH_PASSWORD_OTP_URL"),c.setParameter("passwordOobUrl",c.ParamType.String,this,o,"OAUTH_PASSWORD_OOB_URL"),c.setParameter("passwordFlowPage",c.ParamType.String,this,o,"OAUTH_PASSWORD_FLOW_PAGE"),c.setParameter("deviceCodeFlowPage",c.ParamType.String,this,o,"OAUTH_DEVICECODE_FLOW_PAGE"),c.setParameter("deleteTokensPage",c.ParamType.String,this,o,"OAUTH_DELETE_TOKENS_PAGE"),c.setParameter("deleteTokensGetUrl",c.ParamType.String,this,o,"OAUTH_DELETE_TOKENS_GET_URL"),c.setParameter("deleteTokensPostUrl",c.ParamType.String,this,o,"OAUTH_DELETE_TOKENS_POST_URL"),c.setParameter("apiDeleteTokensPostUrl",c.ParamType.String,this,o,"OAUTHAPI__DELETE_TOKENS_POST_URL"),c.setParameter("mfaOtpPage",c.ParamType.String,this,o,"OAUTH_MFA_OTP_PAGE"),c.setParameter("mfaOobPage",c.ParamType.String,this,o,"OAUTH_MFA_OOB_PAGE"),c.setParameter("deviceCodeFlowUrl",c.ParamType.String,this,o,"OAUTH_DEVICECODE_FLOW_URL"),c.setParameter("deviceCodePollUrl",c.ParamType.String,this,o,"OAUTH_DEVICECODE_POLL_URL"),c.setParameter("bffEndpointName",c.ParamType.String,this,o,"OAUTH_BFF_ENDPOINT_NAME"),c.setParameter("bffBaseUrl",c.ParamType.String,this,o,"OAUTH_BFF_BASEURL"),c.setParameter("validFlows",c.ParamType.JsonArray,this,o,"OAUTH_VALIDFLOWS"),c.setParameter("jwtTokens",c.ParamType.JsonArray,this,o,"OAUTH_JWT_TOKENS"),(i=this.deleteTokensGetUrl)!=null&&i.startsWith("/")&&(this.deleteTokensGetUrl=this.deleteTokensGetUrl.substring(1)),(d=this.deleteTokensPostUrl)!=null&&d.startsWith("/")&&(this.deleteTokensPostUrl=this.deleteTokensPostUrl.substring(1)),(t=this.deleteTokensPostUrl)!=null&&t.startsWith("/")&&(this.deleteTokensPostUrl=this.deleteTokensPostUrl.substring(1)),this.validFlows.length==1&&this.validFlows[0]==e.OAuthFlows.All)this.validFlows=e.OAuthFlows.allFlows();else if(!e.OAuthFlows.areAllValidFlows(this.validFlows))throw new e.CrossauthError(e.ErrorCode.Configuration,"Invalid flows specificied in "+this.validFlows.join(","));if(o.tokenEndpoints&&(this.tokenEndpoints=o.tokenEndpoints),this.bffEndpointName.endsWith("/")&&(this.bffEndpointName=this.bffEndpointName.substring(0,this.bffEndpointName.length-1)),o.bffEndpoints&&(this.bffEndpoints=o.bffEndpoints),this.loginProtectedFlows.length==1&&this.loginProtectedFlows[0]==e.OAuthFlows.All)this.loginProtectedFlows=this.validFlows;else if(!e.OAuthFlows.areAllValidFlows(this.loginProtectedFlows))throw new e.CrossauthError(e.ErrorCode.Configuration,"Invalid flows specificied in "+this.loginProtectedFlows.join(","));if(this.tokenResponseType=="custom"&&!o.receiveTokenFn)throw new e.CrossauthError(e.ErrorCode.Configuration,"Token response type of custom selected but receiveTokenFn not defined");if(this.tokenResponseType=="custom"&&o.receiveTokenFn?this.receiveTokenFn=o.receiveTokenFn:this.tokenResponseType=="sendJson"?this.receiveTokenFn=de:this.tokenResponseType=="sendInPage"?this.receiveTokenFn=Oe:this.tokenResponseType=="saveInSessionAndLoad"?this.receiveTokenFn=Ie:this.tokenResponseType=="saveInSessionAndRedirect"&&(this.receiveTokenFn=Me),this.errorResponseType=="custom"&&!o.errorFn)throw new e.CrossauthError(e.ErrorCode.Configuration,"Error response type of custom selected but errorFn not defined");if(this.errorResponseType=="custom"&&o.errorFn?this.errorFn=o.errorFn:this.errorResponseType=="jsonError"?this.errorFn=ne:this.errorResponseType=="pageError"&&(this.errorFn=je),this.loginProtectedFlows.length>0&&this.loginUrl=="")throw new e.CrossauthError(e.ErrorCode.Configuration,"loginUrl must be set if protecting oauth endpoints");this.prefix.endsWith("/")||(this.prefix+="/"),this.redirect_uri=this.siteUrl+this.prefix+"authzcode",this.validFlows.includes(e.OAuthFlows.AuthorizationCode)&&this.server.app.get(this.prefix+"authzcodeflow",async(n,h)=>{var E;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authzcodeflow",ip:n.ip,user:(E=n.user)==null?void 0:E.username})),!this.server.sessionAdapter){const w=new e.CrossauthError(e.ErrorCode.Configuration,"Need a session server or adapter for authorization code flow");return await this.errorFn(this.server,n,h,w)}if(!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCode))return h.redirect(this.loginUrl+"?next="+encodeURIComponent(n.url),302);if(!this.server.sessionAdapter){const w=new e.CrossauthError(e.ErrorCode.Configuration,"Need a session server or adapter for authorization code flow");return await this.errorFn(this.server,n,h,w)}const g=this.randomValue(this.stateLength),u={scope:n.query.scope,state:g};await this.storeSessionData(u,n,h);const{url:C,error:f,error_description:p}=await this.startAuthorizationCodeFlow(g,n.query.scope);if(f||!C){const w=e.CrossauthError.fromOAuthError(f??"server_error",p);return await this.errorFn(this.server,n,h,w)}return this.oauthLogFetch?e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect",url:C})):e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect"})),h.redirect(C)}),a.app.addHook("preHandler",async(n,h)=>{if(n.user||!a.sessionAdapter)return;let g=await a.sessionAdapter.getSessionData(n,this.sessionDataName);if(g&&g.id_payload){let u=g.expires_at;if(u&&u>Date.now()&&g.id_payload.sub){n.user={id:g.id_payload.userid??g.id_payload.sub,username:g.id_payload.sub,state:g.id_payload.state??"active"},n.idTokenPayload=g.id_payload;let C;try{C=await this.userCreationFn(g.id_payload,this.userStorage,this.userMatchField,this.idTokenMatchField),n.user=C,n.authType=C?"oidc":void 0}catch(f){e.CrossauthLogger.logger.error(e.j({cerr:f})),n.user=void 0,n.authType=void 0}}}this.testMiddleware&&(this.requestObj=n)}),this.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)&&this.server.app.get(this.prefix+"authzcodeflowpkce",async(n,h)=>{var y;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authzcodeflowpkce",ip:n.ip,user:(y=n.user)==null?void 0:y.username})),!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE))return h.redirect(this.loginUrl+"?next="+encodeURIComponent(n.url),302);const g=this.randomValue(this.stateLength),{codeChallenge:u,codeVerifier:C}=await this.codeChallengeAndVerifier(),f={scope:n.query.scope,state:g,codeChallenge:u,codeVerifier:C};await this.storeSessionData(f,n,h);const{url:p,error:E,error_description:w}=await this.startAuthorizationCodeFlow(g,n.query.scope,u,!0);if(E||!p){const S=e.CrossauthError.fromOAuthError(E??"server_error",w);return await this.errorFn(this.server,n,h,S)}return this.oauthLogFetch?e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect",url:p})):e.CrossauthLogger.logger.debug(e.j({msg:"OAuth redirect"})),h.redirect(p)}),(this.validFlows.includes(e.OAuthFlows.AuthorizationCode)||this.validFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.validFlows.includes(e.OAuthFlows.OidcAuthorizationCode))&&this.server.app.get(this.prefix+"authzcode",async(n,h)=>{var C,f,p;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+"authzcode",ip:n.ip,user:(C=n.user)==null?void 0:C.username})),this.oauthLogFetch&&e.CrossauthLogger.logger.debug(e.j({msg:"Received OAuth redirect",url:n.url})),!n.user&&(this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCodeWithPKCE)||this.loginProtectedFlows.includes(e.OAuthFlows.AuthorizationCode)))return h.redirect(this.loginUrl+"?next="+encodeURIComponent(n.url),302);const g=await((f=this.server.sessionAdapter)==null?void 0:f.getSessionData(n,this.sessionDataName));if(!(g!=null&&g.state)||(g==null?void 0:g.state)!=n.query.state)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"State does not match");let u=await this.redirectEndpoint(n.query.code,g==null?void 0:g.scope,g==null?void 0:g.codeVerifier,n.query.error,n.query.error_description);try{if(u.error){const E=e.CrossauthError.fromOAuthError(u.error,u.error_description);return await this.errorFn(this.server,n,h,E)}return await this.receiveTokenFn(u,this,n,h)}catch(E){const w=e.CrossauthError.asCrossauthError(E);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:w,user:(p=n.user)==null?void 0:p.user})),e.CrossauthLogger.logger.debug(e.j({err:E})),await this.errorFn(this.server,n,h,w)}}),this.validFlows.includes(e.OAuthFlows.ClientCredentials)&&this.server.app.post(this.prefix+"clientcredflow",async(n,h)=>{var g,u,C;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"clientcredflow",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.server.sessionAdapter){const{error:f,reply:p}=await a.errorIfCsrfInvalid(n,h,this.errorFn);if(f)return p}if(!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.ClientCredentials))return h.status(401).header(...P).send({ok:!1,msg:"Access denied"});try{const f=await this.clientCredentialsFlow((u=n.body)==null?void 0:u.scope);if(f.error){const p=e.CrossauthError.fromOAuthError(f.error,f.error_description);return await this.errorFn(this.server,n,h,p)}return await this.receiveTokenFn(f,this,n,h)}catch(f){const p=e.CrossauthError.asCrossauthError(f);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:p,user:(C=n.user)==null?void 0:C.user})),e.CrossauthLogger.logger.debug(e.j({err:f})),await this.errorFn(this.server,n,h,p)}}),this.validFlows.includes(e.OAuthFlows.RefreshToken)&&(this.server.app.post(this.prefix+"refreshtokenflow",async(n,h)=>{var f,p;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokenflow",ip:n.ip,user:(f=n.user)==null?void 0:f.username}));const{error:g,reply:u}=await a.errorIfCsrfInvalid(n,h,this.errorFn);if(g)return u;let C=n.body.refreshToken;if(!C&&this.server.sessionAdapter){if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const E=await this.server.sessionAdapter.getSessionData(n,this.sessionDataName);if(!(E!=null&&E.refresh_token)){const w=new e.CrossauthError(e.ErrorCode.BadRequest,"No refresh token in session or in parameters");return await this.errorFn(this.server,n,h,w)}C=E.refresh_token}if(!C){const E=new e.CrossauthError(e.ErrorCode.BadRequest,"No refresh token supplied");return await this.errorFn(this.server,n,h,E)}if(!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.RefreshToken))return h.status(401).header(...P).send({ok:!1,msg:"Access denied"});try{const E=await this.refreshTokenFlow(C);if(E.error){const w=e.CrossauthError.fromOAuthError(E.error,E.error_description);return await this.errorFn(this.server,n,h,w)}return await this.receiveTokenFn(E,this,n,h)}catch(E){const w=e.CrossauthError.asCrossauthError(E);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:w,user:(p=n.user)==null?void 0:p.user})),e.CrossauthLogger.logger.debug(e.j({err:E})),await this.errorFn(this.server,n,h,w)}}),this.server.app.post(this.prefix+"refreshtokensifexpired",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokensifexpired",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!1,!0)}),this.server.app.post(this.prefix+"api/refreshtokensifexpired",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokens",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!0,!0)}),this.server.app.post(this.prefix+"refreshtokens",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokens",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!1,!1)}),this.server.app.post(this.prefix+"api/refreshtokens",async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"refreshtokens",ip:n.ip,user:(g=n.user)==null?void 0:g.username})),this.refreshTokens(n,h,!0,!1)})),(this.validFlows.includes(e.OAuthFlows.Password)||this.validFlows.includes(e.OAuthFlows.PasswordMfa))&&(this.server.app.get(this.prefix+this.passwordFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+this.passwordFlowUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),!n.user&&this.loginProtectedFlows.includes(e.OAuthFlows.Password)?h.redirect(302,this.loginUrl+"?next="+encodeURIComponent(n.url)):h.view(this.passwordFlowPage,{user:n.user,scope:n.query.scope,csrfToken:n.csrfToken})}),this.server.app.post(this.prefix+this.passwordFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.passwordFlowUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.passwordPost(!1,n,h)})),this.validFlows.includes(e.OAuthFlows.PasswordMfa)&&(this.server.app.post(this.prefix+this.passwordOtpUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.passwordOtpUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.passwordOtp(!1,n,h)}),this.server.app.post(this.prefix+this.passwordOobUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.passwordOobUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.passwordOob(!1,n,h)})),this.validFlows.includes(e.OAuthFlows.DeviceCode)&&(this.server.app.post(this.prefix+this.deviceCodeFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deviceCodeFlowPage,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePost(!1,n,h)}),this.server.app.post(this.prefix+"api/"+this.deviceCodeFlowUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"api/"+this.deviceCodeFlowPage,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePost(!0,n,h)}),this.server.app.post(this.prefix+this.deviceCodePollUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deviceCodePollUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePoll(!1,n,h)}),this.server.app.post(this.prefix+"api/"+this.deviceCodePollUrl,async(n,h)=>{var g;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deviceCodePollUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),await this.deviceCodePoll(!0,n,h)})),this.deleteTokensGetUrl&&this.server.app.get(this.prefix+this.deleteTokensGetUrl,async(n,h)=>{var g,u;return e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"GET",url:this.prefix+this.deleteTokensGetUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username})),h.view(this.deleteTokensPage,{user:(u=n.user)==null?void 0:u.username,csrfToken:n.csrfToken})}),this.deleteTokensPostUrl&&this.server.app.post(this.prefix+this.deleteTokensPostUrl,async(n,h)=>{var g,u,C;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.deleteTokensPostUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username}));try{return await this.deleteTokens(n),h.view(this.deleteTokensPage,{ok:!0,user:(u=n.user)==null?void 0:u.username,csrfToken:n.csrfToken})}catch(f){const p=e.CrossauthError.asCrossauthError(f);return e.CrossauthLogger.logger.debug(e.j({err:p})),e.CrossauthLogger.logger.error(e.j({msg:"Couldn't delete oauth tokens",cerr:p})),h.view(this.deleteTokensPage,{ok:!1,user:(C=n.user)==null?void 0:C.username,csrfToken:n.csrfToken,errorMessage:p.message,errorCode:p.code,errorCodeName:p.codeName})}}),this.apiDeleteTokensPostUrl&&this.server.app.post(this.prefix+this.apiDeleteTokensPostUrl,async(n,h)=>{var g;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+this.apiDeleteTokensPostUrl,ip:n.ip,user:(g=n.user)==null?void 0:g.username}));try{return await this.deleteTokens(n),h.header(...P).send('{"ok": true}')}catch(u){const C=e.CrossauthError.asCrossauthError(u);return e.CrossauthLogger.logger.debug(e.j({err:C})),e.CrossauthLogger.logger.error(e.j({msg:"Couldn't delete oauth tokens",cerr:C})),h.header(...P).status(C.httpStatus).send(JSON.stringify({ok:!1,errorMessage:C.message,errorCode:C.code,errorCodeName:C.codeName,csrfToken:n.csrfToken}))}});for(let n of this.tokenEndpoints)this.server.app.post(this.prefix+n,async(h,g)=>{var y;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+n,ip:h.ip,user:(y=h.user)==null?void 0:y.username})),!h.csrfToken)return g.header(...P).status(401).send({ok:!1,msg:"No csrf token given"});let u=!1,C=n;n.startsWith("have_")&&(C=n.replace("have_",""),u=!0);let f=C.replace("_token",""),p=!1;if(this.jwtTokens.includes(f)&&(p=h.body.decode??!0),!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const E=await this.server.sessionAdapter.getSessionData(h,this.sessionDataName);if(!E)return u?g.header(...P).status(200).send({ok:!1}):g.header(...P).status(204).send();let w=E[C];return p&&(w=O(E[C])),w?u?g.header(...P).status(200).send({ok:!0}):g.header(...P).status(200).send({...w}):u?g.header(...P).status(200).send({ok:!1}):g.header(...P).status(204).send()});if(this.server.app.post(this.prefix+"tokens",async(n,h)=>{var C;if(e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:"POST",url:this.prefix+"tokens",ip:n.ip,user:(C=n.user)==null?void 0:C.username})),!n.csrfToken)return h.header(...P).status(401).send({ok:!1,msg:"No csrf token given"});if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const g=await this.server.sessionAdapter.getSessionData(n,this.sessionDataName);if(!g)return h.header(...P).status(204).send();let u={};for(let f of this.tokenEndpoints){let p=!1,E=f;f.startsWith("have_")&&(E=f.replace("have_",""),p=!0);let w=E.replace("_token",""),y=!1;if(this.jwtTokens.includes(w)&&(y=n.body.decode??!0),E in g){let S=g[E];y&&(S=O(g[E])),S&&(u[f]=p?!0:S)}else p&&(u[f]=!1)}return h.header(...P).status(200).send({...u})}),this.bffEndpoints.length>0&&!this.bffBaseUrl)throw new e.CrossauthError(e.ErrorCode.Configuration,"If enabling BFF endpoints, must also define bffBaseUrl");this.bffBaseUrl==null&&(this.bffBaseUrl=""),this.bffBaseUrl.endsWith("/")&&(this.bffBaseUrl=this.bffBaseUrl.substring(0,this.bffBaseUrl.length-1));for(let n=0;n<this.bffEndpoints.length;++n){const h=this.bffEndpoints[n].url;if(h.includes("?")||h.includes("#"))throw new e.CrossauthError(e.ErrorCode.Configuration,"BFF urls may not contain query parameters or page fragments");if(!h.startsWith("/"))throw new e.CrossauthError(e.ErrorCode.Configuration,"BFF urls must be absolute and without the HTTP method, hostname or port");const g=this.bffEndpoints[n].methods,u=this.bffEndpoints[n].matchSubUrls??!1;let C=h;u&&(C.endsWith("/")||(C+="/"),C+="*");for(let f in g)this.server.app.route({method:g[f],url:this.prefix+this.bffEndpointName+C,handler:async(p,E)=>{var S,_;e.CrossauthLogger.logger.info(e.j({msg:"Page visit",method:p.method,url:p.url,ip:p.ip,user:(S=p.user)==null?void 0:S.username}));const w=p.url.substring(this.prefix.length+this.bffEndpointName.length);e.CrossauthLogger.logger.debug(e.j({msg:"Resource server URL "+w}));const y=g[f]!="GET"&&g[f]!="HEAD"&&g[f]!="OPTIONS";if(this.server.sessionAdapter&&y){const{error:A,reply:I}=await a.errorIfCsrfInvalid(p,E,this.errorFn);if(A)return I}try{if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const A=await this.server.sessionAdapter.getSessionData(p,this.sessionDataName);if(!A)return E.header(...P).status(401).send({ok:!1});let I=A==null?void 0:A.access_token;if(A&&A.access_token){const N=await((_=a.oAuthClient)==null?void 0:_.refresh(p,E,!0,!0,A.refresh_token,A.expires_at));N!=null&&N.access_token&&(I=N.access_token)}let H={Accept:"application/json","Content-Type":"application/json"};I&&(H.Authorization="Bearer "+I);let M;p.body?M=await fetch(this.bffBaseUrl+w,{headers:H,method:p.method,body:JSON.stringify(p.body??"{}")}):M=await fetch(this.bffBaseUrl+w,{headers:H,method:p.method});const we=await M.json();for(const N of M.headers.entries())E=E.header(N[0],N[1]);return E.header(...P).status(M.status).send(we)}catch(A){return e.CrossauthLogger.logger.error(e.j({err:A})),E.header(...P).status(500).send({})}}})}}async passwordPost(a,s,o){var i;if(this.server.sessionAdapter){const{error:d,reply:t}=await this.server.errorIfCsrfInvalid(s,o,this.errorFn);if(d)return t}try{let d=await this.passwordFlow(s.body.username,s.body.password,s.body.scope);if(d.error=="mfa_required"&&d.mfa_token&&this.validFlows.includes(e.OAuthFlows.PasswordMfa)){const t=d.mfa_token;if(d=await this.passwordMfa(a,t,s.body.scope,s,o),d.error){const n=e.CrossauthError.fromOAuthError(d.error,d.error_description);return a?await this.errorFn(this.server,s,o,n):o.view(this.passwordFlowPage,{user:s.user,username:s.body.username,password:s.body.password,scope:s.body.scope,errorMessage:n.message,errorCode:n.code,errorCodeName:n.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(d,this,s,o)}else if(d.error){const t=e.CrossauthError.fromOAuthError(d.error,d.error_description);return a?await this.errorFn(this.server,s,o,t):o.view(this.passwordFlowPage,{user:s.user,username:s.body.username,scope:s.body.scope,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(d,this,s,o)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:t,user:(i=s.user)==null?void 0:i.user})),e.CrossauthLogger.logger.debug(e.j({err:d})),a?await this.errorFn(this.server,s,o,t):o.view(this.passwordFlowPage,{user:s.user,username:s.body.username,password:s.body.password,scope:s.body.scope,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}}async passwordMfa(a,s,o,i,d){const t=await this.mfaAuthenticators(s);if(t.error||!t.authenticators||!Array.isArray(t.authenticators)||t.authenticators.length==0||t.authenticators.length>1&&!t.authenticators[0].active)return t.error?t:{error:"access_denied",error_description:"No MFA authenticators available"};const n=t.authenticators[0];if(n.authenticator_type=="otp"){const g=await this.mfaOtpRequest(s,n.id);return g.error||g.challenge_type!="otp"?{error:g.error??"server_error",error_description:g.error_description??"Invalid response from MFA OTP challenge"}:{scope:o,mfa_token:s}}else if(n.authenticator_type=="oob"){const g=await this.mfaOobRequest(s,n.id);return g.error||g.challenge_type!="oob"||!g.oob_code||g.binding_method!="prompt"?{error:g.error??"server_error",error_description:g.error_description??"Invalid response from MFA OOB challenge"}:{scope:o,mfa_token:s,oob_channel:n.oob_channel,challenge_type:g.challenge_type,binding_method:g.binding_method,oob_code:g.oob_code,name:n.name}}const h=new e.CrossauthError(e.ErrorCode.UnknownError,"Unsupported MFA type "+n.authenticator_type+" returned");return{error:h.oauthErrorCode,error_description:h.message}}async passwordOtp(a,s,o){var d;const i=await this.mfaOtpComplete(s.body.mfa_token,s.body.otp);if(i.error){const t=e.CrossauthError.fromOAuthError(i.error,i.error_description??"Error completing MFA");return e.CrossauthLogger.logger.warn(e.j({msg:"Error completing MFA",cerr:t,user:(d=s.user)==null?void 0:d.user,hashedMfaToken:c.Crypto.hash(s.body.mfa_token)})),e.CrossauthLogger.logger.debug(e.j({err:t})),a?await this.errorFn(this.server,s,o,t):o.view(this.mfaOtpPage,{user:s.user,scope:s.body.scope,mfa_token:s.body.mfa_token,challenge_tpye:s.body.challenge_type,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(i,this,s,o)??o}async passwordOob(a,s,o){var d;const i=await this.mfaOobComplete(s.body.mfa_token,s.body.oob_code,s.body.binding_code);if(i.error){const t=e.CrossauthError.fromOAuthError(i.error,i.error_description??"Error completing MFA");return e.CrossauthLogger.logger.warn(e.j({msg:"Error completing MFA",cerr:t,user:(d=s.user)==null?void 0:d.user,hashedMfaToken:c.Crypto.hash(s.body.mfa_token)})),e.CrossauthLogger.logger.debug(e.j({err:t})),a?await this.errorFn(this.server,s,o,t):o.view(this.mfaOobPage,{user:s.user,scope:s.body.scope,oob_code:s.body.mfa_token,name:s.body.name,challenge_tpye:s.body.challenge_type,mfa_token:s.body.mfa_token,errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName,csrfToken:s.csrfToken})}return await this.receiveTokenFn(i,this,s,o)??o}async deviceCodePost(a,s,o){var i;if(this.server.sessionAdapter){const{error:d,reply:t}=await this.server.errorIfCsrfInvalid(s,o,this.errorFn);if(d)return t}try{if(!s.csrfToken)throw new e.CrossauthError(e.ErrorCode.Unauthorized,"CSRF token missing or invalid");let d=this.authServerBaseUrl;d.endsWith("/")||(d+="/"),d+=this.deviceAuthorizationUrl;const t=await this.startDeviceCodeFlow(d,s.body.scope);if(t.error){const h=e.CrossauthError.fromOAuthError(t.error,t.error_description),g={user:s.user,scope:s.body.scope,errorMessage:h.message,errorCode:h.code,errorCodeName:h.codeName,csrfToken:s.csrfToken,error:t.error,error_description:t.error_description};return a?o.header(...P).status(h.httpStatus).send(t):o.view(this.deviceCodeFlowPage,g)}let n;return t.verification_uri_complete&&await Ae.toDataURL(t.verification_uri_complete).then(h=>{n=h}).catch(h=>{e.CrossauthLogger.logger.debug(e.j({err:h})),e.CrossauthLogger.logger.warn(e.j({msg:"Couldn't generate verification URL QR Code"}))}),a?o.header(...P).send(t):o.view(this.deviceCodeFlowPage,{user:s.user,scope:s.body.scope,verification_uri_qrdata:n,...t})}catch(d){const t=e.CrossauthError.asCrossauthError(d);e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:t,user:(i=s.user)==null?void 0:i.user})),e.CrossauthLogger.logger.debug(e.j({err:d}));const n={errorMessage:t.message,errorCode:t.code,errorCodeName:t.codeName};return a?o.header(...P).status(t.httpStatus).send(n):o.view(this.deviceCodeFlowPage,{user:s.user,csrfToken:s.csrfToken,scope:s.body.scope,...n})}}async deviceCodePoll(a,s,o){var i;try{const d=await this.pollDeviceCodeFlow(s.body.device_code);return d.error?o.header(...P).send(d):await this.receiveTokenFn(d,this,s,a?void 0:o)}catch(d){const t=e.CrossauthError.asCrossauthError(d);return e.CrossauthLogger.logger.error(e.j({msg:"Error receiving token",cerr:t,user:(i=s.user)==null?void 0:i.user})),e.CrossauthLogger.logger.debug(e.j({err:d})),await this.errorFn(this.server,s,o,t)}}async refresh(a,s,o,i,d,t){if(!t||!d)return o?void 0:await this.receiveTokenFn({},this,a,o?void 0:s);if(!i||t<=Date.now())try{const n=await this.refreshTokenFlow(d);if(!n.error&&!n.access_token&&(n.error="server_error",n.error_description="Unexpectedly did not receive error or access token"),!n.error){const u=await this.receiveTokenFn(n,this,a,o?void 0:s);if(!o)return u}if(!o){const u=e.CrossauthError.fromOAuthError(n.error??"server_error",n.error_description);return await this.errorFn(this.server,a,s,u)}let h=n.expires_in;if(!h&&n.access_token){const u=R.jwtDecode(n.access_token);u.exp&&(h=u.exp)}if(!h)throw new e.CrossauthError(e.ErrorCode.BadRequest,"OAuth server did not return an expiry for the access token");const g=new Date().getTime()+h*1e3;return{access_token:n.access_token,refresh_token:n.refresh_token,expires_in:n.expires_in,expires_at:g,error:n.error,error_description:n.error_description}}catch(n){if(e.CrossauthLogger.logger.debug(e.j({err:n})),e.CrossauthLogger.logger.error(e.j({cerr:n,msg:"Failed refreshing access token"})),!o){const h=e.CrossauthError.asCrossauthError(n);return await this.errorFn(this.server,a,s,h)}return{error:"server_error",error_description:"Failed refreshing access token"}}}async refreshTokens(a,s,o,i){if(!a.csrfToken)return s.header(...P).status(401).send({ok:!1,msg:"No csrf token given"});if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const d=await this.server.sessionAdapter.getSessionData(a,this.sessionDataName);if(!(d!=null&&d.refresh_token)){if(o)return s.header(...P).status(204).send();{const n=new e.CrossauthError(e.ErrorCode.InvalidSession,"No tokens found in session");return await this.errorFn(this.server,a,s,n)}}const t=await this.refresh(a,s,o,i,d.refresh_token,d.expires_at);if(!o){if(t==null)return this.receiveTokenFn({},this,a,s);if(t!=null)return t}return s.header(...P).status(200).send({ok:!0,expires_at:t==null?void 0:t.expires_at})}async deleteTokens(a){if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot delete tokens if sessions not enabled");if(!a.csrfToken)throw new e.CrossauthError(e.ErrorCode.InvalidSession,"Missing or incorrec CSRF token");if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot delete session data if sessions not enabled");await this.server.sessionAdapter.deleteSessionData(a,this.sessionDataName)}async storeSessionData(a,s,o){var i;if(this.server.sessionServer){let d=this.server.sessionServer.getSessionCookieValue(s);!d&&o?d=await this.server.createAnonymousSession(s,o,{[this.sessionDataName]:a}):await((i=this.server.sessionAdapter)==null?void 0:i.updateSessionData(s,this.sessionDataName,a))}else{if(!this.server.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");await this.server.sessionAdapter.updateSessionData(s,this.sessionDataName,a)}}}class Ee extends c.OAuthResourceServer{constructor(a,s,o={}){super(s,o);l(this,"userStorage");l(this,"protectedEndpoints",{});l(this,"protectedEndpointPrefixes",[]);l(this,"errorBody",{});l(this,"sessionDataName","oauth");l(this,"tokenLocations",["header"]);l(this,"sessionAdapter");if(c.setParameter("errorBody",c.ParamType.Json,this,o,"OAUTH_RESSERVER_ACCESS_DENIED_BODY"),c.setParameter("tokenLocations",c.ParamType.JsonArray,this,o,"OAUTH_TOKEN_LOCATIONS"),c.setParameter("sessionDataName",c.ParamType.String,this,o,"OAUTH_SESSION_DATA_NAME"),this.userStorage=o.userStorage,this.sessionAdapter=o.sessionAdapter,o.protectedEndpoints){const i=/^[!#\$%&'\(\)\*\+,\.\/a-zA-Z\[\]\^_`-]+/;for(const[d,t]of Object.entries(o.protectedEndpoints)){if(!d.startsWith("/"))throw new e.CrossauthError(e.ErrorCode.Configuration,"protected endpoints must be absolute paths without the protocol and hostname");t.scope&&t.scope.forEach(n=>{if(!i.test(n))throw new e.CrossauthError(e.ErrorCode.Configuration,"Illegal characters in scope "+n)})}this.protectedEndpoints={...o.protectedEndpoints};for(let d in o.protectedEndpoints){let t=this.protectedEndpoints[d];t.suburls==!0&&(d.endsWith("/")||(d+="/",this.protectedEndpoints[d]=t),this.protectedEndpointPrefixes.push(d))}}o.protectedEndpoints&&a.addHook("preHandler",async(i,d)=>{var u,C;const t=i.url.split("?",2)[0];let n=!1,h="";if(t in this.protectedEndpoints)n=!0,h=t;else for(let f of this.protectedEndpointPrefixes)t.startsWith(f)&&(n=!0),h=f;if(!n)return;const g=await this.authorized(i);if(!(i.user&&i.authType=="cookie"&&this.protectedEndpoints[h].acceptSessionAuthorization!=!0)){if(!g){i.authError="access_denied",i.authErrorDescription="No access token";const f=this.authenticateHeader(i);return d.header("WWW-Authenticate",f).status(401).send(this.errorBody)}if(!g.authorized){const f=this.authenticateHeader(i);return d.header("WWW-Authenticate",f).status(401).send(this.errorBody)}}if(g){if(i.accessTokenPayload=g.tokenPayload,i.user=g.user,(u=g.tokenPayload)!=null&&u.scope)if(Array.isArray(g.tokenPayload.scope)){let f=[];for(let p of g.tokenPayload.scope)typeof p=="string"&&f.push(p);i.scope=f}else typeof g.tokenPayload.scope=="string"&&(i.scope=g.tokenPayload.scope.split(" "));if(this.protectedEndpoints[h].scope){for(let f of this.protectedEndpoints[h].scope??[])if(!i.scope||!i.scope.includes(f)&&this.protectedEndpoints[h].acceptSessionAuthorization!=!0)return e.CrossauthLogger.logger.warn(e.j({msg:"Access token does not have sufficient scope",username:(C=i.user)==null?void 0:C.username,url:i.url})),i.scope=void 0,i.accessTokenPayload=void 0,i.user=void 0,i.authError="access_denied",i.authErrorDescription="Access token does not have sufficient scope",d.status(401).send(this.errorBody)}if(i.authType="oauth",i.authError=g==null?void 0:g.error,(g==null?void 0:g.error)=="access_denied"){const f=this.authenticateHeader(i);return d.header("WWW-Authenticate",f).status(401).send(this.errorBody)}else if(g!=null&&g.error)return d.status(500).send(this.errorBody);i.authErrorDescription=g==null?void 0:g.error_description,e.CrossauthLogger.logger.debug(e.j({msg:"Resource server url",url:i.url,authorized:i.accessTokenPayload!=null}))}})}authenticateHeader(a){const s=a.url.split("?",2)[0];if(s in this.protectedEndpoints){let o="Bearer";return this.protectedEndpoints[s].scope&&(o+=' scope="'+(this.protectedEndpoints[s].scope??[]).join(" ")),o}return""}async authorized(a){try{let s;for(let i of this.tokenLocations)if(i=="header"){const d=await this.tokenFromHeader(a);if(d){s=d;break}}else{const d=await this.tokenFromSession(a);if(d){s=d;break}}let o;if(s){if(s.sub&&this.userStorage){const i=await this.userStorage.getUserByUsername(s.sub);i&&(o=i.user),a.user=o}else s.sub&&(a.user={id:s.userid??s.sub,username:s.sub,state:s.state??"active"});return{authorized:!0,tokenPayload:s,user:o}}else return{authorized:!1}}catch(s){const o=s;return e.CrossauthLogger.logger.debug(e.j({err:s})),e.CrossauthLogger.logger.error(e.j({cerr:o})),{authorized:!1,error:"server_error",error_description:o.message}}}async tokenFromHeader(a){const s=a.headers.authorization;if(s&&s.startsWith("Bearer ")){const o=s.split(" ");if(o.length==2)return await this.accessTokenAuthorized(o[1])}}async tokenFromSession(a){if(!this.sessionAdapter)throw new e.CrossauthError(e.ErrorCode.Configuration,"Cannot get session data if sessions not enabled");const s=await this.sessionAdapter.getSessionData(a,this.sessionDataName);if(s!=null&&s.session_token)return s.expires_at&&s.expires_at<Date.now()?void 0:await this.accessTokenAuthorized(s.session_token)}}const Re=`<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
|
2
2
|
<html><head>
|
|
3
3
|
<title>400 Bad Request</title>
|
|
4
4
|
</head><body>
|