weifuwu 0.95.1 → 0.95.2

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.
@@ -3214,6 +3214,15 @@ function userSystem(options) {
3214
3214
  open_registration: false
3215
3215
  }).run();
3216
3216
  }
3217
+ const defAppId = await findAppIdBySlug("_default");
3218
+ if (defAppId) {
3219
+ const owners = await M.select("user_id").where({ app_id: { eq: BUILTIN_APP_ID }, role: { eq: "owner" } }).run();
3220
+ for (const o of owners) {
3221
+ const uid = String(o.user_id);
3222
+ if (await findMemberRole(defAppId, uid)) continue;
3223
+ await M.insert({ app_id: defAppId, user_id: uid, role: "owner", invited_by: null, source: "migrate" }).onConflict().run();
3224
+ }
3225
+ }
3217
3226
  const leftover = await U.select("id").run();
3218
3227
  for (const u of leftover) {
3219
3228
  await ensureBuiltinMember(String(u.id), "migrate");
@@ -1 +1 @@
1
- var l=class extends Error{status;constructor(e,t){super(e),this.name="HttpError",this.status=t}};var Y=class extends Error{issues;constructor(e){super(e.map(t=>`${t.path}: ${t.message}`).join("; ")),this.name="ZodError",this.issues=e}},v=class{_meta={};get metaInfo(){return this._meta}meta(e){return this._meta={...this._meta,...e},this}parse(e){let t=this._parse(e);if(!t.ok)throw new Y([{path:t.path??"",message:t.message??"invalid"}]);return t.value}safeParse(e){let t=this._parse(e);return t.ok?{success:!0,data:t.value}:{success:!1,error:new Y([{path:t.path??"",message:t.message??"invalid"}])}}optional(){return new ye(this)}nullable(){return new ke(this)}default(e){return new _e(this,e)}refine(e,t){return new be(this,e,t)}transform(e){return new xe(this,e)}},Q=class extends v{_typeName(){return"string"}_parse(e){if(typeof e!="string")return{ok:!1,path:"",message:"expected string"};let t=e;return this._min!==void 0&&t.length<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t.length>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:this._pattern&&!this._pattern.test(t)?{ok:!1,path:"",message:"invalid pattern"}:this._email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)?{ok:!1,path:"",message:"invalid email"}:this._uuid&&!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)?{ok:!1,path:"",message:"invalid uuid"}:{ok:!0,value:t}}_min;_max;_pattern;_email=!1;_uuid=!1;min(e){return this._min=e,this}max(e){return this._max=e,this}regex(e,t){return this._pattern=e,this}email(){return this._email=!0,this}uuid(){return this._uuid=!0,this}},le=class extends v{_typeName(){return"number"}_parse(e){if(typeof e!="number"||Number.isNaN(e))return{ok:!1,path:"",message:"expected number"};let t=e;return this._int&&!Number.isInteger(t)?{ok:!1,path:"",message:"expected integer"}:this._min!==void 0&&t<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:{ok:!0,value:t}}_int=!1;_min;_max;int(){return this._int=!0,this}min(e){return this._min=e,this}max(e){return this._max=e,this}positive(){return this._min=0,this}},de=class extends v{_typeName(){return"boolean"}_parse(e){return typeof e!="boolean"?{ok:!1,path:"",message:"expected boolean"}:{ok:!0,value:e}}},ee=class extends v{lit;constructor(e){super(),this.lit=e}get value(){return this.lit}_typeName(){return"literal"}_parse(e){return e!==this.lit?{ok:!1,path:"",message:`expected literal ${JSON.stringify(this.lit)}`}:{ok:!0,value:e}}},te=class extends v{raw;constructor(e){super(),this.raw=e}get values(){return this.raw}_typeName(){return"enum"}_parse(e){return typeof e!="string"||!this.raw.includes(e)?{ok:!1,path:"",message:`expected one of ${this.raw.join(" | ")}`}:{ok:!0,value:e}}},pe=class extends v{_typeName(){return"datetime"}_parse(e){return typeof e!="string"||Number.isNaN(Date.parse(e))?{ok:!1,path:"",message:"expected datetime string (ISO)"}:{ok:!0,value:e}}},ce=class extends v{dims;constructor(e){super(),this.dims=e}_typeName(){return"vector"}_parse(e){return typeof e!="object"||e===null||!Array.isArray(e)||!e.every(t=>typeof t=="number")?{ok:!1,path:"",message:"expected number[] vector"}:this.dims>0&&e.length!==this.dims?{ok:!1,path:"",message:`expected vector(${this.dims})\u2014\u2014got ${e.length} dims`}:{ok:!0,value:e}}},ge=class extends v{_typeName(){return"json"}_parse(e){return{ok:!0,value:e}}},me=class n extends v{shape;constructor(e){super(),this.shape=e}_typeName(){return"object"}_parse(e){if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,path:"",message:"expected object"};let t={},y=[];for(let[w,S]of Object.entries(this.shape)){let A=S._parse(e[w]);if(!A.ok){y.push({path:w,message:A.message??""});continue}t[w]=A.value}return y.length?{ok:!1,path:y[0].path,message:y[0].message}:{ok:!0,value:t}}pick(e){let t={};for(let y of e)t[y]=this.shape[y];return new n(t)}omit(e){let t={};for(let[y,w]of Object.entries(this.shape))e.includes(y)||(t[y]=w);return new n(t)}extend(e){return new n({...this.shape,...e})}},fe=class extends v{inner;constructor(e){super(),this.inner=e}get itemSchema(){return this.inner}_typeName(){return"array"}_parse(e){if(!Array.isArray(e))return{ok:!1,path:"",message:"expected array"};if(this._min!==void 0&&e.length<this._min)return{ok:!1,path:"",message:`too small (min ${this._min})`};if(this._max!==void 0&&e.length>this._max)return{ok:!1,path:"",message:`too big (max ${this._max})`};let t=[];for(let y=0;y<e.length;y++){let w=this.inner._parse(e[y]);if(!w.ok)return{ok:!1,path:`${y}`,message:w.message};t.push(w.value)}return{ok:!0,value:t}}_min;_max;min(e){return this._min=e,this}max(e){return this._max=e,this}},we=class extends v{options;constructor(e){super(),this.options=e}_typeName(){return"union"}_parse(e){let t=null;for(let y of this.options){let w=y._parse(e);if(w.ok)return{ok:!0,value:w.value};t||(t={path:w.path??"",message:w.message??""})}return{ok:!1,path:"",message:`expected union (${t?.message})`}}},he=class extends v{disc;options;discValueOf;constructor(e,t,y){super(),this.disc=e,this.options=t,this.discValueOf=y}_typeName(){return"disUnion"}_parse(e){if(typeof e!="object"||e===null)return{ok:!1,path:"",message:"expected object"};let t=e[this.disc];for(let y of this.options)if(this.discValueOf(y.shape)===t){let w=y._parse(e);return w.ok?{ok:!0,value:w.value}:{ok:!1,path:`(${String(t)})`,message:w.message??""}}return{ok:!1,path:this.disc,message:`invalid discriminator value ${JSON.stringify(t)}`}}};function Ke(n,e){let t=n[e];if(t instanceof ee)return t.lit;if(t instanceof te)return t.values;throw new Error(`z.discriminatedUnion: \u5224\u522B\u5B57\u6BB5\u9700\u4E3A literal/enum\u2014\u2014\u5F97\u5230 ${t?._typeName?.()}`)}var ye=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"optional"}_parse(e){return e===void 0?{ok:!0,value:void 0}:this.inner._parse(e)}},ke=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"nullable"}_parse(e){return e===null?{ok:!0,value:null}:this.inner._parse(e)}},_e=class extends v{inner;d;constructor(e,t){super(),this.inner=e,this.d=t}_typeName(){return"default"}_parse(e){if(e===void 0){let t=typeof this.d=="function"?this.d():this.d;return this.inner._parse(t)}return this.inner._parse(e)}},be=class extends v{inner;fn;msg;constructor(e,t,y){super(),this.inner=e,this.fn=t,this.msg=y}_typeName(){return"effects"}_parse(e){let t=this.inner._parse(e);return t.ok?this.fn(t.value)?{ok:!0,value:t.value}:{ok:!1,path:"",message:this.msg??"refine failed"}:{ok:!1,path:t.path??"",message:t.message??""}}},xe=class extends v{inner;fn;constructor(e,t){super(),this.inner=e,this.fn=t}_typeName(){return"transform"}_parse(e){let t=this.inner._parse(e);return t.ok?{ok:!0,value:this.fn(t.value)}:{ok:!1,path:t.path??"",message:t.message??""}}},i={string:()=>new Q,number:()=>new le,boolean:()=>new de,literal:n=>new ee(n),enum:n=>new te(n),date:()=>new pe,uuid:()=>new Q().uuid(),json:()=>new ge,vector:n=>new ce(n),object:n=>new me(n),array:n=>new fe(n),union:n=>new we(n),discriminatedUnion:(n,e)=>new he(n,e,t=>Ke(t,n))};function z(n,e){return n.meta(e)}var x={pk:n=>z(n,{pk:!0,default:"random"}),req:n=>z(n,{notNull:!0}),col:(n,e)=>z(n,{column:e}),now:n=>z(n,{default:"now"}),unique:n=>z(n,{unique:!0}),dflt:(n,e)=>z(n,{default:e})};import{randomBytes as We,scrypt as ze,timingSafeEqual as He}from"node:crypto";import{promisify as Fe}from"node:util";var Ae=Fe(ze),Ve=64,ne={N:16384,r:8,p:1};async function J(n){let e=We(16),t=await Ae(n,e,Ve,ne);return`scrypt$${ne.N}$${ne.r}$${ne.p}$${e.toString("hex")}$${t.toString("hex")}`}async function re(n,e){let t=e.split("$");if(t.length!==6||t[0]!=="scrypt")return!1;let[,y,w,S,A,q]=t,$=Buffer.from(q,"hex"),U=await Ae(n,Buffer.from(A,"hex"),$.length,{N:Number(y),r:Number(w),p:Number(S)});return U.length===$.length&&He(U,$)}import{createHash as Je,createHmac as je,randomBytes as Ge,timingSafeEqual as Xe}from"node:crypto";var Oe=n=>Buffer.from(JSON.stringify(n)).toString("base64url");function se(n,e,t){let y=Math.floor(Date.now()/1e3),w=`${Oe({alg:"HS256",typ:"JWT"})}.${Oe({...n,iat:y,exp:y+t})}`,S=je("sha256",e).update(w).digest("base64url");return`${w}.${S}`}function ae(n,e){let t=n.split(".");if(t.length!==3)return null;let[y,w,S]=t,A=`${y}.${w}`,q=je("sha256",e).update(A).digest("base64url"),$=Buffer.from(S),U=Buffer.from(q);if($.length!==U.length||!Xe($,U))return null;try{let T=JSON.parse(Buffer.from(w,"base64url").toString("utf8"));return typeof T.exp=="number"&&T.exp<Date.now()/1e3?null:T}catch{return null}}function qe(){return Ge(32).toString("hex")}function ie(n){return Je("sha256").update(n).digest("hex")}function Z(n,e){return Response.json(n,{status:200,...e})}function B(n,e){return Response.json(n,{status:201,...e})}function $e(n){return new Response(null,{status:204,...n})}function H(n){return Response.json({error:n??"Bad Request"},{status:400})}import{randomBytes as Pe}from"node:crypto";var oe={users:{id:x.pk(i.uuid()),email:x.req(i.string()).meta({unique:!0}),password_hash:i.string().nullable(),name:i.string().nullable(),role:i.string().nullable(),tenant:i.string().nullable(),email_verified:i.boolean().meta({notNull:!0,default:!1}),created_at:x.now(i.date())},sessions:{token_hash:x.req(i.string()),user_id:x.req(i.uuid()),app_id:i.string().nullable(),expires_at:x.req(i.date()),revoked_at:i.date().nullable(),created_at:x.now(i.date())},apps:{id:x.pk(i.uuid()),slug:x.req(i.string()).meta({unique:!0}),name:x.req(i.string()),owner_user_id:i.uuid().nullable(),open_registration:i.boolean().meta({notNull:!0,default:!1}),app_key:i.string().nullable(),created_at:x.now(i.date())},members:{app_id:x.req(i.uuid()),user_id:x.req(i.uuid()),role:i.string().meta({notNull:!0,default:"member"}),invited_by:i.uuid().nullable(),joined_at:x.now(i.date()),source:i.string().nullable(),last_login_at:i.date().nullable()}},Ye="_weifuwu_users",Qe="_weifuwu_sessions",Se="_weifuwu_apps",N="00000000-0000-4000-8000-0000000000b1",Ze="_weifuwu_app_members",Ne=8,et=1024;function Te(n){if(n.length<Ne)throw new l(`password must be at least ${Ne} characters`,400);if(n.length>et)throw new l("password is too long",400)}function D(n){return n.trim().toLowerCase()}var Ue=null;async function Me(n){Ue??=await J("dummy-timing-equalize-password"),await re(n,Ue)}function Rt(n){let e=n.orm,t=e.table(Ye,oe.users),y=e.table(Qe,oe.sessions),w=e.table(Se,oe.apps),S=e.table(Ze,oe.members),A=n.secret??process.env.AUTH_SECRET;if(!A)throw new Error("userSystem: secret is required \u2014 pass options.secret or set AUTH_SECRET (at least 32 chars)");if(A.length<16)throw new Error("userSystem: secret must be at least 16 characters");let q=A,$=n.hooks,U=n.allowedRoles??["owner","admin","member","viewer"],T=n.sso??null,ve=n.inviteRoles??U;function Re(a){if(a!==void 0&&!U.includes(a))throw new l(`invalid role: ${a}\uFF08allowed: ${U.join("/")}\uFF09`,403)}let De=n.accessTtlSeconds??3600,Ce=n.refreshTtlDays??30,Ee=n.prefix??"/api/auth",G=a=>({id:String(a.id),email:a.email,name:a.name,role:a.role,tenant:a.tenant}),Be=168*3600;async function ue(a){let g=await t.select("id","email","name","role","tenant").where({id:{eq:a}}).run();return g.length?g[0]:null}async function F(a,g){(await S.select("role").where({app_id:{eq:N},user_id:{eq:a}}).run()).length||await S.insert({app_id:N,user_id:a,role:"member",invited_by:null,source:g}).onConflict().run()}async function P(a,g){let h=await S.select("role").where({app_id:{eq:a},user_id:{eq:g}}).run();return h.length?String(h[0].role):null}async function L(a){let g=await w.select("id").where({slug:{eq:a}}).run();return g.length?String(g[0].id):null}async function K(a){let g=await t.select("id","email","name","role","tenant").where({email:{eq:a}}).run();return g.length?g[0]:null}async function Ie(a){return(await e.query.from(`${Ze} m`).select("m.app_id","m.role","a.id","a.slug","a.name").join(`${Se} a`,{"a.id":{col:"m.app_id"}}).where({"m.user_id":{eq:a}}).run()).filter(h=>!String(h.slug).startsWith("_")).map(h=>({id:String(h.id),slug:String(h.slug),name:String(h.name),role:String(h.role)}))}async function C(a,g){let h=se({sub:a.id,...a.email?{email:a.email}:{},...a.name?{name:a.name}:{},...g?.appId?{appId:g.appId,role:g.role}:{},...!g?.appId&&a.tenant?{tenantId:a.tenant}:{}},q,De),d=qe(),s=new Date(Date.now()+Ce*24*3600*1e3);return await y.insert({token_hash:ie(d),user_id:a.id,expires_at:s.toISOString(),...g?.appId?{app_id:g.appId}:{}}).run(),{token:h,refreshToken:d}}async function Le(a){let g=await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(a)},revoked_at:{isNull:!0}}).returning("user_id","expires_at","app_id").run();if(!g.length)throw new l("Invalid refresh token",401);let h=g[0];if(new Date(String(h.expires_at))<new Date)throw new l("Refresh token expired",401);let d=await ue(h.user_id);if(!d)throw new l("User not found",401);return{user:d,appId:h.app_id?String(h.app_id):void 0}}let E=(async(a,g,h)=>{let d=null,s=null,p={},u,f,R,O=a.headers.get("authorization"),M=null;if(O?.startsWith("Bearer "))M=O.slice(7);else if(a.method==="GET"){let r=new URL(a.url??"","http://localhost"),o=r.searchParams.get("ticket");if(o){let m=ae(o,q);m?.type==="download"&&m?.sub&&(s=await ue(String(m.sub)),d=s,p.userId=String(m.sub),m.appId!=null&&(f=m.appId,p.appId=String(m.appId)))}else M=r.searchParams.get("token")}if(M&&!s){let r=ae(M,q);if(r?.sub){s=await ue(String(r.sub)),d=s;for(let[o,m]of Object.entries(r))o==="sub"||o==="iat"||o==="exp"||o==="type"||(p[o]=m);p.userId=String(r.sub),u=r.tenantId,f=r.appId,R=r.role}}g.user=s,f!=null&&(g.appId=String(f)),g.session=f!=null?{userId:String(s.id),appId:String(f),role:String(R??"")}:null,u!=null&&(g.tenantId=String(u));let W={...p,async register(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let o=D(r.email),m=await J(r.password),c=await t.insert({email:o,password_hash:m,name:r.name??null,role:null,tenant:r.tenant??null}).run(),_=G(c[0]),b=await C(_);return await F(_.id,"register"),d=_,{...b,user:_}},async registerWithApp(r){let o=await this.register({email:r.email,password:r.password,name:r.name});await F(o.user.id,"register");let m=(r.appSlug??r.email.split("@")[1]??"default").trim().toLowerCase(),c=m;for(let k=1;k<=200&&await L(c);k++)c=`${m}-${k}`;let _=await this.createApp({slug:c,name:r.appName??`${r.name??"\u7528\u6237"} \u7684\u5E94\u7528`,openRegistration:!1}),b=await C(o.user,{appId:_.id,role:"owner"});return await $?.onRegisterApp?.(o.user.id,_),{...b,user:o.user,app:{..._,role:"owner"}}},async login(r,o){let c=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(r)}}).run())[0];if(!c||c.password_hash==null)throw await Me(o),new l("Invalid email or password",401);if(!await re(o,String(c.password_hash)))throw new l("Invalid email or password",401);let b={id:String(c.id),email:String(c.email),name:c.name,role:c.role,tenant:c.tenant},k=await C(b),j=await Ie(b.id);return d=b,{...k,user:b,apps:j}},async logout(r){await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(r)}}).run()},requireAuth(){if(!s)throw new l("Unauthorized",401);return s},async setPassword(r,o){Te(o);let m=await J(o);await t.update({password_hash:m}).where({id:{eq:r}}).run()},createToken(r,o,m){return se({type:r,...o},q,m.ttlSeconds)},async createApp(r){if(!d)throw new l("Unauthorized",401);if(!r.slug||!r.name)throw new l("slug and name are required",400);let o=r.slug.trim().toLowerCase();if(o.startsWith("_"))throw new l("slug \u4EE5 _ \u5F00\u5934\u4E3A\u7CFB\u7EDF\u4FDD\u7559\u540D",400);if(!await P(N,d.id))throw new l("\u4EC5\u5E94\u7528\u7BA1\u7406\u9762\uFF08_builtin\uFF09\u6210\u5458\u53EF\u521B\u5EFA\u5E94\u7528",403);let c=Pe(32).toString("hex"),b=(await w.insert({slug:o,name:r.name,owner_user_id:d.id,open_registration:r.openRegistration??!1,app_key:c}).run())[0];return await S.insert({app_id:b.id,user_id:d.id,role:"owner",invited_by:d.id}).run(),b},async registerInApp(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let o=await w.select("id","open_registration").where({slug:{eq:r.appSlug}}).run();if(!o.length)throw new l("Application not found",404);let m=o[0],c=String(m.id),_=D(r.email),b="member";if(!m.open_registration){if(!r.inviteToken)throw new l("Registration closed for this application",403);let I=ae(r.inviteToken,q);if(!I||I.type!=="app-invite"||I.appId!==c)throw new l("Invalid invite",403);if(I.email&&String(I.email)!==_)throw new l("Invite email mismatch",403);typeof I.role=="string"&&I.role!=="owner"&&(Re(I.role),b=I.role)}let k=await K(_);if(!k){let I=await J(r.password),X=await t.insert({email:_,password_hash:I,name:r.name??null,role:null,tenant:null}).onConflict("email").run();if(k=X.length?G(X[0]):await K(_),!k)throw new l("User creation failed",500)}await F(k.id,"register"),await P(c,k.id)||await S.insert({app_id:c,user_id:k.id,role:b,invited_by:d?.id??null,source:"invite"}).onConflict().run();let V=await C(k,{appId:c,role:b});return await S.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:c},user_id:{eq:k.id}}).run(),d=k,await $?.onJoinApp?.(k.id,c,b),{...V,user:k}},async loginApp(r,o,m){let c=await w.select("id").where({slug:{eq:r}}).run();if(!c.length)throw new l("Application not found",404);let _=String(c[0].id),k=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(o)}}).run())[0];if(!k||k.password_hash==null)throw await Me(m),new l("Invalid email or password",401);if(!await re(m,String(k.password_hash)))throw new l("Invalid email or password",401);let V=await P(_,String(k.id));if(!V)throw new l("Not a member of this application",401);let I={id:String(k.id),email:String(k.email),name:k.name,role:k.role,tenant:k.tenant},X=await C(I,{appId:_,role:V});return await S.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:_},user_id:{eq:I.id}}).run(),d=I,{...X,user:I,role:V}},async ssoLogin(r,o){let m=D(r),c=await K(m);if(!c){let k=await t.insert({email:m,password_hash:null,name:o?.name??null,role:null,tenant:null}).onConflict("email").run();if(c=k.length?G(k[0]):await K(m),!c)throw new l("User creation failed",500)}await F(c.id,"sso");let _;o?.appId&&(await P(o.appId,c.id)||await S.insert({app_id:o.appId,user_id:c.id,role:"member",invited_by:null,source:"sso"}).onConflict().run(),_=o.appId,await S.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:o.appId},user_id:{eq:c.id}}).run());let b=await C(c,_?{appId:_,role:"member"}:void 0);return d=c,{...b,user:c}},async createInvite(r,o){if(r===N)throw new l("_builtin \u4E0D\u8D70\u9080\u8BF7\u6D41\uFF08\u4EFB\u547D\u5236\uFF09",403);if(o.role!==void 0&&!ve.includes(o.role))throw new l(`invite role not allowed: ${o.role}\uFF08allowed: ${ve.join("/")}\uFF09`,403);if(!d)throw new l("Unauthorized",401);if(await P(r,d.id)!=="owner")throw new l("Owner only",403);let c=o.role&&o.role!=="owner"?o.role:"member";return{inviteToken:se({type:"app-invite",appId:r,...o.email?{email:D(o.email)}:{},role:c},q,Be)}},async addMember(r,o,m){if(Re(m),r===N&&m!==void 0&&m==="viewer")throw new l("_builtin \u65E0\u53EA\u8BFB\uFF08viewer \u7981\u2014\u2014\u7BA1\u7406\u9762\u8EAB\u4EFD\uFF09",403);if(!d)throw new l("Unauthorized",401);if(await P(r,d.id)!=="owner")throw new l("Owner only",403);let _=await t.select("id").where({email:{eq:D(o)}}).run();if(!_.length)throw new l("User not found \u2014 invite them to register first",400);let b=m&&m!=="owner"?m:"member";await S.insert({app_id:r,user_id:String(_[0].id),role:b,invited_by:d.id}).run()},async setOpenRegistration(r,o){if(!d)throw new l("Unauthorized",401);if(r===N)throw new l("_builtin \u6CE8\u518C\u5F00\u5173\u6052 false",403);if(await P(r,d.id)!=="owner")throw new l("Owner only",403);await w.update({open_registration:o}).where({id:{eq:r}}).run()},async listMyApps(){if(!d)throw new l("Unauthorized",401);return Ie(d.id)},async requireApp(r){if(!d)throw new l("Unauthorized",401);let o=await P(r,d.id);if(!o)throw new l("Not a member of this application",403);return{userId:d.id,appId:r,role:o}}};return g.auth=W,h(a,g)});return E.__meta={injects:["user","auth"],depends:[]},E.seedBuiltinOwners=async a=>{let g={owner:null,admins:[]};for(let h of a){let d=D(h);if(!d)continue;let s=await K(d);if(!s){let u=await t.insert({email:d,password_hash:null,name:null,role:null,tenant:null}).onConflict("email").run();s=u.length?G(u[0]):await K(d)}if(!s)continue;let p=await S.select("role").where({app_id:{eq:N},user_id:{eq:s.id}}).run();if(p.length){String(p[0].role??"")==="owner"?g.owner=String(s.id):g.admins.push(String(s.id));continue}if(g.owner)await S.insert({app_id:N,user_id:String(s.id),role:"admin",invited_by:g.owner,source:"seed"}).run(),g.admins.push(String(s.id));else{await S.insert({app_id:N,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run(),g.owner=String(s.id);let u=await L("_default");u&&(await P(u,String(s.id))||await S.insert({app_id:u,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run())}}return g},E.migrate=async()=>{(await w.select("id").where({slug:{eq:"_builtin"}}).run()).length||await e.query.insert(Se).values({id:N,slug:"_builtin",name:"System",owner_user_id:null,open_registration:!1}).returning("id").run();let g=await w.select("id").where({app_key:{isNull:!0}}).run();for(let s of g)await w.update({app_key:Pe(32).toString("hex")}).where({id:{eq:String(s.id)}}).run();(await w.select("id").where({slug:{eq:"_default"}}).run()).length||await w.insert({slug:"_default",name:"Default",owner_user_id:null,open_registration:!1}).run();let d=await t.select("id").run();for(let s of d)await F(String(s.id),"migrate")},E.routes=(a,g)=>{let h=g?.prefix??Ee,d=new Set(g?.exclude??[]);if(d.has("register")||a.post(`${h}/register`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.register(u);return B(f)}),d.has("register")||a.post(`${h}/register-app`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.registerWithApp(u);return B(f)}),d.has("login")||a.post(`${h}/login`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(!u.email||!u.password)return H("email and password are required");let f=await p.auth.login(u.email,u.password);return Z(f)}),d.has("logout")||a.post(`${h}/logout`,async(s,p)=>{let u=await s.json().catch(()=>({}));return u.refreshToken?(await p.auth.logout(u.refreshToken),$e()):H("refreshToken is required")}),d.has("refresh")||a.post(`${h}/refresh`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(!u.refreshToken)throw new l("refreshToken is required",400);let{user:f,appId:R}=await Le(u.refreshToken),O;if(R){let W=await P(R,f.id);W&&(O={appId:R,role:W})}let M=await C(f,O);return Z({...M,user:f})}),d.has("me")||a.get(`${h}/me`,async(s,p)=>{if(!p.user)throw new l("Unauthorized",401);return Z({user:p.user,session:p.session??null})}),T){let s=`${h}/apps/_builtin/auth/sso`;a.get(`${s}/enabled`,async()=>Z({enabled:!0,appSlug:T.defaultAppSlug??null})),a.get(`${s}/login`,async p=>{let f=new URL(p.url??"","http://localhost").searchParams.get("app")??T.defaultAppSlug,R=new URLSearchParams({response_type:"code",client_id:T.clientId,redirect_uri:T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${new URL(p.url,"http://localhost").origin}${s}/callback`,scope:T.scope??"openid email profile",state:f??"sso"});return new Response(null,{status:302,headers:{Location:`${T.issuer.replace(/\/$/,"")}/authorize?${R}`}})}),a.get(`${s}/callback`,async(p,u)=>{let f=new URL(p.url??"","http://localhost"),R=f.searchParams.get("code");if(!R)throw new l("SSO \u56DE\u8C03\u7F3A\u5C11 code",400);let O=T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${f.origin}${s}/callback`,M=await fetch(`${T.issuer.replace(/\/$/,"")}/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:R,redirect_uri:O,client_id:T.clientId,client_secret:T.clientSecret})});if(!M.ok)throw new l("SSO token \u4EA4\u6362\u5931\u8D25",401);let W=await M.json(),r=await fetch(`${T.issuer.replace(/\/$/,"")}/userinfo`,{headers:{Authorization:`Bearer ${W.access_token}`}});if(!r.ok)throw new l("SSO \u7528\u6237\u4FE1\u606F\u83B7\u53D6\u5931\u8D25",401);let o=await r.json(),m=(o.email??"").trim().toLowerCase();if(!m)throw new l("SSO \u672A\u8FD4\u56DE\u90AE\u7BB1\uFF08\u9700\u8981 email scope\uFF09",401);let c=f.searchParams.get("state"),_;if(c&&c!=="sso"){let j=await L(c);j&&(_=j)}let b=await u.auth.ssoLogin(m,{appId:_,name:o.name});await $?.onSsoLogin?.(b.user.id,_);let k={token:b.token,refreshToken:b.refreshToken,user:b.user};if(T.renderCallback){let j=T.renderCallback(k);return typeof j=="string"?new Response(j,{headers:{"Content-Type":"text/html; charset=utf-8"}}):j}return Z(k)})}d.has("apps")||(a.post(`${h}/apps`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.createApp(u);return B({app:f})}),a.get(`${h}/apps`,async(s,p)=>{let u=await p.auth.listMyApps();return Z({apps:u})}),a.post(`${h}/apps/:appSlug/auth/members`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await L(p.params.appSlug);if(!f)throw new l("Application not found",404);return await p.auth.addMember(f,u.email??"",u.role),B({ok:!0})}),a.post(`${h}/apps/:appSlug/auth/invites`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await L(p.params.appSlug);if(!f)throw new l("Application not found",404);let R=await p.auth.createInvite(f,{email:u.email,role:u.role});return B(R)}),a.post(`${h}/apps/:appSlug/auth/login`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(!u.email||!u.password)return H("email and password are required");let f=await p.auth.loginApp(p.params.appSlug,u.email,u.password);return Z(f)}),a.patch(`${h}/apps/:appSlug/auth/registration`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(typeof u.open!="boolean")return H("open is required (boolean)");let f=await L(p.params.appSlug);if(!f)throw new l("Application not found",404);return await p.auth.setOpenRegistration(f,u.open),Z({open:u.open})}),a.post(`${h}/apps/:appSlug/auth/verify`,async(s,p)=>{let u=s.headers.get("x-wf-app-id"),f=s.headers.get("x-wf-app-key");if(!u||!f)return H("X-Wf-App-Id / X-Wf-App-Key required");let R=await w.select("id","slug","name").where({id:{eq:u},app_key:{eq:f}}).run();if(!R.length)throw new l("Invalid app credentials",403);let O=R[0];return Z({app:{id:String(O.id),slug:String(O.slug),name:String(O.name)}})}),a.post(`${h}/apps/:appSlug/auth/register`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.registerInApp({appSlug:p.params.appSlug,email:u.email??"",password:u.password??"",name:u.name,inviteToken:u.inviteToken});return B(f)}))},E.asAppAuth=()=>E,E}var It={tables:[{name:"_weifuwu_users",columns:{id:x.pk(i.uuid()),email:x.req(i.string()).meta({unique:!0}),password_hash:i.string(),name:i.string(),role:i.string(),tenant:i.string(),email_verified:i.boolean().meta({default:!1}),created_at:i.date().meta({default:"now"})},dropNotNull:["password_hash"]},{name:"_weifuwu_sessions",columns:{token_hash:i.string().meta({pk:!0}),user_id:x.req(i.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),app_id:i.string(),expires_at:i.date().meta({notNull:!0}),revoked_at:i.date(),created_at:i.date().meta({default:"now"})}},{name:"_weifuwu_apps",columns:{id:x.pk(i.uuid()),slug:x.req(i.string()).meta({unique:!0}),name:x.req(i.string()),owner_user_id:i.uuid().meta({references:"_weifuwu_users",onDelete:"cascade"}),open_registration:i.boolean().meta({default:!1}),created_at:i.date().meta({default:"now"}),app_key:i.string()}},{name:"_weifuwu_app_members",columns:{app_id:x.req(i.uuid()).meta({references:"_weifuwu_apps",onDelete:"cascade"}),user_id:x.req(i.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),role:i.string().meta({default:"member"}),invited_by:i.uuid(),joined_at:i.date().meta({default:"now"}),source:i.string(),last_login_at:i.date()},uniques:[["app_id","user_id"]]}]};export{N as BUILTIN_APP_ID,oe as USER_TABLES,It as WEIFUWU_USER_SCHEMA,Rt as userSystem};
1
+ var l=class extends Error{status;constructor(e,t){super(e),this.name="HttpError",this.status=t}};var Y=class extends Error{issues;constructor(e){super(e.map(t=>`${t.path}: ${t.message}`).join("; ")),this.name="ZodError",this.issues=e}},v=class{_meta={};get metaInfo(){return this._meta}meta(e){return this._meta={...this._meta,...e},this}parse(e){let t=this._parse(e);if(!t.ok)throw new Y([{path:t.path??"",message:t.message??"invalid"}]);return t.value}safeParse(e){let t=this._parse(e);return t.ok?{success:!0,data:t.value}:{success:!1,error:new Y([{path:t.path??"",message:t.message??"invalid"}])}}optional(){return new ye(this)}nullable(){return new _e(this)}default(e){return new ke(this,e)}refine(e,t){return new be(this,e,t)}transform(e){return new xe(this,e)}},Q=class extends v{_typeName(){return"string"}_parse(e){if(typeof e!="string")return{ok:!1,path:"",message:"expected string"};let t=e;return this._min!==void 0&&t.length<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t.length>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:this._pattern&&!this._pattern.test(t)?{ok:!1,path:"",message:"invalid pattern"}:this._email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)?{ok:!1,path:"",message:"invalid email"}:this._uuid&&!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)?{ok:!1,path:"",message:"invalid uuid"}:{ok:!0,value:t}}_min;_max;_pattern;_email=!1;_uuid=!1;min(e){return this._min=e,this}max(e){return this._max=e,this}regex(e,t){return this._pattern=e,this}email(){return this._email=!0,this}uuid(){return this._uuid=!0,this}},le=class extends v{_typeName(){return"number"}_parse(e){if(typeof e!="number"||Number.isNaN(e))return{ok:!1,path:"",message:"expected number"};let t=e;return this._int&&!Number.isInteger(t)?{ok:!1,path:"",message:"expected integer"}:this._min!==void 0&&t<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:{ok:!0,value:t}}_int=!1;_min;_max;int(){return this._int=!0,this}min(e){return this._min=e,this}max(e){return this._max=e,this}positive(){return this._min=0,this}},de=class extends v{_typeName(){return"boolean"}_parse(e){return typeof e!="boolean"?{ok:!1,path:"",message:"expected boolean"}:{ok:!0,value:e}}},ee=class extends v{lit;constructor(e){super(),this.lit=e}get value(){return this.lit}_typeName(){return"literal"}_parse(e){return e!==this.lit?{ok:!1,path:"",message:`expected literal ${JSON.stringify(this.lit)}`}:{ok:!0,value:e}}},te=class extends v{raw;constructor(e){super(),this.raw=e}get values(){return this.raw}_typeName(){return"enum"}_parse(e){return typeof e!="string"||!this.raw.includes(e)?{ok:!1,path:"",message:`expected one of ${this.raw.join(" | ")}`}:{ok:!0,value:e}}},pe=class extends v{_typeName(){return"datetime"}_parse(e){return typeof e!="string"||Number.isNaN(Date.parse(e))?{ok:!1,path:"",message:"expected datetime string (ISO)"}:{ok:!0,value:e}}},ce=class extends v{dims;constructor(e){super(),this.dims=e}_typeName(){return"vector"}_parse(e){return typeof e!="object"||e===null||!Array.isArray(e)||!e.every(t=>typeof t=="number")?{ok:!1,path:"",message:"expected number[] vector"}:this.dims>0&&e.length!==this.dims?{ok:!1,path:"",message:`expected vector(${this.dims})\u2014\u2014got ${e.length} dims`}:{ok:!0,value:e}}},ge=class extends v{_typeName(){return"json"}_parse(e){return{ok:!0,value:e}}},me=class n extends v{shape;constructor(e){super(),this.shape=e}_typeName(){return"object"}_parse(e){if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,path:"",message:"expected object"};let t={},y=[];for(let[w,x]of Object.entries(this.shape)){let A=x._parse(e[w]);if(!A.ok){y.push({path:w,message:A.message??""});continue}t[w]=A.value}return y.length?{ok:!1,path:y[0].path,message:y[0].message}:{ok:!0,value:t}}pick(e){let t={};for(let y of e)t[y]=this.shape[y];return new n(t)}omit(e){let t={};for(let[y,w]of Object.entries(this.shape))e.includes(y)||(t[y]=w);return new n(t)}extend(e){return new n({...this.shape,...e})}},fe=class extends v{inner;constructor(e){super(),this.inner=e}get itemSchema(){return this.inner}_typeName(){return"array"}_parse(e){if(!Array.isArray(e))return{ok:!1,path:"",message:"expected array"};if(this._min!==void 0&&e.length<this._min)return{ok:!1,path:"",message:`too small (min ${this._min})`};if(this._max!==void 0&&e.length>this._max)return{ok:!1,path:"",message:`too big (max ${this._max})`};let t=[];for(let y=0;y<e.length;y++){let w=this.inner._parse(e[y]);if(!w.ok)return{ok:!1,path:`${y}`,message:w.message};t.push(w.value)}return{ok:!0,value:t}}_min;_max;min(e){return this._min=e,this}max(e){return this._max=e,this}},we=class extends v{options;constructor(e){super(),this.options=e}_typeName(){return"union"}_parse(e){let t=null;for(let y of this.options){let w=y._parse(e);if(w.ok)return{ok:!0,value:w.value};t||(t={path:w.path??"",message:w.message??""})}return{ok:!1,path:"",message:`expected union (${t?.message})`}}},he=class extends v{disc;options;discValueOf;constructor(e,t,y){super(),this.disc=e,this.options=t,this.discValueOf=y}_typeName(){return"disUnion"}_parse(e){if(typeof e!="object"||e===null)return{ok:!1,path:"",message:"expected object"};let t=e[this.disc];for(let y of this.options)if(this.discValueOf(y.shape)===t){let w=y._parse(e);return w.ok?{ok:!0,value:w.value}:{ok:!1,path:`(${String(t)})`,message:w.message??""}}return{ok:!1,path:this.disc,message:`invalid discriminator value ${JSON.stringify(t)}`}}};function Ke(n,e){let t=n[e];if(t instanceof ee)return t.lit;if(t instanceof te)return t.values;throw new Error(`z.discriminatedUnion: \u5224\u522B\u5B57\u6BB5\u9700\u4E3A literal/enum\u2014\u2014\u5F97\u5230 ${t?._typeName?.()}`)}var ye=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"optional"}_parse(e){return e===void 0?{ok:!0,value:void 0}:this.inner._parse(e)}},_e=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"nullable"}_parse(e){return e===null?{ok:!0,value:null}:this.inner._parse(e)}},ke=class extends v{inner;d;constructor(e,t){super(),this.inner=e,this.d=t}_typeName(){return"default"}_parse(e){if(e===void 0){let t=typeof this.d=="function"?this.d():this.d;return this.inner._parse(t)}return this.inner._parse(e)}},be=class extends v{inner;fn;msg;constructor(e,t,y){super(),this.inner=e,this.fn=t,this.msg=y}_typeName(){return"effects"}_parse(e){let t=this.inner._parse(e);return t.ok?this.fn(t.value)?{ok:!0,value:t.value}:{ok:!1,path:"",message:this.msg??"refine failed"}:{ok:!1,path:t.path??"",message:t.message??""}}},xe=class extends v{inner;fn;constructor(e,t){super(),this.inner=e,this.fn=t}_typeName(){return"transform"}_parse(e){let t=this.inner._parse(e);return t.ok?{ok:!0,value:this.fn(t.value)}:{ok:!1,path:t.path??"",message:t.message??""}}},o={string:()=>new Q,number:()=>new le,boolean:()=>new de,literal:n=>new ee(n),enum:n=>new te(n),date:()=>new pe,uuid:()=>new Q().uuid(),json:()=>new ge,vector:n=>new ce(n),object:n=>new me(n),array:n=>new fe(n),union:n=>new we(n),discriminatedUnion:(n,e)=>new he(n,e,t=>Ke(t,n))};function z(n,e){return n.meta(e)}var S={pk:n=>z(n,{pk:!0,default:"random"}),req:n=>z(n,{notNull:!0}),col:(n,e)=>z(n,{column:e}),now:n=>z(n,{default:"now"}),unique:n=>z(n,{unique:!0}),dflt:(n,e)=>z(n,{default:e})};import{randomBytes as We,scrypt as ze,timingSafeEqual as He}from"node:crypto";import{promisify as Fe}from"node:util";var Ae=Fe(ze),Ve=64,ne={N:16384,r:8,p:1};async function J(n){let e=We(16),t=await Ae(n,e,Ve,ne);return`scrypt$${ne.N}$${ne.r}$${ne.p}$${e.toString("hex")}$${t.toString("hex")}`}async function re(n,e){let t=e.split("$");if(t.length!==6||t[0]!=="scrypt")return!1;let[,y,w,x,A,P]=t,Z=Buffer.from(P,"hex"),U=await Ae(n,Buffer.from(A,"hex"),Z.length,{N:Number(y),r:Number(w),p:Number(x)});return U.length===Z.length&&He(U,Z)}import{createHash as Je,createHmac as qe,randomBytes as Ge,timingSafeEqual as Xe}from"node:crypto";var Oe=n=>Buffer.from(JSON.stringify(n)).toString("base64url");function se(n,e,t){let y=Math.floor(Date.now()/1e3),w=`${Oe({alg:"HS256",typ:"JWT"})}.${Oe({...n,iat:y,exp:y+t})}`,x=qe("sha256",e).update(w).digest("base64url");return`${w}.${x}`}function ae(n,e){let t=n.split(".");if(t.length!==3)return null;let[y,w,x]=t,A=`${y}.${w}`,P=qe("sha256",e).update(A).digest("base64url"),Z=Buffer.from(x),U=Buffer.from(P);if(Z.length!==U.length||!Xe(Z,U))return null;try{let T=JSON.parse(Buffer.from(w,"base64url").toString("utf8"));return typeof T.exp=="number"&&T.exp<Date.now()/1e3?null:T}catch{return null}}function je(){return Ge(32).toString("hex")}function ie(n){return Je("sha256").update(n).digest("hex")}function N(n,e){return Response.json(n,{status:200,...e})}function L(n,e){return Response.json(n,{status:201,...e})}function $e(n){return new Response(null,{status:204,...n})}function H(n){return Response.json({error:n??"Bad Request"},{status:400})}import{randomBytes as Pe}from"node:crypto";var oe={users:{id:S.pk(o.uuid()),email:S.req(o.string()).meta({unique:!0}),password_hash:o.string().nullable(),name:o.string().nullable(),role:o.string().nullable(),tenant:o.string().nullable(),email_verified:o.boolean().meta({notNull:!0,default:!1}),created_at:S.now(o.date())},sessions:{token_hash:S.req(o.string()),user_id:S.req(o.uuid()),app_id:o.string().nullable(),expires_at:S.req(o.date()),revoked_at:o.date().nullable(),created_at:S.now(o.date())},apps:{id:S.pk(o.uuid()),slug:S.req(o.string()).meta({unique:!0}),name:S.req(o.string()),owner_user_id:o.uuid().nullable(),open_registration:o.boolean().meta({notNull:!0,default:!1}),app_key:o.string().nullable(),created_at:S.now(o.date())},members:{app_id:S.req(o.uuid()),user_id:S.req(o.uuid()),role:o.string().meta({notNull:!0,default:"member"}),invited_by:o.uuid().nullable(),joined_at:S.now(o.date()),source:o.string().nullable(),last_login_at:o.date().nullable()}},Ye="_weifuwu_users",Qe="_weifuwu_sessions",Se="_weifuwu_apps",$="00000000-0000-4000-8000-0000000000b1",Ze="_weifuwu_app_members",Ne=8,et=1024;function Te(n){if(n.length<Ne)throw new l(`password must be at least ${Ne} characters`,400);if(n.length>et)throw new l("password is too long",400)}function D(n){return n.trim().toLowerCase()}var Ue=null;async function Me(n){Ue??=await J("dummy-timing-equalize-password"),await re(n,Ue)}function Rt(n){let e=n.orm,t=e.table(Ye,oe.users),y=e.table(Qe,oe.sessions),w=e.table(Se,oe.apps),x=e.table(Ze,oe.members),A=n.secret??process.env.AUTH_SECRET;if(!A)throw new Error("userSystem: secret is required \u2014 pass options.secret or set AUTH_SECRET (at least 32 chars)");if(A.length<16)throw new Error("userSystem: secret must be at least 16 characters");let P=A,Z=n.hooks,U=n.allowedRoles??["owner","admin","member","viewer"],T=n.sso??null,ve=n.inviteRoles??U;function Re(a){if(a!==void 0&&!U.includes(a))throw new l(`invalid role: ${a}\uFF08allowed: ${U.join("/")}\uFF09`,403)}let De=n.accessTtlSeconds??3600,Ce=n.refreshTtlDays??30,Ee=n.prefix??"/api/auth",G=a=>({id:String(a.id),email:a.email,name:a.name,role:a.role,tenant:a.tenant}),Be=168*3600;async function ue(a){let m=await t.select("id","email","name","role","tenant").where({id:{eq:a}}).run();return m.length?m[0]:null}async function F(a,m){(await x.select("role").where({app_id:{eq:$},user_id:{eq:a}}).run()).length||await x.insert({app_id:$,user_id:a,role:"member",invited_by:null,source:m}).onConflict().run()}async function O(a,m){let h=await x.select("role").where({app_id:{eq:a},user_id:{eq:m}}).run();return h.length?String(h[0].role):null}async function C(a){let m=await w.select("id").where({slug:{eq:a}}).run();return m.length?String(m[0].id):null}async function K(a){let m=await t.select("id","email","name","role","tenant").where({email:{eq:a}}).run();return m.length?m[0]:null}async function Ie(a){return(await e.query.from(`${Ze} m`).select("m.app_id","m.role","a.id","a.slug","a.name").join(`${Se} a`,{"a.id":{col:"m.app_id"}}).where({"m.user_id":{eq:a}}).run()).filter(h=>!String(h.slug).startsWith("_")).map(h=>({id:String(h.id),slug:String(h.slug),name:String(h.name),role:String(h.role)}))}async function E(a,m){let h=se({sub:a.id,...a.email?{email:a.email}:{},...a.name?{name:a.name}:{},...m?.appId?{appId:m.appId,role:m.role}:{},...!m?.appId&&a.tenant?{tenantId:a.tenant}:{}},P,De),p=je(),s=new Date(Date.now()+Ce*24*3600*1e3);return await y.insert({token_hash:ie(p),user_id:a.id,expires_at:s.toISOString(),...m?.appId?{app_id:m.appId}:{}}).run(),{token:h,refreshToken:p}}async function Le(a){let m=await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(a)},revoked_at:{isNull:!0}}).returning("user_id","expires_at","app_id").run();if(!m.length)throw new l("Invalid refresh token",401);let h=m[0];if(new Date(String(h.expires_at))<new Date)throw new l("Refresh token expired",401);let p=await ue(h.user_id);if(!p)throw new l("User not found",401);return{user:p,appId:h.app_id?String(h.app_id):void 0}}let B=(async(a,m,h)=>{let p=null,s=null,d={},i,c,R,q=a.headers.get("authorization"),M=null;if(q?.startsWith("Bearer "))M=q.slice(7);else if(a.method==="GET"){let r=new URL(a.url??"","http://localhost"),u=r.searchParams.get("ticket");if(u){let f=ae(u,P);f?.type==="download"&&f?.sub&&(s=await ue(String(f.sub)),p=s,d.userId=String(f.sub),f.appId!=null&&(c=f.appId,d.appId=String(f.appId)))}else M=r.searchParams.get("token")}if(M&&!s){let r=ae(M,P);if(r?.sub){s=await ue(String(r.sub)),p=s;for(let[u,f]of Object.entries(r))u==="sub"||u==="iat"||u==="exp"||u==="type"||(d[u]=f);d.userId=String(r.sub),i=r.tenantId,c=r.appId,R=r.role}}m.user=s,c!=null&&(m.appId=String(c)),m.session=c!=null?{userId:String(s.id),appId:String(c),role:String(R??"")}:null,i!=null&&(m.tenantId=String(i));let W={...d,async register(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let u=D(r.email),f=await J(r.password),g=await t.insert({email:u,password_hash:f,name:r.name??null,role:null,tenant:r.tenant??null}).run(),k=G(g[0]),b=await E(k);return await F(k.id,"register"),p=k,{...b,user:k}},async registerWithApp(r){let u=await this.register({email:r.email,password:r.password,name:r.name});await F(u.user.id,"register");let f=(r.appSlug??r.email.split("@")[1]??"default").trim().toLowerCase(),g=f;for(let _=1;_<=200&&await C(g);_++)g=`${f}-${_}`;let k=await this.createApp({slug:g,name:r.appName??`${r.name??"\u7528\u6237"} \u7684\u5E94\u7528`,openRegistration:!1}),b=await E(u.user,{appId:k.id,role:"owner"});return await Z?.onRegisterApp?.(u.user.id,k),{...b,user:u.user,app:{...k,role:"owner"}}},async login(r,u){let g=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(r)}}).run())[0];if(!g||g.password_hash==null)throw await Me(u),new l("Invalid email or password",401);if(!await re(u,String(g.password_hash)))throw new l("Invalid email or password",401);let b={id:String(g.id),email:String(g.email),name:g.name,role:g.role,tenant:g.tenant},_=await E(b),j=await Ie(b.id);return p=b,{..._,user:b,apps:j}},async logout(r){await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(r)}}).run()},requireAuth(){if(!s)throw new l("Unauthorized",401);return s},async setPassword(r,u){Te(u);let f=await J(u);await t.update({password_hash:f}).where({id:{eq:r}}).run()},createToken(r,u,f){return se({type:r,...u},P,f.ttlSeconds)},async createApp(r){if(!p)throw new l("Unauthorized",401);if(!r.slug||!r.name)throw new l("slug and name are required",400);let u=r.slug.trim().toLowerCase();if(u.startsWith("_"))throw new l("slug \u4EE5 _ \u5F00\u5934\u4E3A\u7CFB\u7EDF\u4FDD\u7559\u540D",400);if(!await O($,p.id))throw new l("\u4EC5\u5E94\u7528\u7BA1\u7406\u9762\uFF08_builtin\uFF09\u6210\u5458\u53EF\u521B\u5EFA\u5E94\u7528",403);let g=Pe(32).toString("hex"),b=(await w.insert({slug:u,name:r.name,owner_user_id:p.id,open_registration:r.openRegistration??!1,app_key:g}).run())[0];return await x.insert({app_id:b.id,user_id:p.id,role:"owner",invited_by:p.id}).run(),b},async registerInApp(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let u=await w.select("id","open_registration").where({slug:{eq:r.appSlug}}).run();if(!u.length)throw new l("Application not found",404);let f=u[0],g=String(f.id),k=D(r.email),b="member";if(!f.open_registration){if(!r.inviteToken)throw new l("Registration closed for this application",403);let I=ae(r.inviteToken,P);if(!I||I.type!=="app-invite"||I.appId!==g)throw new l("Invalid invite",403);if(I.email&&String(I.email)!==k)throw new l("Invite email mismatch",403);typeof I.role=="string"&&I.role!=="owner"&&(Re(I.role),b=I.role)}let _=await K(k);if(!_){let I=await J(r.password),X=await t.insert({email:k,password_hash:I,name:r.name??null,role:null,tenant:null}).onConflict("email").run();if(_=X.length?G(X[0]):await K(k),!_)throw new l("User creation failed",500)}await F(_.id,"register"),await O(g,_.id)||await x.insert({app_id:g,user_id:_.id,role:b,invited_by:p?.id??null,source:"invite"}).onConflict().run();let V=await E(_,{appId:g,role:b});return await x.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:g},user_id:{eq:_.id}}).run(),p=_,await Z?.onJoinApp?.(_.id,g,b),{...V,user:_}},async loginApp(r,u,f){let g=await w.select("id").where({slug:{eq:r}}).run();if(!g.length)throw new l("Application not found",404);let k=String(g[0].id),_=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(u)}}).run())[0];if(!_||_.password_hash==null)throw await Me(f),new l("Invalid email or password",401);if(!await re(f,String(_.password_hash)))throw new l("Invalid email or password",401);let V=await O(k,String(_.id));if(!V)throw new l("Not a member of this application",401);let I={id:String(_.id),email:String(_.email),name:_.name,role:_.role,tenant:_.tenant},X=await E(I,{appId:k,role:V});return await x.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:k},user_id:{eq:I.id}}).run(),p=I,{...X,user:I,role:V}},async ssoLogin(r,u){let f=D(r),g=await K(f);if(!g){let _=await t.insert({email:f,password_hash:null,name:u?.name??null,role:null,tenant:null}).onConflict("email").run();if(g=_.length?G(_[0]):await K(f),!g)throw new l("User creation failed",500)}await F(g.id,"sso");let k;u?.appId&&(await O(u.appId,g.id)||await x.insert({app_id:u.appId,user_id:g.id,role:"member",invited_by:null,source:"sso"}).onConflict().run(),k=u.appId,await x.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:u.appId},user_id:{eq:g.id}}).run());let b=await E(g,k?{appId:k,role:"member"}:void 0);return p=g,{...b,user:g}},async createInvite(r,u){if(r===$)throw new l("_builtin \u4E0D\u8D70\u9080\u8BF7\u6D41\uFF08\u4EFB\u547D\u5236\uFF09",403);if(u.role!==void 0&&!ve.includes(u.role))throw new l(`invite role not allowed: ${u.role}\uFF08allowed: ${ve.join("/")}\uFF09`,403);if(!p)throw new l("Unauthorized",401);if(await O(r,p.id)!=="owner")throw new l("Owner only",403);let g=u.role&&u.role!=="owner"?u.role:"member";return{inviteToken:se({type:"app-invite",appId:r,...u.email?{email:D(u.email)}:{},role:g},P,Be)}},async addMember(r,u,f){if(Re(f),r===$&&f!==void 0&&f==="viewer")throw new l("_builtin \u65E0\u53EA\u8BFB\uFF08viewer \u7981\u2014\u2014\u7BA1\u7406\u9762\u8EAB\u4EFD\uFF09",403);if(!p)throw new l("Unauthorized",401);if(await O(r,p.id)!=="owner")throw new l("Owner only",403);let k=await t.select("id").where({email:{eq:D(u)}}).run();if(!k.length)throw new l("User not found \u2014 invite them to register first",400);let b=f&&f!=="owner"?f:"member";await x.insert({app_id:r,user_id:String(k[0].id),role:b,invited_by:p.id}).run()},async setOpenRegistration(r,u){if(!p)throw new l("Unauthorized",401);if(r===$)throw new l("_builtin \u6CE8\u518C\u5F00\u5173\u6052 false",403);if(await O(r,p.id)!=="owner")throw new l("Owner only",403);await w.update({open_registration:u}).where({id:{eq:r}}).run()},async listMyApps(){if(!p)throw new l("Unauthorized",401);return Ie(p.id)},async requireApp(r){if(!p)throw new l("Unauthorized",401);let u=await O(r,p.id);if(!u)throw new l("Not a member of this application",403);return{userId:p.id,appId:r,role:u}}};return m.auth=W,h(a,m)});return B.__meta={injects:["user","auth"],depends:[]},B.seedBuiltinOwners=async a=>{let m={owner:null,admins:[]};for(let h of a){let p=D(h);if(!p)continue;let s=await K(p);if(!s){let i=await t.insert({email:p,password_hash:null,name:null,role:null,tenant:null}).onConflict("email").run();s=i.length?G(i[0]):await K(p)}if(!s)continue;let d=await x.select("role").where({app_id:{eq:$},user_id:{eq:s.id}}).run();if(d.length){String(d[0].role??"")==="owner"?m.owner=String(s.id):m.admins.push(String(s.id));continue}if(m.owner)await x.insert({app_id:$,user_id:String(s.id),role:"admin",invited_by:m.owner,source:"seed"}).run(),m.admins.push(String(s.id));else{await x.insert({app_id:$,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run(),m.owner=String(s.id);let i=await C("_default");i&&(await O(i,String(s.id))||await x.insert({app_id:i,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run())}}return m},B.migrate=async()=>{(await w.select("id").where({slug:{eq:"_builtin"}}).run()).length||await e.query.insert(Se).values({id:$,slug:"_builtin",name:"System",owner_user_id:null,open_registration:!1}).returning("id").run();let m=await w.select("id").where({app_key:{isNull:!0}}).run();for(let d of m)await w.update({app_key:Pe(32).toString("hex")}).where({id:{eq:String(d.id)}}).run();(await w.select("id").where({slug:{eq:"_default"}}).run()).length||await w.insert({slug:"_default",name:"Default",owner_user_id:null,open_registration:!1}).run();let p=await C("_default");if(p){let d=await x.select("user_id").where({app_id:{eq:$},role:{eq:"owner"}}).run();for(let i of d){let c=String(i.user_id);await O(p,c)||await x.insert({app_id:p,user_id:c,role:"owner",invited_by:null,source:"migrate"}).onConflict().run()}}let s=await t.select("id").run();for(let d of s)await F(String(d.id),"migrate")},B.routes=(a,m)=>{let h=m?.prefix??Ee,p=new Set(m?.exclude??[]);if(p.has("register")||a.post(`${h}/register`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.register(i);return L(c)}),p.has("register")||a.post(`${h}/register-app`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.registerWithApp(i);return L(c)}),p.has("login")||a.post(`${h}/login`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(!i.email||!i.password)return H("email and password are required");let c=await d.auth.login(i.email,i.password);return N(c)}),p.has("logout")||a.post(`${h}/logout`,async(s,d)=>{let i=await s.json().catch(()=>({}));return i.refreshToken?(await d.auth.logout(i.refreshToken),$e()):H("refreshToken is required")}),p.has("refresh")||a.post(`${h}/refresh`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(!i.refreshToken)throw new l("refreshToken is required",400);let{user:c,appId:R}=await Le(i.refreshToken),q;if(R){let W=await O(R,c.id);W&&(q={appId:R,role:W})}let M=await E(c,q);return N({...M,user:c})}),p.has("me")||a.get(`${h}/me`,async(s,d)=>{if(!d.user)throw new l("Unauthorized",401);return N({user:d.user,session:d.session??null})}),T){let s=`${h}/apps/_builtin/auth/sso`;a.get(`${s}/enabled`,async()=>N({enabled:!0,appSlug:T.defaultAppSlug??null})),a.get(`${s}/login`,async d=>{let c=new URL(d.url??"","http://localhost").searchParams.get("app")??T.defaultAppSlug,R=new URLSearchParams({response_type:"code",client_id:T.clientId,redirect_uri:T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${new URL(d.url,"http://localhost").origin}${s}/callback`,scope:T.scope??"openid email profile",state:c??"sso"});return new Response(null,{status:302,headers:{Location:`${T.issuer.replace(/\/$/,"")}/authorize?${R}`}})}),a.get(`${s}/callback`,async(d,i)=>{let c=new URL(d.url??"","http://localhost"),R=c.searchParams.get("code");if(!R)throw new l("SSO \u56DE\u8C03\u7F3A\u5C11 code",400);let q=T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${c.origin}${s}/callback`,M=await fetch(`${T.issuer.replace(/\/$/,"")}/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:R,redirect_uri:q,client_id:T.clientId,client_secret:T.clientSecret})});if(!M.ok)throw new l("SSO token \u4EA4\u6362\u5931\u8D25",401);let W=await M.json(),r=await fetch(`${T.issuer.replace(/\/$/,"")}/userinfo`,{headers:{Authorization:`Bearer ${W.access_token}`}});if(!r.ok)throw new l("SSO \u7528\u6237\u4FE1\u606F\u83B7\u53D6\u5931\u8D25",401);let u=await r.json(),f=(u.email??"").trim().toLowerCase();if(!f)throw new l("SSO \u672A\u8FD4\u56DE\u90AE\u7BB1\uFF08\u9700\u8981 email scope\uFF09",401);let g=c.searchParams.get("state"),k;if(g&&g!=="sso"){let j=await C(g);j&&(k=j)}let b=await i.auth.ssoLogin(f,{appId:k,name:u.name});await Z?.onSsoLogin?.(b.user.id,k);let _={token:b.token,refreshToken:b.refreshToken,user:b.user};if(T.renderCallback){let j=T.renderCallback(_);return typeof j=="string"?new Response(j,{headers:{"Content-Type":"text/html; charset=utf-8"}}):j}return N(_)})}p.has("apps")||(a.post(`${h}/apps`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.createApp(i);return L({app:c})}),a.get(`${h}/apps`,async(s,d)=>{let i=await d.auth.listMyApps();return N({apps:i})}),a.post(`${h}/apps/:appSlug/auth/members`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await C(d.params.appSlug);if(!c)throw new l("Application not found",404);return await d.auth.addMember(c,i.email??"",i.role),L({ok:!0})}),a.post(`${h}/apps/:appSlug/auth/invites`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await C(d.params.appSlug);if(!c)throw new l("Application not found",404);let R=await d.auth.createInvite(c,{email:i.email,role:i.role});return L(R)}),a.post(`${h}/apps/:appSlug/auth/login`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(!i.email||!i.password)return H("email and password are required");let c=await d.auth.loginApp(d.params.appSlug,i.email,i.password);return N(c)}),a.patch(`${h}/apps/:appSlug/auth/registration`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(typeof i.open!="boolean")return H("open is required (boolean)");let c=await C(d.params.appSlug);if(!c)throw new l("Application not found",404);return await d.auth.setOpenRegistration(c,i.open),N({open:i.open})}),a.post(`${h}/apps/:appSlug/auth/verify`,async(s,d)=>{let i=s.headers.get("x-wf-app-id"),c=s.headers.get("x-wf-app-key");if(!i||!c)return H("X-Wf-App-Id / X-Wf-App-Key required");let R=await w.select("id","slug","name").where({id:{eq:i},app_key:{eq:c}}).run();if(!R.length)throw new l("Invalid app credentials",403);let q=R[0];return N({app:{id:String(q.id),slug:String(q.slug),name:String(q.name)}})}),a.post(`${h}/apps/:appSlug/auth/register`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.registerInApp({appSlug:d.params.appSlug,email:i.email??"",password:i.password??"",name:i.name,inviteToken:i.inviteToken});return L(c)}))},B.asAppAuth=()=>B,B}var It={tables:[{name:"_weifuwu_users",columns:{id:S.pk(o.uuid()),email:S.req(o.string()).meta({unique:!0}),password_hash:o.string(),name:o.string(),role:o.string(),tenant:o.string(),email_verified:o.boolean().meta({default:!1}),created_at:o.date().meta({default:"now"})},dropNotNull:["password_hash"]},{name:"_weifuwu_sessions",columns:{token_hash:o.string().meta({pk:!0}),user_id:S.req(o.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),app_id:o.string(),expires_at:o.date().meta({notNull:!0}),revoked_at:o.date(),created_at:o.date().meta({default:"now"})}},{name:"_weifuwu_apps",columns:{id:S.pk(o.uuid()),slug:S.req(o.string()).meta({unique:!0}),name:S.req(o.string()),owner_user_id:o.uuid().meta({references:"_weifuwu_users",onDelete:"cascade"}),open_registration:o.boolean().meta({default:!1}),created_at:o.date().meta({default:"now"}),app_key:o.string()}},{name:"_weifuwu_app_members",columns:{app_id:S.req(o.uuid()).meta({references:"_weifuwu_apps",onDelete:"cascade"}),user_id:S.req(o.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),role:o.string().meta({default:"member"}),invited_by:o.uuid(),joined_at:o.date().meta({default:"now"}),source:o.string(),last_login_at:o.date()},uniques:[["app_id","user_id"]]}]};export{$ as BUILTIN_APP_ID,oe as USER_TABLES,It as WEIFUWU_USER_SCHEMA,Rt as userSystem};
@@ -939,6 +939,18 @@ export function userSystem(options: UserSystemOptions): UserSystemClient {
939
939
  open_registration: false,
940
940
  }).run()
941
941
  }
942
+ // _default 存量补挂(0.95.1 实证:_default 建于 _builtin owner 之后——首 owner 关联分支
943
+ // 永远错过 → UI 单应用模式(agent-platform = _default)登录 401 "Not a member")
944
+ // 定案:_builtin owner(超级管理员)→ _default owner 成员——幂等循环补足(存量库修复面)
945
+ const defAppId = await findAppIdBySlug('_default')
946
+ if (defAppId) {
947
+ const owners = await M.select('user_id').where({ app_id: { eq: BUILTIN_APP_ID }, role: { eq: 'owner' } }).run()
948
+ for (const o of owners) {
949
+ const uid = String(o.user_id)
950
+ if (await findMemberRole(defAppId, uid)) continue
951
+ await M.insert({ app_id: defAppId, user_id: uid, role: 'owner', invited_by: null, source: 'migrate' }).onConflict().run()
952
+ }
953
+ }
942
954
  // 存量补挂(定案:全员应用管理面入册——旧库 users 无 _builtin 成员行着补——
943
955
  // query builder 循环——memory/真库双后端一致——迁移期一次性 O(N) 可接受)
944
956
  const leftover = await U.select('id').run()
@@ -3214,6 +3214,15 @@ function userSystem(options) {
3214
3214
  open_registration: false
3215
3215
  }).run();
3216
3216
  }
3217
+ const defAppId = await findAppIdBySlug("_default");
3218
+ if (defAppId) {
3219
+ const owners = await M.select("user_id").where({ app_id: { eq: BUILTIN_APP_ID }, role: { eq: "owner" } }).run();
3220
+ for (const o of owners) {
3221
+ const uid = String(o.user_id);
3222
+ if (await findMemberRole(defAppId, uid)) continue;
3223
+ await M.insert({ app_id: defAppId, user_id: uid, role: "owner", invited_by: null, source: "migrate" }).onConflict().run();
3224
+ }
3225
+ }
3217
3226
  const leftover = await U.select("id").run();
3218
3227
  for (const u of leftover) {
3219
3228
  await ensureBuiltinMember(String(u.id), "migrate");
@@ -1 +1 @@
1
- var l=class extends Error{status;constructor(e,t){super(e),this.name="HttpError",this.status=t}};var Y=class extends Error{issues;constructor(e){super(e.map(t=>`${t.path}: ${t.message}`).join("; ")),this.name="ZodError",this.issues=e}},v=class{_meta={};get metaInfo(){return this._meta}meta(e){return this._meta={...this._meta,...e},this}parse(e){let t=this._parse(e);if(!t.ok)throw new Y([{path:t.path??"",message:t.message??"invalid"}]);return t.value}safeParse(e){let t=this._parse(e);return t.ok?{success:!0,data:t.value}:{success:!1,error:new Y([{path:t.path??"",message:t.message??"invalid"}])}}optional(){return new ye(this)}nullable(){return new ke(this)}default(e){return new _e(this,e)}refine(e,t){return new be(this,e,t)}transform(e){return new xe(this,e)}},Q=class extends v{_typeName(){return"string"}_parse(e){if(typeof e!="string")return{ok:!1,path:"",message:"expected string"};let t=e;return this._min!==void 0&&t.length<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t.length>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:this._pattern&&!this._pattern.test(t)?{ok:!1,path:"",message:"invalid pattern"}:this._email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)?{ok:!1,path:"",message:"invalid email"}:this._uuid&&!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)?{ok:!1,path:"",message:"invalid uuid"}:{ok:!0,value:t}}_min;_max;_pattern;_email=!1;_uuid=!1;min(e){return this._min=e,this}max(e){return this._max=e,this}regex(e,t){return this._pattern=e,this}email(){return this._email=!0,this}uuid(){return this._uuid=!0,this}},le=class extends v{_typeName(){return"number"}_parse(e){if(typeof e!="number"||Number.isNaN(e))return{ok:!1,path:"",message:"expected number"};let t=e;return this._int&&!Number.isInteger(t)?{ok:!1,path:"",message:"expected integer"}:this._min!==void 0&&t<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:{ok:!0,value:t}}_int=!1;_min;_max;int(){return this._int=!0,this}min(e){return this._min=e,this}max(e){return this._max=e,this}positive(){return this._min=0,this}},de=class extends v{_typeName(){return"boolean"}_parse(e){return typeof e!="boolean"?{ok:!1,path:"",message:"expected boolean"}:{ok:!0,value:e}}},ee=class extends v{lit;constructor(e){super(),this.lit=e}get value(){return this.lit}_typeName(){return"literal"}_parse(e){return e!==this.lit?{ok:!1,path:"",message:`expected literal ${JSON.stringify(this.lit)}`}:{ok:!0,value:e}}},te=class extends v{raw;constructor(e){super(),this.raw=e}get values(){return this.raw}_typeName(){return"enum"}_parse(e){return typeof e!="string"||!this.raw.includes(e)?{ok:!1,path:"",message:`expected one of ${this.raw.join(" | ")}`}:{ok:!0,value:e}}},pe=class extends v{_typeName(){return"datetime"}_parse(e){return typeof e!="string"||Number.isNaN(Date.parse(e))?{ok:!1,path:"",message:"expected datetime string (ISO)"}:{ok:!0,value:e}}},ce=class extends v{dims;constructor(e){super(),this.dims=e}_typeName(){return"vector"}_parse(e){return typeof e!="object"||e===null||!Array.isArray(e)||!e.every(t=>typeof t=="number")?{ok:!1,path:"",message:"expected number[] vector"}:this.dims>0&&e.length!==this.dims?{ok:!1,path:"",message:`expected vector(${this.dims})\u2014\u2014got ${e.length} dims`}:{ok:!0,value:e}}},ge=class extends v{_typeName(){return"json"}_parse(e){return{ok:!0,value:e}}},me=class n extends v{shape;constructor(e){super(),this.shape=e}_typeName(){return"object"}_parse(e){if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,path:"",message:"expected object"};let t={},y=[];for(let[w,S]of Object.entries(this.shape)){let A=S._parse(e[w]);if(!A.ok){y.push({path:w,message:A.message??""});continue}t[w]=A.value}return y.length?{ok:!1,path:y[0].path,message:y[0].message}:{ok:!0,value:t}}pick(e){let t={};for(let y of e)t[y]=this.shape[y];return new n(t)}omit(e){let t={};for(let[y,w]of Object.entries(this.shape))e.includes(y)||(t[y]=w);return new n(t)}extend(e){return new n({...this.shape,...e})}},fe=class extends v{inner;constructor(e){super(),this.inner=e}get itemSchema(){return this.inner}_typeName(){return"array"}_parse(e){if(!Array.isArray(e))return{ok:!1,path:"",message:"expected array"};if(this._min!==void 0&&e.length<this._min)return{ok:!1,path:"",message:`too small (min ${this._min})`};if(this._max!==void 0&&e.length>this._max)return{ok:!1,path:"",message:`too big (max ${this._max})`};let t=[];for(let y=0;y<e.length;y++){let w=this.inner._parse(e[y]);if(!w.ok)return{ok:!1,path:`${y}`,message:w.message};t.push(w.value)}return{ok:!0,value:t}}_min;_max;min(e){return this._min=e,this}max(e){return this._max=e,this}},we=class extends v{options;constructor(e){super(),this.options=e}_typeName(){return"union"}_parse(e){let t=null;for(let y of this.options){let w=y._parse(e);if(w.ok)return{ok:!0,value:w.value};t||(t={path:w.path??"",message:w.message??""})}return{ok:!1,path:"",message:`expected union (${t?.message})`}}},he=class extends v{disc;options;discValueOf;constructor(e,t,y){super(),this.disc=e,this.options=t,this.discValueOf=y}_typeName(){return"disUnion"}_parse(e){if(typeof e!="object"||e===null)return{ok:!1,path:"",message:"expected object"};let t=e[this.disc];for(let y of this.options)if(this.discValueOf(y.shape)===t){let w=y._parse(e);return w.ok?{ok:!0,value:w.value}:{ok:!1,path:`(${String(t)})`,message:w.message??""}}return{ok:!1,path:this.disc,message:`invalid discriminator value ${JSON.stringify(t)}`}}};function Ke(n,e){let t=n[e];if(t instanceof ee)return t.lit;if(t instanceof te)return t.values;throw new Error(`z.discriminatedUnion: \u5224\u522B\u5B57\u6BB5\u9700\u4E3A literal/enum\u2014\u2014\u5F97\u5230 ${t?._typeName?.()}`)}var ye=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"optional"}_parse(e){return e===void 0?{ok:!0,value:void 0}:this.inner._parse(e)}},ke=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"nullable"}_parse(e){return e===null?{ok:!0,value:null}:this.inner._parse(e)}},_e=class extends v{inner;d;constructor(e,t){super(),this.inner=e,this.d=t}_typeName(){return"default"}_parse(e){if(e===void 0){let t=typeof this.d=="function"?this.d():this.d;return this.inner._parse(t)}return this.inner._parse(e)}},be=class extends v{inner;fn;msg;constructor(e,t,y){super(),this.inner=e,this.fn=t,this.msg=y}_typeName(){return"effects"}_parse(e){let t=this.inner._parse(e);return t.ok?this.fn(t.value)?{ok:!0,value:t.value}:{ok:!1,path:"",message:this.msg??"refine failed"}:{ok:!1,path:t.path??"",message:t.message??""}}},xe=class extends v{inner;fn;constructor(e,t){super(),this.inner=e,this.fn=t}_typeName(){return"transform"}_parse(e){let t=this.inner._parse(e);return t.ok?{ok:!0,value:this.fn(t.value)}:{ok:!1,path:t.path??"",message:t.message??""}}},i={string:()=>new Q,number:()=>new le,boolean:()=>new de,literal:n=>new ee(n),enum:n=>new te(n),date:()=>new pe,uuid:()=>new Q().uuid(),json:()=>new ge,vector:n=>new ce(n),object:n=>new me(n),array:n=>new fe(n),union:n=>new we(n),discriminatedUnion:(n,e)=>new he(n,e,t=>Ke(t,n))};function z(n,e){return n.meta(e)}var x={pk:n=>z(n,{pk:!0,default:"random"}),req:n=>z(n,{notNull:!0}),col:(n,e)=>z(n,{column:e}),now:n=>z(n,{default:"now"}),unique:n=>z(n,{unique:!0}),dflt:(n,e)=>z(n,{default:e})};import{randomBytes as We,scrypt as ze,timingSafeEqual as He}from"node:crypto";import{promisify as Fe}from"node:util";var Ae=Fe(ze),Ve=64,ne={N:16384,r:8,p:1};async function J(n){let e=We(16),t=await Ae(n,e,Ve,ne);return`scrypt$${ne.N}$${ne.r}$${ne.p}$${e.toString("hex")}$${t.toString("hex")}`}async function re(n,e){let t=e.split("$");if(t.length!==6||t[0]!=="scrypt")return!1;let[,y,w,S,A,q]=t,$=Buffer.from(q,"hex"),U=await Ae(n,Buffer.from(A,"hex"),$.length,{N:Number(y),r:Number(w),p:Number(S)});return U.length===$.length&&He(U,$)}import{createHash as Je,createHmac as je,randomBytes as Ge,timingSafeEqual as Xe}from"node:crypto";var Oe=n=>Buffer.from(JSON.stringify(n)).toString("base64url");function se(n,e,t){let y=Math.floor(Date.now()/1e3),w=`${Oe({alg:"HS256",typ:"JWT"})}.${Oe({...n,iat:y,exp:y+t})}`,S=je("sha256",e).update(w).digest("base64url");return`${w}.${S}`}function ae(n,e){let t=n.split(".");if(t.length!==3)return null;let[y,w,S]=t,A=`${y}.${w}`,q=je("sha256",e).update(A).digest("base64url"),$=Buffer.from(S),U=Buffer.from(q);if($.length!==U.length||!Xe($,U))return null;try{let T=JSON.parse(Buffer.from(w,"base64url").toString("utf8"));return typeof T.exp=="number"&&T.exp<Date.now()/1e3?null:T}catch{return null}}function qe(){return Ge(32).toString("hex")}function ie(n){return Je("sha256").update(n).digest("hex")}function Z(n,e){return Response.json(n,{status:200,...e})}function B(n,e){return Response.json(n,{status:201,...e})}function $e(n){return new Response(null,{status:204,...n})}function H(n){return Response.json({error:n??"Bad Request"},{status:400})}import{randomBytes as Pe}from"node:crypto";var oe={users:{id:x.pk(i.uuid()),email:x.req(i.string()).meta({unique:!0}),password_hash:i.string().nullable(),name:i.string().nullable(),role:i.string().nullable(),tenant:i.string().nullable(),email_verified:i.boolean().meta({notNull:!0,default:!1}),created_at:x.now(i.date())},sessions:{token_hash:x.req(i.string()),user_id:x.req(i.uuid()),app_id:i.string().nullable(),expires_at:x.req(i.date()),revoked_at:i.date().nullable(),created_at:x.now(i.date())},apps:{id:x.pk(i.uuid()),slug:x.req(i.string()).meta({unique:!0}),name:x.req(i.string()),owner_user_id:i.uuid().nullable(),open_registration:i.boolean().meta({notNull:!0,default:!1}),app_key:i.string().nullable(),created_at:x.now(i.date())},members:{app_id:x.req(i.uuid()),user_id:x.req(i.uuid()),role:i.string().meta({notNull:!0,default:"member"}),invited_by:i.uuid().nullable(),joined_at:x.now(i.date()),source:i.string().nullable(),last_login_at:i.date().nullable()}},Ye="_weifuwu_users",Qe="_weifuwu_sessions",Se="_weifuwu_apps",N="00000000-0000-4000-8000-0000000000b1",Ze="_weifuwu_app_members",Ne=8,et=1024;function Te(n){if(n.length<Ne)throw new l(`password must be at least ${Ne} characters`,400);if(n.length>et)throw new l("password is too long",400)}function D(n){return n.trim().toLowerCase()}var Ue=null;async function Me(n){Ue??=await J("dummy-timing-equalize-password"),await re(n,Ue)}function Rt(n){let e=n.orm,t=e.table(Ye,oe.users),y=e.table(Qe,oe.sessions),w=e.table(Se,oe.apps),S=e.table(Ze,oe.members),A=n.secret??process.env.AUTH_SECRET;if(!A)throw new Error("userSystem: secret is required \u2014 pass options.secret or set AUTH_SECRET (at least 32 chars)");if(A.length<16)throw new Error("userSystem: secret must be at least 16 characters");let q=A,$=n.hooks,U=n.allowedRoles??["owner","admin","member","viewer"],T=n.sso??null,ve=n.inviteRoles??U;function Re(a){if(a!==void 0&&!U.includes(a))throw new l(`invalid role: ${a}\uFF08allowed: ${U.join("/")}\uFF09`,403)}let De=n.accessTtlSeconds??3600,Ce=n.refreshTtlDays??30,Ee=n.prefix??"/api/auth",G=a=>({id:String(a.id),email:a.email,name:a.name,role:a.role,tenant:a.tenant}),Be=168*3600;async function ue(a){let g=await t.select("id","email","name","role","tenant").where({id:{eq:a}}).run();return g.length?g[0]:null}async function F(a,g){(await S.select("role").where({app_id:{eq:N},user_id:{eq:a}}).run()).length||await S.insert({app_id:N,user_id:a,role:"member",invited_by:null,source:g}).onConflict().run()}async function P(a,g){let h=await S.select("role").where({app_id:{eq:a},user_id:{eq:g}}).run();return h.length?String(h[0].role):null}async function L(a){let g=await w.select("id").where({slug:{eq:a}}).run();return g.length?String(g[0].id):null}async function K(a){let g=await t.select("id","email","name","role","tenant").where({email:{eq:a}}).run();return g.length?g[0]:null}async function Ie(a){return(await e.query.from(`${Ze} m`).select("m.app_id","m.role","a.id","a.slug","a.name").join(`${Se} a`,{"a.id":{col:"m.app_id"}}).where({"m.user_id":{eq:a}}).run()).filter(h=>!String(h.slug).startsWith("_")).map(h=>({id:String(h.id),slug:String(h.slug),name:String(h.name),role:String(h.role)}))}async function C(a,g){let h=se({sub:a.id,...a.email?{email:a.email}:{},...a.name?{name:a.name}:{},...g?.appId?{appId:g.appId,role:g.role}:{},...!g?.appId&&a.tenant?{tenantId:a.tenant}:{}},q,De),d=qe(),s=new Date(Date.now()+Ce*24*3600*1e3);return await y.insert({token_hash:ie(d),user_id:a.id,expires_at:s.toISOString(),...g?.appId?{app_id:g.appId}:{}}).run(),{token:h,refreshToken:d}}async function Le(a){let g=await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(a)},revoked_at:{isNull:!0}}).returning("user_id","expires_at","app_id").run();if(!g.length)throw new l("Invalid refresh token",401);let h=g[0];if(new Date(String(h.expires_at))<new Date)throw new l("Refresh token expired",401);let d=await ue(h.user_id);if(!d)throw new l("User not found",401);return{user:d,appId:h.app_id?String(h.app_id):void 0}}let E=(async(a,g,h)=>{let d=null,s=null,p={},u,f,R,O=a.headers.get("authorization"),M=null;if(O?.startsWith("Bearer "))M=O.slice(7);else if(a.method==="GET"){let r=new URL(a.url??"","http://localhost"),o=r.searchParams.get("ticket");if(o){let m=ae(o,q);m?.type==="download"&&m?.sub&&(s=await ue(String(m.sub)),d=s,p.userId=String(m.sub),m.appId!=null&&(f=m.appId,p.appId=String(m.appId)))}else M=r.searchParams.get("token")}if(M&&!s){let r=ae(M,q);if(r?.sub){s=await ue(String(r.sub)),d=s;for(let[o,m]of Object.entries(r))o==="sub"||o==="iat"||o==="exp"||o==="type"||(p[o]=m);p.userId=String(r.sub),u=r.tenantId,f=r.appId,R=r.role}}g.user=s,f!=null&&(g.appId=String(f)),g.session=f!=null?{userId:String(s.id),appId:String(f),role:String(R??"")}:null,u!=null&&(g.tenantId=String(u));let W={...p,async register(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let o=D(r.email),m=await J(r.password),c=await t.insert({email:o,password_hash:m,name:r.name??null,role:null,tenant:r.tenant??null}).run(),_=G(c[0]),b=await C(_);return await F(_.id,"register"),d=_,{...b,user:_}},async registerWithApp(r){let o=await this.register({email:r.email,password:r.password,name:r.name});await F(o.user.id,"register");let m=(r.appSlug??r.email.split("@")[1]??"default").trim().toLowerCase(),c=m;for(let k=1;k<=200&&await L(c);k++)c=`${m}-${k}`;let _=await this.createApp({slug:c,name:r.appName??`${r.name??"\u7528\u6237"} \u7684\u5E94\u7528`,openRegistration:!1}),b=await C(o.user,{appId:_.id,role:"owner"});return await $?.onRegisterApp?.(o.user.id,_),{...b,user:o.user,app:{..._,role:"owner"}}},async login(r,o){let c=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(r)}}).run())[0];if(!c||c.password_hash==null)throw await Me(o),new l("Invalid email or password",401);if(!await re(o,String(c.password_hash)))throw new l("Invalid email or password",401);let b={id:String(c.id),email:String(c.email),name:c.name,role:c.role,tenant:c.tenant},k=await C(b),j=await Ie(b.id);return d=b,{...k,user:b,apps:j}},async logout(r){await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(r)}}).run()},requireAuth(){if(!s)throw new l("Unauthorized",401);return s},async setPassword(r,o){Te(o);let m=await J(o);await t.update({password_hash:m}).where({id:{eq:r}}).run()},createToken(r,o,m){return se({type:r,...o},q,m.ttlSeconds)},async createApp(r){if(!d)throw new l("Unauthorized",401);if(!r.slug||!r.name)throw new l("slug and name are required",400);let o=r.slug.trim().toLowerCase();if(o.startsWith("_"))throw new l("slug \u4EE5 _ \u5F00\u5934\u4E3A\u7CFB\u7EDF\u4FDD\u7559\u540D",400);if(!await P(N,d.id))throw new l("\u4EC5\u5E94\u7528\u7BA1\u7406\u9762\uFF08_builtin\uFF09\u6210\u5458\u53EF\u521B\u5EFA\u5E94\u7528",403);let c=Pe(32).toString("hex"),b=(await w.insert({slug:o,name:r.name,owner_user_id:d.id,open_registration:r.openRegistration??!1,app_key:c}).run())[0];return await S.insert({app_id:b.id,user_id:d.id,role:"owner",invited_by:d.id}).run(),b},async registerInApp(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let o=await w.select("id","open_registration").where({slug:{eq:r.appSlug}}).run();if(!o.length)throw new l("Application not found",404);let m=o[0],c=String(m.id),_=D(r.email),b="member";if(!m.open_registration){if(!r.inviteToken)throw new l("Registration closed for this application",403);let I=ae(r.inviteToken,q);if(!I||I.type!=="app-invite"||I.appId!==c)throw new l("Invalid invite",403);if(I.email&&String(I.email)!==_)throw new l("Invite email mismatch",403);typeof I.role=="string"&&I.role!=="owner"&&(Re(I.role),b=I.role)}let k=await K(_);if(!k){let I=await J(r.password),X=await t.insert({email:_,password_hash:I,name:r.name??null,role:null,tenant:null}).onConflict("email").run();if(k=X.length?G(X[0]):await K(_),!k)throw new l("User creation failed",500)}await F(k.id,"register"),await P(c,k.id)||await S.insert({app_id:c,user_id:k.id,role:b,invited_by:d?.id??null,source:"invite"}).onConflict().run();let V=await C(k,{appId:c,role:b});return await S.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:c},user_id:{eq:k.id}}).run(),d=k,await $?.onJoinApp?.(k.id,c,b),{...V,user:k}},async loginApp(r,o,m){let c=await w.select("id").where({slug:{eq:r}}).run();if(!c.length)throw new l("Application not found",404);let _=String(c[0].id),k=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(o)}}).run())[0];if(!k||k.password_hash==null)throw await Me(m),new l("Invalid email or password",401);if(!await re(m,String(k.password_hash)))throw new l("Invalid email or password",401);let V=await P(_,String(k.id));if(!V)throw new l("Not a member of this application",401);let I={id:String(k.id),email:String(k.email),name:k.name,role:k.role,tenant:k.tenant},X=await C(I,{appId:_,role:V});return await S.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:_},user_id:{eq:I.id}}).run(),d=I,{...X,user:I,role:V}},async ssoLogin(r,o){let m=D(r),c=await K(m);if(!c){let k=await t.insert({email:m,password_hash:null,name:o?.name??null,role:null,tenant:null}).onConflict("email").run();if(c=k.length?G(k[0]):await K(m),!c)throw new l("User creation failed",500)}await F(c.id,"sso");let _;o?.appId&&(await P(o.appId,c.id)||await S.insert({app_id:o.appId,user_id:c.id,role:"member",invited_by:null,source:"sso"}).onConflict().run(),_=o.appId,await S.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:o.appId},user_id:{eq:c.id}}).run());let b=await C(c,_?{appId:_,role:"member"}:void 0);return d=c,{...b,user:c}},async createInvite(r,o){if(r===N)throw new l("_builtin \u4E0D\u8D70\u9080\u8BF7\u6D41\uFF08\u4EFB\u547D\u5236\uFF09",403);if(o.role!==void 0&&!ve.includes(o.role))throw new l(`invite role not allowed: ${o.role}\uFF08allowed: ${ve.join("/")}\uFF09`,403);if(!d)throw new l("Unauthorized",401);if(await P(r,d.id)!=="owner")throw new l("Owner only",403);let c=o.role&&o.role!=="owner"?o.role:"member";return{inviteToken:se({type:"app-invite",appId:r,...o.email?{email:D(o.email)}:{},role:c},q,Be)}},async addMember(r,o,m){if(Re(m),r===N&&m!==void 0&&m==="viewer")throw new l("_builtin \u65E0\u53EA\u8BFB\uFF08viewer \u7981\u2014\u2014\u7BA1\u7406\u9762\u8EAB\u4EFD\uFF09",403);if(!d)throw new l("Unauthorized",401);if(await P(r,d.id)!=="owner")throw new l("Owner only",403);let _=await t.select("id").where({email:{eq:D(o)}}).run();if(!_.length)throw new l("User not found \u2014 invite them to register first",400);let b=m&&m!=="owner"?m:"member";await S.insert({app_id:r,user_id:String(_[0].id),role:b,invited_by:d.id}).run()},async setOpenRegistration(r,o){if(!d)throw new l("Unauthorized",401);if(r===N)throw new l("_builtin \u6CE8\u518C\u5F00\u5173\u6052 false",403);if(await P(r,d.id)!=="owner")throw new l("Owner only",403);await w.update({open_registration:o}).where({id:{eq:r}}).run()},async listMyApps(){if(!d)throw new l("Unauthorized",401);return Ie(d.id)},async requireApp(r){if(!d)throw new l("Unauthorized",401);let o=await P(r,d.id);if(!o)throw new l("Not a member of this application",403);return{userId:d.id,appId:r,role:o}}};return g.auth=W,h(a,g)});return E.__meta={injects:["user","auth"],depends:[]},E.seedBuiltinOwners=async a=>{let g={owner:null,admins:[]};for(let h of a){let d=D(h);if(!d)continue;let s=await K(d);if(!s){let u=await t.insert({email:d,password_hash:null,name:null,role:null,tenant:null}).onConflict("email").run();s=u.length?G(u[0]):await K(d)}if(!s)continue;let p=await S.select("role").where({app_id:{eq:N},user_id:{eq:s.id}}).run();if(p.length){String(p[0].role??"")==="owner"?g.owner=String(s.id):g.admins.push(String(s.id));continue}if(g.owner)await S.insert({app_id:N,user_id:String(s.id),role:"admin",invited_by:g.owner,source:"seed"}).run(),g.admins.push(String(s.id));else{await S.insert({app_id:N,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run(),g.owner=String(s.id);let u=await L("_default");u&&(await P(u,String(s.id))||await S.insert({app_id:u,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run())}}return g},E.migrate=async()=>{(await w.select("id").where({slug:{eq:"_builtin"}}).run()).length||await e.query.insert(Se).values({id:N,slug:"_builtin",name:"System",owner_user_id:null,open_registration:!1}).returning("id").run();let g=await w.select("id").where({app_key:{isNull:!0}}).run();for(let s of g)await w.update({app_key:Pe(32).toString("hex")}).where({id:{eq:String(s.id)}}).run();(await w.select("id").where({slug:{eq:"_default"}}).run()).length||await w.insert({slug:"_default",name:"Default",owner_user_id:null,open_registration:!1}).run();let d=await t.select("id").run();for(let s of d)await F(String(s.id),"migrate")},E.routes=(a,g)=>{let h=g?.prefix??Ee,d=new Set(g?.exclude??[]);if(d.has("register")||a.post(`${h}/register`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.register(u);return B(f)}),d.has("register")||a.post(`${h}/register-app`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.registerWithApp(u);return B(f)}),d.has("login")||a.post(`${h}/login`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(!u.email||!u.password)return H("email and password are required");let f=await p.auth.login(u.email,u.password);return Z(f)}),d.has("logout")||a.post(`${h}/logout`,async(s,p)=>{let u=await s.json().catch(()=>({}));return u.refreshToken?(await p.auth.logout(u.refreshToken),$e()):H("refreshToken is required")}),d.has("refresh")||a.post(`${h}/refresh`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(!u.refreshToken)throw new l("refreshToken is required",400);let{user:f,appId:R}=await Le(u.refreshToken),O;if(R){let W=await P(R,f.id);W&&(O={appId:R,role:W})}let M=await C(f,O);return Z({...M,user:f})}),d.has("me")||a.get(`${h}/me`,async(s,p)=>{if(!p.user)throw new l("Unauthorized",401);return Z({user:p.user,session:p.session??null})}),T){let s=`${h}/apps/_builtin/auth/sso`;a.get(`${s}/enabled`,async()=>Z({enabled:!0,appSlug:T.defaultAppSlug??null})),a.get(`${s}/login`,async p=>{let f=new URL(p.url??"","http://localhost").searchParams.get("app")??T.defaultAppSlug,R=new URLSearchParams({response_type:"code",client_id:T.clientId,redirect_uri:T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${new URL(p.url,"http://localhost").origin}${s}/callback`,scope:T.scope??"openid email profile",state:f??"sso"});return new Response(null,{status:302,headers:{Location:`${T.issuer.replace(/\/$/,"")}/authorize?${R}`}})}),a.get(`${s}/callback`,async(p,u)=>{let f=new URL(p.url??"","http://localhost"),R=f.searchParams.get("code");if(!R)throw new l("SSO \u56DE\u8C03\u7F3A\u5C11 code",400);let O=T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${f.origin}${s}/callback`,M=await fetch(`${T.issuer.replace(/\/$/,"")}/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:R,redirect_uri:O,client_id:T.clientId,client_secret:T.clientSecret})});if(!M.ok)throw new l("SSO token \u4EA4\u6362\u5931\u8D25",401);let W=await M.json(),r=await fetch(`${T.issuer.replace(/\/$/,"")}/userinfo`,{headers:{Authorization:`Bearer ${W.access_token}`}});if(!r.ok)throw new l("SSO \u7528\u6237\u4FE1\u606F\u83B7\u53D6\u5931\u8D25",401);let o=await r.json(),m=(o.email??"").trim().toLowerCase();if(!m)throw new l("SSO \u672A\u8FD4\u56DE\u90AE\u7BB1\uFF08\u9700\u8981 email scope\uFF09",401);let c=f.searchParams.get("state"),_;if(c&&c!=="sso"){let j=await L(c);j&&(_=j)}let b=await u.auth.ssoLogin(m,{appId:_,name:o.name});await $?.onSsoLogin?.(b.user.id,_);let k={token:b.token,refreshToken:b.refreshToken,user:b.user};if(T.renderCallback){let j=T.renderCallback(k);return typeof j=="string"?new Response(j,{headers:{"Content-Type":"text/html; charset=utf-8"}}):j}return Z(k)})}d.has("apps")||(a.post(`${h}/apps`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.createApp(u);return B({app:f})}),a.get(`${h}/apps`,async(s,p)=>{let u=await p.auth.listMyApps();return Z({apps:u})}),a.post(`${h}/apps/:appSlug/auth/members`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await L(p.params.appSlug);if(!f)throw new l("Application not found",404);return await p.auth.addMember(f,u.email??"",u.role),B({ok:!0})}),a.post(`${h}/apps/:appSlug/auth/invites`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await L(p.params.appSlug);if(!f)throw new l("Application not found",404);let R=await p.auth.createInvite(f,{email:u.email,role:u.role});return B(R)}),a.post(`${h}/apps/:appSlug/auth/login`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(!u.email||!u.password)return H("email and password are required");let f=await p.auth.loginApp(p.params.appSlug,u.email,u.password);return Z(f)}),a.patch(`${h}/apps/:appSlug/auth/registration`,async(s,p)=>{let u=await s.json().catch(()=>({}));if(typeof u.open!="boolean")return H("open is required (boolean)");let f=await L(p.params.appSlug);if(!f)throw new l("Application not found",404);return await p.auth.setOpenRegistration(f,u.open),Z({open:u.open})}),a.post(`${h}/apps/:appSlug/auth/verify`,async(s,p)=>{let u=s.headers.get("x-wf-app-id"),f=s.headers.get("x-wf-app-key");if(!u||!f)return H("X-Wf-App-Id / X-Wf-App-Key required");let R=await w.select("id","slug","name").where({id:{eq:u},app_key:{eq:f}}).run();if(!R.length)throw new l("Invalid app credentials",403);let O=R[0];return Z({app:{id:String(O.id),slug:String(O.slug),name:String(O.name)}})}),a.post(`${h}/apps/:appSlug/auth/register`,async(s,p)=>{let u=await s.json().catch(()=>({})),f=await p.auth.registerInApp({appSlug:p.params.appSlug,email:u.email??"",password:u.password??"",name:u.name,inviteToken:u.inviteToken});return B(f)}))},E.asAppAuth=()=>E,E}var It={tables:[{name:"_weifuwu_users",columns:{id:x.pk(i.uuid()),email:x.req(i.string()).meta({unique:!0}),password_hash:i.string(),name:i.string(),role:i.string(),tenant:i.string(),email_verified:i.boolean().meta({default:!1}),created_at:i.date().meta({default:"now"})},dropNotNull:["password_hash"]},{name:"_weifuwu_sessions",columns:{token_hash:i.string().meta({pk:!0}),user_id:x.req(i.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),app_id:i.string(),expires_at:i.date().meta({notNull:!0}),revoked_at:i.date(),created_at:i.date().meta({default:"now"})}},{name:"_weifuwu_apps",columns:{id:x.pk(i.uuid()),slug:x.req(i.string()).meta({unique:!0}),name:x.req(i.string()),owner_user_id:i.uuid().meta({references:"_weifuwu_users",onDelete:"cascade"}),open_registration:i.boolean().meta({default:!1}),created_at:i.date().meta({default:"now"}),app_key:i.string()}},{name:"_weifuwu_app_members",columns:{app_id:x.req(i.uuid()).meta({references:"_weifuwu_apps",onDelete:"cascade"}),user_id:x.req(i.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),role:i.string().meta({default:"member"}),invited_by:i.uuid(),joined_at:i.date().meta({default:"now"}),source:i.string(),last_login_at:i.date()},uniques:[["app_id","user_id"]]}]};export{N as BUILTIN_APP_ID,oe as USER_TABLES,It as WEIFUWU_USER_SCHEMA,Rt as userSystem};
1
+ var l=class extends Error{status;constructor(e,t){super(e),this.name="HttpError",this.status=t}};var Y=class extends Error{issues;constructor(e){super(e.map(t=>`${t.path}: ${t.message}`).join("; ")),this.name="ZodError",this.issues=e}},v=class{_meta={};get metaInfo(){return this._meta}meta(e){return this._meta={...this._meta,...e},this}parse(e){let t=this._parse(e);if(!t.ok)throw new Y([{path:t.path??"",message:t.message??"invalid"}]);return t.value}safeParse(e){let t=this._parse(e);return t.ok?{success:!0,data:t.value}:{success:!1,error:new Y([{path:t.path??"",message:t.message??"invalid"}])}}optional(){return new ye(this)}nullable(){return new _e(this)}default(e){return new ke(this,e)}refine(e,t){return new be(this,e,t)}transform(e){return new xe(this,e)}},Q=class extends v{_typeName(){return"string"}_parse(e){if(typeof e!="string")return{ok:!1,path:"",message:"expected string"};let t=e;return this._min!==void 0&&t.length<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t.length>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:this._pattern&&!this._pattern.test(t)?{ok:!1,path:"",message:"invalid pattern"}:this._email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)?{ok:!1,path:"",message:"invalid email"}:this._uuid&&!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)?{ok:!1,path:"",message:"invalid uuid"}:{ok:!0,value:t}}_min;_max;_pattern;_email=!1;_uuid=!1;min(e){return this._min=e,this}max(e){return this._max=e,this}regex(e,t){return this._pattern=e,this}email(){return this._email=!0,this}uuid(){return this._uuid=!0,this}},le=class extends v{_typeName(){return"number"}_parse(e){if(typeof e!="number"||Number.isNaN(e))return{ok:!1,path:"",message:"expected number"};let t=e;return this._int&&!Number.isInteger(t)?{ok:!1,path:"",message:"expected integer"}:this._min!==void 0&&t<this._min?{ok:!1,path:"",message:`too small (min ${this._min})`}:this._max!==void 0&&t>this._max?{ok:!1,path:"",message:`too big (max ${this._max})`}:{ok:!0,value:t}}_int=!1;_min;_max;int(){return this._int=!0,this}min(e){return this._min=e,this}max(e){return this._max=e,this}positive(){return this._min=0,this}},de=class extends v{_typeName(){return"boolean"}_parse(e){return typeof e!="boolean"?{ok:!1,path:"",message:"expected boolean"}:{ok:!0,value:e}}},ee=class extends v{lit;constructor(e){super(),this.lit=e}get value(){return this.lit}_typeName(){return"literal"}_parse(e){return e!==this.lit?{ok:!1,path:"",message:`expected literal ${JSON.stringify(this.lit)}`}:{ok:!0,value:e}}},te=class extends v{raw;constructor(e){super(),this.raw=e}get values(){return this.raw}_typeName(){return"enum"}_parse(e){return typeof e!="string"||!this.raw.includes(e)?{ok:!1,path:"",message:`expected one of ${this.raw.join(" | ")}`}:{ok:!0,value:e}}},pe=class extends v{_typeName(){return"datetime"}_parse(e){return typeof e!="string"||Number.isNaN(Date.parse(e))?{ok:!1,path:"",message:"expected datetime string (ISO)"}:{ok:!0,value:e}}},ce=class extends v{dims;constructor(e){super(),this.dims=e}_typeName(){return"vector"}_parse(e){return typeof e!="object"||e===null||!Array.isArray(e)||!e.every(t=>typeof t=="number")?{ok:!1,path:"",message:"expected number[] vector"}:this.dims>0&&e.length!==this.dims?{ok:!1,path:"",message:`expected vector(${this.dims})\u2014\u2014got ${e.length} dims`}:{ok:!0,value:e}}},ge=class extends v{_typeName(){return"json"}_parse(e){return{ok:!0,value:e}}},me=class n extends v{shape;constructor(e){super(),this.shape=e}_typeName(){return"object"}_parse(e){if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,path:"",message:"expected object"};let t={},y=[];for(let[w,x]of Object.entries(this.shape)){let A=x._parse(e[w]);if(!A.ok){y.push({path:w,message:A.message??""});continue}t[w]=A.value}return y.length?{ok:!1,path:y[0].path,message:y[0].message}:{ok:!0,value:t}}pick(e){let t={};for(let y of e)t[y]=this.shape[y];return new n(t)}omit(e){let t={};for(let[y,w]of Object.entries(this.shape))e.includes(y)||(t[y]=w);return new n(t)}extend(e){return new n({...this.shape,...e})}},fe=class extends v{inner;constructor(e){super(),this.inner=e}get itemSchema(){return this.inner}_typeName(){return"array"}_parse(e){if(!Array.isArray(e))return{ok:!1,path:"",message:"expected array"};if(this._min!==void 0&&e.length<this._min)return{ok:!1,path:"",message:`too small (min ${this._min})`};if(this._max!==void 0&&e.length>this._max)return{ok:!1,path:"",message:`too big (max ${this._max})`};let t=[];for(let y=0;y<e.length;y++){let w=this.inner._parse(e[y]);if(!w.ok)return{ok:!1,path:`${y}`,message:w.message};t.push(w.value)}return{ok:!0,value:t}}_min;_max;min(e){return this._min=e,this}max(e){return this._max=e,this}},we=class extends v{options;constructor(e){super(),this.options=e}_typeName(){return"union"}_parse(e){let t=null;for(let y of this.options){let w=y._parse(e);if(w.ok)return{ok:!0,value:w.value};t||(t={path:w.path??"",message:w.message??""})}return{ok:!1,path:"",message:`expected union (${t?.message})`}}},he=class extends v{disc;options;discValueOf;constructor(e,t,y){super(),this.disc=e,this.options=t,this.discValueOf=y}_typeName(){return"disUnion"}_parse(e){if(typeof e!="object"||e===null)return{ok:!1,path:"",message:"expected object"};let t=e[this.disc];for(let y of this.options)if(this.discValueOf(y.shape)===t){let w=y._parse(e);return w.ok?{ok:!0,value:w.value}:{ok:!1,path:`(${String(t)})`,message:w.message??""}}return{ok:!1,path:this.disc,message:`invalid discriminator value ${JSON.stringify(t)}`}}};function Ke(n,e){let t=n[e];if(t instanceof ee)return t.lit;if(t instanceof te)return t.values;throw new Error(`z.discriminatedUnion: \u5224\u522B\u5B57\u6BB5\u9700\u4E3A literal/enum\u2014\u2014\u5F97\u5230 ${t?._typeName?.()}`)}var ye=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"optional"}_parse(e){return e===void 0?{ok:!0,value:void 0}:this.inner._parse(e)}},_e=class extends v{inner;constructor(e){super(),this.inner=e}_typeName(){return"nullable"}_parse(e){return e===null?{ok:!0,value:null}:this.inner._parse(e)}},ke=class extends v{inner;d;constructor(e,t){super(),this.inner=e,this.d=t}_typeName(){return"default"}_parse(e){if(e===void 0){let t=typeof this.d=="function"?this.d():this.d;return this.inner._parse(t)}return this.inner._parse(e)}},be=class extends v{inner;fn;msg;constructor(e,t,y){super(),this.inner=e,this.fn=t,this.msg=y}_typeName(){return"effects"}_parse(e){let t=this.inner._parse(e);return t.ok?this.fn(t.value)?{ok:!0,value:t.value}:{ok:!1,path:"",message:this.msg??"refine failed"}:{ok:!1,path:t.path??"",message:t.message??""}}},xe=class extends v{inner;fn;constructor(e,t){super(),this.inner=e,this.fn=t}_typeName(){return"transform"}_parse(e){let t=this.inner._parse(e);return t.ok?{ok:!0,value:this.fn(t.value)}:{ok:!1,path:t.path??"",message:t.message??""}}},o={string:()=>new Q,number:()=>new le,boolean:()=>new de,literal:n=>new ee(n),enum:n=>new te(n),date:()=>new pe,uuid:()=>new Q().uuid(),json:()=>new ge,vector:n=>new ce(n),object:n=>new me(n),array:n=>new fe(n),union:n=>new we(n),discriminatedUnion:(n,e)=>new he(n,e,t=>Ke(t,n))};function z(n,e){return n.meta(e)}var S={pk:n=>z(n,{pk:!0,default:"random"}),req:n=>z(n,{notNull:!0}),col:(n,e)=>z(n,{column:e}),now:n=>z(n,{default:"now"}),unique:n=>z(n,{unique:!0}),dflt:(n,e)=>z(n,{default:e})};import{randomBytes as We,scrypt as ze,timingSafeEqual as He}from"node:crypto";import{promisify as Fe}from"node:util";var Ae=Fe(ze),Ve=64,ne={N:16384,r:8,p:1};async function J(n){let e=We(16),t=await Ae(n,e,Ve,ne);return`scrypt$${ne.N}$${ne.r}$${ne.p}$${e.toString("hex")}$${t.toString("hex")}`}async function re(n,e){let t=e.split("$");if(t.length!==6||t[0]!=="scrypt")return!1;let[,y,w,x,A,P]=t,Z=Buffer.from(P,"hex"),U=await Ae(n,Buffer.from(A,"hex"),Z.length,{N:Number(y),r:Number(w),p:Number(x)});return U.length===Z.length&&He(U,Z)}import{createHash as Je,createHmac as qe,randomBytes as Ge,timingSafeEqual as Xe}from"node:crypto";var Oe=n=>Buffer.from(JSON.stringify(n)).toString("base64url");function se(n,e,t){let y=Math.floor(Date.now()/1e3),w=`${Oe({alg:"HS256",typ:"JWT"})}.${Oe({...n,iat:y,exp:y+t})}`,x=qe("sha256",e).update(w).digest("base64url");return`${w}.${x}`}function ae(n,e){let t=n.split(".");if(t.length!==3)return null;let[y,w,x]=t,A=`${y}.${w}`,P=qe("sha256",e).update(A).digest("base64url"),Z=Buffer.from(x),U=Buffer.from(P);if(Z.length!==U.length||!Xe(Z,U))return null;try{let T=JSON.parse(Buffer.from(w,"base64url").toString("utf8"));return typeof T.exp=="number"&&T.exp<Date.now()/1e3?null:T}catch{return null}}function je(){return Ge(32).toString("hex")}function ie(n){return Je("sha256").update(n).digest("hex")}function N(n,e){return Response.json(n,{status:200,...e})}function L(n,e){return Response.json(n,{status:201,...e})}function $e(n){return new Response(null,{status:204,...n})}function H(n){return Response.json({error:n??"Bad Request"},{status:400})}import{randomBytes as Pe}from"node:crypto";var oe={users:{id:S.pk(o.uuid()),email:S.req(o.string()).meta({unique:!0}),password_hash:o.string().nullable(),name:o.string().nullable(),role:o.string().nullable(),tenant:o.string().nullable(),email_verified:o.boolean().meta({notNull:!0,default:!1}),created_at:S.now(o.date())},sessions:{token_hash:S.req(o.string()),user_id:S.req(o.uuid()),app_id:o.string().nullable(),expires_at:S.req(o.date()),revoked_at:o.date().nullable(),created_at:S.now(o.date())},apps:{id:S.pk(o.uuid()),slug:S.req(o.string()).meta({unique:!0}),name:S.req(o.string()),owner_user_id:o.uuid().nullable(),open_registration:o.boolean().meta({notNull:!0,default:!1}),app_key:o.string().nullable(),created_at:S.now(o.date())},members:{app_id:S.req(o.uuid()),user_id:S.req(o.uuid()),role:o.string().meta({notNull:!0,default:"member"}),invited_by:o.uuid().nullable(),joined_at:S.now(o.date()),source:o.string().nullable(),last_login_at:o.date().nullable()}},Ye="_weifuwu_users",Qe="_weifuwu_sessions",Se="_weifuwu_apps",$="00000000-0000-4000-8000-0000000000b1",Ze="_weifuwu_app_members",Ne=8,et=1024;function Te(n){if(n.length<Ne)throw new l(`password must be at least ${Ne} characters`,400);if(n.length>et)throw new l("password is too long",400)}function D(n){return n.trim().toLowerCase()}var Ue=null;async function Me(n){Ue??=await J("dummy-timing-equalize-password"),await re(n,Ue)}function Rt(n){let e=n.orm,t=e.table(Ye,oe.users),y=e.table(Qe,oe.sessions),w=e.table(Se,oe.apps),x=e.table(Ze,oe.members),A=n.secret??process.env.AUTH_SECRET;if(!A)throw new Error("userSystem: secret is required \u2014 pass options.secret or set AUTH_SECRET (at least 32 chars)");if(A.length<16)throw new Error("userSystem: secret must be at least 16 characters");let P=A,Z=n.hooks,U=n.allowedRoles??["owner","admin","member","viewer"],T=n.sso??null,ve=n.inviteRoles??U;function Re(a){if(a!==void 0&&!U.includes(a))throw new l(`invalid role: ${a}\uFF08allowed: ${U.join("/")}\uFF09`,403)}let De=n.accessTtlSeconds??3600,Ce=n.refreshTtlDays??30,Ee=n.prefix??"/api/auth",G=a=>({id:String(a.id),email:a.email,name:a.name,role:a.role,tenant:a.tenant}),Be=168*3600;async function ue(a){let m=await t.select("id","email","name","role","tenant").where({id:{eq:a}}).run();return m.length?m[0]:null}async function F(a,m){(await x.select("role").where({app_id:{eq:$},user_id:{eq:a}}).run()).length||await x.insert({app_id:$,user_id:a,role:"member",invited_by:null,source:m}).onConflict().run()}async function O(a,m){let h=await x.select("role").where({app_id:{eq:a},user_id:{eq:m}}).run();return h.length?String(h[0].role):null}async function C(a){let m=await w.select("id").where({slug:{eq:a}}).run();return m.length?String(m[0].id):null}async function K(a){let m=await t.select("id","email","name","role","tenant").where({email:{eq:a}}).run();return m.length?m[0]:null}async function Ie(a){return(await e.query.from(`${Ze} m`).select("m.app_id","m.role","a.id","a.slug","a.name").join(`${Se} a`,{"a.id":{col:"m.app_id"}}).where({"m.user_id":{eq:a}}).run()).filter(h=>!String(h.slug).startsWith("_")).map(h=>({id:String(h.id),slug:String(h.slug),name:String(h.name),role:String(h.role)}))}async function E(a,m){let h=se({sub:a.id,...a.email?{email:a.email}:{},...a.name?{name:a.name}:{},...m?.appId?{appId:m.appId,role:m.role}:{},...!m?.appId&&a.tenant?{tenantId:a.tenant}:{}},P,De),p=je(),s=new Date(Date.now()+Ce*24*3600*1e3);return await y.insert({token_hash:ie(p),user_id:a.id,expires_at:s.toISOString(),...m?.appId?{app_id:m.appId}:{}}).run(),{token:h,refreshToken:p}}async function Le(a){let m=await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(a)},revoked_at:{isNull:!0}}).returning("user_id","expires_at","app_id").run();if(!m.length)throw new l("Invalid refresh token",401);let h=m[0];if(new Date(String(h.expires_at))<new Date)throw new l("Refresh token expired",401);let p=await ue(h.user_id);if(!p)throw new l("User not found",401);return{user:p,appId:h.app_id?String(h.app_id):void 0}}let B=(async(a,m,h)=>{let p=null,s=null,d={},i,c,R,q=a.headers.get("authorization"),M=null;if(q?.startsWith("Bearer "))M=q.slice(7);else if(a.method==="GET"){let r=new URL(a.url??"","http://localhost"),u=r.searchParams.get("ticket");if(u){let f=ae(u,P);f?.type==="download"&&f?.sub&&(s=await ue(String(f.sub)),p=s,d.userId=String(f.sub),f.appId!=null&&(c=f.appId,d.appId=String(f.appId)))}else M=r.searchParams.get("token")}if(M&&!s){let r=ae(M,P);if(r?.sub){s=await ue(String(r.sub)),p=s;for(let[u,f]of Object.entries(r))u==="sub"||u==="iat"||u==="exp"||u==="type"||(d[u]=f);d.userId=String(r.sub),i=r.tenantId,c=r.appId,R=r.role}}m.user=s,c!=null&&(m.appId=String(c)),m.session=c!=null?{userId:String(s.id),appId:String(c),role:String(R??"")}:null,i!=null&&(m.tenantId=String(i));let W={...d,async register(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let u=D(r.email),f=await J(r.password),g=await t.insert({email:u,password_hash:f,name:r.name??null,role:null,tenant:r.tenant??null}).run(),k=G(g[0]),b=await E(k);return await F(k.id,"register"),p=k,{...b,user:k}},async registerWithApp(r){let u=await this.register({email:r.email,password:r.password,name:r.name});await F(u.user.id,"register");let f=(r.appSlug??r.email.split("@")[1]??"default").trim().toLowerCase(),g=f;for(let _=1;_<=200&&await C(g);_++)g=`${f}-${_}`;let k=await this.createApp({slug:g,name:r.appName??`${r.name??"\u7528\u6237"} \u7684\u5E94\u7528`,openRegistration:!1}),b=await E(u.user,{appId:k.id,role:"owner"});return await Z?.onRegisterApp?.(u.user.id,k),{...b,user:u.user,app:{...k,role:"owner"}}},async login(r,u){let g=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(r)}}).run())[0];if(!g||g.password_hash==null)throw await Me(u),new l("Invalid email or password",401);if(!await re(u,String(g.password_hash)))throw new l("Invalid email or password",401);let b={id:String(g.id),email:String(g.email),name:g.name,role:g.role,tenant:g.tenant},_=await E(b),j=await Ie(b.id);return p=b,{..._,user:b,apps:j}},async logout(r){await y.update({revoked_at:new Date().toISOString()}).where({token_hash:{eq:ie(r)}}).run()},requireAuth(){if(!s)throw new l("Unauthorized",401);return s},async setPassword(r,u){Te(u);let f=await J(u);await t.update({password_hash:f}).where({id:{eq:r}}).run()},createToken(r,u,f){return se({type:r,...u},P,f.ttlSeconds)},async createApp(r){if(!p)throw new l("Unauthorized",401);if(!r.slug||!r.name)throw new l("slug and name are required",400);let u=r.slug.trim().toLowerCase();if(u.startsWith("_"))throw new l("slug \u4EE5 _ \u5F00\u5934\u4E3A\u7CFB\u7EDF\u4FDD\u7559\u540D",400);if(!await O($,p.id))throw new l("\u4EC5\u5E94\u7528\u7BA1\u7406\u9762\uFF08_builtin\uFF09\u6210\u5458\u53EF\u521B\u5EFA\u5E94\u7528",403);let g=Pe(32).toString("hex"),b=(await w.insert({slug:u,name:r.name,owner_user_id:p.id,open_registration:r.openRegistration??!1,app_key:g}).run())[0];return await x.insert({app_id:b.id,user_id:p.id,role:"owner",invited_by:p.id}).run(),b},async registerInApp(r){if(!r.email||!r.password)throw new l("email and password are required",400);Te(r.password);let u=await w.select("id","open_registration").where({slug:{eq:r.appSlug}}).run();if(!u.length)throw new l("Application not found",404);let f=u[0],g=String(f.id),k=D(r.email),b="member";if(!f.open_registration){if(!r.inviteToken)throw new l("Registration closed for this application",403);let I=ae(r.inviteToken,P);if(!I||I.type!=="app-invite"||I.appId!==g)throw new l("Invalid invite",403);if(I.email&&String(I.email)!==k)throw new l("Invite email mismatch",403);typeof I.role=="string"&&I.role!=="owner"&&(Re(I.role),b=I.role)}let _=await K(k);if(!_){let I=await J(r.password),X=await t.insert({email:k,password_hash:I,name:r.name??null,role:null,tenant:null}).onConflict("email").run();if(_=X.length?G(X[0]):await K(k),!_)throw new l("User creation failed",500)}await F(_.id,"register"),await O(g,_.id)||await x.insert({app_id:g,user_id:_.id,role:b,invited_by:p?.id??null,source:"invite"}).onConflict().run();let V=await E(_,{appId:g,role:b});return await x.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:g},user_id:{eq:_.id}}).run(),p=_,await Z?.onJoinApp?.(_.id,g,b),{...V,user:_}},async loginApp(r,u,f){let g=await w.select("id").where({slug:{eq:r}}).run();if(!g.length)throw new l("Application not found",404);let k=String(g[0].id),_=(await t.select("id","email","password_hash","name","role","tenant").where({email:{eq:D(u)}}).run())[0];if(!_||_.password_hash==null)throw await Me(f),new l("Invalid email or password",401);if(!await re(f,String(_.password_hash)))throw new l("Invalid email or password",401);let V=await O(k,String(_.id));if(!V)throw new l("Not a member of this application",401);let I={id:String(_.id),email:String(_.email),name:_.name,role:_.role,tenant:_.tenant},X=await E(I,{appId:k,role:V});return await x.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:k},user_id:{eq:I.id}}).run(),p=I,{...X,user:I,role:V}},async ssoLogin(r,u){let f=D(r),g=await K(f);if(!g){let _=await t.insert({email:f,password_hash:null,name:u?.name??null,role:null,tenant:null}).onConflict("email").run();if(g=_.length?G(_[0]):await K(f),!g)throw new l("User creation failed",500)}await F(g.id,"sso");let k;u?.appId&&(await O(u.appId,g.id)||await x.insert({app_id:u.appId,user_id:g.id,role:"member",invited_by:null,source:"sso"}).onConflict().run(),k=u.appId,await x.update({last_login_at:new Date().toISOString()}).where({app_id:{eq:u.appId},user_id:{eq:g.id}}).run());let b=await E(g,k?{appId:k,role:"member"}:void 0);return p=g,{...b,user:g}},async createInvite(r,u){if(r===$)throw new l("_builtin \u4E0D\u8D70\u9080\u8BF7\u6D41\uFF08\u4EFB\u547D\u5236\uFF09",403);if(u.role!==void 0&&!ve.includes(u.role))throw new l(`invite role not allowed: ${u.role}\uFF08allowed: ${ve.join("/")}\uFF09`,403);if(!p)throw new l("Unauthorized",401);if(await O(r,p.id)!=="owner")throw new l("Owner only",403);let g=u.role&&u.role!=="owner"?u.role:"member";return{inviteToken:se({type:"app-invite",appId:r,...u.email?{email:D(u.email)}:{},role:g},P,Be)}},async addMember(r,u,f){if(Re(f),r===$&&f!==void 0&&f==="viewer")throw new l("_builtin \u65E0\u53EA\u8BFB\uFF08viewer \u7981\u2014\u2014\u7BA1\u7406\u9762\u8EAB\u4EFD\uFF09",403);if(!p)throw new l("Unauthorized",401);if(await O(r,p.id)!=="owner")throw new l("Owner only",403);let k=await t.select("id").where({email:{eq:D(u)}}).run();if(!k.length)throw new l("User not found \u2014 invite them to register first",400);let b=f&&f!=="owner"?f:"member";await x.insert({app_id:r,user_id:String(k[0].id),role:b,invited_by:p.id}).run()},async setOpenRegistration(r,u){if(!p)throw new l("Unauthorized",401);if(r===$)throw new l("_builtin \u6CE8\u518C\u5F00\u5173\u6052 false",403);if(await O(r,p.id)!=="owner")throw new l("Owner only",403);await w.update({open_registration:u}).where({id:{eq:r}}).run()},async listMyApps(){if(!p)throw new l("Unauthorized",401);return Ie(p.id)},async requireApp(r){if(!p)throw new l("Unauthorized",401);let u=await O(r,p.id);if(!u)throw new l("Not a member of this application",403);return{userId:p.id,appId:r,role:u}}};return m.auth=W,h(a,m)});return B.__meta={injects:["user","auth"],depends:[]},B.seedBuiltinOwners=async a=>{let m={owner:null,admins:[]};for(let h of a){let p=D(h);if(!p)continue;let s=await K(p);if(!s){let i=await t.insert({email:p,password_hash:null,name:null,role:null,tenant:null}).onConflict("email").run();s=i.length?G(i[0]):await K(p)}if(!s)continue;let d=await x.select("role").where({app_id:{eq:$},user_id:{eq:s.id}}).run();if(d.length){String(d[0].role??"")==="owner"?m.owner=String(s.id):m.admins.push(String(s.id));continue}if(m.owner)await x.insert({app_id:$,user_id:String(s.id),role:"admin",invited_by:m.owner,source:"seed"}).run(),m.admins.push(String(s.id));else{await x.insert({app_id:$,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run(),m.owner=String(s.id);let i=await C("_default");i&&(await O(i,String(s.id))||await x.insert({app_id:i,user_id:String(s.id),role:"owner",invited_by:null,source:"seed"}).run())}}return m},B.migrate=async()=>{(await w.select("id").where({slug:{eq:"_builtin"}}).run()).length||await e.query.insert(Se).values({id:$,slug:"_builtin",name:"System",owner_user_id:null,open_registration:!1}).returning("id").run();let m=await w.select("id").where({app_key:{isNull:!0}}).run();for(let d of m)await w.update({app_key:Pe(32).toString("hex")}).where({id:{eq:String(d.id)}}).run();(await w.select("id").where({slug:{eq:"_default"}}).run()).length||await w.insert({slug:"_default",name:"Default",owner_user_id:null,open_registration:!1}).run();let p=await C("_default");if(p){let d=await x.select("user_id").where({app_id:{eq:$},role:{eq:"owner"}}).run();for(let i of d){let c=String(i.user_id);await O(p,c)||await x.insert({app_id:p,user_id:c,role:"owner",invited_by:null,source:"migrate"}).onConflict().run()}}let s=await t.select("id").run();for(let d of s)await F(String(d.id),"migrate")},B.routes=(a,m)=>{let h=m?.prefix??Ee,p=new Set(m?.exclude??[]);if(p.has("register")||a.post(`${h}/register`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.register(i);return L(c)}),p.has("register")||a.post(`${h}/register-app`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.registerWithApp(i);return L(c)}),p.has("login")||a.post(`${h}/login`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(!i.email||!i.password)return H("email and password are required");let c=await d.auth.login(i.email,i.password);return N(c)}),p.has("logout")||a.post(`${h}/logout`,async(s,d)=>{let i=await s.json().catch(()=>({}));return i.refreshToken?(await d.auth.logout(i.refreshToken),$e()):H("refreshToken is required")}),p.has("refresh")||a.post(`${h}/refresh`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(!i.refreshToken)throw new l("refreshToken is required",400);let{user:c,appId:R}=await Le(i.refreshToken),q;if(R){let W=await O(R,c.id);W&&(q={appId:R,role:W})}let M=await E(c,q);return N({...M,user:c})}),p.has("me")||a.get(`${h}/me`,async(s,d)=>{if(!d.user)throw new l("Unauthorized",401);return N({user:d.user,session:d.session??null})}),T){let s=`${h}/apps/_builtin/auth/sso`;a.get(`${s}/enabled`,async()=>N({enabled:!0,appSlug:T.defaultAppSlug??null})),a.get(`${s}/login`,async d=>{let c=new URL(d.url??"","http://localhost").searchParams.get("app")??T.defaultAppSlug,R=new URLSearchParams({response_type:"code",client_id:T.clientId,redirect_uri:T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${new URL(d.url,"http://localhost").origin}${s}/callback`,scope:T.scope??"openid email profile",state:c??"sso"});return new Response(null,{status:302,headers:{Location:`${T.issuer.replace(/\/$/,"")}/authorize?${R}`}})}),a.get(`${s}/callback`,async(d,i)=>{let c=new URL(d.url??"","http://localhost"),R=c.searchParams.get("code");if(!R)throw new l("SSO \u56DE\u8C03\u7F3A\u5C11 code",400);let q=T.redirectBase?`${T.redirectBase.replace(/\/$/,"")}${s}/callback`:`${c.origin}${s}/callback`,M=await fetch(`${T.issuer.replace(/\/$/,"")}/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:R,redirect_uri:q,client_id:T.clientId,client_secret:T.clientSecret})});if(!M.ok)throw new l("SSO token \u4EA4\u6362\u5931\u8D25",401);let W=await M.json(),r=await fetch(`${T.issuer.replace(/\/$/,"")}/userinfo`,{headers:{Authorization:`Bearer ${W.access_token}`}});if(!r.ok)throw new l("SSO \u7528\u6237\u4FE1\u606F\u83B7\u53D6\u5931\u8D25",401);let u=await r.json(),f=(u.email??"").trim().toLowerCase();if(!f)throw new l("SSO \u672A\u8FD4\u56DE\u90AE\u7BB1\uFF08\u9700\u8981 email scope\uFF09",401);let g=c.searchParams.get("state"),k;if(g&&g!=="sso"){let j=await C(g);j&&(k=j)}let b=await i.auth.ssoLogin(f,{appId:k,name:u.name});await Z?.onSsoLogin?.(b.user.id,k);let _={token:b.token,refreshToken:b.refreshToken,user:b.user};if(T.renderCallback){let j=T.renderCallback(_);return typeof j=="string"?new Response(j,{headers:{"Content-Type":"text/html; charset=utf-8"}}):j}return N(_)})}p.has("apps")||(a.post(`${h}/apps`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.createApp(i);return L({app:c})}),a.get(`${h}/apps`,async(s,d)=>{let i=await d.auth.listMyApps();return N({apps:i})}),a.post(`${h}/apps/:appSlug/auth/members`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await C(d.params.appSlug);if(!c)throw new l("Application not found",404);return await d.auth.addMember(c,i.email??"",i.role),L({ok:!0})}),a.post(`${h}/apps/:appSlug/auth/invites`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await C(d.params.appSlug);if(!c)throw new l("Application not found",404);let R=await d.auth.createInvite(c,{email:i.email,role:i.role});return L(R)}),a.post(`${h}/apps/:appSlug/auth/login`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(!i.email||!i.password)return H("email and password are required");let c=await d.auth.loginApp(d.params.appSlug,i.email,i.password);return N(c)}),a.patch(`${h}/apps/:appSlug/auth/registration`,async(s,d)=>{let i=await s.json().catch(()=>({}));if(typeof i.open!="boolean")return H("open is required (boolean)");let c=await C(d.params.appSlug);if(!c)throw new l("Application not found",404);return await d.auth.setOpenRegistration(c,i.open),N({open:i.open})}),a.post(`${h}/apps/:appSlug/auth/verify`,async(s,d)=>{let i=s.headers.get("x-wf-app-id"),c=s.headers.get("x-wf-app-key");if(!i||!c)return H("X-Wf-App-Id / X-Wf-App-Key required");let R=await w.select("id","slug","name").where({id:{eq:i},app_key:{eq:c}}).run();if(!R.length)throw new l("Invalid app credentials",403);let q=R[0];return N({app:{id:String(q.id),slug:String(q.slug),name:String(q.name)}})}),a.post(`${h}/apps/:appSlug/auth/register`,async(s,d)=>{let i=await s.json().catch(()=>({})),c=await d.auth.registerInApp({appSlug:d.params.appSlug,email:i.email??"",password:i.password??"",name:i.name,inviteToken:i.inviteToken});return L(c)}))},B.asAppAuth=()=>B,B}var It={tables:[{name:"_weifuwu_users",columns:{id:S.pk(o.uuid()),email:S.req(o.string()).meta({unique:!0}),password_hash:o.string(),name:o.string(),role:o.string(),tenant:o.string(),email_verified:o.boolean().meta({default:!1}),created_at:o.date().meta({default:"now"})},dropNotNull:["password_hash"]},{name:"_weifuwu_sessions",columns:{token_hash:o.string().meta({pk:!0}),user_id:S.req(o.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),app_id:o.string(),expires_at:o.date().meta({notNull:!0}),revoked_at:o.date(),created_at:o.date().meta({default:"now"})}},{name:"_weifuwu_apps",columns:{id:S.pk(o.uuid()),slug:S.req(o.string()).meta({unique:!0}),name:S.req(o.string()),owner_user_id:o.uuid().meta({references:"_weifuwu_users",onDelete:"cascade"}),open_registration:o.boolean().meta({default:!1}),created_at:o.date().meta({default:"now"}),app_key:o.string()}},{name:"_weifuwu_app_members",columns:{app_id:S.req(o.uuid()).meta({references:"_weifuwu_apps",onDelete:"cascade"}),user_id:S.req(o.uuid()).meta({references:"_weifuwu_users",onDelete:"cascade"}),role:o.string().meta({default:"member"}),invited_by:o.uuid(),joined_at:o.date().meta({default:"now"}),source:o.string(),last_login_at:o.date()},uniques:[["app_id","user_id"]]}]};export{$ as BUILTIN_APP_ID,oe as USER_TABLES,It as WEIFUWU_USER_SCHEMA,Rt as userSystem};
package/docs/level.md CHANGED
@@ -102,7 +102,7 @@ npm run test:levels # 内核回归(level0–4——无 docker/无浏览
102
102
  | L3 客户端运行时 | 51 | 7707 | 203 | 0 | 0 |
103
103
  | L4 生成器 | 7 | 693 | 22 | 0 | 0 |
104
104
  | L5 装备 | 235 | 35992 | 895 | 167 | 347 |
105
- | L6 装配+应用 | 173 | 35617 | 999 | 17 | 0 |
105
+ | L6 装配+应用 | 173 | 35629 | 999 | 17 | 0 |
106
106
 
107
107
  闭包读数:(迁移后)由目录定级——level1 通用 19 · level3 客户端 51(剥注释口径——分类规则见 git log)
108
108
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.95.1",
4
+ "version": "0.95.2",
5
5
  "description": "AI SaaS full-stack framework — backend HTTP + frontend VDOM + 138 components + CSS design system + SaaS foundation (auth/queue/AI). Zero-build, zero-dependency.",
6
6
  "files": [
7
7
  "dist/",