modelence 0.24.2 → 0.25.0

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.
@@ -1,2 +1,2 @@
1
- export{m as author,i as bin,o as bugs,t as default,r as dependencies,d as description,q as devDependencies,j as engines,g as exports,h as files,p as homepage,n as license,e as main,b as name,s as peerDependencies,l as repository,k as scripts,a as type,f as types,c as version}from'./chunk-RDPMWFVZ.js';//# sourceMappingURL=package-4RCR3AFN.js.map
2
- //# sourceMappingURL=package-4RCR3AFN.js.map
1
+ export{m as author,i as bin,o as bugs,t as default,r as dependencies,d as description,q as devDependencies,j as engines,g as exports,h as files,p as homepage,n as license,e as main,b as name,s as peerDependencies,l as repository,k as scripts,a as type,f as types,c as version}from'./chunk-7MIBHVZS.js';//# sourceMappingURL=package-PKGSNHLQ.js.map
2
+ //# sourceMappingURL=package-PKGSNHLQ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"package-4RCR3AFN.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"package-PKGSNHLQ.js"}
@@ -0,0 +1,2 @@
1
+ export{g as getCallContext,f as startServer}from'./chunk-J3ZZEZIV.js';import'./chunk-7MIBHVZS.js';import'./chunk-BVJJCQ6Y.js';import'./chunk-VYR7VQMQ.js';import'./chunk-UW37F3GV.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=server-LBKWH4I3.js.map
2
+ //# sourceMappingURL=server-LBKWH4I3.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"server-BCQLVBIZ.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"server-LBKWH4I3.js"}
package/dist/server.d.ts CHANGED
@@ -483,8 +483,43 @@ type AuthConfig = {
483
483
  * (or `null`/`undefined` to fall back to the default JSON response).
484
484
  *
485
485
  * Always escape interpolated values to prevent XSS.
486
+ *
487
+ * @deprecated Use {@link AuthConfig.oauthErrorRedirectUrl} instead. It sends
488
+ * the browser back into the app, where the error can be shown on a real page
489
+ * with the app's own UI; this only ever renders a standalone document at the
490
+ * callback URL. Ignored when `oauthErrorRedirectUrl` is set.
486
491
  */
487
492
  errorComponent?: (props: OAuthErrorInfo) => string | null | undefined;
493
+ /**
494
+ * Where a failed web OAuth flow sends the browser. A path (`'/login'`),
495
+ * resolved against `_system.site.url`, or an absolute URL. The failure is
496
+ * appended as `?error=<message>&errorCode=<code>`,
497
+ * the same contract a mobile flow delivers on its deep link, so the app can
498
+ * show the message on a real page instead of the raw callback response.
499
+ *
500
+ * OAuth errors surface at the provider callback URL, outside any client
501
+ * bundle, so nothing there can render app UI. Without this (or
502
+ * {@link AuthConfig.errorComponent}) the user lands on a JSON body with no
503
+ * way back. When both are set, the redirect wins and `errorComponent` is not
504
+ * called.
505
+ *
506
+ * `error` is for display and its wording may change; branch on `errorCode`.
507
+ *
508
+ * Applies to the provider callback only. A request rejected at the sign-in
509
+ * *initiation* endpoint (auth not configured, a disallowed mobile
510
+ * `redirectUri`, a missing `codeChallenge`) still answers the calling client
511
+ * with JSON, since nothing has left for the provider yet.
512
+ *
513
+ * @example
514
+ * ```typescript
515
+ * startApp({
516
+ * auth: {
517
+ * oauthErrorRedirectUrl: '/login',
518
+ * },
519
+ * });
520
+ * ```
521
+ */
522
+ oauthErrorRedirectUrl?: string;
488
523
  /**
489
524
  * Overrides the built-in rate limits for authentication endpoints. Each rule
490
525
  * you provide is merged into the defaults by `(bucket, type, window)`:
@@ -556,6 +591,18 @@ type AuthConfig = {
556
591
  * },
557
592
  * });
558
593
  * ```
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * // Allow a browser client on another origin to call this app's API (e.g. Expo
598
+ * // Web, which Metro serves on :8081 while the API runs on :3000). Applies to
599
+ * // module routes and framework API routes, not to SSR pages or static assets.
600
+ * startApp({
601
+ * security: {
602
+ * allowedOrigins: ['http://localhost:8081'],
603
+ * },
604
+ * });
605
+ * ```
559
606
  */
560
607
  type SecurityConfig = {
561
608
  /**
@@ -566,6 +613,46 @@ type SecurityConfig = {
566
613
  * When set, `X-Frame-Options` is omitted since it cannot express multiple origins.
567
614
  */
568
615
  frameAncestors?: string[];
616
+ /**
617
+ * Origins allowed to read this app's responses from a browser (CORS).
618
+ *
619
+ * Browsers block a cross-origin `fetch` unless the response carries a
620
+ * matching `Access-Control-Allow-Origin`. The common case is Expo Web, which
621
+ * Metro serves on a different port from the API — a different port is a
622
+ * different origin, so every method call is blocked without this.
623
+ *
624
+ * Scope: this covers your module routes and the framework's own API routes
625
+ * (method calls and the OAuth endpoints). SSR pages and static assets are
626
+ * excluded, so a listed origin can call your API but cannot read your rendered
627
+ * pages with credentials.
628
+ *
629
+ * The scope is derived from the routes actually registered, not matched by
630
+ * path prefix: module routes carry no framework-imposed prefix (the docs'
631
+ * example mounts `/todos` at the root), so a prefix rule would silently drop
632
+ * CORS from the user-defined routes that most need it.
633
+ *
634
+ * Each entry must be an exact origin (`scheme://host[:port]`); patterns and
635
+ * wildcards are not supported, since the response header carries one concrete
636
+ * origin. Entries are normalized (trimmed, lowercased, default port and
637
+ * trailing slash dropped) to match what browsers send, and anything that is
638
+ * not a valid origin throws at startup rather than silently never matching.
639
+ *
640
+ * The matched origin is echoed back rather than `*`, and
641
+ * `Access-Control-Allow-Credentials` is sent, so the browser will expose a
642
+ * credentialed response to JS. Note this only covers same-site requests: the
643
+ * auth cookie is `SameSite=Lax`, so a genuinely cross-site caller
644
+ * (`app.example.com` → `api.other.com`) never has the cookie attached in the
645
+ * first place, regardless of this setting. The Expo Web case works because
646
+ * `localhost:8081` and `localhost:3000` differ only by port, and port is not
647
+ * part of a site.
648
+ *
649
+ * Opt-in by design: when unset, no CORS headers are sent at all. Deployments
650
+ * that already add CORS at a proxy or router therefore stay untouched — a
651
+ * duplicated `Access-Control-Allow-Origin` is invalid and would break them.
652
+ *
653
+ * Native iOS/Android do not enforce CORS and never need this.
654
+ */
655
+ allowedOrigins?: string[];
569
656
  /**
570
657
  * IP addresses or CIDR ranges of reverse proxies that are allowed to supply
571
658
  * the client IP through `X-Forwarded-For`. This uses Express's `trust proxy`
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- export{m as ObjectId,k as ServerChannel,a as consumeRateLimit,c as deleteFile,j as deleteUser,i as disableUser,d as downloadFile,e as getFileUrl,b as getUploadUrl,l as sendEmail,h as startApp}from'./chunk-RAWHQTR6.js';import'./chunk-RDPMWFVZ.js';export{A as LiveData,a as Module,c as Store,z as authenticate,m as clearSessionUser,C as createQuery,o as createSession,j as dbSessions,s as dbUsers,n as invalidateAllUserSessions,k as obtainSession,d as schema,p as setAuthTokenCookie,l as setSessionUser}from'./chunk-BVJJCQ6Y.js';import'./chunk-VYR7VQMQ.js';export{a as getConfig,h as getEnvironmentId}from'./chunk-UW37F3GV.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=server.js.map
1
+ export{m as ObjectId,k as ServerChannel,a as consumeRateLimit,c as deleteFile,j as deleteUser,i as disableUser,d as downloadFile,e as getFileUrl,b as getUploadUrl,l as sendEmail,h as startApp}from'./chunk-J3ZZEZIV.js';import'./chunk-7MIBHVZS.js';export{A as LiveData,a as Module,c as Store,z as authenticate,m as clearSessionUser,C as createQuery,o as createSession,j as dbSessions,s as dbUsers,n as invalidateAllUserSessions,k as obtainSession,d as schema,p as setAuthTokenCookie,l as setSessionUser}from'./chunk-BVJJCQ6Y.js';import'./chunk-VYR7VQMQ.js';export{a as getConfig,h as getEnvironmentId}from'./chunk-UW37F3GV.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=server.js.map
2
2
  //# sourceMappingURL=server.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "modelence",
4
- "version": "0.24.2",
4
+ "version": "0.25.0",
5
5
  "description": "The Node.js Framework for Real-Time MongoDB Apps",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/global.d.ts",
@@ -1,41 +0,0 @@
1
- import {t as t$1}from'./chunk-RDPMWFVZ.js';import {a,b as b$1,c,d,s,t,u,v,w as w$1,o,p,i,l,e,B,n,m,q,r,x,C,D,E as E$1,F,f as f$1,G as G$1,z as z$2,y,g as g$1,h,J,H,I}from'./chunk-BVJJCQ6Y.js';import {b as b$2,c as c$1,d as d$1,a as a$4}from'./chunk-VYR7VQMQ.js';import {a as a$2,f,e as e$1,g,c as c$2,i as i$1,m as m$1,l as l$1,d as d$2,k as k$1,o as o$1,n as n$1,j}from'./chunk-UW37F3GV.js';import {a as a$3,b as b$3}from'./chunk-5M6FUMUK.js';import {a as a$1}from'./chunk-DO5TZLF5.js';import Ro from'dotenv';import gi from'fs/promises';import Xr from'os';import Z from'path';import {Server}from'socket.io';import {createAdapter}from'@socket.io/mongo-adapter';import {ObjectId,MongoServerError,MongoClient,MongoError}from'mongodb';export{ObjectId as m}from'mongodb';import {randomBytes,randomInt,randomUUID,createHash}from'crypto';import hr from'bcrypt';import F$1,{z}from'zod';import Te from'fs';import {createServer,defineConfig,loadConfigFromFile,mergeConfig}from'vite';import ei from'@vitejs/plugin-react';import z$1,{Router}from'express';import ns from'cookie-parser';import is from'http';var ue=new a("_system",{queries:{setupStatus:async()=>({setupRequired:b$1()})},configSchema:{mongodbUri:{type:"secret",isPublic:false,default:""},mongodbPoolSize:{type:"number",isPublic:false,default:10},"env.type":{type:"string",isPublic:true,default:""},"site.url":{type:"string",isPublic:true,default:""},multiInstance:{type:"boolean",isPublic:false,default:false}}});var V=null;async function ir(){if(V)return V;let e=N();if(!e)throw new Error("MongoDB URI is not set");let t=ue.getConfig("mongodbPoolSize");V=new MongoClient(e,{driverInfo:{name:"Modelence",version:t$1.version},ignoreUndefined:true,maxPoolSize:t});try{return await V.connect(),await V.db("admin").command({ping:1}),console.log("Pinged your deployment. You successfully connected to MongoDB!"),V}catch(r){throw console.error(r),V=null,r}}function N(){return ue.getConfig("mongodbUri")||void 0}function Se(){return V}var me=null,Ko="_modelenceSocketio",sr=60;async function Yo({httpServer:e,channels:t}){let r=Se(),o=!!a$2("_system.multiInstance");console.log("Initializing Socket.IO server...");let i=null;if(o&&r){i=r.db().collection(Ko);try{await i.createIndex({createdAt:1},{expireAfterSeconds:sr,background:!0});}catch(n){if(n instanceof Error&&"code"in n&&n.code===85)try{await i.dropIndex("createdAt_1"),await i.createIndex({createdAt:1},{expireAfterSeconds:sr,background:!0});}catch(s){console.error("Failed to recreate index on MongoDB collection for Socket.IO:",s);}else console.error("Failed to create index on MongoDB collection for Socket.IO:",n);}}me=new Server(e,{cors:{origin:"*",methods:["GET","POST"]},adapter:i?createAdapter(i):void 0,transports:["websocket"],perMessageDeflate:false}),me.on("error",n=>{console.error("Socket.IO error:",n);}),me.use(async(n,s)=>{let a=n.handshake.auth.token;try{n.data=await z$2(a),s();}catch(c){s(c instanceof Error?c:new Error(String(c)));}}),me.on("connection",n=>{n.on("disconnect",()=>{J(n);}),n.on("joinChannel",async s=>{let[a]=s.split(":"),c=false;for(let l of t)if(l.category===a){(!l.canAccessChannel||await l.canAccessChannel(n.data))&&(n.join(s),c=true,n.emit("joinedChannel",s));break}c||n.emit("joinError",{channel:s,error:"Access denied"});}),n.on("leaveChannel",s=>{n.leave(s),console.log(`User ${n.id} left channel ${s}`),n.emit("leftChannel",s);}),n.on("subscribeLiveQuery",s=>H(n,s)),n.on("unsubscribeLiveQuery",s=>I(n,s));}),console.log("Socket.IO server initialized");}function Xo({category:e,id:t,data:r}){me?.to(`${e}:${t}`).emit(e,r);}var ar={init:Yo,broadcast:Xo};async function Ae(e){let t$1=e.toLowerCase().trim().split("@");if(t$1.length!==2)return false;let r=t$1[1];return !!await t.findOne({domain:r})}var cr={interval:a$1.days(1),async handler(){let e=await fetch("https://disposable.github.io/disposable-email-domains/domains.txt");if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);let r=(await e.text()).split(`
2
- `).map(n=>n.trim().toLowerCase()).filter(n=>n.length>0),o=new Date,i=500;for(let n=0;n<r.length;n+=i){let s=r.slice(n,n+i);try{await t.insertMany(s.map(a=>({domain:a,addedAt:o})));}catch(a){a&&typeof a=="object"&&"name"in a&&a.name;}}}};var Qe=3,oe=50,Qo=/^[a-zA-Z0-9_-]+$/,lr=8,dr=128,ur=254,Zo=e=>z.string().trim().min(e.min??1,{message:`must be at least ${e.min??1} characters`}).max(e.max,{message:`must be at most ${e.max} characters`}),Xe=e=>z.string().trim().max(e.max,{message:`must be at most ${e.max} characters`}).transform(t=>t===""?void 0:t).optional(),mr=Zo({min:Qe,max:oe}).regex(Qo,{message:"must contain only letters, numbers, underscores, and hyphens"}),en=z.object({firstName:Xe({max:50}),lastName:Xe({max:50}),avatarUrl:Xe({max:400}),handle:mr}).strict();function Re(e){let t=en.partial().safeParse(e);if(!t.success){let r=t.error.issues[0],o=r.path.join("."),i=o?`${o}: ${r.message}`:r.message;throw new c$1(i)}return t.data}function Oe(e){return z.string().min(lr,{message:`Password must contain at least ${lr} characters`}).max(dr,{message:`Password must be at most ${dr} characters`}).parse(e)}function L(e){return z.string().max(ur,{message:`Email must be at most ${ur} characters`}).email({message:"Invalid email address"}).parse(e).toLowerCase()}function pr(e){return mr.parse(e)}function xe(e){return e instanceof MongoServerError&&e.code===11e3&&typeof e.keyPattern=="object"&&e.keyPattern!==null&&"emails.address"in e.keyPattern}function _e(e,t){return t?t.startsWith("http://")||t.startsWith("https://")?t:`${e}${t.startsWith("/")?"":"/"}${t}`:e}function G(e){return {id:e._id,handle:e.handle,roles:e.roles||[],firstName:e.firstName??void 0,lastName:e.lastName??void 0,avatarUrl:e.avatarUrl??void 0}}async function fr(e){let t=e.slice(0,oe);try{if(!await s.findOne({handle:t},{collation:{locale:"en",strength:2}}))return t}catch(i){throw new Error(`Database error while checking handle availability: ${i}`)}let r=51;for(let i=2;i<=r;i++){let n=`_${i}`,s$1=`${t.slice(0,oe-n.length)}${n}`;try{if(!await s.findOne({handle:s$1},{collation:{locale:"en",strength:2}}))return s$1}catch(a){throw new Error(`Database error while checking handle "${s$1}": ${a}`)}}let o=10;for(let i=0;i<o;i++){let n=`_${randomBytes(3).toString("hex")}`,s$1=`${t.slice(0,oe-n.length)}${n}`;try{if(!await s.findOne({handle:s$1},{collation:{locale:"en",strength:2}}))return s$1}catch(a){throw new Error(`Database error while checking handle "${s$1}": ${a}`)}}throw new Error(`Could not generate a unique handle for base "${e}" after exhausting all attempts.`)}async function U(e,t,{throwOnConflict:r=true}={}){if(e!=null&&String(e).trim()!==""){let n=pr(String(e).trim());if(r){if(await s.findOne({handle:n},{collation:{locale:"en",strength:2}}))throw new Error("Handle already taken.");return n}return fr(n)}let i=t.split("@")[0].replace(/[^a-zA-Z0-9_-]/g,"_").padEnd(Qe,"_").slice(0,oe);return fr(i)}var Ze=Object.freeze({});function et(e){Ze=Object.freeze(Object.assign({},Ze,e));}function b(){return Ze}var tt=Object.freeze({});function gr(e){tt=Object.freeze(Object.assign({},tt,e));}function w(){return tt}function sn(){return b()?.provider?!!a$2("_system.user.auth.email.verification"):false}var an=10,cn=hr.hashSync(randomBytes(32).toString("hex"),an);async function wr(e,{user:t,session:r,connectionInfo:o,res:i}){try{if(!r)throw new Error("Session is not initialized");if(t)throw new c$1("User is already authenticated","ALREADY_AUTHENTICATED");let n=o?.ip;n&&await k({bucket:"signin",type:"ip",value:n});let s$1=L(e.email),a=z.string().parse(e.password),c=await s.findOne({"emails.address":s$1,status:{$nin:["deleted","disabled"]}},{collation:{locale:"en",strength:2}}),l$1=c?.authMethods?.password?.hash,d=l$1||cn,u=await hr.compare(a,d);if(!l$1||!u)throw ln();if(!c.emails?.find(g=>g.address.toLowerCase()===s$1)?.verified&&sn())throw new b$2("Your email address hasn't been verified yet. Please check your inbox for the verification email.","EMAIL_NOT_VERIFIED");return await l(r.authToken,c._id),i&&p(i,r.authToken),w().onAfterLogin?.({provider:"email",user:c,session:r,connectionInfo:o}),w().login?.onSuccess?.(c),{user:G(c),session:{authToken:r.authToken}}}catch(n){throw n instanceof Error&&(w().onLoginError?.({provider:"email",error:n,session:r,connectionInfo:o}),w().login?.onError?.(n)),n}}async function yr(e,{session:t,res:r}){if(!t)throw new Error("Session is not initialized");await m(t.authToken),r&&q(r);}function ln(){return new Error("Incorrect email/password combination")}var fe=new c("_modelenceRateLimits",{schema:{bucket:d.string(),type:d.enum(["ip","user","email"]),value:d.string(),windowMs:d.number(),windowStart:d.date(),windowCount:d.number(),prevWindowCount:d.number(),expiresAt:d.date()},indexes:[{key:{bucket:1,type:1,value:1,windowMs:1},unique:true},{key:{expiresAt:1},expireAfterSeconds:0}]});var rt=[];function Er(e){if(rt.length>0)throw new Error("Duplicate call to initRateLimits - already initialized");rt=e;}async function k(e){let{bucket:t,type:r,value:o,message:i}=e,n=rt.filter(a=>a.bucket===t&&a.type===r),s=i?()=>new d$1(i):void 0;for(let a of n)await dn(a,o,s);}async function dn(e,t,r){let o=()=>r?r():new d$1(`Rate limit exceeded for ${e.bucket}`),i={bucket:e.bucket,type:e.type,value:t,windowMs:e.window},n=await fe.findOne(i),s=Date.now(),a=Math.floor(s/e.window)*e.window,{count:c,modifier:l}=n?un(n,a,s):{count:0,modifier:{$setOnInsert:{windowStart:new Date(a),windowCount:1,prevWindowCount:0,expiresAt:new Date(a+e.window+e.window)}}};if(c>=e.limit)throw o();await fe.upsertOne(i,l);}function un(e,t,r){let o=t-e.windowMs;if(e.windowStart.getTime()===t){let i=e.windowCount,n=e.prevWindowCount,s=1-(r-t)/e.windowMs;return {count:Math.round(i+n*s),modifier:{$inc:{windowCount:1},$setOnInsert:{windowStart:new Date(t),prevWindowCount:0,expiresAt:new Date(t+e.windowMs+e.windowMs)}}}}if(e.windowStart.getTime()===o){let i=1-(r-t)/e.windowMs;return {count:Math.round(e.windowCount*i),modifier:{$set:{windowStart:new Date(t),windowCount:1,prevWindowCount:e.windowCount,expiresAt:new Date(t+e.windowMs+e.windowMs)}}}}return {count:0,modifier:{$set:{windowStart:new Date(t),windowCount:1,prevWindowCount:0,expiresAt:new Date(t+e.windowMs+e.windowMs)}}}}async function br(e,{user:t}){if(!t)throw new b$2("Not authenticated");let r=await s.requireById(t.id);return {handle:r.handle,emails:r.emails,authMethods:Object.keys(r.authMethods||{}),firstName:r.firstName??void 0,lastName:r.lastName??void 0,avatarUrl:r.avatarUrl??void 0}}async function vr(e,{user:t}){if(!t)throw new b$2("Not authenticated");let r=await s.requireById(t.id),o=e,i=typeof o.handle=="string"&&o.handle.trim()===r.handle,{handle:n,...s$1}=o,a=Re(i?s$1:o);if(await w().validateProfileUpdate?.(a),"handle"in a&&a.handle!==void 0&&await s.findOne({handle:a.handle,_id:{$ne:r._id}},{collation:{locale:"en",strength:2}}))throw new c$1("Handle already taken.");if(Object.keys(a).length>0){await k({bucket:"updateProfile",type:"user",value:t.id});let c={},l={};for(let[u,m]of Object.entries(a))m===void 0?l[u]="":c[u]=m;let d={};Object.keys(c).length>0&&(d.$set=c),Object.keys(l).length>0&&(d.$unset=l);try{await s.updateOne({_id:r._id},d);let u=Object.fromEntries(Object.keys(l).map(m=>[m,void 0]));r={...r,...c,...u};}catch(u){throw u instanceof Error&&"code"in u&&u.code===11e3?new c$1("Handle already taken."):u}}return {user:G(r)}}var ot=["google","github"];async function Cr({provider:e},{user:t}){if(!t)throw new Error("You must be signed in to unlink a provider.");if(typeof e!="string"||!ot.includes(e))throw new Error(`Invalid provider. Supported providers are: ${ot.join(", ")}.`);let r=await s.requireById(t.id),o=r.authMethods??{};if(!o[e])throw new Error(`${e} is not linked to your account.`);if(Object.values(o).filter(Boolean).length<=1)throw new Error("Cannot unlink your only authentication method. Please add another method first.");let s$1=Object.keys(o).filter(l=>l!==e&&o[l]),a=s$1.length>0?{$or:s$1.map(l=>({[`authMethods.${l}`]:{$exists:true}}))}:{};if((await s.updateOne({_id:r._id,...a},{$unset:{[`authMethods.${e}`]:""}})).matchedCount===0)throw new Error("Cannot unlink your only authentication method. Please add another method first.")}function kr({name:e,email:t,verificationUrl:r}){return `
3
- <p>Hi${e?` ${e}`:""},</p>
4
- <p>Please verify your email address ${t} by clicking the link below:</p>
5
- <p><a href="${r}">${r}</a></p>
6
- <p>If you did not request this, please ignore this email.</p>
7
- `}var fn={locale:"en",strength:2};async function gn(e){let t=await u.findOne({token:e,expiresAt:{$gt:new Date}});if(!t)throw new Error("Invalid or expired verification token");let r=await s.findOne({_id:t.userId,status:{$nin:["deleted","disabled"]}});if(!r)throw new Error("User not found");let o=t.email;if(!o)throw new Error("Email not found in token");let i=await s.findOneAndUpdate({_id:t.userId,status:{$nin:["deleted","disabled"]},emails:{$elemMatch:{address:o,verified:{$ne:true}}}},{$set:{"emails.$.verified":true}},{collation:fn,returnDocument:"after"});if(await u.deleteOne({_id:t._id}),!i){let n=r.emails?.find(s=>s.address.toLowerCase()===o);throw n?n.verified?new Error("Email is already verified"):new Error("Unable to verify email address"):new Error("Email address not found for this user")}return {userDoc:i,email:o}}async function Sr(e){let t=a$2("_system.site.url"),r=b().verification?.redirectUrl||b().emailVerifiedRedirectUrl||t||"/";try{let o$1=z.string().parse(e.query.token),{userDoc:i}=await gn(o$1);w().onAfterEmailVerification?.({provider:"email",user:i,session:null,connectionInfo:{baseUrl:t,ip:e.req.ip||e.req.socket.remoteAddress,userAgent:e.headers["user-agent"],acceptLanguage:e.headers["accept-language"],referrer:e.headers.referer}});let{authToken:s}=await o(i._id);return p(e.res,s),{status:301,headers:{"Referrer-Policy":"no-referrer"},redirect:`${r}?status=verified`}}catch(o){let i=o instanceof Error?o.message:"An unexpected error occurred";return o instanceof Error&&(w().onEmailVerificationError?.({provider:"email",error:o,session:null,connectionInfo:{baseUrl:t,ip:e.req.ip||e.req.socket.remoteAddress,userAgent:e.headers["user-agent"],acceptLanguage:e.headers["accept-language"],referrer:e.headers.referer}}),console.error("Error verifying email:",o)),{status:301,headers:{"Referrer-Policy":"no-referrer"},redirect:`${r}?status=error&message=${encodeURIComponent(i)}`}}}async function it({userId:e,email:t,baseUrl:r}){let o=a$2("_system.site.url")||r;if(b().provider){let i=b().provider,n=randomBytes(32).toString("hex"),s=new Date(Date.now()+a$1.hours(24));await u.insertOne({userId:e,email:t,token:n,createdAt:new Date,expiresAt:s});let a=`${o}/api/_internal/auth/verify-email?token=${n}`,l=(b()?.verification?.template||kr)({name:"",email:t,verificationUrl:a}),d=B(l);await i?.sendEmail({to:t,from:b()?.from||"noreply@modelence.com",subject:b()?.verification?.subject||"Verify your email address",text:d,html:l});}}var nt={success:true,message:"If that email is registered and not yet verified, a verification email has been sent"};async function Ar(e,{connectionInfo:t}){let r=L(e.email),o=await s.findOne({"emails.address":r,status:{$nin:["deleted","disabled"]}},{collation:{locale:"en",strength:2}});if(!o)return nt;let i=o.emails?.find(n=>n.address.toLowerCase()===r);if(!i||i.verified)return nt;if(!b().provider)throw new Error("Email provider is not configured");return await k({bucket:"verification",type:"user",value:o._id.toString(),message:"Please wait at least 60 seconds before requesting another verification email"}),await it({userId:o._id,email:r,baseUrl:t?.baseUrl}),nt}async function Rr(e,{user:t,session:r,connectionInfo:o}){let i=w();try{if(t)throw new c$1("User is already authenticated","ALREADY_AUTHENTICATED");let n=e,{firstName:s$1,lastName:a,avatarUrl:c,handle:l}=n,d=L(n.email),u=Oe(n.password),m=o?.ip;if(m&&await k({bucket:"signupAttempt",type:"ip",value:m}),!i.allowDisposableEmails&&await Ae(d))throw new Error("Please use a permanent email address");if(await s.findOne({"emails.address":d},{collation:{locale:"en",strength:2}}))throw new Error("Unable to create account");await i.onBeforeSignup?.({email:d,firstName:s$1,lastName:a,handle:l,provider:"email",connectionInfo:o}),m&&await k({bucket:"signup",type:"ip",value:m});let y=Re({firstName:s$1,lastName:a,avatarUrl:c,handle:l});await i.validateSignup?.({email:d,password:u,...y}),await i.validatePassword?.({password:u,email:d,context:"signup"});let v;if(y.handle)v=await U(y.handle,d);else if(i.generateHandle){let D=await i.generateHandle({email:d,firstName:y.firstName,lastName:y.lastName});v=await U(D,d,{throwOnConflict:!1});}else v=await U(void 0,d);let x=await hr.hash(u,10),T;try{T=await s.insertOne({handle:v,status:"active",emails:[{address:d,verified:!1}],createdAt:new Date,authMethods:{password:{hash:x}},...y.firstName!==void 0&&{firstName:y.firstName},...y.lastName!==void 0&&{lastName:y.lastName},...y.avatarUrl!==void 0&&{avatarUrl:y.avatarUrl}});}catch(D){throw xe(D)?new Error("Unable to create account"):D}let S=await s.findOne({_id:T.insertedId},{readPreference:"primary"});if(!S)throw new Error("User not found");return await it({userId:T?.insertedId,email:d,baseUrl:o?.baseUrl}),i.onAfterSignup?.({provider:"email",user:S,session:r,connectionInfo:o}),i.signup?.onSuccess?.(S),T.insertedId}catch(n){throw n instanceof Error&&(i.onSignupError?.({provider:"email",error:n,session:r,connectionInfo:o}),i.signup?.onError?.(n)),n}}var st="resetPasswordToken",En="/api/_internal/",Lr={httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:En};async function Ur(e$1){return v.findOne({token:e(e$1)})}async function Or(e){return v.findOneAndDelete({_id:e})}function bn({email:e,resetUrl:t}){return `
8
- <p>Hi,</p>
9
- <p>We received a request to reset your password for ${e}.</p>
10
- <p>Click the link below to reset your password:</p>
11
- <p><a href="${t}">${t}</a></p>
12
- <p>This link will expire in 1 hour.</p>
13
- <p>If you did not request this password reset, please ignore this email.</p>
14
- `}var xr={success:true,message:"If an account with that email exists, a password reset link has been sent"};async function Pr(e$1,{connectionInfo:t}){let r=L(e$1.email),o=t?.ip;o&&await k({bucket:"passwordReset",type:"ip",value:o}),await k({bucket:"passwordReset",type:"email",value:r});let i=await s.findOne({"emails.address":r,status:{$nin:["deleted","disabled"]}},{collation:{locale:"en",strength:2}});if(!i)return xr;let n=b().provider;if(!n)throw new Error("Email provider is not configured");let s$1=randomBytes(32).toString("hex"),a=Date.now(),c=new Date(a),l=new Date(a+a$1.hours(1));await v.insertOne({userId:i._id,email:r,token:e(s$1),createdAt:c,expiresAt:l});let d=a$2("_system.site.url")||t?.baseUrl;if(!d)throw new Error("Unable to build password reset link: set _system.site.url (MODELENCE_SITE_URL)");let u=`${d}/api/_internal/auth/reset-password?token=${s$1}`,g=(b()?.passwordReset?.template||bn)({email:r,resetUrl:u,name:""}),y=B(g);return await n.sendEmail({to:r,from:b()?.from||"noreply@modelence.com",subject:b()?.passwordReset?.subject||"Reset your password",text:y,html:g}),xr}async function Tr(e){let t=a$2("_system.site.url")||`${e.req.protocol}://${e.req.get("host")}`,r=_e(t,b().passwordReset?.redirectUrl);try{let o=z.string().parse(e.query.token),i=await Ur(o);if(!i||i.expiresAt<new Date)throw new Error("This password reset link is invalid or has expired.");return e.res.cookie(st,o,{...Lr,maxAge:a$1.hours(1)}),{status:302,headers:{"Referrer-Policy":"no-referrer"},redirect:r}}catch(o){return console.error("Error handling password reset landing:",o),{status:302,headers:{"Referrer-Policy":"no-referrer"},redirect:`${r}?status=error&message=${encodeURIComponent("This password reset link is invalid or has expired.")}`}}}async function Mr(e,t){let r=t.req?.cookies?.[st];!r&&e.token&&console.warn("[modelence] resetPassword received a token via request args instead of the httpOnly cookie. This path is deprecated and will be removed; ensure password reset emails link to /api/_internal/auth/reset-password so the token is exchanged server-side.");let o=z.string().parse(r??e.token),i=Oe(e.password),n$1=()=>{t.res?.clearCookie(st,Lr);},s$1=await Ur(o);if(!s$1)throw n$1(),new Error("Invalid or expired reset token");if(s$1.expiresAt<new Date)throw await Or(s$1._id),n$1(),new Error("Reset token has expired");let a=await s.findOne({_id:s$1.userId});if(!a)throw new Error("User not found");await w().validatePassword?.({password:i,email:s$1.email??a.emails?.[0]?.address?.toLowerCase()??"",context:"reset"});let c=await hr.hash(i,10);if(!await Or(s$1._id))throw n$1(),new Error("Invalid or expired reset token");await s.updateOne({_id:a._id},{$set:{"authMethods.password.hash":c}}),s$1.email&&await s.updateOne({_id:a._id,"emails.address":s$1.email},{$set:{"emails.$.verified":true}},{collation:{locale:"en",strength:2}}),await n(a._id);let d=Array.from(new Set((a.emails??[]).map(u=>u.address?.toLowerCase()).filter(u=>!!u)));return d.length>0&&await w$1.deleteMany({email:{$in:d}}),n$1(),{success:true,message:"Password has been reset successfully"}}function Ir({name:e,email:t,magicLinkUrl:r,code:o}){return `
15
- <p>Hi${e?` ${e}`:""},</p>
16
- <p>Click the link below to sign in as ${t}:</p>
17
- <p><a href="${r}">${r}</a></p>
18
- <p>Or enter this one-time code in the app:</p>
19
- <p><strong style="font-size: 24px; letter-spacing: 4px;">${o}</strong></p>
20
- <p>The link and code can only be used once and will expire in 15 minutes.</p>
21
- <p>If you did not request this, please ignore this email.</p>
22
- `}var lt="magicLinkToken",kn="/api/_internal/",Dr={httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:kn},$r=15,Nr=6,Sn=5;function Le(e,t){try{Promise.resolve(t?.()).catch(r=>{console.error(`Error in ${e} hook:`,r);});}catch(r){console.error(`Error in ${e} hook:`,r);}}function mt(){return !!w().magicLink?.enabled}function jr(){return !!w().magicLink?.allowSignup}async function Hr(e$1){return w$1.findOne({token:e(e$1)})}async function pt(e){return w$1.findOneAndDelete({_id:e})}async function at(e){try{await w$1.insertOne(e);}catch(t){console.error("Failed to restore a magic link token after a failed signup:",t);}}var ct={success:true,message:"If this email can be used to sign in, a link has been sent"};async function Br(e$1,{connectionInfo:t}){if(!mt())throw new Error("Magic link authentication is not enabled");let r=L(e$1.email),o=t?.ip;o&&await k({bucket:"magicLink",type:"ip",value:o}),await k({bucket:"magicLink",type:"email",value:r});let i=b().provider;if(!i)throw new Error("Email provider is not configured");let n=b().from;if(!n)throw new Error("Email `from` address is not configured");let s$1=a$2("_system.site.url");if(!s$1)throw new Error("Unable to build magic link: set _system.site.url (MODELENCE_SITE_URL)");if(!w().allowDisposableEmails&&await Ae(r))throw new Error("Please use a permanent email address");let a=await s.findOne({"emails.address":r},{collation:{locale:"en",strength:2}});if(a&&(a.status==="disabled"||a.status==="deleted")||!a&&!jr())return ct;let c=randomBytes(32).toString("hex"),l=randomInt(0,10**Nr).toString().padStart(Nr,"0"),d=Date.now(),u=new Date(d),m=new Date(d+a$1.minutes($r));await w$1.insertOne({email:r,token:e(c),code:e(l),attempts:0,createdAt:u,expiresAt:m});let g=`${s$1}/api/_internal/auth/magic-link?token=${c}`,v=(b()?.magicLink?.template||Ir)({email:r,magicLinkUrl:g,code:l,name:""}),x=B(v);return await i.sendEmail({to:r,from:n,subject:b()?.magicLink?.subject||"Your sign-in link",text:x,html:v}),ct}async function Vr(e){let t=a$2("_system.site.url")||"",r=_e(t,b().magicLink?.redirectUrl)||"/";try{let o=z.string().parse(e.query.token),i=await Hr(o);if(!i||i.expiresAt<new Date)throw new Error("This sign-in link is invalid or has expired.");return e.res.cookie(lt,o,{...Dr,maxAge:a$1.minutes($r)}),{status:302,headers:{"Referrer-Policy":"no-referrer"},redirect:r}}catch(o){return console.error("Error handling magic link landing:",o),{status:302,headers:{"Referrer-Policy":"no-referrer"},redirect:`${r}?status=error&message=${encodeURIComponent("This sign-in link is invalid or has expired.")}`}}}var dt={locale:"en",strength:2};async function Gr(e){let{tokenDoc:t,session:r,connectionInfo:o,res:i,clearCookie:n}=e,s$1=t.email,a=w(),c=jr(),l$1=false;try{let d=await pt(t._id);if(!d)throw n(),new Error("Invalid or expired code or magic link");let u;if(c)try{if(!await s.findOne({"emails.address":s$1},{collation:dt})){l$1=!0,await a.onBeforeSignup?.({email:s$1,provider:"magicLink",connectionInfo:o});let D=o?.ip;D&&await k({bucket:"signup",type:"ip",value:D}),u=a.generateHandle?await U(await a.generateHandle({email:s$1}),s$1,{throwOnConflict:!1}):await U(void 0,s$1);}}catch(S){throw await at(d),S}let m;try{m=await An(s$1,u,c);}catch(S){throw await at(d),S}let{userDoc:g,isNew:y}=m;if(!g)throw await at(d),n(),c&&!l$1?new Error("User account is not active"):(l$1=!0,new Error("Sign up with magic link is not enabled"));if(l$1=y,g.status==="disabled"||g.status==="deleted")throw n(),new Error("User account is not active");let v=g,x=g.emails?.find(S=>S.address.toLowerCase()===s$1),T=!y&&!x?.verified;return T&&(v=await s.findOneAndUpdate({_id:g._id,"emails.address":s$1},{$set:{"emails.$.verified":!0}},{collation:dt,returnDocument:"after"})??g),await l(r.authToken,v._id),i&&p(i,r.authToken),y?Le("onAfterSignup",()=>a.onAfterSignup?.({provider:"magicLink",user:v,session:r,connectionInfo:o})):(T&&Le("onAfterEmailVerification",()=>a.onAfterEmailVerification?.({provider:"magicLink",user:v,session:r,connectionInfo:o})),Le("onAfterLogin",()=>a.onAfterLogin?.({provider:"magicLink",user:v,session:r,connectionInfo:o}))),n(),{user:G(v),session:{authToken:r.authToken}}}catch(d){if(d instanceof Error){let u=l$1?"onSignupError":"onLoginError",m=l$1?a.onSignupError:a.onLoginError;Le(u,()=>m?.({provider:"magicLink",error:d,session:r,connectionInfo:o}));}throw d}}async function An(e,t,r){let o=r&&t!==void 0,i=()=>s.findOneAndUpsert({emails:{$elemMatch:{address:e}}},{$setOnInsert:{handle:t,status:"active",emails:[{address:e,verified:true}],createdAt:new Date,authMethods:{}}},{upsert:o,collation:dt});try{let{doc:n,isNew:s}=await i();return {userDoc:n,isNew:s}}catch(n){if(!xe(n))throw n;let{doc:s}=await i();return {userDoc:s,isNew:false}}}async function Fr(e,t){let{session:r,connectionInfo:o,res:i,req:n}=t;if(!r)throw new Error("Session is not initialized");if(!mt())throw new Error("Magic link authentication is not enabled");let s=z.string().parse(n?.cookies?.[lt]),a=()=>{i?.clearCookie(lt,Dr);},c=await Hr(s);if(!c)throw a(),new Error("Invalid or expired code or magic link");if(c.expiresAt<new Date)throw await pt(c._id),a(),new Error("Code or magic link has expired");return Gr({tokenDoc:{_id:c._id,email:c.email},session:r,connectionInfo:o,res:i,clearCookie:a})}async function zr(e$1,t){let{session:r,connectionInfo:o,res:i}=t;if(!r)throw new Error("Session is not initialized");if(!mt())throw new Error("Magic link authentication is not enabled");let n=L(e$1.email),s=z.string().parse(e$1.code).replace(/[\s-]/g,""),a=o?.ip;a&&await k({bucket:"oneTimeCode",type:"ip",value:a}),await k({bucket:"oneTimeCode",type:"email",value:n});let c=await w$1.findOne({email:n,code:e(s),attempts:{$lt:Sn}});if(!c)throw await w$1.updateMany({email:n},{$inc:{attempts:1}}),new Error("Invalid or expired code or magic link");if(c.expiresAt<new Date)throw await pt(c._id),new Error("Code or magic link has expired");return Gr({tokenDoc:{_id:c._id,email:c.email},session:r,connectionInfo:o,res:i,clearCookie:()=>{}})}function Jr(){return new b$2("Invalid or expired sign-in code","INVALID_OAUTH_CODE")}async function Kr(e,{user:t,session:r,connectionInfo:o}){let i$1;try{if(!r)throw new Error("Session is not initialized");if(t)throw new c$1("User is already authenticated","ALREADY_AUTHENTICATED");let n=o?.ip;n&&await k({bucket:"oauthExchange",type:"ip",value:n});let s$1=z.string().min(1).parse(e.code),a=z.string().min(1).optional().parse(e.codeVerifier??void 0),c=await i(s$1,a);if(!c||!ObjectId.isValid(c.userId))throw Jr();i$1=c.provider;let l$1=await s.findOne({_id:new ObjectId(c.userId),status:{$nin:["deleted","disabled"]}});if(!l$1)throw Jr();return await l(r.authToken,l$1._id),w().onAfterLogin?.({provider:i$1,user:l$1,session:r,connectionInfo:o}),w().login?.onSuccess?.(l$1),{user:G(l$1),session:{authToken:r.authToken}}}catch(n){throw n instanceof Error&&i$1&&(w().onLoginError?.({provider:i$1,error:n,session:r,connectionInfo:o}),w().login?.onError?.(n)),n}}function Yr(e){return `${e.bucket}
23
- ${e.type}
24
- ${e.window}`}function Rn(){return [{bucket:"signup",type:"ip",window:a$1.minutes(15),limit:20},{bucket:"signup",type:"ip",window:a$1.days(1),limit:200},{bucket:"signupAttempt",type:"ip",window:a$1.minutes(15),limit:50},{bucket:"signupAttempt",type:"ip",window:a$1.days(1),limit:500},{bucket:"signin",type:"ip",window:a$1.minutes(15),limit:50},{bucket:"signin",type:"ip",window:a$1.days(1),limit:500},{bucket:"verification",type:"user",window:a$1.seconds(60),limit:1},{bucket:"verification",type:"user",window:a$1.days(1),limit:10},{bucket:"passwordReset",type:"ip",window:a$1.minutes(15),limit:10},{bucket:"passwordReset",type:"ip",window:a$1.days(1),limit:100},{bucket:"passwordReset",type:"email",window:a$1.hours(1),limit:5},{bucket:"passwordReset",type:"email",window:a$1.days(1),limit:10},{bucket:"magicLink",type:"ip",window:a$1.minutes(15),limit:10},{bucket:"magicLink",type:"ip",window:a$1.days(1),limit:100},{bucket:"magicLink",type:"email",window:a$1.hours(1),limit:5},{bucket:"magicLink",type:"email",window:a$1.days(1),limit:10},{bucket:"oneTimeCode",type:"ip",window:a$1.minutes(15),limit:20},{bucket:"oneTimeCode",type:"ip",window:a$1.days(1),limit:100},{bucket:"oneTimeCode",type:"email",window:a$1.hours(1),limit:10},{bucket:"oneTimeCode",type:"email",window:a$1.days(1),limit:20},{bucket:"oauthExchange",type:"ip",window:a$1.minutes(15),limit:30},{bucket:"oauthExchange",type:"ip",window:a$1.days(1),limit:300},{bucket:"updateProfile",type:"user",window:a$1.minutes(15),limit:30},{bucket:"updateProfile",type:"user",window:a$1.days(1),limit:200}]}function On(e){let t=[],r=["signup","signupAttempt","signin","verification","passwordReset","magicLink","oneTimeCode","oauthExchange","updateProfile"];for(let o of r){let i=e[o];if(i!==void 0)for(let n of i)t.push({bucket:o,...n});}return t}function ft(e={}){let t=Rn(),r=On(e),o=new Map;for(let s of r)o.set(Yr(s),s);let i=[],n=new Set;for(let s of t){let a=Yr(s),c=o.get(a);c!==void 0?(i.push(c),n.add(a)):i.push(s);}for(let[s,a]of o)n.has(s)||i.push(a);return i}var gt=new a("_system.user",{stores:[s,t,u,v,w$1],queries:{getOwnProfile:br},mutations:{signupWithPassword:Rr,loginWithPassword:wr,logout:yr,resendEmailVerification:Ar,sendResetPasswordToken:Pr,resetPassword:Mr,sendMagicLink:Br,loginWithMagicLink:Fr,loginWithOneTimeCode:zr,loginWithOAuth:Kr,updateProfile:vr,unlinkOAuthProvider:Cr},cronJobs:{updateDisposableEmailList:cr},rateLimits:ft(),configSchema:{"auth.email.enabled":{type:"boolean",isPublic:true,default:true},"auth.email.from":{type:"string",isPublic:false,default:""},"auth.email.verification":{type:"boolean",isPublic:true,default:true},"auth.google.enabled":{type:"boolean",isPublic:true,default:false},"auth.google.clientId":{type:"string",isPublic:false,default:""},"auth.google.clientSecret":{type:"secret",isPublic:false,default:""},"auth.github.enabled":{type:"boolean",isPublic:true,default:false},"auth.github.clientId":{type:"string",isPublic:false,default:""},"auth.github.clientSecret":{type:"secret",isPublic:false,default:""},"auth.mobile.redirectUrls":{type:"text",isPublic:false,default:""}},routes:[{path:"/api/_internal/auth/verify-email",handlers:{get:Sr}},{path:"/api/_internal/auth/reset-password",handlers:{get:Tr}},{path:"/api/_internal/auth/magic-link",handlers:{get:Vr}}]});var xn={withoutRemoteServer:{MONGODB_URI:"_system.mongodbUri",MONGODB_POOL_SIZE:"_system.mongodbPoolSize",MODELENCE_AUTH_GOOGLE_ENABLED:"_system.user.auth.google.enabled",MODELENCE_AUTH_GOOGLE_CLIENT_ID:"_system.user.auth.google.clientId",MODELENCE_AUTH_GOOGLE_CLIENT_SECRET:"_system.user.auth.google.clientSecret",MODELENCE_AUTH_GITHUB_ENABLED:"_system.user.auth.github.enabled",MODELENCE_AUTH_GITHUB_CLIENT_ID:"_system.user.auth.github.clientId",MODELENCE_AUTH_GITHUB_CLIENT_SECRET:"_system.user.auth.github.clientSecret",MODELENCE_AUTH_GITHUB_CLIENT_SCOPES:"_system.user.auth.github.scopes",MODELENCE_AUTH_MOBILE_REDIRECT_URLS:"_system.user.auth.mobile.redirectUrls",MODELENCE_EMAIL_RESEND_API_KEY:"_system.email.resend.apiKey",MODELENCE_EMAIL_AWS_SES_REGION:"_system.email.awsSes.region",MODELENCE_EMAIL_AWS_SES_ACCESS_KEY_ID:"_system.email.awsSes.accessKeyId",MODELENCE_EMAIL_AWS_SES_SECRET_ACCESS_KEY:"_system.email.awsSes.secretAccessKey",MODELENCE_EMAIL_SMTP_HOST:"_system.email.smtp.host",MODELENCE_EMAIL_SMTP_PORT:"_system.email.smtp.port",MODELENCE_EMAIL_SMTP_USER:"_system.email.smtp.user",MODELENCE_EMAIL_SMTP_PASS:"_system.email.smtp.pass",MODELENCE_SITE_URL:"_system.site.url",MODELENCE_ENV_TYPE:"_system.env.type",MODELENCE_MULTI_INSTANCE:"_system.multiInstance",MODELENCE_ENV:"_system.env",GOOGLE_AUTH_ENABLED:"_system.user.auth.google.enabled",GOOGLE_AUTH_CLIENT_ID:"_system.user.auth.google.clientId",GOOGLE_AUTH_CLIENT_SECRET:"_system.user.auth.google.clientSecret"},withRemoteServer:{MODELENCE_SITE_URL:"_system.site.url"}};function _n(e,t){if(t==="number"){let r=Number(e);if(isNaN(r))throw new Error(`Invalid number value for config: ${e}`);return r}if(t==="boolean"){if(e.toLowerCase()==="true")return true;if(e.toLowerCase()==="false")return false;throw new Error(`Invalid boolean value for config: ${e}`)}return e}function Ln(e,t){let r=[];for(let[o,i]of Object.entries(e)){let n=process.env[o],s=t[i];if(n){let a=s?.type??"string";r.push({key:i,type:a,value:_n(n,a)});}}return r}function ge(){let e=process.env.MODELENCE_PORT||process.env.PORT||3e3;return process.env.MODELENCE_SITE_URL||`http://localhost:${e}`}function Ue(e,t="withoutRemoteServer"){let r=xn[t],o=Ln(r,e);return t==="withRemoteServer"&&process.env.MODELENCE_RUNTIME==="local"&&!o.some(({key:i})=>i==="_system.site.url")&&o.push({key:"_system.site.url",type:"string",value:ge()}),o}var wt;function Tn(){try{let e=Z.join(Xr.homedir(),".modelence"),t=Z.join(e,"machine-id");try{let o=Te.readFileSync(t,"utf8").trim();if(o)return o}catch{}let r=randomUUID();return Te.mkdirSync(e,{recursive:!0}),Te.writeFileSync(t,r+`
25
- `),r}catch{return Xr.hostname()}}function Mn(){return wt||(wt=createHash("sha256").update(`${Tn()}
26
- ${process.cwd()}
27
- ${process.env.MODELENCE_ENVIRONMENT_ID||""}`).digest("hex").slice(0,32)),wt}function he(){let e=process.env.MODELENCE_RUNTIME;return e==="local"||e==="sandbox"}function Pe(){return process.env.MODELENCE_RUNTIME==="local"}function yt(){return he()?Mn():process.env.MODELENCE_CONTAINER_ID}async function Zr({configSchema:e,cronJobsMetadata:t,stores:r,roles:o}){let i=yt();if(!i)throw new Error("Unable to connect to Modelence Cloud: MODELENCE_CONTAINER_ID is not set");let n=Pe()&&process.env.MODELENCE_TAKEOVER==="1";try{let s=(r??[]).map(c=>({name:c.getName(),schema:c.getSerializedSchema(),collections:[c.getName()],version:2,indexes:c.getIndexes(),searchIndexes:c.getSearchIndexes(),indexCreationMode:c.getIndexCreationMode()})),a=await P("/api/connect","POST",{hostname:Xr.hostname(),runtime:process.env.MODELENCE_RUNTIME,containerId:i,...Pe()?{localSiteUrl:ge()}:{},...n?{force:!0}:{},dataModels:s,configSchema:e,cronJobsMetadata:t,roles:o});if(a.status==="error")throw Object.assign(new Error(`Unable to connect to Modelence Cloud: ${a.error}`),{responseBody:a});return console.log("Successfully connected to Modelence Cloud"),a}catch(s){let a=s;if(Pe()&&a?.status===409&&!n){let c=a.responseBody?.error??a.message;console.error(`${c}
28
- Re-run with the --takeover flag (e.g. npm run dev -- --takeover) to disconnect it and connect this instance instead.`),process.exit(1);}throw he()&&(a?.status??a?.responseBody)!==void 0&&(console.error(a.message),process.exit(1)),console.error("Unable to connect to Modelence Cloud:",s),s}}async function eo(){return P("/api/configs","GET")}async function to(){return await P("/api/sync","POST",{containerId:yt()})}async function P(e,t,r){let{MODELENCE_SERVICE_ENDPOINT:o,MODELENCE_SERVICE_TOKEN:i}=process.env;if(!o)throw new Error("Unable to connect to Modelence Cloud: MODELENCE_SERVICE_ENDPOINT is not set");let n=await fetch(`${o}${e}`,{method:t,headers:{Authorization:`Bearer ${i}`,...r?{"Content-Type":"application/json"}:{}},body:r?JSON.stringify(r):void 0});if(!n.ok){let s=await n.text(),a,c=s;try{a=JSON.parse(s);let d=a.error;if(typeof d=="string")c=d;else if(d&&typeof d=="object"){let u=d.message;typeof u=="string"&&(c=u);}}catch{}let l=new Error(`Unable to connect to Modelence Cloud: HTTP status: ${n.status}, ${c}`);throw a!==void 0&&(l.responseBody=a),l.status=n.status,l}if(!(n.status===204||n.headers?.get("content-length")==="0"))return await n.json()}var Et=false,Nn=a$1.seconds(10);function ro(){setInterval(async()=>{if(!Et){Et=true;try{let e=await to();e?.status==="detached"&&he()&&(console.error(`Detached from Modelence Cloud: ${e.message??"this environment is now connected from another instance."}`),process.exit(1));}catch(e){console.error("Error syncing status",e);}try{await Dn();}catch(e){console.error("Error syncing config",e);}Et=false;}},Nn);}function bt(e){c$2(e),c$2(Ue(d$2(),"withRemoteServer"));}async function Dn(){let{configs:e}=await eo();bt(e);}var K=new c("_modelenceLocks",{schema:{_id:d.string(),instanceId:d.string(),acquiredAt:d.date(),resource:d.string()},indexes:[{key:{resource:1},unique:true},{key:{resource:1,instanceId:1}},{key:{resource:1,acquiredAt:1}}],indexCreationMode:"blocking"});var Y={},oo=a$1.seconds(10),ao=randomBytes(32).toString("base64url"),Hn=a$1.seconds(30),ne=new Map,vt=e=>e instanceof MongoError&&e.code===11e3,no=(e,t)=>typeof e.keyPattern=="object"&&e.keyPattern!==null&&Object.prototype.hasOwnProperty.call(e.keyPattern,t),Bn=async({error:e,resource:t})=>{if(no(e,"resource"))return true;if(no(e,"_id"))return false;let r=await K.findOne({resource:t});return !!r&&r._id!==t},io=async({resource:e,staleThresholdDate:t,instanceId:r})=>{let o=await K.upsertOne({_id:e,$or:[{instanceId:r},{acquiredAt:{$lt:t}}]},{$set:{resource:e,instanceId:r,acquiredAt:new Date},$setOnInsert:{_id:e}});return o.upsertedCount>0||o.modifiedCount>0},co=async({resource:e,instanceId:t,staleThresholdDate:r})=>{let o=r?{resource:e,_id:{$ne:e},$or:[{instanceId:t},{acquiredAt:{$lt:r}}]}:{resource:e,instanceId:t};return (await K.deleteOne(o)).deletedCount>0},Vn=e=>{let t=e,r=ne.get(t);r&&(r.stopRequested=true,r.timer&&(clearTimeout(r.timer),r.timer=null),ne.delete(t));},so=({resource:e,lockDuration:t,instanceId:r})=>{let o=Math.floor(t/3),i=e,n=ne.get(i);if(n&&!n.stopRequested&&n.heartbeatInterval===o&&n.lockDuration===t)return;n&&(n.stopRequested=true,n.timer&&(clearTimeout(n.timer),n.timer=null),ne.delete(i));let s={timer:null,stopRequested:false,lockDuration:t,heartbeatInterval:o},a=()=>{s.timer=setTimeout(()=>{X(e,{lockDuration:t,bypassCache:true,instanceId:r}).then(c=>{c||(s.stopRequested=true,k$1(`Lost lock while refreshing heartbeat: ${e}`,{source:"lock",resource:e,instanceId:r}));}).finally(()=>{if(s.stopRequested){ne.delete(i);return}a();});},o);};ne.set(i,s),a();};async function X(e,{lockDuration:t=Hn,successfulLockCacheDuration:r=oo,failedLockCacheDuration:o=oo,heartbeat:i,bypassCache:n,instanceId:s=ao}={}){let a=Date.now();if(!n&&Y[e]&&a<Y[e].expiresAt)return Y[e].value&&i&&so({resource:e,lockDuration:t,instanceId:s}),Y[e].value;let c=new Date(a-t);k$1(`Attempting to acquire lock: ${e}`,{source:"lock",resource:e,instanceId:s});try{let l=await Gn({resource:e,staleThresholdDate:c,instanceId:s});return Y[e]={value:l,expiresAt:a+(l?r:o)},l?(i&&so({resource:e,lockDuration:t,instanceId:s}),k$1(`Lock acquired: ${e}`,{source:"lock",resource:e,instanceId:s})):k$1(`Failed to acquire lock (already held): ${e}`,{source:"lock",resource:e,instanceId:s}),l}catch{return Y[e]={value:false,expiresAt:a+o},k$1(`Failed to acquire lock (already held): ${e}`,{source:"lock",resource:e,instanceId:s}),false}}var Gn=async({resource:e,staleThresholdDate:t,instanceId:r})=>{try{return await io({resource:e,staleThresholdDate:t,instanceId:r})}catch(o){if(vt(o)&&await Bn({error:o,resource:e})){if(!await co({resource:e,staleThresholdDate:t,instanceId:r}))return false;try{return await io({resource:e,staleThresholdDate:t,instanceId:r})}catch(n){if(vt(n))return false;throw n}}if(vt(o))return false;throw o}};async function we(e,{instanceId:t=ao}={}){Vn(e);try{let r=await K.deleteOne({_id:e,instanceId:t});return r.deletedCount===0?await co({resource:e,instanceId:t}):r.deletedCount>0}catch{return false}finally{delete Y[e];}}var Q={},Ct=null,kt=new c("_modelenceCronJobs",{schema:{alias:d.string(),lastStartDate:d.date().optional()},indexes:[{key:{alias:1},unique:true,background:true}]});function uo(e,{description:t="",interval:r,timeout:o=Math.min(Math.max(r,a$1.minutes(1)),a$1.days(1)),handler:i}){if(Q[e])throw new Error(`Duplicate cron job declaration: '${e}' already exists`);if(Ct)throw new Error(`Unable to add a cron job - cron jobs have already been initialized: [${e}]`);if(r<a$1.seconds(5))throw new Error(`Cron job interval should not be less than 5 second [${e}]`);if(o>a$1.days(1))throw new Error(`Cron job timeout should not be longer than 1 day [${e}]`);Q[e]={alias:e,params:{description:t,interval:r,timeout:o},handler:i,state:{isRunning:false}};}async function mo(){if(Ct)throw new Error("Cron jobs already started");let e=Object.keys(Q);if(e.length>0){if(!N()){console.log("MongoDB URI is not configured. Skipping cron jobs.");return}let t={alias:{$in:e}},r=await kt.fetch(t),o=Date.now();r.forEach(i=>{let n=Q[i.alias];n&&(n.state.scheduledRunTs=i.lastStartDate?i.lastStartDate.getTime()+n.params.interval:o);}),Object.values(Q).forEach(i=>{i.state.scheduledRunTs||(i.state.scheduledRunTs=o);}),Ct=setInterval(Fn,a$1.seconds(1));}}async function Fn(){let e=Date.now();await X("cron",{successfulLockCacheDuration:a$1.seconds(10),failedLockCacheDuration:a$1.seconds(30)})&&Object.values(Q).forEach(async r=>{let{alias:o,params:i,state:n}=r;if(n.isRunning){if(n.startTs&&n.startTs+i.timeout<e){let s=new Error(`Cron job '${o}' timed out after ${i.timeout}ms`);o$1(s),n.isRunning=false;}return}n.scheduledRunTs&&n.scheduledRunTs<=e&&await zn(r);});}async function zn(e){let{alias:t,params:r,handler:o,state:i}=e;i.isRunning=true,i.startTs=Date.now();let n=n$1("cron",`cron:${t}`);try{await kt.upsertOne({alias:t},{$set:{lastStartDate:new Date(i.startTs)},$setOnInsert:{alias:t}}),await o(),lo(i,r),n.end("success");}catch(s){lo(i,r);let a=s instanceof Error?s:new Error(String(s));o$1(a),n.end("error"),console.error(`Error in cron job '${t}':`,s);}}function lo(e,t){e.scheduledRunTs=e.startTs?e.startTs+t.interval:Date.now(),e.startTs=void 0,e.isRunning=false;}function po(){return Object.values(Q).map(({alias:e,params:t})=>({alias:e,description:t.description,interval:t.interval,timeout:t.timeout}))}var fo=new a("_system.cron",{stores:[kt]});function go(e){let t=[...new Set(e)],r=new Map;for(let n of t){let s=n.getChainRoot();r.set(s,s.getChainTail());}let o=[...new Set(r.values())],i=new Map;for(let[n,s]of r){let a=s.getName(),c=i.get(a);if(c!==void 0&&c!==n)throw new Error(`Store collision: multiple unrelated stores use collection name '${a}'. Use .extend() to create a single extension chain instead of independent stores.`);i.set(a,n);}return {storesToInit:t,effectiveStores:o}}var St=new a("_system.lock",{stores:[K]});var ye=new c("_modelenceMigrations",{schema:{version:d.number(),status:d.enum(["completed","failed"]),description:d.string().optional(),output:d.string().optional(),appliedAt:d.date()},indexes:[{key:{version:1},unique:true},{key:{version:1,status:1}}]});async function At(e,{lockMode:t="acquire"}={}){if(e.length!==0){if(t==="acquire"&&!await X("migrations")){l$1("Another instance is running migrations. Skipping migration run.",{source:"migrations"});return}try{let r=e.map(({version:s})=>s),o=await ye.fetch({version:{$in:r}}),i=new Set(o.map(({version:s})=>s)),n=e.filter(({version:s})=>!i.has(s));if(n.length===0)return;l$1(`Running migrations (${n.length})...`,{source:"migrations"});for(let{version:s,description:a,handler:c}of n){l$1(`Running migration v${s}: ${a}`,{source:"migrations"});try{let d=(await c()||"").toString().trim(),u=15*1024*1024,m=d.length>u?d.slice(0,u)+`
29
- [Output truncated - exceeded size limit]`:d;await ye.upsertOne({version:s},{$set:{version:s,status:"completed",description:a,output:m,appliedAt:new Date}}),l$1(`Migration v${s} complete`,{source:"migrations"});}catch(l){l instanceof Error&&(await ye.upsertOne({version:s},{$set:{version:s,status:"failed",description:a,output:l.message||"",appliedAt:new Date}}),l$1(`Migration v${s} is failed: ${l.message}`,{source:"migrations"}));}}}finally{t==="acquire"&&await we("migrations");}}}function ho(e){setTimeout(()=>{At(e).catch(t=>{console.error("Error running migrations:",t);});},0);}var wo=new a("_system.migration",{stores:[ye]});var yo=new a("_system.rateLimit",{stores:[fe]});async function qn({filePath:e,contentType:t,visibility:r}){return await P("/api/files/upload","POST",{filePath:e,contentType:t,visibility:r})}async function Wn(e){await P("/api/files/delete","POST",{filePath:e});}async function Jn(e){return await P("/api/files/download","POST",{filePath:e})}async function Kn(e){return await P("/api/files/url","POST",{filePath:e})}var Eo=new a("_system.files",{});function Rt(e){return e.replace(/[<>&\u2028\u2029]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}var Ee="./.modelence/build/client".replace(/\\/g,"/"),vo="./.modelence/build/ssr".replace(/\\/g,"/"),Co="/index.tsx",ti=/(<div\b[^>]*\bid\s*=\s*["']root["'][^>]*>)\s*<\/div>/i,ri="</head>",Ot=class{constructor(){this.ssrEnabled=false;this.ssrTransportInstalled=false;this.prodEntryLoaded=false;}enableSsr(){this.ssrEnabled=true;}async init({httpServer:t}){if(this.config=await li(this.isDev()?t:void 0,{ssr:this.ssrEnabled}),this.isDev())console.log("Starting Vite dev server..."),this.viteServer=await createServer(this.config);else if(this.ssrEnabled){let r=Z.resolve(process.cwd(),vo,"index.mjs");if(!Te.existsSync(r))throw new Error(`Modelence: SSR is enabled (startApp({ ssr: true })) but the SSR bundle is missing at ${r}.
30
-
31
- This usually means \`postBuildCommand\` is set in modelence.config.ts, which replaces the default Vite client build (and the SSR build along with it). Either:
32
- \u2022 remove \`postBuildCommand\` so Modelence builds the SSR bundle, or
33
- \u2022 remove \`ssr: true\` from startApp() if your custom toolchain handles SSR itself.`)}if(this.ssrEnabled&&!this.ssrTransportInstalled){let{installSsrCallMethodTransport:r}=await import('./transport-QMFZ67HR.js');r(),this.ssrTransportInstalled=true;}}middlewares(){if(this.isDev())return this.viteServer?.middlewares??[];let t=this.ssrEnabled?{index:false}:void 0,r=[z$1.static(Ee,t)];return this.config?.publicDir&&r.push(z$1.static(this.config.publicDir,t)),r}async handler(t,r){if(this.ssrEnabled&&oi(t)){if(t.method==="HEAD"){r.setHeader("Content-Type","text/html; charset=utf-8"),r.setHeader("Cache-Control","no-store"),r.status(200).end();return}try{await this.handleSsr(t,r);}catch(o){if(this.isDev()&&this.viteServer&&o instanceof Error&&this.viteServer.ssrFixStacktrace(o),console.error("SSR render error:",{url:t.originalUrl,method:t.method,userAgent:t.get("user-agent"),error:o}),r.headersSent){r.end();return}this.serveStaticShell(r);}return}if(this.ssrEnabled){r.status(404).end();return}this.serveStaticShell(r);}async handleSsr(t,r){let o=await this.getTemplate(t.originalUrl),i=await this.captureSsrSnapshot();if(!i)throw new Error("Modelence SSR is enabled but no SSR snapshot was captured. Make sure 'src/client/index.tsx' calls renderApp(...) from 'modelence/client'.");let[{renderSsrTreeStream:n},{getCallContext:s},a]=await Promise.all([import('./render-GH7MWT27.js'),import('./server-BCQLVBIZ.js'),import('./collectCss-4YETFI7P.js')]),c=await s(t,r),l=this.collectCssAssets(a);si(r,a.buildEarlyHintsLink(l));let{sessionState:d,pipe:u,getQueryState:m}=await n({callContext:c,loadingElement:i.loadingElement,routesElement:i.routesElement,router:i.router,location:t.originalUrl}),{prelude:g,rootOpenTag:y,epilogue:v}=ni(o),x=ii(g,a.renderStylesheetLinks(l));r.setHeader("Content-Type","text/html; charset=utf-8"),r.setHeader("Cache-Control","no-store"),r.status(200),r.write(x),r.write(`<script id="__MODELENCE_STATE__" type="application/json">${Rt(d)}</script>`),r.write(y),await u(r),r.write("</div>"),r.write(`<script id="__MODELENCE_QUERY_STATE__" type="application/json">${Rt(m())}</script>`),r.end(v);}collectCssAssets(t){return this.isDev()?this.viteServer?t.collectDevCssAssets(this.viteServer,Co):{hrefs:[],source:"dev"}:(this.prodCssAssetsCache||(this.prodCssAssetsCache=t.loadProdCssAssets(Ee)),this.prodCssAssetsCache)}async getTemplate(t){if(this.isDev()){let r=Z.resolve(process.cwd(),"src/client/index.html"),o=Te.readFileSync(r,"utf-8");return this.viteServer&&(o=await this.viteServer.transformIndexHtml(t,o)),o}if(!this.prodTemplateCache){let r=Z.resolve(process.cwd(),Ee,"index.html");this.prodTemplateCache=Te.readFileSync(r,"utf-8");}return this.prodTemplateCache}async captureSsrSnapshot(){let{_getSsrSnapshot:t}=await import('./renderApp-5P62J4NO.js');if(this.isDev()){if(!this.viteServer)throw new Error("Vite dev server not initialized");return await this.viteServer.ssrLoadModule(Co),t()}return this.prodEntryLoaded||(await import(Z.resolve(process.cwd(),vo,"index.mjs")),this.prodEntryLoaded=true),t()}serveStaticShell(t){if(this.isDev())try{t.setHeader("Cache-Control","no-store"),t.sendFile("index.html",{root:"./src/client"});}catch(r){console.error("Error serving index.html:",r),t.status(500).send("Internal Server Error");}else t.sendFile("index.html",{root:Ee});}isDev(){return process.env.NODE_ENV!=="production"}};function oi(e){if(e.method!=="GET"&&e.method!=="HEAD")return false;let t=e.get("accept")??"";if(t&&!t.includes("text/html")&&!t.includes("*/*"))return false;let r=(e.path??e.url??"").split("?")[0];if(r.startsWith("/api/"))return false;let o=r.split("/").pop()??"",i=o.lastIndexOf(".");if(i>0){let n=o.slice(i).toLowerCase();if(n!==".html"&&n!==".htm")return false}return true}function ni(e){let t=e.match(ti);if(!t||t.index===void 0)throw new Error('SSR template is missing the expected `<div id="root"></div>` placeholder.');let r=e.slice(0,t.index),o=e.slice(t.index+t[0].length);return {prelude:r,rootOpenTag:t[1],epilogue:o}}function ii(e,t){if(!t)return e;let r=e.lastIndexOf(ri);return r===-1?t+e:e.slice(0,r)+t+e.slice(r)}function si(e,t){if(t.length===0)return;let r=e;if(typeof r.writeEarlyHints=="function")try{r.writeEarlyHints({link:t});}catch(o){process.env.NODE_ENV!=="production"&&console.warn("Modelence SSR: writeEarlyHints failed",o);}}async function ai(){let e=process.cwd();try{return (await loadConfigFromFile({command:"serve",mode:"development"},void 0,e))?.config||{}}catch(t){return console.warn("Could not load vite config:",t),{}}}function ci(e,t){let r=mergeConfig(e,t);if(r.plugins&&Array.isArray(r.plugins)){let o=new Set;r.plugins=r.plugins.flat().filter(i=>{if(!i||typeof i!="object"||Array.isArray(i))return true;let n=i.name;return !n||o.has(n)?false:(o.add(n),true)}).reverse(),r.plugins.reverse();}return r}async function li(e,t={}){let r=process.cwd(),o=await ai(),i=[".eslintrc.js",".eslintrc.json",".eslintrc","eslint.config.js",".eslintrc.yml",".eslintrc.yaml"].find(a=>Te.existsSync(Z.join(r,a))),n=[ei(),di()];if(i){let a=(await import('vite-plugin-eslint')).default;n.push(a({failOnError:false,include:["src/**/*.js","src/**/*.jsx","src/**/*.ts","src/**/*.tsx"],cwd:r,overrideConfigFile:Z.resolve(r,i)}));}let s=defineConfig({plugins:n,build:{outDir:Ee,emptyOutDir:true},server:{middlewareMode:true,hmr:e?{server:e}:void 0},appType:t.ssr?"custom":"spa",root:"./src/client",resolve:{alias:{"@":Z.resolve(r,"src").replace(/\\/g,"/")}}});return ci(s,o)}function di(){return {name:"modelence-asset-handler",async transform(e,t){if(/\.(png|jpe?g|gif|svg|mpwebm|ogg|mp3|wav|flac|aac)$/.test(t))return process.env.NODE_ENV==="development",e}}}var Me=new Ot;function ui(e){return e?e.match(/^\s*"?([^"<]+?)"?\s*<[^>]+>\s*$/)?.[1]?.trim():void 0}function mi(e){if(e)return Array.isArray(e)?e:[e]}function pi(e){return {to:Array.isArray(e.to)?e.to:[e.to],subject:e.subject,html:e.html,text:e.text,fromName:ui(e.from),replyTo:mi(e.replyTo)}}function fi(e){if(!(e instanceof Error))return new Error("Managed email send failed");let t=e.responseBody,r=t?.error?.code,o=t?.error?.message;return r&&o?new Error(`Managed email rejected (${r}): ${o}`):e}var ko={async sendEmail(e){if(e.cc||e.bcc||e.attachments||e.headers)throw new Error("Modelence managed email does not support cc, bcc, attachments, or custom headers in v1. Configure your own provider (Resend, SES, SMTP) to use these features. See https://docs.modelence.com/email/managed.");try{await P("/api/email/send","POST",pi(e));}catch(t){throw fi(t)}}};var xt=Object.freeze({});function So(e){xt=Object.freeze(Object.assign({},xt,e));}function Ie(){return xt}var _t=Object.freeze({});function Ao(e){_t=Object.freeze(Object.assign({},_t,e));}function Ne(){return _t}async function yi({modules:e=[],roles:t={},defaultRoles:r$1={},server:o=Me,migrations:i=[],email:n={},auth:s={},security:a={},websocket:c={},ssr:l=false}){l&&o===Me&&Me.enableSsr(),Ro.config(),Ro.config({path:".modelence.env"});let d=!!process.env.MODELENCE_SERVICE_ENDPOINT;_i().then(()=>{}).catch(()=>{});for(let ee of e)if(ee.name.toLowerCase().startsWith("_system."))throw new Error(`Invalid module name: '${ee.name}'
34
-
35
- The '_system.' prefix is reserved for internal use and cannot be used in user-defined modules.
36
-
37
- Rename your module to something that does not start with '_system.'`);let u=[gt,r,fo,wo,yo,ue,St,Eo],m=[...u,...e];f(),bi(u),Ei(e),x(t,r$1);let g$1=Ri(m);e$1(g$1);let y=vi(m),v=Ci(m);Oi(m),gt.rateLimits=ft(s.rateLimits);let x$1=ki(m);Er(x$1);let{storesToInit:T,effectiveStores:S}=go(y);if(d){let{configs:ee,environmentId:Vo,appAlias:Go,environmentAlias:Fo,telemetry:zo}=await Zr({configSchema:g$1,cronJobsMetadata:po(),stores:S,roles:t});bt(ee),g({environmentId:Vo,appAlias:Go,environmentAlias:Fo,telemetry:zo});}else c$2(Ue(g$1));if(d&&!n.provider?et({...n,provider:ko}):et(n),gr(s),So(a),Ao({...c,provider:c.provider||ar}),N()){await ir();let ee=[...new Set([...T,...S])];xi(ee),await Ai(S,i);}else ho(i);d&&(await i$1(),ro()),mo().catch(console.error),await Oo(o,{combinedModules:m,channels:v});}function Ei(e){for(let t of e){for(let[r,o]of Object.entries(t.queries))C(`${t.name}.${r}`,o);for(let[r,o]of Object.entries(t.mutations))D(`${t.name}.${r}`,o);}}function bi(e){for(let t of e){for(let[r,o]of Object.entries(t.queries))E$1(`${t.name}.${r}`,o);for(let[r,o]of Object.entries(t.mutations))F(`${t.name}.${r}`,o);}}function vi(e){return e.flatMap(t=>t.stores)}function Ci(e){return e.flatMap(t=>t.channels)}function ki(e){return e.flatMap(t=>t.rateLimits)}function Si(e,t){console.warn(`Failed to create indexes for store '${e}'. Continuing startup.`,t);}var Lt="migrations";async function Ai(e,t){if(!await X(Lt,{lockDuration:a$1.seconds(30),heartbeat:true}))return;let o,i;try{o=e.filter(a=>a.getIndexCreationMode()==="blocking"),i=e.filter(a=>a.getIndexCreationMode()==="background");for(let a of o)await Ut(a,"full");for(let a of i)await Ut(a,"drop-only");}catch(a){throw await we(Lt),a}let n=(async()=>{for(let a of i)await Ut(a,"create-only");})(),s=At(t,{lockMode:"skip"});Promise.allSettled([n,s]).then(([a,c])=>{a.status==="rejected"&&console.error("Error creating background indexes:",a.reason),c.status==="rejected"&&console.error("Error running migrations:",c.reason);}).finally(async()=>{await we(Lt);});}async function Ut(e,t="full"){let r=e.getName();try{await e.createIndexes(t);}catch(o){Si(r,o);}}function Ri(e){let t={};for(let r of e)for(let[o,i]of Object.entries(r.configSchema)){let n=`${r.name}.${o}`;if(n in t)throw new Error(`Duplicate config schema key: ${n} (${r.name})`);t[n]=i;}return t}function Oi(e){for(let t of e)for(let[r,o]of Object.entries(t.cronJobs))uo(`${t.name}.${r}`,o);}function xi(e){let t=Se();if(!t)throw new Error("Failed to initialize stores: MongoDB client not initialized");for(let r of e)r.init(t);}async function _i(){if(process.env.MODELENCE_TRACKING_ENABLED!=="false"){let t=process.env.MODELENCE_SERVICE_ENDPOINT??"https://cloud.modelence.com",r=process.env.MODELENCE_ENVIRONMENT_ID,o=await Li(),i=await import('./package-4RCR3AFN.js');await fetch(`${t}/api/track/app-start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectName:o.name,version:i.default.version,localHostname:Xr.hostname(),environmentId:r})});}}async function Li(){try{let e=Z.join(process.cwd(),"package.json"),t=await gi.readFile(e,"utf-8");return {name:JSON.parse(t).name||"unknown"}}catch{return {name:"unknown"}}}async function xo(e){await u.deleteMany({userId:e}),await v.deleteMany({userId:e});let t=await s.findOne({_id:e}),r=Array.from(new Set((t?.emails??[]).map(o=>o.address?.toLowerCase()).filter(o=>!!o)));r.length>0&&await w$1.deleteMany({email:{$in:r}});}async function Pi(e){await xo(e),await s.updateOne(e,{$set:{status:"disabled",disabledAt:new Date}});}async function Ti(e){await xo(e),await s.updateOne({_id:e},{$set:{handle:`deleted-${e}-${randomUUID()}`,status:"deleted",deletedAt:new Date,authMethods:{},emails:[]}});}var Pt=class{constructor(t,r){this.category=t,this.canAccessChannel=r||null;}broadcast(t,r){let o=Ne().provider;if(!o){m$1("Websockets provider should be added to startApp",{});return}o.broadcast({category:this.category,id:t,data:r});}};function Mi(e){if(!b().provider)throw new Error("Email provider is not configured, see https://docs.modelence.com/email for more details.");return b().provider?.sendEmail(e)}var _o=new Set(["javascript:","data:","file:","vbscript:","blob:"]);function Lo(e){try{return new URL(e)}catch{return null}}function De(){let e=String(a$2("_system.user.auth.mobile.redirectUrls")??"").split(/[\n,]/).map(r=>r.trim()).filter(Boolean),t=(w().mobile?.redirectUrls??[]).map(r=>String(r).trim()).filter(Boolean);return [...new Set([...e,...t])]}function Tt(e){if(!e||typeof e!="string")return false;let t=Lo(e);return !t||_o.has(t.protocol.toLowerCase())||Uo(t)?false:De().some(r=>{let o=Lo(r);return !o||_o.has(o.protocol.toLowerCase())||Uo(o)?false:t.protocol.toLowerCase()===o.protocol.toLowerCase()&&t.host.toLowerCase()===o.host.toLowerCase()&&Po(t.pathname)===Po(o.pathname)})}function Uo(e){return e.username!==""||e.password!==""}function Po(e){let t=e.replace(/\/+$/,"");return t===""?"/":t}var Ii=["code","error","errorCode","linked"];function $e(e,t){let r=new URL(e);for(let o of Ii)r.searchParams.delete(o);for(let[o,i]of Object.entries(t))r.searchParams.set(o,i);return r.toString()}function je(e){return e.platform==="mobile"&&e.redirectUri?{platform:"mobile",redirectUri:e.redirectUri,...e.codeChallenge?{codeChallenge:e.codeChallenge}:{}}:{platform:"web"}}function $i(e){return e.platform==="mobile"&&typeof e.codeChallenge=="string"}function Mo(e){return e.platform!=="mobile"}async function ji(e){return !e||typeof e!="string"?null:g$1(e)}function E(e,t,r,o={platform:"web"},i="oauth_failed"){if(o.platform==="mobile")return e.set("Referrer-Policy","no-referrer"),e.redirect($e(o.redirectUri,{error:r,errorCode:i}));let n=w(),s=e.status(t);if(n.errorComponent)try{let a=n.errorComponent({error:r,statusCode:t});if(a)return s.send(a)}catch(a){console.error("Unhandled error in authConfig.errorComponent:",a);}return s.json({error:r})}async function Mt(e,t,r,o$1={platform:"web"}){if(o$1.platform==="mobile"){if(!$i(o$1)){E(e,400,"This sign-in could not be completed. Please try signing in again.",o$1,"invalid_state");return}let n=await h(t.toString(),r,o$1.codeChallenge);e.set("Referrer-Policy","no-referrer"),e.redirect($e(o$1.redirectUri,{code:n}));return}let{authToken:i}=await o(t);p(e,i),e.redirect("/");}async function Hi(e,t,r,o,i,n){let s$1=w();try{if(r.status==="disabled"||r.status==="deleted"){E(e,400,"User account is not active.",n);return}let a={};r.firstName===void 0&&t.firstName&&(a.firstName=t.firstName),r.lastName===void 0&&t.lastName&&(a.lastName=t.lastName),r.avatarUrl===void 0&&t.avatarUrl&&(a.avatarUrl=t.avatarUrl);let c=r;Object.keys(a).length>0&&(await s.updateOne({_id:r._id},{$set:a}),c={...r,...a}),await Mt(e,r._id,t.providerName,n),Mo(n)&&(s$1.onAfterLogin?.({provider:t.providerName,user:c,session:o,connectionInfo:i}),s$1.login?.onSuccess?.(c));}catch(a){throw a instanceof Error&&(s$1.login?.onError?.(a),s$1.onLoginError?.({provider:t.providerName,error:a,session:o,connectionInfo:i})),a}}async function Bi(e,t,r,o,i,n){let s$1=w();if((s$1.oauthAccountLinking??"manual")==="auto"&&t.emailVerified){if(r.status==="disabled"||r.status==="deleted"){E(e,400,"User account is not active.",n);return}if(!r.emails?.find(l=>l.address.toLowerCase()===t.email.toLowerCase())?.verified){E(e,400,"User with this email already exists. Please log in instead.",n);return}try{let l={...r.firstName===void 0&&t.firstName&&{firstName:t.firstName},...r.lastName===void 0&&t.lastName&&{lastName:t.lastName},...r.avatarUrl===void 0&&t.avatarUrl&&{avatarUrl:t.avatarUrl}};if(!((await s.updateOne({_id:r._id,status:{$nin:["deleted","disabled"]},$or:[{[`authMethods.${t.providerName}.id`]:{$exists:!1}},{[`authMethods.${t.providerName}.id`]:t.id}]},{$set:{[`authMethods.${t.providerName}.id`]:t.id,...l}})).matchedCount>0)){E(e,400,"User with this email already exists. Please log in instead.",n);return}await Mt(e,r._id,t.providerName,n);let m={...r,...l,authMethods:{...r.authMethods,[t.providerName]:{id:t.id}}};Mo(n)&&(s$1.onAfterLogin?.({provider:t.providerName,user:m,session:o,connectionInfo:i}),s$1.login?.onSuccess?.(m));return}catch(l){throw l instanceof Error&&(s$1.login?.onError?.(l),s$1.onLoginError?.({provider:t.providerName,error:l,session:o,connectionInfo:i})),l}}E(e,400,"User with this email already exists. Please log in instead.",n);}async function Vi(e,t,r,o,i){let n=w();try{let s$1;if(n.generateHandle){let d=await n.generateHandle({email:t.email,firstName:t.firstName,lastName:t.lastName});s$1=await U(d,t.email,{throwOnConflict:!1});}else s$1=await U(void 0,t.email);let a={handle:s$1,status:"active",emails:[{address:t.email,verified:t.emailVerified}],createdAt:new Date,authMethods:{[t.providerName]:{id:t.id}},...t.firstName!==void 0&&{firstName:t.firstName},...t.lastName!==void 0&&{lastName:t.lastName},...t.avatarUrl!==void 0&&{avatarUrl:t.avatarUrl}},c=await s.insertOne(a);await Mt(e,c.insertedId,t.providerName,i);let l=await s.findOne({_id:c.insertedId},{readPreference:"primary"});l&&(n.onAfterSignup?.({provider:t.providerName,user:l,session:r,connectionInfo:o}),n.signup?.onSuccess?.(l));}catch(s){throw s instanceof Error&&(n.onSignupError?.({provider:t.providerName,error:s,session:r,connectionInfo:o}),n.signup?.onError?.(s)),s}}function se(e){return `${a$2("_system.site.url")}/api/_internal/auth/${e}/callback`}async function He(e,t,r,o={platform:"web"}){let i=await s.findOne({[`authMethods.${r.providerName}.id`]:r.id}),{session:n,connectionInfo:s$1}=await ae(e,t);if(i)return Hi(t,r,i,n,s$1,o);if(!r.email){E(t,400,`Email address is required for ${r.providerName} authentication.`,o);return}let a;try{a=await s.findOne({"emails.address":r.email,status:{$ne:"deleted"}},{collation:{locale:"en",strength:2}});}catch(c){if(c instanceof Error){let l=w();l.onSignupError?.({provider:r.providerName,error:c,session:n,connectionInfo:s$1}),l.signup?.onError?.(c);}throw c}return a?Bi(t,r,a,n,s$1,o):Vi(t,r,n,s$1,o)}function O(e){e.cookie("oauthLinkToken","",{httpOnly:true,maxAge:0,path:"/api/_internal/auth/",sameSite:"lax",secure:process.env.NODE_ENV==="production"});}function ie(e){if(e)try{e();}catch(t){console.error("Error executing OAuth hook:",t);}}function Gi(e){let{state:t,mode:r,linkedUserId:o,platform:i="web",redirectUri:n,codeChallenge:s}=e,a=[t,r,o??""];return (i!=="web"||n)&&(a.push(i),a.push(n?Buffer.from(n,"utf8").toString("base64url"):""),a.push(s??"")),a.join(":")}function Io(e){let[t,r,o,i,n,s]=(e||"").split(":"),a=n&&Buffer.from(n,"base64url").toString("utf8")||void 0;return {stateValue:t,mode:r||"login",...o?{linkedUserId:o}:{},platform:i==="mobile"?"mobile":"web",...a?{redirectUri:a}:{},...s?{codeChallenge:s}:{}}}function Be(e,t,r){let o=e.query.state,i=e.cookies[r],n=Io(i||""),s=No(n);return !o||!i||o!==n.stateValue?(E(t,400,"Invalid OAuth state - possible CSRF attack",s??{platform:"web"},"invalid_state"),null):(t.clearCookie(r),n.platform==="mobile"&&!s?(E(t,400,"Invalid OAuth redirect target.",{platform:"web"},"invalid_redirect"),null):{mode:n.mode,...n.linkedUserId?{linkedUserId:n.linkedUserId}:{},platform:n.platform,...n.redirectUri?{redirectUri:n.redirectUri}:{},...n.codeChallenge?{codeChallenge:n.codeChallenge}:{}})}function No(e){return e.platform!=="mobile"||!e.redirectUri||!Tt(e.redirectUri)?null:{platform:"mobile",redirectUri:e.redirectUri}}function Ve(e,t){return No(Io(e.cookies?.[t]||""))}function Fi(e){if(De().length===0)return "Mobile sign-in is not configured: auth.mobile.redirectUrls is empty. Add your app's deep link (e.g. 'myapp://auth') to enable it.";let r;try{r=new URL(e).protocol.toLowerCase();}catch{return "This redirectUri is not a valid URL. Pass your app's deep link, e.g. 'myapp://auth', and list it in auth.mobile.redirectUrls."}return r==="http:"||r==="https:"||r==="exp:"?"This redirectUri is not in auth.mobile.redirectUrls. Add it verbatim. Note that Linking.createURL returns a different value per build target \u2014 your app's scheme in a native build, an https URL on Expo Web, an exp:// URL in Expo Go \u2014 so each one you test needs its own entry.":"This redirectUri is not in auth.mobile.redirectUrls. Add it verbatim \u2014 entries match on scheme, host and path, so a differing path or port is a different target."}function zi(e,t,r){if(e.query.platform!=="mobile")return {ok:true,redirectUri:null,codeChallenge:null};let o=typeof e.query.redirectUri=="string"?e.query.redirectUri:"";if(!o)return E(t,400,"A redirectUri is required for mobile authentication."),{ok:false};if(!Tt(o))return console.error(`[modelence] Rejected mobile OAuth redirectUri: ${JSON.stringify(o)}. Allowed: ${JSON.stringify(De())}`),E(t,400,Fi(o),void 0,"invalid_redirect"),{ok:false};let i=typeof e.query.codeChallenge=="string"?e.query.codeChallenge:"",n=/^[A-Za-z0-9._~-]{16,256}$/.test(i);return r!=="link"&&!n?(E(t,400,"This sign-in request is missing a valid codeChallenge. Update the Modelence client package \u2014 signInWithOAuth generates it automatically."),{ok:false}):{ok:true,redirectUri:o,codeChallenge:n?i:null}}async function Ge(e,t,r){let o=randomBytes(32).toString("hex"),i=e.query.mode==="link"?"link":"login",n=zi(e,t,i);if(!n.ok)return null;let{redirectUri:s,codeChallenge:a}=n,c=null;if(i==="link"&&e.query.linkNonce&&(c=await ji(e.query.linkNonce),!c))return E(t,401,"Invalid or expired link nonce for OAuth linking.",s?{platform:"mobile",redirectUri:s}:void 0,"invalid_link_nonce"),null;let l=Gi({state:o,mode:i,linkedUserId:c,platform:s?"mobile":"web",redirectUri:s,codeChallenge:a});return t.cookie(r,l,{httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",maxAge:a$1.minutes(10)}),{state:o,mode:i}}async function Fe(e,t,r,o,i={platform:"web"}){let n=w(),{session:s$1,connectionInfo:a}=await ae(e,t),c=null;if(o){if(!ObjectId.isValid(o)){O(t),E(t,400,"Invalid OAuth linking state.",i);return}c=new ObjectId(o);}else c=s$1?.userId??null;if(!c){O(t),E(t,401,"You must be signed in to link a provider.",i);return}let l=c;try{let d=`authMethods.${r.providerName}.id`;if((await s.updateOne({_id:l,status:{$nin:["deleted","disabled"]},$or:[{[d]:{$exists:!1}},{[d]:r.id}]},{$set:{[d]:r.id}})).matchedCount===0){let g=await s.findOne({_id:l});if(!g||g.status==="deleted"||g.status==="disabled"){ie(()=>n.onOAuthLinkError?.({provider:r.providerName,error:new Error("User account not found or not active"),session:s$1,connectionInfo:a})),O(t),E(t,400,"User account is not active.",i);return}let y=g?.authMethods?.[r.providerName]?.id;if(y&&y!==r.id){ie(()=>n.onOAuthLinkError?.({provider:r.providerName,error:new Error(`User already has a different ${r.providerName} account linked`),session:s$1,connectionInfo:a})),O(t),E(t,400,`You have already linked a different ${r.providerName} account.`,i);return}ie(()=>n.onOAuthLinkError?.({provider:r.providerName,error:new Error(`Unexpected OAuth linking state for ${r.providerName}`),session:s$1,connectionInfo:a})),O(t),E(t,400,`Unable to link ${r.providerName} account.`,i);return}let m=await s.findOne({_id:l},{readPreference:"primary"});if(m&&ie(()=>n.onAfterOAuthLink?.({provider:r.providerName,user:m,session:s$1,connectionInfo:a})),O(t),i.platform==="mobile"){t.set("Referrer-Policy","no-referrer"),t.redirect($e(i.redirectUri,{linked:r.providerName}));return}t.redirect("/");}catch(d){if(d instanceof MongoServerError&&d.code===11e3){ie(()=>n.onOAuthLinkError?.({provider:r.providerName,error:d,session:s$1,connectionInfo:a})),O(t),E(t,400,`This ${r.providerName} account is already linked to a different user.`,i);return}if(d instanceof Error&&ie(()=>n.onOAuthLinkError?.({provider:r.providerName,error:d,session:s$1,connectionInfo:a})),O(t),!t.headersSent)throw d}}function ze(e){return !e||typeof e!="string"?null:e}async function Wi(e,t,r,o){let i=await fetch("https://oauth2.googleapis.com/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({code:e,client_id:t,client_secret:r,redirect_uri:o,grant_type:"authorization_code"})});if(!i.ok)throw new Error(`Failed to exchange code for token: ${i.statusText}`);return i.json()}async function Ji(e){let t=await fetch("https://www.googleapis.com/oauth2/v2/userinfo",{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error(`Failed to fetch user info: ${t.statusText}`);return t.json()}async function Ki(e,t){let r=ze(e.query.code);if(!r){E(t,400,"Missing authorization code",Ve(e,"authStateGoogle")??void 0,"missing_code");return}let o=Be(e,t,"authStateGoogle");if(!o)return;let{mode:i,linkedUserId:n}=o,s=je(o),a=String(a$2("_system.user.auth.google.clientId")),c=String(a$2("_system.user.auth.google.clientSecret")),l=se("google");try{let d=await Wi(r,a,c,l),u=await Ji(d.access_token),m={id:u.id,email:u.email,emailVerified:u.verified_email,providerName:"google",firstName:u.given_name||void 0,lastName:u.family_name||void 0,avatarUrl:u.picture||void 0};i==="link"?await Fe(e,t,m,n,s):await He(e,t,m,s);}catch(d){console.error("Google OAuth error:",d),i==="link"&&O(t),E(t,500,"Authentication failed",s);}}function Yi(){let e=Router(),t=(r,o,i)=>{let n=!!a$2("_system.user.auth.google.enabled"),s=String(a$2("_system.user.auth.google.clientId")),a=String(a$2("_system.user.auth.google.clientSecret"));if(!n||!s||!a){E(o,503,"Google authentication is not configured");return}i();};return e.get("/api/_internal/auth/google",t,async(r,o)=>{let i=String(a$2("_system.user.auth.google.clientId")),n=se("google"),s=await Ge(r,o,"authStateGoogle");if(!s)return;let{state:a}=s,c=new URL("https://accounts.google.com/o/oauth2/v2/auth");c.searchParams.append("client_id",i),c.searchParams.append("redirect_uri",n),c.searchParams.append("response_type","code"),c.searchParams.append("scope","profile email"),c.searchParams.append("access_type","online"),c.searchParams.append("state",a),o.redirect(c.toString());}),e.get("/api/_internal/auth/google/callback",t,Ki),e}var Do=Yi;async function Qi(e,t,r,o){let i=await fetch("https://github.com/login/oauth/access_token",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({client_id:t,client_secret:r,code:e,redirect_uri:o})});if(!i.ok)throw new Error(`Failed to exchange code for token: ${i.statusText}`);return i.json()}async function Zi(e){let t=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${e}`,Accept:"application/vnd.github.v3+json"}});if(!t.ok)throw new Error(`Failed to fetch user info: ${t.statusText}`);return t.json()}async function es(e){let t=await fetch("https://api.github.com/user/emails",{headers:{Authorization:`Bearer ${e}`,Accept:"application/vnd.github.v3+json"}});if(!t.ok)throw new Error(`Failed to fetch user emails: ${t.statusText}`);return t.json()}async function ts(e,t){return e.email?e.email:(await es(t)).find(o=>o.primary&&o.verified)?.email??null}async function rs(e,t){let r=ze(e.query.code);if(!r){E(t,400,"Missing authorization code",Ve(e,"authStateGithub")??void 0,"missing_code");return}let o=Be(e,t,"authStateGithub");if(!o)return;let{mode:i,linkedUserId:n}=o,s=je(o),a=String(a$2("_system.user.auth.github.clientId")),c=String(a$2("_system.user.auth.github.clientSecret")),l=se("github");try{let d=await Qi(r,a,c,l),u=await Zi(d.access_token),m=await ts(u,d.access_token);if(!m){i==="link"&&O(t),E(t,400,"Unable to retrieve a primary verified email from GitHub. Please ensure your GitHub account has a verified email set as primary.",s);return}let g=u.name?u.name.trim().split(/\s+/):[],y=g[0]||void 0,v=g.length>1?g.slice(1).join(" "):void 0,x={id:String(u.id),email:m,emailVerified:!0,providerName:"github",firstName:y,lastName:v,avatarUrl:u.avatar_url||void 0};i==="link"?await Fe(e,t,x,n,s):await He(e,t,x,s);}catch(d){console.error("GitHub OAuth error:",d),i==="link"&&O(t),E(t,500,"Authentication failed",s);}}function os(){let e=Router(),t=(r,o,i)=>{let n=!!a$2("_system.user.auth.github.enabled"),s=String(a$2("_system.user.auth.github.clientId")),a=String(a$2("_system.user.auth.github.clientSecret"));if(!n||!s||!a){E(o,503,"GitHub authentication is not configured");return}i();};return e.get("/api/_internal/auth/github",t,async(r,o)=>{let i=String(a$2("_system.user.auth.github.clientId")),n=se("github"),s=a$2("_system.user.auth.github.scopes"),a=s?String(s).split(",").map(u=>u.trim()).join(" "):"user:email",c=await Ge(r,o,"authStateGithub");if(!c)return;let{state:l}=c,d=new URL("https://github.com/login/oauth/authorize");d.searchParams.append("client_id",i),d.searchParams.append("redirect_uri",n),d.searchParams.append("scope",a),d.searchParams.append("state",l),o.redirect(d.toString());}),e.get("/api/_internal/auth/github/callback",t,rs),e}var $o=os;function jo(e,t,r){return async(o,i,n)=>{let s=o.headers["x-modelence-auth-token"],a={session:null,user:null};if(typeof s=="string"&&N())try{let{session:l,user:d}=await z$2(s);a={session:l,user:d};}catch{}let c=n$1("route",`route:${e.toLowerCase()}:${t}`,{method:e,path:t,query:j(o.query),body:j(o.body),params:j(o.params)});try{let l=await r({query:o.query,body:o.body,params:o.params,headers:o.headers,cookies:o.cookies,rawBody:Buffer.isBuffer(o.body)?o.body:void 0,req:o,res:i,next:n},a);c.end(),l&&(i.status(l.status||200),l.contentType&&i.setHeader("Content-Type",l.contentType),l.headers&&Object.entries(l.headers).forEach(([d,u])=>{i.setHeader(d,u);}),l.redirect?i.redirect(l.redirect):i.send(l.data));}catch(l){c.end("error"),l instanceof a$4?i.status(l.status).send(l.message):(console.error(`Error in route handler: ${o.path}`),console.error(l),i.status(500).send(String(l)));}}}function ss(e){let t=[];if(!e)return t.push(z$1.json({limit:"16mb"})),t.push(z$1.urlencoded({extended:true,limit:"16mb"})),t;if(e.json!==false){let r=typeof e.json=="object"?e.json:{limit:"16mb"};t.push(z$1.json(r));}if(e.urlencoded!==false){let r=typeof e.urlencoded=="object"?e.urlencoded:{extended:true,limit:"16mb"};t.push(z$1.urlencoded(r));}if(e.raw){let r=typeof e.raw=="object"?e.raw:{},o={limit:r.limit||"16mb",type:r.type||"*/*"};t.push(z$1.raw(o));}return t}function as(e,t){for(let r of t)for(let o of r.routes){let{path:i,handlers:n,body:s}=o,a=ss(s);Object.entries(n).forEach(([c,l])=>{e[c](i,...a,jo(c,i,l));});}}var Ho=false;async function Oo(e,{combinedModules:t,channels:r}){let o=z$1(),{trustedProxies:i}=Ie(),s=ms(process.env.MODELENCE_TRUSTED_PROXIES)??i;o.set("trust proxy",s===void 0?true:s),o.use(ns()),o.use(ds()),as(o,t),o.use(z$1.json({limit:"16mb"})),o.use(z$1.urlencoded({extended:true,limit:"16mb"})),o.use(Do()),o.use($o()),o.post("/api/_internal/auth/set-link-cookie",async(u,m)=>{let{session:g}=await ae(u,m);if(!g?.userId){m.status(401).json({error:"Not authenticated"});return}m.cookie("oauthLinkToken",g.authToken,{httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:"/api/_internal/auth/",maxAge:10*60*1e3}),m.json({ok:true});}),o.post("/api/_internal/auth/issue-link-nonce",async(u,m)=>{let{session:g}=await ae(u,m);if(!g?.userId){m.status(401).json({error:"Not authenticated"});return}let y=await f$1(String(g.userId));m.json({nonce:y});}),o.post("/api/_internal/method/:methodName(*)",async(u,m)=>{let g=u.params.methodName,y=await ae(u,m);try{let v=a$3(await G$1(g,u.body.args,y));m.json({data:v,typeMap:b$3(v)});}catch(v){cs(m,g,v);}});let a=is.createServer(o),c=Number(process.env.MODELENCE_KEEP_ALIVE_TIMEOUT_MS)||65e3;a.keepAliveTimeout=c,a.headersTimeout=c+5e3,await e.init({httpServer:a}),e.middlewares&&o.use(e.middlewares()),o.all("*",(u,m,g)=>{Promise.resolve(e.handler(u,m)).catch(g);}),Ho||(Ho=true,process.on("unhandledRejection",(u,m)=>{console.error("Unhandled Promise Rejection:"),console.error(u instanceof Error?u.stack:u),console.error("Promise:",m);}),process.on("uncaughtException",u=>{console.error("Uncaught Exception:"),console.error(u.stack),console.trace("Full application stack:");}));let l=Ne()?.provider;l&&l.init({httpServer:a,channels:r});let d=process.env.MODELENCE_PORT||process.env.PORT||3e3;a.listen(d,()=>{l$1("Application started",{source:"app"});let u=a$2("_system.site.url")||ge();console.log(`
38
- Application started on ${u}
39
- `),b$1()&&console.log("This project is not connected to a backend yet, so the app serves setup instructions instead of its UI.\nTo connect it to Modelence Cloud, run `npx modelence@latest setup` and restart the dev server.\n");});}async function ae(e,t=null){let r=(e.path??e.url??"").split("?")[0],o=r.startsWith("/api/_internal/auth/")&&r.endsWith("/callback"),i=e.body??{},n=F$1.string().nullish().transform(l=>l??null).parse(e.cookies.authToken||(o?e.cookies.oauthLinkToken:null)||i.authToken),s=F$1.object({screenWidth:F$1.number(),screenHeight:F$1.number(),windowWidth:F$1.number(),windowHeight:F$1.number(),pixelRatio:F$1.number(),orientation:F$1.string().nullable()}).nullish().parse(i.clientInfo)??{screenWidth:0,screenHeight:0,windowWidth:0,windowHeight:0,pixelRatio:1,orientation:null},a={ip:fs(e),userAgent:e.get("user-agent"),acceptLanguage:e.get("accept-language"),referrer:e.get("referrer"),baseUrl:us(e)};if(!!N()){let{session:l,user:d,roles:u}=await z$2(n);return {clientInfo:s,connectionInfo:a,session:l,user:d,roles:u,req:e,res:t}}return {clientInfo:s,connectionInfo:a,session:null,user:null,roles:y(),req:e,res:t}}function cs(e,t,r){if(r instanceof a$4){r.status>=500&&r.status<600&&console.error(`Error calling ${t}:`,r),r.code&&e.setHeader("X-Modelence-Error-Code",r.code),e.status(r.status).send(r.message);return}if(r instanceof Error&&r?.constructor?.name==="ZodError"&&"errors"in r){let o="";try{o=ls(r);}catch(i){console.error(`Error parsing Zod error in ${t}:`,i),o="Validation failed";}e.status(400).send(o);return}console.error(`Error calling ${t}:`,r),e.status(500).send(r instanceof Error?r.message:String(r));}function ls(e){let t=e.flatten(),r=Object.entries(t.fieldErrors).map(([n,s])=>`${n}: ${(s??[]).join(", ")}`),o=t.formErrors;return [...r,...o].filter(Boolean).join("; ")}function ds(){let{frameAncestors:e}=Ie(),t=e&&e.length>0,r=t?["'self'",...e].join(" "):"'self'";return (o,i,n)=>{i.setHeader("Content-Security-Policy",`frame-ancestors ${r}`),t||i.setHeader("X-Frame-Options","SAMEORIGIN"),n();}}function us(e){let t=e.headers["x-forwarded-host"],r=(Array.isArray(t)?t[0]:t?.split(",")[0])?.trim()||e.get("host");return `${e.protocol}://${r}`}function ms(e){let t=e?.split(",").map(r=>r.trim()).filter(r=>r.length>0);return t?.length?t:void 0}function ps(e){let t=e.socket?.remoteAddress;if(!t)return false;let r=e.app?.get?.("trust proxy fn");return typeof r!="function"?e.app?.get?.("trust proxy")!==false:!!r(t,0)}function fs(e){let{clientIpHeader:t}=Ie();if(t&&ps(e)){let o=e.headers[t.toLowerCase()],i=(Array.isArray(o)?o[0]:o)?.trim();if(i)return Bo(i)}let r=e.ip||e.socket?.remoteAddress;if(r)return Bo(r)}function Bo(e){return e.startsWith("::ffff:")?e.substring(7):e}
40
- export{k as a,qn as b,Wn as c,Jn as d,Kn as e,Oo as f,ae as g,yi as h,Pi as i,Ti as j,Pt as k,Mi as l};//# sourceMappingURL=chunk-RAWHQTR6.js.map
41
- //# sourceMappingURL=chunk-RAWHQTR6.js.map