sentri 4.1.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +462 -211
- package/dist/adapters/elysia/index.cjs +1 -0
- package/dist/adapters/elysia/index.d.cts +149 -0
- package/dist/adapters/elysia/index.d.ts +149 -0
- package/dist/adapters/elysia/index.js +1 -0
- package/dist/adapters/express/index.cjs +1 -0
- package/dist/adapters/express/index.d.cts +308 -0
- package/dist/adapters/express/index.d.ts +308 -0
- package/dist/adapters/express/index.js +1 -0
- package/dist/adapters/fastify/index.cjs +1 -0
- package/dist/adapters/fastify/index.d.cts +98 -0
- package/dist/adapters/fastify/index.d.ts +98 -0
- package/dist/adapters/fastify/index.js +1 -0
- package/dist/adapters/hono/index.cjs +1 -0
- package/dist/adapters/hono/index.d.cts +114 -0
- package/dist/adapters/hono/index.d.ts +114 -0
- package/dist/adapters/hono/index.js +1 -0
- package/dist/adapters/koa/index.cjs +1 -0
- package/dist/adapters/koa/index.d.cts +90 -0
- package/dist/adapters/koa/index.d.ts +90 -0
- package/dist/adapters/koa/index.js +1 -0
- package/dist/cli.cjs +514 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.js +434 -50
- package/dist/core/index.cjs +1 -0
- package/dist/core/index.d.cts +340 -0
- package/dist/core/index.d.ts +340 -0
- package/dist/core/index.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -671
- package/dist/index.js +1 -1
- package/package.json +78 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var crypto=require('crypto'),kysely=require('kysely'),Ge=require('bcrypt'),Y=require('jsonwebtoken'),elysia=require('elysia');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var Ge__default=/*#__PURE__*/_interopDefault(Ge);var Y__default=/*#__PURE__*/_interopDefault(Y);var Nr=Object.defineProperty;var o=(e,r)=>Nr(e,"name",{value:r,configurable:true});var $e=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),a=class extends Error{static{o(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??$e[r]??500;}};var Ve=new WeakMap,Me=32,qe=10,Be=31;function ze(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new a("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new a("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Me)throw new a("CONFIGURATION_ERROR",`secret must be at least ${Me} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<qe||s>Be)throw new a("CONFIGURATION_ERROR",`saltRounds must be an integer between ${qe} and ${Be}`);if(!e.validRoles||e.validRoles.length===0)throw new a("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new a("CONFIGURATION_ERROR","dialect is required in server mode")}o(ze,"validateConfig");function A(e){let r=Ve.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}o(A,"resolveServerConfig");var Dr=/^(\d+)([smhdw])$/,Sr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},je=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=je.get(e);if(r!==void 0)return r;let t=Dr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Sr[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let n=parseInt(t[1],10)*s;return je.set(e,n),n}o($,"parseExpiry");var S={info:o(()=>{},"info"),warn:o(()=>{},"warn"),error:o(()=>{},"error")};function h(e,r,t){return {service:e,event:r,...t}}o(h,"buildLogData");async function Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}o(Xe,"runMigrations");var Ze=new Map;function D(e){let r=Ze.get(e);return r||(r=new kysely.Kysely({dialect:e}),Ze.set(e,r)),r}o(D,"getDatabase");async function W(e,r=12){return Ge__default.default.hash(e,r)}o(W,"hashPassword");async function J(e,r){return Ge__default.default.compare(e,r)}o(J,"verifyPassword");var We=new Map,Je=new Map,Lr=3600*1e3;function Qe(e){let r=We.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),n=s.export({format:"jwk"}),d=crypto.createHash("sha256").update(JSON.stringify({e:n.e,kty:n.kty,n:n.n})).digest("base64url"),c={...n,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:c},We.set(e,r);}return r}o(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}o(er,"buildJwks");function rr(e){return Qe(e).publicKey}o(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Lr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new a("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let n=await s.json();if(!n.keys||n.keys.length===0)throw new a("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let u=n.keys[0],d=crypto.createPublicKey({key:u,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}o(tr,"fetchPublicKey");var sr=new Map,or=new Map,nr=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}o(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}o(ar,"getHsSecrets");function Pr(e){let r=or.get(e);return r||(r=crypto.createPrivateKey(e),or.set(e,r)),r}o(Pr,"getRsPrivateKey");function ur(e){let r=A(e);if(ir(r.algorithm)){let n=Pr(e.secret);return {accessKey:n,refreshKey:n}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}o(ur,"getSigningKeys");function cr(e,r){let t=A(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:n}=ar(e.secret);return r==="access"?s:n}o(cr,"getVerifyKey");function dr(e,r,t,s){let n=`${t}:${s}`,u=nr.get(n);return u||(u={expiresIn:t,algorithm:s},nr.set(n,u)),Y__default.default.sign(e,r,u)}o(dr,"sign");function lr(e,r,t){try{let s=Y__default.default.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new a("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof a?s:s instanceof Y__default.default.TokenExpiredError?new a("TOKEN_EXPIRED","Token has expired"):new a("TOKEN_INVALID","Token is invalid or malformed")}}o(lr,"verify");function Q(e,r){let t=A(r),{accessKey:s}=ur(r);return dr(e,s,t.accessExpiresIn,t.algorithm)}o(Q,"signAccessToken");function ee(e,r){let t=A(r),{refreshKey:s}=ur(r);return dr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}o(ee,"signRefreshToken");function V(e,r){let t=A(r),s=cr(r,"access");return lr(e,s,t.algorithm)}o(V,"verifyAccessToken");function re(e,r){let t=A(r),s=cr(r,"refresh");return lr(e,s,t.algorithm)}o(re,"verifyRefreshToken");function fr(e,r){try{let t=Y__default.default.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new a("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof a?t:t instanceof Y__default.default.TokenExpiredError?new a("TOKEN_EXPIRED","Token has expired"):new a("TOKEN_INVALID","Token is invalid or malformed")}}o(fr,"verifyTokenWithPublicKey");function xe(e){try{return JSON.parse(e)}catch{return []}}o(xe,"parseRoles");function Kr(e){return JSON.stringify(e)}o(Kr,"serializeRoles");function mr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}o(mr,"mapIdentifier");async function te(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:xe(t.roles)}:null}o(te,"findUserByIdentifierValue");async function ue(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:xe(t.roles)}:null}o(ue,"findUserById");async function pr(e,r,t){let s=t.map(n=>({id:crypto.randomUUID(),user_id:r,type:n.type,value:n.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(n=>({id:n.id,userId:n.user_id,type:n.type,value:n.value,createdAt:new Date}))}o(pr,"createIdentifiers");async function se(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(mr)}o(se,"findIdentifiersByUserId");async function Ce(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?mr(s):null}o(Ce,"findIdentifierById");async function wr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}o(wr,"countIdentifiersByUserId");async function gr(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}o(gr,"updateIdentifier");async function yr(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}o(yr,"deleteIdentifiers");async function Ir(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}o(Ir,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:Kr(t)}).where("id","=",r).execute();}o(Rr,"updateUserRoles");async function Ne(e,r){let t=crypto.randomUUID();return await e.insertInto("sentri_sessions").values({id:t,user_id:r.userId,expires_at:r.expiresAt.toISOString(),ip_address:r.ipAddress??null,user_agent:r.userAgent??null}).execute(),{id:t}}o(Ne,"createSession");async function De(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:xe(t.roles)}}:null}o(De,"findSessionById");async function Se(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}o(Se,"deleteSession");async function _r(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}o(_r,"markSessionReplaced");async function ce(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}o(ce,"deleteAllSessionsForUser");async function de(e,r,t){let s=A(r),n=D(r.dialect),u=e.roles??[],d=u.filter(k=>!s.validRolesSet.has(k));if(d.length>0)return {success:false,error:new a("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one identifier is required")};let c=e.identifiers.map(k=>({type:k.type.trim(),value:k.value.trim()}));if(new Set(c.map(k=>k.value)).size!==c.length)return {success:false,error:new a("VALIDATION_ERROR","Duplicate identifier values in request")};for(let k of c)if(await te(n,k.value))return {success:false,error:new a("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${k.value}`)};let g=await W(e.password,s.saltRounds),{userId:x,identifierRows:C}=await n.transaction().execute(async k=>{let H=crypto.randomUUID();await k.insertInto("sentri_users").values({id:H,password_hash:g,roles:JSON.stringify(u)}).execute();let ae=c.map(X=>({id:crypto.randomUUID(),user_id:H,type:X.type,value:X.value}));return await k.insertInto("sentri_identifiers").values(ae).execute(),{userId:H,identifierRows:ae}}),N={success:true,user:{id:x,roles:u,identifiers:C.map(k=>({id:k.id,type:k.type,value:k.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(N.user),N}o(de,"register");async function le(e,r,t){let s=A(r),n=D(r.dialect),u=await te(n,e.identifier.trim());if(!u){let N=new a("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,N,{ip:t?.ip??""}),{success:false,error:N}}if(!await J(e.password,u.passwordHash)){let N=new a("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,N,{ip:t?.ip??""}),{success:false,error:N}}let c=new Date(Date.now()+$(s.refreshExpiresIn)),i=await Ne(n,{userId:u.id,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),g={id:u.id,roles:u.roles},x=Q({id:u.id,roles:u.roles,sessionId:i.id},r),C=ee(i.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(g,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:x,refreshToken:C,user:g}}o(le,"login");async function M(e,r,t){let s=A(r),n=D(r.dialect),u;try{({sessionId:u}=re(e,r));}catch(N){return N instanceof a?{success:false,error:N}:{success:false,error:new a("TOKEN_INVALID","Invalid refresh token")}}let d=await De(n,u);if(!d)return {success:false,error:new a("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await ce(n,d.userId),{success:false,error:new a("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await Se(n,u),{success:false,error:new a("TOKEN_EXPIRED","Session has expired")};let c=new Date(Date.now()+$(s.refreshExpiresIn)),i=await Ne(n,{userId:d.userId,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(n,u,i.id);let g={id:d.user.id,roles:d.user.roles},x=Q({...g,sessionId:i.id},r),C=ee(i.id,r);return {success:true,accessToken:x,refreshToken:C,user:g}}o(M,"refresh");async function fe(e,r){let t=D(r.dialect),s;try{({sessionId:s}=re(e,r));}catch{return}let n=await De(t,s);n&&(await Se(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(n.userId));}o(fe,"logout");async function he(e,r){let t=D(r.dialect);await ce(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}o(he,"logoutAll");async function me(e,r){let t=D(r.dialect),s=await ue(t,e);if(!s)return {success:false,error:new a("USER_NOT_FOUND","User not found")};let n=await se(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:n.map(u=>({id:u.id,type:u.type,value:u.value}))}}}o(me,"getUser");async function pe(e,r,t,s){let n=A(s),u=D(s.dialect),d=await ue(u,e);if(!d)return {success:false,error:new a("USER_NOT_FOUND","User not found")};if(!await J(r,d.passwordHash))return {success:false,error:new a("INVALID_CREDENTIALS","Invalid credentials")};let i=await W(t,n.saltRounds);return await Ir(u,e,i),await ce(u,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}o(pe,"changePassword");async function we(e,r,t){let s=A(t),n=D(t.dialect),u=r.filter(g=>!s.validRolesSet.has(g));if(u.length>0)return {success:false,error:new a("INVALID_ROLE",`Invalid roles: ${u.join(", ")}`)};let d=await ue(n,e);if(!d)return {success:false,error:new a("USER_NOT_FOUND","User not found")};let c=new Set(d.roles);for(let g of r)c.add(g);let i=Array.from(c);return await Rr(n,e,i),{success:true,user:{id:d.id,roles:i}}}o(we,"assignRoles");async function ge(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one identifier is required")};let n=r.map(c=>({type:c.type.trim(),value:c.value.trim()}));if(new Set(n.map(c=>c.value)).size!==n.length)return {success:false,error:new a("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of n)if(await te(s,c.value))return {success:false,error:new a("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await pr(s,e,n),{success:true,identifiers:(await se(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}o(ge,"bulkCreateIdentifiers");async function ye(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one update is required")};let n=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()}));if(new Set(n.map(c=>c.value)).size!==n.length)return {success:false,error:new a("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of n){let i=await Ce(s,c.id,e);if(!i)return {success:false,error:new a("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(i.value!==c.value&&await te(s,c.value))return {success:false,error:new a("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await s.transaction().execute(async c=>{for(let i of n)await gr(c,i.id,e,{type:i.type,value:i.value});}),{success:true,identifiers:(await se(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}o(ye,"bulkUpdateIdentifiers");async function Ie(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one ID is required")};let n=Array.from(new Set(r));for(let c of n)if(!await Ce(s,c,e))return {success:false,error:new a("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c}`)};return await wr(s,e)-n.length<1?{success:false,error:new a("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await yr(s,e,n),{success:true,identifiers:(await se(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))})}o(Ie,"bulkDeleteIdentifiers");function L(e){return A(e).cookieName}o(L,"getCookieName");function Re(e){return A(e).accessCookieName}o(Re,"getAccessCookieName");function P(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let n=e.indexOf(";",s),u=n===-1?e.length:n;if(e.startsWith(t,s))return e.slice(s+t.length,u);s=u+1;}}o(P,"readCookie");function oe(e,r,t){let s=t.cookie??{},n=$(A(t).refreshExpiresIn)/1e3;e.cookie??={},e.cookie[L(t)]={value:r,httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:n};}o(oe,"setCookieFromConfig");function _e(e,r){let t=r.cookie??{};e.cookie??={},e.cookie[L(r)]={value:"",httpOnly:t.httpOnly??true,path:t.path??"/",maxAge:0};}o(_e,"clearCookieFromConfig");function ne(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,n=$(A(t).accessExpiresIn)/1e3;e.cookie??={},e.cookie[Re(t)]={value:r,httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:n};}o(ne,"setAccessCookieFromConfig");function Oe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.cookie??={},e.cookie[Re(r)]={value:"",httpOnly:false,path:t.path??"/",maxAge:0};}o(Oe,"clearAccessCookieFromConfig");function q(e,r){let t=e.headers.get("authorization");if(t?.startsWith("Bearer "))return t.slice(7);let s=e.headers.get("cookie");return s?P(s,Re(r)):void 0}o(q,"getCurrentAccessToken");function be(e){let r=e.logger??S,t=e.loggerService??"sentri";if(e.mode==="client"){let n=e;return new elysia.Elysia().derive({as:"global"},async({request:u})=>{let d=u.headers.get("authorization"),c=d?.startsWith("Bearer ")?d.slice(7):void 0;if(!c)throw r.warn(h(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED"})),new a("UNAUTHORIZED","Missing or malformed Authorization header");try{let i=await tr(n.keyUri),g=fr(c,i),x={id:g.id,roles:g.roles};return r.info(h(t,"auth.protect.success",{mode:"client",userId:g.id})),{user:x}}catch(i){let g=i instanceof a?i.code:"TOKEN_INVALID";throw r.warn(h(t,"auth.protect.failure",{mode:"client",errorCode:g})),i}})}let s=e;return new elysia.Elysia().derive({as:"global"},async({request:n,set:u})=>{let d=q(n,s);if(!d)throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED"})),new a("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=V(d,s);if(s.isTokenRevoked&&await s.isTokenRevoked(c.sessionId))throw r.warn(h(t,"auth.protect.token_revoked",{mode:"server",userId:c.id})),new a("UNAUTHORIZED","Token has been revoked");let i={id:c.id,roles:c.roles};return r.info(h(t,"auth.protect.success",{mode:"server",userId:c.id})),{user:i}}catch(c){if(c instanceof a&&c.code==="TOKEN_EXPIRED"){let g=n.headers.get("cookie"),x=g?P(g,L(s)):void 0;if(!x)throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED"})),new a("UNAUTHORIZED","Token expired. Please login again.");let C;try{C=await M(x,s);}catch{throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED"})),new a("UNAUTHORIZED","Session expired. Please login again.")}if(!C.success)throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:C.error.code})),new a("UNAUTHORIZED","Session expired. Please login again.");return oe(u,C.refreshToken,s),ne(u,C.accessToken,s),u.headers["X-New-Access-Token"]=C.accessToken,r.info(h(t,"auth.protect.auto_refresh",{mode:"server",userId:C.user.id})),{user:C.user}}let i=c instanceof a?c.code:"TOKEN_INVALID";throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:i})),c}})}o(be,"protect");function Ar(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return ({user:n})=>{if(!n)throw r.warn(h(t,"auth.authorize.unauthenticated",{requiredRoles:e})),new a("UNAUTHORIZED","Not authenticated");let u=n.roles;if(!e.some(d=>u.includes(d)))throw r.warn(h(t,"auth.authorize.denied",{userId:n.id,userRoles:[...u],requiredRoles:e})),new a("FORBIDDEN",s);r.info(h(t,"auth.authorize.passed",{userId:n.id,userRoles:[...u],requiredRoles:e}));}}o(Ar,"createAuthorizeHandler");function Ue(e,r){return o(function(...s){return Ar(s,e,r)},"authorize")}o(Ue,"createAuthorize");function Hr(...e){return Ar(e,S,"sentri")}o(Hr,"authorize");var $r=new a("FORBIDDEN","You do not have permission to perform this action");function Er(e,r,t){return async s=>{let n=s.user;if(!n)throw r.warn(h(t,"auth.permit.unauthenticated",{})),new a("UNAUTHORIZED","Not authenticated");if(e.roles&&e.roles.length>0){let u=n.roles;if(e.roles.some(d=>u.includes(d))){r.info(h(t,"auth.permit.role_bypass",{userId:n.id,bypassedByRole:true}));return}}try{let u=e.check(s);if(u instanceof Promise?await u:u)r.info(h(t,"auth.permit.passed",{userId:n.id}));else throw r.warn(h(t,"auth.permit.denied",{userId:n.id})),$r}catch(u){throw u}}}o(Er,"createPermitHandler");function Le(e,r){return o(function(s){return Er(typeof s=="function"?{check:s}:s,e,r)},"permit")}o(Le,"createPermit");function Vr(e){return Er(typeof e=="function"?{check:e}:e,S,"sentri")}o(Vr,"permit");function ie(e){return ({error:r,set:t})=>r instanceof a?(t.status=r.statusCode,{error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null}):(e?.onUnhandled?.(r),t.status=500,{error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null})}o(ie,"createErrorHandler");var Fe=class{static{o(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let n=Date.now(),u=this.store.get(r);if((!u||u.expiresAt<n)&&(u={count:0,expiresAt:n+s},this.store.set(r,u)),u.count>t){let d=Math.ceil((u.expiresAt-n)/1e3);throw new Error(`Rate limit exceeded. Try again in ${d} seconds.`)}u.count++;}},Pe=class{static{o(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let n=`sentri:rl:${r}`,u=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(n).expire(n,u,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(d){if(d instanceof Error&&d.message.includes("Rate limit exceeded"))throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},B=null;async function Tr(e,r){if(B)return B;if(e)try{let{Redis:t}=await import('ioredis'),s=new t(e,{lazyConnect:!0,maxRetriesPerRequest:1});return await s.connect(),r?.info({msg:"Connected to Redis for Rate Limiting"}),B=new Pe(s,r??S),B}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return B=new Fe,B}o(Tr,"getRateLimiter");var ke=8,j=72,ve=255,xr=100,z=50;function _(e){return new a("VALIDATION_ERROR",e)}o(_,"badRequest");function O(e,r,t,s){return e.status=r,{error:false,statusCode:r,message:t,data:s}}o(O,"ok");function K(e){if(e==null||typeof e!="object"||Array.isArray(e))throw new a("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you set Content-Type: application/json?");return e}o(K,"parseBody");function qr(e,r){if(!r.apiKey)return;let t=e.headers.get("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new a("UNAUTHORIZED","Invalid or missing API key")}o(qr,"validateApiKey");function Ke(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}o(Ke,"fireHook");function Br(e){return e.startsWith("RS")||e.startsWith("PS")}o(Br,"isRSA");function He(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw _(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw _(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>xr)throw _(`identifiers[${r}].type must not exceed ${xr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw _(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>ve)throw _(`identifiers[${r}].value must not exceed ${ve} characters`);return {type:t.type,value:t.value}}o(He,"validateIdentifierInput");async function F(e,r,t){let s=q(e,t);if(!s)throw new a("UNAUTHORIZED","Missing or malformed Authorization header");let n=V(s,t);if(t.isTokenRevoked&&await t.isTokenRevoked(n.sessionId))throw new a("UNAUTHORIZED","Token has been revoked");return {id:n.id,roles:n.roles}}o(F,"requireAuth");function Cr(e){let r=e,t=A(r),s=e.logger??S,n=e.loggerService??"sentri",u=e.router?.register??(l=>de(l,r)),d=e.router?.login??(l=>le(l,r)),c=e.router?.refresh??(l=>M(l,r)),i=e.router?.logout??(l=>l!==void 0?fe(l,r):Promise.resolve()),g=e.router?.logoutAll??(l=>he(l,r)),x=e.router?.getUser??(l=>me(l,r)),C=e.router?.assignRoles??((l,f)=>we(l,f,r)),N=e.router?.changePassword??((l,f,w)=>pe(l,f,w,r)),k=e.router?.bulkCreateIdentifiers??((l,f)=>ge(l,f,r)),H=e.router?.bulkUpdateIdentifiers??((l,f)=>ye(l,f,r)),ae=e.router?.bulkDeleteIdentifiers??((l,f)=>Ie(l,f,r)),X=Tr(e.redisUrl,s),Ae=new elysia.Elysia().onError(ie());return Br(t.algorithm)&&Ae.get("/keys",({set:l})=>(l.headers["Cache-Control"]="public, max-age=3600",er(e.secret))),Ae.post("/register",async({body:l,request:f,set:w})=>{let I=Date.now();qr(f,r);let m=K(l),{identifiers:T,password:p,roles:R}=m;if(!Array.isArray(T)||T.length===0)throw _("identifiers is required and must be a non-empty array");if(T.length>z)throw _(`identifiers must not exceed ${z} entries`);let y=T.map((G,Ee)=>He(G,Ee));if(typeof p!="string"||p.length<ke)throw _(`password is required and must be at least ${ke} characters`);if(p.length>j)throw _(`password must not exceed ${j} characters`);if(R!==void 0&&!Array.isArray(R))throw _("roles must be an array of strings when provided");if(Array.isArray(R)&&!R.every(G=>typeof G=="string"))throw _("each role must be a string");let E=Array.isArray(R)?R:void 0,v=E!==void 0?{identifiers:y,password:p,roles:E}:{identifiers:y,password:p},U=f.headers.get("x-forwarded-for")||"127.0.0.1",Z=f.headers.get("user-agent")||"Unknown";if(e.rateLimit!==false){let G=await X,Ee=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await G.consume(`register:${U}`,Ee,900*1e3);}let b=await u(v,{ip:U,userAgent:Z});return b.success?(s.info(h(n,"auth.register.success",{userId:b.user.id,duration_ms:Date.now()-I})),O(w,201,"User registered successfully",{user:b.user})):(s.warn(h(n,"auth.register.failure",{errorCode:b.error.code,duration_ms:Date.now()-I})),w.status=b.error.statusCode,{error:true,statusCode:b.error.statusCode,code:b.error.code,message:b.error.message,data:null})}).post("/login",async({body:l,request:f,set:w})=>{let I=Date.now(),m=K(l),{identifier:T,password:p}=m;if(typeof T!="string"||T.trim().length===0)throw _("identifier is required and must be a non-empty string");if(T.length>ve)throw _(`identifier must not exceed ${ve} characters`);if(typeof p!="string"||p.length===0)throw _("password is required");if(p.length>j)throw _(`password must not exceed ${j} characters`);let R=T.trim(),y=f.headers.get("x-forwarded-for")||"127.0.0.1",E=f.headers.get("user-agent")||"Unknown";if(e.rateLimit!==false){let U=await X,Z=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await U.consume(`login:${y}`,Z,900*1e3);}let v=await d({identifier:R,password:p},{ip:y,userAgent:E});return v.success?(Ke(()=>r.hooks?.onLoginSuccess?.(v.user,{ip:y,userAgent:E})),oe(w,v.refreshToken,r),ne(w,v.accessToken,r),s.info(h(n,"auth.login.success",{userId:v.user.id,duration_ms:Date.now()-I})),O(w,200,"Login successful",{accessToken:v.accessToken,user:v.user})):(Ke(()=>r.hooks?.onLoginFailed?.(R,v.error,{ip:y})),s.warn(h(n,"auth.login.failure",{errorCode:v.error.code,duration_ms:Date.now()-I})),w.status=v.error.statusCode,{error:true,statusCode:v.error.statusCode,code:v.error.code,message:v.error.message,data:null})}).post("/refresh",async({request:l,set:f})=>{let w=Date.now(),I=P(l.headers.get("cookie")??void 0,L(r));if(!I)throw new a("UNAUTHORIZED","Refresh token cookie is missing");let m=l.headers.get("x-forwarded-for")||"127.0.0.1",T=l.headers.get("user-agent")||"Unknown",p=await c(I,{ip:m,userAgent:T});return p.success?(oe(f,p.refreshToken,r),ne(f,p.accessToken,r),s.info(h(n,"auth.refresh.success",{userId:p.user.id,duration_ms:Date.now()-w})),O(f,200,"Token refreshed",{accessToken:p.accessToken})):(_e(f,r),s.warn(h(n,"auth.refresh.failure",{errorCode:p.error.code,duration_ms:Date.now()-w})),f.status=p.error.statusCode,{error:true,statusCode:p.error.statusCode,code:p.error.code,message:p.error.message,data:null})}).post("/logout",async({request:l,set:f})=>{let w=Date.now(),I=P(l.headers.get("cookie")??void 0,L(r));return await i(I),_e(f,r),Oe(f,r),s.info(h(n,"auth.logout",{duration_ms:Date.now()-w})),O(f,200,"Logged out",null)}).post("/logout-all",async({request:l,set:f})=>{let w=Date.now(),I=await F(l,f,r);return await g(I.id),Ke(()=>r.hooks?.onLogout?.(I.id)),_e(f,r),Oe(f,r),s.info(h(n,"auth.logout_all",{userId:I.id,duration_ms:Date.now()-w})),O(f,200,"All sessions revoked",null)}).get("/me",async({request:l,set:f})=>{let w=Date.now(),I=await F(l,f,r),m=await x(I.id);return m.success?(s.info(h(n,"auth.me.success",{userId:m.user.id,duration_ms:Date.now()-w})),O(f,200,"OK",m.user)):(s.warn(h(n,"auth.me.failure",{userId:I.id,errorCode:m.error.code,duration_ms:Date.now()-w})),f.status=m.error.statusCode,{error:true,statusCode:m.error.statusCode,code:m.error.code,message:m.error.message,data:null})}).get("/me/identifiers",async({request:l,set:f})=>{let w=Date.now(),I=await F(l,f,r),m=await x(I.id);return m.success?(s.info(h(n,"auth.me.identifiers.success",{userId:m.user.id,count:m.user.identifiers?.length??0,duration_ms:Date.now()-w})),O(f,200,"OK",{identifiers:m.user.identifiers??[]})):(s.warn(h(n,"auth.me.identifiers.failure",{userId:I.id,errorCode:m.error.code,duration_ms:Date.now()-w})),f.status=m.error.statusCode,{error:true,statusCode:m.error.statusCode,code:m.error.code,message:m.error.message,data:null})}).post("/me/identifiers",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{identifiers:p}=T;if(!Array.isArray(p)||p.length===0)throw _("identifiers is required and must be a non-empty array");if(p.length>z)throw _(`identifiers must not exceed ${z} entries`);let R=p.map((E,v)=>He(E,v)),y=await k(m.id,R);return y.success?(s.info(h(n,"auth.identifiers.created",{userId:m.id,count:y.identifiers.length,duration_ms:Date.now()-I})),O(w,201,"Identifiers added successfully",{identifiers:y.identifiers})):(s.warn(h(n,"auth.identifiers.create_failure",{userId:m.id,errorCode:y.error.code,duration_ms:Date.now()-I})),w.status=y.error.statusCode,{error:true,statusCode:y.error.statusCode,code:y.error.code,message:y.error.message,data:null})}).put("/me/identifiers",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{identifiers:p}=T;if(!Array.isArray(p)||p.length===0)throw _("identifiers is required and must be a non-empty array");if(p.length>z)throw _(`identifiers must not exceed ${z} entries`);let R=p.map((E,v)=>{if(typeof E!="object"||E===null||Array.isArray(E))throw _(`identifiers[${v}] must be an object`);let U=E;if(typeof U.id!="string"||U.id.trim().length===0)throw _(`identifiers[${v}].id is required and must be a non-empty string`);let{type:Z,value:b}=He(E,v);return {id:U.id,type:Z,value:b}}),y=await H(m.id,R);return y.success?(s.info(h(n,"auth.identifiers.updated",{userId:m.id,count:y.identifiers.length,duration_ms:Date.now()-I})),O(w,200,"Identifiers updated successfully",{identifiers:y.identifiers})):(s.warn(h(n,"auth.identifiers.update_failure",{userId:m.id,errorCode:y.error.code,duration_ms:Date.now()-I})),w.status=y.error.statusCode,{error:true,statusCode:y.error.statusCode,code:y.error.code,message:y.error.message,data:null})}).delete("/me/identifiers",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{ids:p}=T;if(!Array.isArray(p)||p.length===0)throw _("ids is required and must be a non-empty array of strings");if(!p.every(y=>typeof y=="string"))throw _("each id must be a string");let R=await ae(m.id,p);return R.success?(s.info(h(n,"auth.identifiers.deleted",{userId:m.id,duration_ms:Date.now()-I})),O(w,200,"Identifiers deleted successfully",{identifiers:R.identifiers})):(s.warn(h(n,"auth.identifiers.delete_failure",{userId:m.id,errorCode:R.error.code,duration_ms:Date.now()-I})),w.status=R.error.statusCode,{error:true,statusCode:R.error.statusCode,code:R.error.code,message:R.error.message,data:null})}).patch("/me/password",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{currentPassword:p,newPassword:R}=T;if(typeof p!="string"||p.length===0)throw _("currentPassword is required");if(typeof R!="string"||R.length<ke)throw _(`newPassword must be at least ${ke} characters`);if(R.length>j)throw _(`newPassword must not exceed ${j} characters`);if(p===R)throw _("newPassword must be different from currentPassword");let y=await N(m.id,p,R);return y.success?(s.info(h(n,"auth.password.changed",{userId:m.id,duration_ms:Date.now()-I})),O(w,200,"Password updated successfully. All sessions have been revoked.",null)):(s.warn(h(n,"auth.password.change_failure",{userId:m.id,errorCode:y.error.code,duration_ms:Date.now()-I})),w.status=y.error.statusCode,{error:true,statusCode:y.error.statusCode,code:y.error.code,message:y.error.message,data:null})}).post("/users/:userId/roles",async({body:l,request:f,set:w,params:I})=>{let m=Date.now();if(!(await F(f,w,r)).roles.includes("admin"))throw new a("FORBIDDEN","Requires one of roles: admin");let p=K(l),{roles:R}=p,{userId:y}=I;if(!y)throw _("userId is required");if(!Array.isArray(R)||R.length===0)throw _("roles must be a non-empty array of strings");if(!R.every(v=>typeof v=="string"))throw _("each role must be a string");let E=await C(y,R);return E.success?(s.info(h(n,"auth.roles.assigned",{targetUserId:y,roles:R,duration_ms:Date.now()-m})),O(w,200,"Roles assigned successfully",{user:E.user})):(s.warn(h(n,"auth.roles.assign_failure",{targetUserId:y,errorCode:E.error.code,duration_ms:Date.now()-m})),w.status=E.error.statusCode,{error:true,statusCode:E.error.statusCode,code:E.error.code,message:E.error.message,data:null})}),Ae}o(Cr,"createAuthRouter");function Ls(e){if(e.mode==="client"){let i={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};ze(i);let g=i.logger??S,x=i.loggerService??"sentri",C=Ue(g,x),N=Le(g,x);return {protect:o(()=>be(i),"protect"),authorize:o((...k)=>C(...k),"authorize"),permit:o(k=>N(k),"permit"),errorHandler:o(k=>ie(k),"errorHandler")}}let{privateKey:r}=crypto.generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),t={mode:"server",dialect:e.dialect,secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??S,n=t.loggerService??"sentri",u=A(t),d=Ue(s,n),c=Le(s,n);return {protect:o(()=>be(t),"protect"),authorize:o((...i)=>d(...i),"authorize"),permit:o(i=>c(i),"permit"),errorHandler:o(i=>ie(i),"errorHandler"),router:o(()=>Cr(t),"router"),migrate:o(()=>Xe(D(t.dialect)),"migrate"),getCurrentAccessToken:o(i=>q(i,t),"getCurrentAccessToken"),hashPassword:o(i=>W(i,u.saltRounds),"hashPassword"),verifyPassword:o((i,g)=>J(i,g),"verifyPassword"),signAccessToken:o(i=>Q(i,t),"signAccessToken"),signRefreshToken:o(i=>ee(i,t),"signRefreshToken"),verifyAccessToken:o(i=>V(i,t),"verifyAccessToken"),verifyRefreshToken:o(i=>re(i,t),"verifyRefreshToken"),register:o(i=>de(i,t),"register"),login:o(i=>le(i,t),"login"),refresh:o(i=>M(i,t),"refresh"),logout:o(i=>fe(i,t),"logout"),logoutAll:o(i=>he(i,t),"logoutAll"),getUser:o(i=>me(i,t),"getUser"),changePassword:o((i,g,x)=>pe(i,g,x,t),"changePassword"),assignRoles:o((i,g)=>we(i,g,t),"assignRoles"),bulkCreateIdentifiers:o((i,g)=>ge(i,g,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:o((i,g)=>ye(i,g,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:o((i,g)=>Ie(i,g,t),"bulkDeleteIdentifiers")}}o(Ls,"createAuthElysia");exports.SENTRI_ERROR_STATUS=$e;exports.SentriError=a;exports.authorize=Hr;exports.createAuthElysia=Ls;exports.createAuthRouter=Cr;exports.createAuthorize=Ue;exports.createErrorHandler=ie;exports.createPermit=Le;exports.getCurrentAccessToken=q;exports.permit=Vr;exports.protect=be;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as kysely from 'kysely';
|
|
2
|
+
import * as elysia from 'elysia';
|
|
3
|
+
import { Elysia } from 'elysia';
|
|
4
|
+
import { AuthConfig, AuthUser, SentriLogger, ServerAuthConfig, CookieConfig, AccessCookieConfig, AuthHooks, RouterHandlers, RegisterInput, RegisterResult, LoginInput, AuthResult, RefreshResult, GetUserResult, ChangePasswordResult, AssignRolesResult, IdentifierInput, BulkIdentifiersResult } from '../../core/index.cjs';
|
|
5
|
+
export { ApiResponse, ClientAuthConfig, IdentifierRecord, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode } from '../../core/index.cjs';
|
|
6
|
+
|
|
7
|
+
declare function protect(config: AuthConfig): Elysia<"", {
|
|
8
|
+
decorator: {};
|
|
9
|
+
store: {};
|
|
10
|
+
derive: {
|
|
11
|
+
readonly user: AuthUser<string>;
|
|
12
|
+
};
|
|
13
|
+
resolve: {};
|
|
14
|
+
}, {
|
|
15
|
+
typebox: {};
|
|
16
|
+
error: {};
|
|
17
|
+
}, {
|
|
18
|
+
schema: {};
|
|
19
|
+
standaloneSchema: {};
|
|
20
|
+
macro: {};
|
|
21
|
+
macroFn: {};
|
|
22
|
+
parser: {};
|
|
23
|
+
response: elysia.ExtractErrorFromHandle<{
|
|
24
|
+
readonly user: AuthUser<string>;
|
|
25
|
+
}>;
|
|
26
|
+
}, {}, {
|
|
27
|
+
derive: {};
|
|
28
|
+
resolve: {};
|
|
29
|
+
schema: {};
|
|
30
|
+
standaloneSchema: {};
|
|
31
|
+
response: {};
|
|
32
|
+
}, {
|
|
33
|
+
derive: {};
|
|
34
|
+
resolve: {};
|
|
35
|
+
schema: {};
|
|
36
|
+
standaloneSchema: {};
|
|
37
|
+
response: {};
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
type ElysiaContext = {
|
|
41
|
+
user?: AuthUser;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
};
|
|
44
|
+
declare function createAuthorize(logger: SentriLogger, service: string): <TRole extends string>(...allowedRoles: TRole[]) => ({ user }: ElysiaContext) => void;
|
|
45
|
+
declare function authorize<TRole extends string>(...allowedRoles: TRole[]): ({ user }: ElysiaContext) => void;
|
|
46
|
+
|
|
47
|
+
type ElysiaPermitCheck<T = Record<string, unknown>> = (ctx: T & {
|
|
48
|
+
user?: AuthUser;
|
|
49
|
+
}) => boolean | Promise<boolean>;
|
|
50
|
+
interface ElysiaPermitOptions<TRole extends string, T = Record<string, unknown>> {
|
|
51
|
+
roles?: TRole[];
|
|
52
|
+
check: ElysiaPermitCheck<T>;
|
|
53
|
+
}
|
|
54
|
+
declare function createPermit(logger: SentriLogger, service: string): <TRole extends string>(optionsOrCheck: ElysiaPermitOptions<TRole> | ElysiaPermitCheck) => (ctx: {
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
user?: AuthUser;
|
|
57
|
+
}) => Promise<void>;
|
|
58
|
+
declare function permit<TRole extends string>(optionsOrCheck: ElysiaPermitOptions<TRole> | ElysiaPermitCheck): (ctx: {
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
user?: AuthUser;
|
|
61
|
+
}) => Promise<void>;
|
|
62
|
+
|
|
63
|
+
interface ElysiaErrorHandlerOptions {
|
|
64
|
+
onUnhandled?: (error: unknown) => void;
|
|
65
|
+
}
|
|
66
|
+
type ElysiaErrorHandler = any;
|
|
67
|
+
declare function createErrorHandler(options?: ElysiaErrorHandlerOptions): ElysiaErrorHandler;
|
|
68
|
+
|
|
69
|
+
interface SentriElysiaVariables {
|
|
70
|
+
user: AuthUser | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare function getCurrentAccessToken(request: Request, config: ServerAuthConfig): string | undefined;
|
|
74
|
+
|
|
75
|
+
declare function createAuthRouter<TRole extends string>(config: ServerAuthConfig<TRole>): Elysia;
|
|
76
|
+
|
|
77
|
+
interface CreateElysiaServerOptions<TRole extends string = string> {
|
|
78
|
+
mode: 'server';
|
|
79
|
+
validRoles: readonly TRole[];
|
|
80
|
+
dialect: kysely.Dialect;
|
|
81
|
+
accessExpiresIn?: string | number;
|
|
82
|
+
refreshExpiresIn?: string | number;
|
|
83
|
+
saltRounds?: number;
|
|
84
|
+
apiKey?: string;
|
|
85
|
+
cookie?: CookieConfig;
|
|
86
|
+
accessCookie?: AccessCookieConfig;
|
|
87
|
+
hooks?: AuthHooks;
|
|
88
|
+
router?: RouterHandlers;
|
|
89
|
+
isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
|
|
90
|
+
redisUrl?: string;
|
|
91
|
+
logger?: SentriLogger;
|
|
92
|
+
loggerService?: string;
|
|
93
|
+
}
|
|
94
|
+
interface CreateElysiaClientOptions<TRole extends string = string> {
|
|
95
|
+
mode: 'client';
|
|
96
|
+
keyUri: string;
|
|
97
|
+
validRoles?: readonly TRole[];
|
|
98
|
+
logger?: SentriLogger;
|
|
99
|
+
loggerService?: string;
|
|
100
|
+
}
|
|
101
|
+
interface ElysiaAuthClient<TRole extends string = string> {
|
|
102
|
+
protect(): any;
|
|
103
|
+
authorize(...roles: TRole[]): (ctx: {
|
|
104
|
+
user?: AuthUser;
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
}) => void;
|
|
107
|
+
permit(check: ElysiaPermitCheck): (ctx: {
|
|
108
|
+
user?: AuthUser;
|
|
109
|
+
[key: string]: unknown;
|
|
110
|
+
}) => void | Promise<void>;
|
|
111
|
+
permit(options: ElysiaPermitOptions<TRole>): (ctx: {
|
|
112
|
+
user?: AuthUser;
|
|
113
|
+
[key: string]: unknown;
|
|
114
|
+
}) => void | Promise<void>;
|
|
115
|
+
errorHandler(options?: ElysiaErrorHandlerOptions): ElysiaErrorHandler;
|
|
116
|
+
}
|
|
117
|
+
interface ElysiaServerAuthClient<TRole extends string = string> extends ElysiaAuthClient<TRole> {
|
|
118
|
+
router(): any;
|
|
119
|
+
migrate(): Promise<void>;
|
|
120
|
+
getCurrentAccessToken(request: Request): string | undefined;
|
|
121
|
+
hashPassword(plain: string): Promise<string>;
|
|
122
|
+
verifyPassword(plain: string, hash: string): Promise<boolean>;
|
|
123
|
+
signAccessToken(payload: AuthUser<TRole>): string;
|
|
124
|
+
signRefreshToken(sessionId: string): string;
|
|
125
|
+
verifyAccessToken(token: string): AuthUser<TRole>;
|
|
126
|
+
verifyRefreshToken(token: string): {
|
|
127
|
+
sessionId: string;
|
|
128
|
+
};
|
|
129
|
+
register(input: RegisterInput<TRole>): Promise<RegisterResult<TRole>>;
|
|
130
|
+
login(input: LoginInput): Promise<AuthResult<TRole>>;
|
|
131
|
+
refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
|
|
132
|
+
logout(refreshToken: string): Promise<void>;
|
|
133
|
+
logoutAll(userId: string): Promise<void>;
|
|
134
|
+
getUser(userId: string): Promise<GetUserResult<TRole>>;
|
|
135
|
+
changePassword(userId: string, currentPassword: string, newPassword: string): Promise<ChangePasswordResult>;
|
|
136
|
+
assignRoles(userId: string, roles: TRole[]): Promise<AssignRolesResult<TRole>>;
|
|
137
|
+
bulkCreateIdentifiers(userId: string, identifiers: IdentifierInput[]): Promise<BulkIdentifiersResult>;
|
|
138
|
+
bulkUpdateIdentifiers(userId: string, updates: Array<{
|
|
139
|
+
id: string;
|
|
140
|
+
type: string;
|
|
141
|
+
value: string;
|
|
142
|
+
}>): Promise<BulkIdentifiersResult>;
|
|
143
|
+
bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
|
|
144
|
+
}
|
|
145
|
+
type ElysiaClientAuthClient<TRole extends string = string> = ElysiaAuthClient<TRole>;
|
|
146
|
+
declare function createAuthElysia<TRole extends string = string>(options: CreateElysiaServerOptions<TRole>): ElysiaServerAuthClient<TRole>;
|
|
147
|
+
declare function createAuthElysia<TRole extends string = string>(options: CreateElysiaClientOptions<TRole>): ElysiaClientAuthClient<TRole>;
|
|
148
|
+
|
|
149
|
+
export { AccessCookieConfig, AssignRolesResult, AuthConfig, AuthHooks, AuthResult, AuthUser, BulkIdentifiersResult, ChangePasswordResult, CookieConfig, type CreateElysiaClientOptions, type CreateElysiaServerOptions, type ElysiaAuthClient, type ElysiaClientAuthClient, type ElysiaErrorHandler, type ElysiaErrorHandlerOptions, type ElysiaPermitCheck, type ElysiaPermitOptions, type ElysiaServerAuthClient, GetUserResult, IdentifierInput, LoginInput, RefreshResult, RegisterInput, RegisterResult, RouterHandlers, type SentriElysiaVariables, SentriLogger, ServerAuthConfig, authorize, createAuthElysia, createAuthRouter, createAuthorize, createErrorHandler, createPermit, getCurrentAccessToken, permit, protect };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as kysely from 'kysely';
|
|
2
|
+
import * as elysia from 'elysia';
|
|
3
|
+
import { Elysia } from 'elysia';
|
|
4
|
+
import { AuthConfig, AuthUser, SentriLogger, ServerAuthConfig, CookieConfig, AccessCookieConfig, AuthHooks, RouterHandlers, RegisterInput, RegisterResult, LoginInput, AuthResult, RefreshResult, GetUserResult, ChangePasswordResult, AssignRolesResult, IdentifierInput, BulkIdentifiersResult } from '../../core/index.js';
|
|
5
|
+
export { ApiResponse, ClientAuthConfig, IdentifierRecord, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode } from '../../core/index.js';
|
|
6
|
+
|
|
7
|
+
declare function protect(config: AuthConfig): Elysia<"", {
|
|
8
|
+
decorator: {};
|
|
9
|
+
store: {};
|
|
10
|
+
derive: {
|
|
11
|
+
readonly user: AuthUser<string>;
|
|
12
|
+
};
|
|
13
|
+
resolve: {};
|
|
14
|
+
}, {
|
|
15
|
+
typebox: {};
|
|
16
|
+
error: {};
|
|
17
|
+
}, {
|
|
18
|
+
schema: {};
|
|
19
|
+
standaloneSchema: {};
|
|
20
|
+
macro: {};
|
|
21
|
+
macroFn: {};
|
|
22
|
+
parser: {};
|
|
23
|
+
response: elysia.ExtractErrorFromHandle<{
|
|
24
|
+
readonly user: AuthUser<string>;
|
|
25
|
+
}>;
|
|
26
|
+
}, {}, {
|
|
27
|
+
derive: {};
|
|
28
|
+
resolve: {};
|
|
29
|
+
schema: {};
|
|
30
|
+
standaloneSchema: {};
|
|
31
|
+
response: {};
|
|
32
|
+
}, {
|
|
33
|
+
derive: {};
|
|
34
|
+
resolve: {};
|
|
35
|
+
schema: {};
|
|
36
|
+
standaloneSchema: {};
|
|
37
|
+
response: {};
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
type ElysiaContext = {
|
|
41
|
+
user?: AuthUser;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
};
|
|
44
|
+
declare function createAuthorize(logger: SentriLogger, service: string): <TRole extends string>(...allowedRoles: TRole[]) => ({ user }: ElysiaContext) => void;
|
|
45
|
+
declare function authorize<TRole extends string>(...allowedRoles: TRole[]): ({ user }: ElysiaContext) => void;
|
|
46
|
+
|
|
47
|
+
type ElysiaPermitCheck<T = Record<string, unknown>> = (ctx: T & {
|
|
48
|
+
user?: AuthUser;
|
|
49
|
+
}) => boolean | Promise<boolean>;
|
|
50
|
+
interface ElysiaPermitOptions<TRole extends string, T = Record<string, unknown>> {
|
|
51
|
+
roles?: TRole[];
|
|
52
|
+
check: ElysiaPermitCheck<T>;
|
|
53
|
+
}
|
|
54
|
+
declare function createPermit(logger: SentriLogger, service: string): <TRole extends string>(optionsOrCheck: ElysiaPermitOptions<TRole> | ElysiaPermitCheck) => (ctx: {
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
user?: AuthUser;
|
|
57
|
+
}) => Promise<void>;
|
|
58
|
+
declare function permit<TRole extends string>(optionsOrCheck: ElysiaPermitOptions<TRole> | ElysiaPermitCheck): (ctx: {
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
user?: AuthUser;
|
|
61
|
+
}) => Promise<void>;
|
|
62
|
+
|
|
63
|
+
interface ElysiaErrorHandlerOptions {
|
|
64
|
+
onUnhandled?: (error: unknown) => void;
|
|
65
|
+
}
|
|
66
|
+
type ElysiaErrorHandler = any;
|
|
67
|
+
declare function createErrorHandler(options?: ElysiaErrorHandlerOptions): ElysiaErrorHandler;
|
|
68
|
+
|
|
69
|
+
interface SentriElysiaVariables {
|
|
70
|
+
user: AuthUser | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare function getCurrentAccessToken(request: Request, config: ServerAuthConfig): string | undefined;
|
|
74
|
+
|
|
75
|
+
declare function createAuthRouter<TRole extends string>(config: ServerAuthConfig<TRole>): Elysia;
|
|
76
|
+
|
|
77
|
+
interface CreateElysiaServerOptions<TRole extends string = string> {
|
|
78
|
+
mode: 'server';
|
|
79
|
+
validRoles: readonly TRole[];
|
|
80
|
+
dialect: kysely.Dialect;
|
|
81
|
+
accessExpiresIn?: string | number;
|
|
82
|
+
refreshExpiresIn?: string | number;
|
|
83
|
+
saltRounds?: number;
|
|
84
|
+
apiKey?: string;
|
|
85
|
+
cookie?: CookieConfig;
|
|
86
|
+
accessCookie?: AccessCookieConfig;
|
|
87
|
+
hooks?: AuthHooks;
|
|
88
|
+
router?: RouterHandlers;
|
|
89
|
+
isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
|
|
90
|
+
redisUrl?: string;
|
|
91
|
+
logger?: SentriLogger;
|
|
92
|
+
loggerService?: string;
|
|
93
|
+
}
|
|
94
|
+
interface CreateElysiaClientOptions<TRole extends string = string> {
|
|
95
|
+
mode: 'client';
|
|
96
|
+
keyUri: string;
|
|
97
|
+
validRoles?: readonly TRole[];
|
|
98
|
+
logger?: SentriLogger;
|
|
99
|
+
loggerService?: string;
|
|
100
|
+
}
|
|
101
|
+
interface ElysiaAuthClient<TRole extends string = string> {
|
|
102
|
+
protect(): any;
|
|
103
|
+
authorize(...roles: TRole[]): (ctx: {
|
|
104
|
+
user?: AuthUser;
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
}) => void;
|
|
107
|
+
permit(check: ElysiaPermitCheck): (ctx: {
|
|
108
|
+
user?: AuthUser;
|
|
109
|
+
[key: string]: unknown;
|
|
110
|
+
}) => void | Promise<void>;
|
|
111
|
+
permit(options: ElysiaPermitOptions<TRole>): (ctx: {
|
|
112
|
+
user?: AuthUser;
|
|
113
|
+
[key: string]: unknown;
|
|
114
|
+
}) => void | Promise<void>;
|
|
115
|
+
errorHandler(options?: ElysiaErrorHandlerOptions): ElysiaErrorHandler;
|
|
116
|
+
}
|
|
117
|
+
interface ElysiaServerAuthClient<TRole extends string = string> extends ElysiaAuthClient<TRole> {
|
|
118
|
+
router(): any;
|
|
119
|
+
migrate(): Promise<void>;
|
|
120
|
+
getCurrentAccessToken(request: Request): string | undefined;
|
|
121
|
+
hashPassword(plain: string): Promise<string>;
|
|
122
|
+
verifyPassword(plain: string, hash: string): Promise<boolean>;
|
|
123
|
+
signAccessToken(payload: AuthUser<TRole>): string;
|
|
124
|
+
signRefreshToken(sessionId: string): string;
|
|
125
|
+
verifyAccessToken(token: string): AuthUser<TRole>;
|
|
126
|
+
verifyRefreshToken(token: string): {
|
|
127
|
+
sessionId: string;
|
|
128
|
+
};
|
|
129
|
+
register(input: RegisterInput<TRole>): Promise<RegisterResult<TRole>>;
|
|
130
|
+
login(input: LoginInput): Promise<AuthResult<TRole>>;
|
|
131
|
+
refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
|
|
132
|
+
logout(refreshToken: string): Promise<void>;
|
|
133
|
+
logoutAll(userId: string): Promise<void>;
|
|
134
|
+
getUser(userId: string): Promise<GetUserResult<TRole>>;
|
|
135
|
+
changePassword(userId: string, currentPassword: string, newPassword: string): Promise<ChangePasswordResult>;
|
|
136
|
+
assignRoles(userId: string, roles: TRole[]): Promise<AssignRolesResult<TRole>>;
|
|
137
|
+
bulkCreateIdentifiers(userId: string, identifiers: IdentifierInput[]): Promise<BulkIdentifiersResult>;
|
|
138
|
+
bulkUpdateIdentifiers(userId: string, updates: Array<{
|
|
139
|
+
id: string;
|
|
140
|
+
type: string;
|
|
141
|
+
value: string;
|
|
142
|
+
}>): Promise<BulkIdentifiersResult>;
|
|
143
|
+
bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
|
|
144
|
+
}
|
|
145
|
+
type ElysiaClientAuthClient<TRole extends string = string> = ElysiaAuthClient<TRole>;
|
|
146
|
+
declare function createAuthElysia<TRole extends string = string>(options: CreateElysiaServerOptions<TRole>): ElysiaServerAuthClient<TRole>;
|
|
147
|
+
declare function createAuthElysia<TRole extends string = string>(options: CreateElysiaClientOptions<TRole>): ElysiaClientAuthClient<TRole>;
|
|
148
|
+
|
|
149
|
+
export { AccessCookieConfig, AssignRolesResult, AuthConfig, AuthHooks, AuthResult, AuthUser, BulkIdentifiersResult, ChangePasswordResult, CookieConfig, type CreateElysiaClientOptions, type CreateElysiaServerOptions, type ElysiaAuthClient, type ElysiaClientAuthClient, type ElysiaErrorHandler, type ElysiaErrorHandlerOptions, type ElysiaPermitCheck, type ElysiaPermitOptions, type ElysiaServerAuthClient, GetUserResult, IdentifierInput, LoginInput, RefreshResult, RegisterInput, RegisterResult, RouterHandlers, type SentriElysiaVariables, SentriLogger, ServerAuthConfig, authorize, createAuthElysia, createAuthRouter, createAuthorize, createErrorHandler, createPermit, getCurrentAccessToken, permit, protect };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import Ge from'bcrypt';import Y from'jsonwebtoken';import {Elysia}from'elysia';var Nr=Object.defineProperty;var o=(e,r)=>Nr(e,"name",{value:r,configurable:true});var $e=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),a=class extends Error{static{o(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??$e[r]??500;}};var Ve=new WeakMap,Me=32,qe=10,Be=31;function ze(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new a("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new a("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Me)throw new a("CONFIGURATION_ERROR",`secret must be at least ${Me} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<qe||s>Be)throw new a("CONFIGURATION_ERROR",`saltRounds must be an integer between ${qe} and ${Be}`);if(!e.validRoles||e.validRoles.length===0)throw new a("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new a("CONFIGURATION_ERROR","dialect is required in server mode")}o(ze,"validateConfig");function A(e){let r=Ve.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}o(A,"resolveServerConfig");var Dr=/^(\d+)([smhdw])$/,Sr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},je=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=je.get(e);if(r!==void 0)return r;let t=Dr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Sr[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let n=parseInt(t[1],10)*s;return je.set(e,n),n}o($,"parseExpiry");var S={info:o(()=>{},"info"),warn:o(()=>{},"warn"),error:o(()=>{},"error")};function h(e,r,t){return {service:e,event:r,...t}}o(h,"buildLogData");async function Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}o(Xe,"runMigrations");var Ze=new Map;function D(e){let r=Ze.get(e);return r||(r=new Kysely({dialect:e}),Ze.set(e,r)),r}o(D,"getDatabase");async function W(e,r=12){return Ge.hash(e,r)}o(W,"hashPassword");async function J(e,r){return Ge.compare(e,r)}o(J,"verifyPassword");var We=new Map,Je=new Map,Lr=3600*1e3;function Qe(e){let r=We.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),n=s.export({format:"jwk"}),d=createHash("sha256").update(JSON.stringify({e:n.e,kty:n.kty,n:n.n})).digest("base64url"),c={...n,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:c},We.set(e,r);}return r}o(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}o(er,"buildJwks");function rr(e){return Qe(e).publicKey}o(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Lr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new a("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let n=await s.json();if(!n.keys||n.keys.length===0)throw new a("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let u=n.keys[0],d=createPublicKey({key:u,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}o(tr,"fetchPublicKey");var sr=new Map,or=new Map,nr=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}o(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}o(ar,"getHsSecrets");function Pr(e){let r=or.get(e);return r||(r=createPrivateKey(e),or.set(e,r)),r}o(Pr,"getRsPrivateKey");function ur(e){let r=A(e);if(ir(r.algorithm)){let n=Pr(e.secret);return {accessKey:n,refreshKey:n}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}o(ur,"getSigningKeys");function cr(e,r){let t=A(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:n}=ar(e.secret);return r==="access"?s:n}o(cr,"getVerifyKey");function dr(e,r,t,s){let n=`${t}:${s}`,u=nr.get(n);return u||(u={expiresIn:t,algorithm:s},nr.set(n,u)),Y.sign(e,r,u)}o(dr,"sign");function lr(e,r,t){try{let s=Y.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new a("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof a?s:s instanceof Y.TokenExpiredError?new a("TOKEN_EXPIRED","Token has expired"):new a("TOKEN_INVALID","Token is invalid or malformed")}}o(lr,"verify");function Q(e,r){let t=A(r),{accessKey:s}=ur(r);return dr(e,s,t.accessExpiresIn,t.algorithm)}o(Q,"signAccessToken");function ee(e,r){let t=A(r),{refreshKey:s}=ur(r);return dr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}o(ee,"signRefreshToken");function V(e,r){let t=A(r),s=cr(r,"access");return lr(e,s,t.algorithm)}o(V,"verifyAccessToken");function re(e,r){let t=A(r),s=cr(r,"refresh");return lr(e,s,t.algorithm)}o(re,"verifyRefreshToken");function fr(e,r){try{let t=Y.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new a("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof a?t:t instanceof Y.TokenExpiredError?new a("TOKEN_EXPIRED","Token has expired"):new a("TOKEN_INVALID","Token is invalid or malformed")}}o(fr,"verifyTokenWithPublicKey");function xe(e){try{return JSON.parse(e)}catch{return []}}o(xe,"parseRoles");function Kr(e){return JSON.stringify(e)}o(Kr,"serializeRoles");function mr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}o(mr,"mapIdentifier");async function te(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:xe(t.roles)}:null}o(te,"findUserByIdentifierValue");async function ue(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:xe(t.roles)}:null}o(ue,"findUserById");async function pr(e,r,t){let s=t.map(n=>({id:randomUUID(),user_id:r,type:n.type,value:n.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(n=>({id:n.id,userId:n.user_id,type:n.type,value:n.value,createdAt:new Date}))}o(pr,"createIdentifiers");async function se(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(mr)}o(se,"findIdentifiersByUserId");async function Ce(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?mr(s):null}o(Ce,"findIdentifierById");async function wr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}o(wr,"countIdentifiersByUserId");async function gr(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}o(gr,"updateIdentifier");async function yr(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}o(yr,"deleteIdentifiers");async function Ir(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}o(Ir,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:Kr(t)}).where("id","=",r).execute();}o(Rr,"updateUserRoles");async function Ne(e,r){let t=randomUUID();return await e.insertInto("sentri_sessions").values({id:t,user_id:r.userId,expires_at:r.expiresAt.toISOString(),ip_address:r.ipAddress??null,user_agent:r.userAgent??null}).execute(),{id:t}}o(Ne,"createSession");async function De(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:xe(t.roles)}}:null}o(De,"findSessionById");async function Se(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}o(Se,"deleteSession");async function _r(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}o(_r,"markSessionReplaced");async function ce(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}o(ce,"deleteAllSessionsForUser");async function de(e,r,t){let s=A(r),n=D(r.dialect),u=e.roles??[],d=u.filter(k=>!s.validRolesSet.has(k));if(d.length>0)return {success:false,error:new a("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one identifier is required")};let c=e.identifiers.map(k=>({type:k.type.trim(),value:k.value.trim()}));if(new Set(c.map(k=>k.value)).size!==c.length)return {success:false,error:new a("VALIDATION_ERROR","Duplicate identifier values in request")};for(let k of c)if(await te(n,k.value))return {success:false,error:new a("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${k.value}`)};let g=await W(e.password,s.saltRounds),{userId:x,identifierRows:C}=await n.transaction().execute(async k=>{let H=randomUUID();await k.insertInto("sentri_users").values({id:H,password_hash:g,roles:JSON.stringify(u)}).execute();let ae=c.map(X=>({id:randomUUID(),user_id:H,type:X.type,value:X.value}));return await k.insertInto("sentri_identifiers").values(ae).execute(),{userId:H,identifierRows:ae}}),N={success:true,user:{id:x,roles:u,identifiers:C.map(k=>({id:k.id,type:k.type,value:k.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(N.user),N}o(de,"register");async function le(e,r,t){let s=A(r),n=D(r.dialect),u=await te(n,e.identifier.trim());if(!u){let N=new a("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,N,{ip:t?.ip??""}),{success:false,error:N}}if(!await J(e.password,u.passwordHash)){let N=new a("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,N,{ip:t?.ip??""}),{success:false,error:N}}let c=new Date(Date.now()+$(s.refreshExpiresIn)),i=await Ne(n,{userId:u.id,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),g={id:u.id,roles:u.roles},x=Q({id:u.id,roles:u.roles,sessionId:i.id},r),C=ee(i.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(g,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:x,refreshToken:C,user:g}}o(le,"login");async function M(e,r,t){let s=A(r),n=D(r.dialect),u;try{({sessionId:u}=re(e,r));}catch(N){return N instanceof a?{success:false,error:N}:{success:false,error:new a("TOKEN_INVALID","Invalid refresh token")}}let d=await De(n,u);if(!d)return {success:false,error:new a("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await ce(n,d.userId),{success:false,error:new a("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await Se(n,u),{success:false,error:new a("TOKEN_EXPIRED","Session has expired")};let c=new Date(Date.now()+$(s.refreshExpiresIn)),i=await Ne(n,{userId:d.userId,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(n,u,i.id);let g={id:d.user.id,roles:d.user.roles},x=Q({...g,sessionId:i.id},r),C=ee(i.id,r);return {success:true,accessToken:x,refreshToken:C,user:g}}o(M,"refresh");async function fe(e,r){let t=D(r.dialect),s;try{({sessionId:s}=re(e,r));}catch{return}let n=await De(t,s);n&&(await Se(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(n.userId));}o(fe,"logout");async function he(e,r){let t=D(r.dialect);await ce(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}o(he,"logoutAll");async function me(e,r){let t=D(r.dialect),s=await ue(t,e);if(!s)return {success:false,error:new a("USER_NOT_FOUND","User not found")};let n=await se(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:n.map(u=>({id:u.id,type:u.type,value:u.value}))}}}o(me,"getUser");async function pe(e,r,t,s){let n=A(s),u=D(s.dialect),d=await ue(u,e);if(!d)return {success:false,error:new a("USER_NOT_FOUND","User not found")};if(!await J(r,d.passwordHash))return {success:false,error:new a("INVALID_CREDENTIALS","Invalid credentials")};let i=await W(t,n.saltRounds);return await Ir(u,e,i),await ce(u,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}o(pe,"changePassword");async function we(e,r,t){let s=A(t),n=D(t.dialect),u=r.filter(g=>!s.validRolesSet.has(g));if(u.length>0)return {success:false,error:new a("INVALID_ROLE",`Invalid roles: ${u.join(", ")}`)};let d=await ue(n,e);if(!d)return {success:false,error:new a("USER_NOT_FOUND","User not found")};let c=new Set(d.roles);for(let g of r)c.add(g);let i=Array.from(c);return await Rr(n,e,i),{success:true,user:{id:d.id,roles:i}}}o(we,"assignRoles");async function ge(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one identifier is required")};let n=r.map(c=>({type:c.type.trim(),value:c.value.trim()}));if(new Set(n.map(c=>c.value)).size!==n.length)return {success:false,error:new a("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of n)if(await te(s,c.value))return {success:false,error:new a("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await pr(s,e,n),{success:true,identifiers:(await se(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}o(ge,"bulkCreateIdentifiers");async function ye(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one update is required")};let n=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()}));if(new Set(n.map(c=>c.value)).size!==n.length)return {success:false,error:new a("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of n){let i=await Ce(s,c.id,e);if(!i)return {success:false,error:new a("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(i.value!==c.value&&await te(s,c.value))return {success:false,error:new a("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await s.transaction().execute(async c=>{for(let i of n)await gr(c,i.id,e,{type:i.type,value:i.value});}),{success:true,identifiers:(await se(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}o(ye,"bulkUpdateIdentifiers");async function Ie(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new a("VALIDATION_ERROR","At least one ID is required")};let n=Array.from(new Set(r));for(let c of n)if(!await Ce(s,c,e))return {success:false,error:new a("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c}`)};return await wr(s,e)-n.length<1?{success:false,error:new a("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await yr(s,e,n),{success:true,identifiers:(await se(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))})}o(Ie,"bulkDeleteIdentifiers");function L(e){return A(e).cookieName}o(L,"getCookieName");function Re(e){return A(e).accessCookieName}o(Re,"getAccessCookieName");function P(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let n=e.indexOf(";",s),u=n===-1?e.length:n;if(e.startsWith(t,s))return e.slice(s+t.length,u);s=u+1;}}o(P,"readCookie");function oe(e,r,t){let s=t.cookie??{},n=$(A(t).refreshExpiresIn)/1e3;e.cookie??={},e.cookie[L(t)]={value:r,httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:n};}o(oe,"setCookieFromConfig");function _e(e,r){let t=r.cookie??{};e.cookie??={},e.cookie[L(r)]={value:"",httpOnly:t.httpOnly??true,path:t.path??"/",maxAge:0};}o(_e,"clearCookieFromConfig");function ne(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,n=$(A(t).accessExpiresIn)/1e3;e.cookie??={},e.cookie[Re(t)]={value:r,httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:n};}o(ne,"setAccessCookieFromConfig");function Oe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.cookie??={},e.cookie[Re(r)]={value:"",httpOnly:false,path:t.path??"/",maxAge:0};}o(Oe,"clearAccessCookieFromConfig");function q(e,r){let t=e.headers.get("authorization");if(t?.startsWith("Bearer "))return t.slice(7);let s=e.headers.get("cookie");return s?P(s,Re(r)):void 0}o(q,"getCurrentAccessToken");function be(e){let r=e.logger??S,t=e.loggerService??"sentri";if(e.mode==="client"){let n=e;return new Elysia().derive({as:"global"},async({request:u})=>{let d=u.headers.get("authorization"),c=d?.startsWith("Bearer ")?d.slice(7):void 0;if(!c)throw r.warn(h(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED"})),new a("UNAUTHORIZED","Missing or malformed Authorization header");try{let i=await tr(n.keyUri),g=fr(c,i),x={id:g.id,roles:g.roles};return r.info(h(t,"auth.protect.success",{mode:"client",userId:g.id})),{user:x}}catch(i){let g=i instanceof a?i.code:"TOKEN_INVALID";throw r.warn(h(t,"auth.protect.failure",{mode:"client",errorCode:g})),i}})}let s=e;return new Elysia().derive({as:"global"},async({request:n,set:u})=>{let d=q(n,s);if(!d)throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED"})),new a("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=V(d,s);if(s.isTokenRevoked&&await s.isTokenRevoked(c.sessionId))throw r.warn(h(t,"auth.protect.token_revoked",{mode:"server",userId:c.id})),new a("UNAUTHORIZED","Token has been revoked");let i={id:c.id,roles:c.roles};return r.info(h(t,"auth.protect.success",{mode:"server",userId:c.id})),{user:i}}catch(c){if(c instanceof a&&c.code==="TOKEN_EXPIRED"){let g=n.headers.get("cookie"),x=g?P(g,L(s)):void 0;if(!x)throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED"})),new a("UNAUTHORIZED","Token expired. Please login again.");let C;try{C=await M(x,s);}catch{throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED"})),new a("UNAUTHORIZED","Session expired. Please login again.")}if(!C.success)throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:C.error.code})),new a("UNAUTHORIZED","Session expired. Please login again.");return oe(u,C.refreshToken,s),ne(u,C.accessToken,s),u.headers["X-New-Access-Token"]=C.accessToken,r.info(h(t,"auth.protect.auto_refresh",{mode:"server",userId:C.user.id})),{user:C.user}}let i=c instanceof a?c.code:"TOKEN_INVALID";throw r.warn(h(t,"auth.protect.failure",{mode:"server",errorCode:i})),c}})}o(be,"protect");function Ar(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return ({user:n})=>{if(!n)throw r.warn(h(t,"auth.authorize.unauthenticated",{requiredRoles:e})),new a("UNAUTHORIZED","Not authenticated");let u=n.roles;if(!e.some(d=>u.includes(d)))throw r.warn(h(t,"auth.authorize.denied",{userId:n.id,userRoles:[...u],requiredRoles:e})),new a("FORBIDDEN",s);r.info(h(t,"auth.authorize.passed",{userId:n.id,userRoles:[...u],requiredRoles:e}));}}o(Ar,"createAuthorizeHandler");function Ue(e,r){return o(function(...s){return Ar(s,e,r)},"authorize")}o(Ue,"createAuthorize");function Hr(...e){return Ar(e,S,"sentri")}o(Hr,"authorize");var $r=new a("FORBIDDEN","You do not have permission to perform this action");function Er(e,r,t){return async s=>{let n=s.user;if(!n)throw r.warn(h(t,"auth.permit.unauthenticated",{})),new a("UNAUTHORIZED","Not authenticated");if(e.roles&&e.roles.length>0){let u=n.roles;if(e.roles.some(d=>u.includes(d))){r.info(h(t,"auth.permit.role_bypass",{userId:n.id,bypassedByRole:true}));return}}try{let u=e.check(s);if(u instanceof Promise?await u:u)r.info(h(t,"auth.permit.passed",{userId:n.id}));else throw r.warn(h(t,"auth.permit.denied",{userId:n.id})),$r}catch(u){throw u}}}o(Er,"createPermitHandler");function Le(e,r){return o(function(s){return Er(typeof s=="function"?{check:s}:s,e,r)},"permit")}o(Le,"createPermit");function Vr(e){return Er(typeof e=="function"?{check:e}:e,S,"sentri")}o(Vr,"permit");function ie(e){return ({error:r,set:t})=>r instanceof a?(t.status=r.statusCode,{error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null}):(e?.onUnhandled?.(r),t.status=500,{error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null})}o(ie,"createErrorHandler");var Fe=class{static{o(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let n=Date.now(),u=this.store.get(r);if((!u||u.expiresAt<n)&&(u={count:0,expiresAt:n+s},this.store.set(r,u)),u.count>t){let d=Math.ceil((u.expiresAt-n)/1e3);throw new Error(`Rate limit exceeded. Try again in ${d} seconds.`)}u.count++;}},Pe=class{static{o(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let n=`sentri:rl:${r}`,u=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(n).expire(n,u,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(d){if(d instanceof Error&&d.message.includes("Rate limit exceeded"))throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},B=null;async function Tr(e,r){if(B)return B;if(e)try{let{Redis:t}=await import('ioredis'),s=new t(e,{lazyConnect:!0,maxRetriesPerRequest:1});return await s.connect(),r?.info({msg:"Connected to Redis for Rate Limiting"}),B=new Pe(s,r??S),B}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return B=new Fe,B}o(Tr,"getRateLimiter");var ke=8,j=72,ve=255,xr=100,z=50;function _(e){return new a("VALIDATION_ERROR",e)}o(_,"badRequest");function O(e,r,t,s){return e.status=r,{error:false,statusCode:r,message:t,data:s}}o(O,"ok");function K(e){if(e==null||typeof e!="object"||Array.isArray(e))throw new a("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you set Content-Type: application/json?");return e}o(K,"parseBody");function qr(e,r){if(!r.apiKey)return;let t=e.headers.get("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new a("UNAUTHORIZED","Invalid or missing API key")}o(qr,"validateApiKey");function Ke(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}o(Ke,"fireHook");function Br(e){return e.startsWith("RS")||e.startsWith("PS")}o(Br,"isRSA");function He(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw _(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw _(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>xr)throw _(`identifiers[${r}].type must not exceed ${xr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw _(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>ve)throw _(`identifiers[${r}].value must not exceed ${ve} characters`);return {type:t.type,value:t.value}}o(He,"validateIdentifierInput");async function F(e,r,t){let s=q(e,t);if(!s)throw new a("UNAUTHORIZED","Missing or malformed Authorization header");let n=V(s,t);if(t.isTokenRevoked&&await t.isTokenRevoked(n.sessionId))throw new a("UNAUTHORIZED","Token has been revoked");return {id:n.id,roles:n.roles}}o(F,"requireAuth");function Cr(e){let r=e,t=A(r),s=e.logger??S,n=e.loggerService??"sentri",u=e.router?.register??(l=>de(l,r)),d=e.router?.login??(l=>le(l,r)),c=e.router?.refresh??(l=>M(l,r)),i=e.router?.logout??(l=>l!==void 0?fe(l,r):Promise.resolve()),g=e.router?.logoutAll??(l=>he(l,r)),x=e.router?.getUser??(l=>me(l,r)),C=e.router?.assignRoles??((l,f)=>we(l,f,r)),N=e.router?.changePassword??((l,f,w)=>pe(l,f,w,r)),k=e.router?.bulkCreateIdentifiers??((l,f)=>ge(l,f,r)),H=e.router?.bulkUpdateIdentifiers??((l,f)=>ye(l,f,r)),ae=e.router?.bulkDeleteIdentifiers??((l,f)=>Ie(l,f,r)),X=Tr(e.redisUrl,s),Ae=new Elysia().onError(ie());return Br(t.algorithm)&&Ae.get("/keys",({set:l})=>(l.headers["Cache-Control"]="public, max-age=3600",er(e.secret))),Ae.post("/register",async({body:l,request:f,set:w})=>{let I=Date.now();qr(f,r);let m=K(l),{identifiers:T,password:p,roles:R}=m;if(!Array.isArray(T)||T.length===0)throw _("identifiers is required and must be a non-empty array");if(T.length>z)throw _(`identifiers must not exceed ${z} entries`);let y=T.map((G,Ee)=>He(G,Ee));if(typeof p!="string"||p.length<ke)throw _(`password is required and must be at least ${ke} characters`);if(p.length>j)throw _(`password must not exceed ${j} characters`);if(R!==void 0&&!Array.isArray(R))throw _("roles must be an array of strings when provided");if(Array.isArray(R)&&!R.every(G=>typeof G=="string"))throw _("each role must be a string");let E=Array.isArray(R)?R:void 0,v=E!==void 0?{identifiers:y,password:p,roles:E}:{identifiers:y,password:p},U=f.headers.get("x-forwarded-for")||"127.0.0.1",Z=f.headers.get("user-agent")||"Unknown";if(e.rateLimit!==false){let G=await X,Ee=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await G.consume(`register:${U}`,Ee,900*1e3);}let b=await u(v,{ip:U,userAgent:Z});return b.success?(s.info(h(n,"auth.register.success",{userId:b.user.id,duration_ms:Date.now()-I})),O(w,201,"User registered successfully",{user:b.user})):(s.warn(h(n,"auth.register.failure",{errorCode:b.error.code,duration_ms:Date.now()-I})),w.status=b.error.statusCode,{error:true,statusCode:b.error.statusCode,code:b.error.code,message:b.error.message,data:null})}).post("/login",async({body:l,request:f,set:w})=>{let I=Date.now(),m=K(l),{identifier:T,password:p}=m;if(typeof T!="string"||T.trim().length===0)throw _("identifier is required and must be a non-empty string");if(T.length>ve)throw _(`identifier must not exceed ${ve} characters`);if(typeof p!="string"||p.length===0)throw _("password is required");if(p.length>j)throw _(`password must not exceed ${j} characters`);let R=T.trim(),y=f.headers.get("x-forwarded-for")||"127.0.0.1",E=f.headers.get("user-agent")||"Unknown";if(e.rateLimit!==false){let U=await X,Z=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await U.consume(`login:${y}`,Z,900*1e3);}let v=await d({identifier:R,password:p},{ip:y,userAgent:E});return v.success?(Ke(()=>r.hooks?.onLoginSuccess?.(v.user,{ip:y,userAgent:E})),oe(w,v.refreshToken,r),ne(w,v.accessToken,r),s.info(h(n,"auth.login.success",{userId:v.user.id,duration_ms:Date.now()-I})),O(w,200,"Login successful",{accessToken:v.accessToken,user:v.user})):(Ke(()=>r.hooks?.onLoginFailed?.(R,v.error,{ip:y})),s.warn(h(n,"auth.login.failure",{errorCode:v.error.code,duration_ms:Date.now()-I})),w.status=v.error.statusCode,{error:true,statusCode:v.error.statusCode,code:v.error.code,message:v.error.message,data:null})}).post("/refresh",async({request:l,set:f})=>{let w=Date.now(),I=P(l.headers.get("cookie")??void 0,L(r));if(!I)throw new a("UNAUTHORIZED","Refresh token cookie is missing");let m=l.headers.get("x-forwarded-for")||"127.0.0.1",T=l.headers.get("user-agent")||"Unknown",p=await c(I,{ip:m,userAgent:T});return p.success?(oe(f,p.refreshToken,r),ne(f,p.accessToken,r),s.info(h(n,"auth.refresh.success",{userId:p.user.id,duration_ms:Date.now()-w})),O(f,200,"Token refreshed",{accessToken:p.accessToken})):(_e(f,r),s.warn(h(n,"auth.refresh.failure",{errorCode:p.error.code,duration_ms:Date.now()-w})),f.status=p.error.statusCode,{error:true,statusCode:p.error.statusCode,code:p.error.code,message:p.error.message,data:null})}).post("/logout",async({request:l,set:f})=>{let w=Date.now(),I=P(l.headers.get("cookie")??void 0,L(r));return await i(I),_e(f,r),Oe(f,r),s.info(h(n,"auth.logout",{duration_ms:Date.now()-w})),O(f,200,"Logged out",null)}).post("/logout-all",async({request:l,set:f})=>{let w=Date.now(),I=await F(l,f,r);return await g(I.id),Ke(()=>r.hooks?.onLogout?.(I.id)),_e(f,r),Oe(f,r),s.info(h(n,"auth.logout_all",{userId:I.id,duration_ms:Date.now()-w})),O(f,200,"All sessions revoked",null)}).get("/me",async({request:l,set:f})=>{let w=Date.now(),I=await F(l,f,r),m=await x(I.id);return m.success?(s.info(h(n,"auth.me.success",{userId:m.user.id,duration_ms:Date.now()-w})),O(f,200,"OK",m.user)):(s.warn(h(n,"auth.me.failure",{userId:I.id,errorCode:m.error.code,duration_ms:Date.now()-w})),f.status=m.error.statusCode,{error:true,statusCode:m.error.statusCode,code:m.error.code,message:m.error.message,data:null})}).get("/me/identifiers",async({request:l,set:f})=>{let w=Date.now(),I=await F(l,f,r),m=await x(I.id);return m.success?(s.info(h(n,"auth.me.identifiers.success",{userId:m.user.id,count:m.user.identifiers?.length??0,duration_ms:Date.now()-w})),O(f,200,"OK",{identifiers:m.user.identifiers??[]})):(s.warn(h(n,"auth.me.identifiers.failure",{userId:I.id,errorCode:m.error.code,duration_ms:Date.now()-w})),f.status=m.error.statusCode,{error:true,statusCode:m.error.statusCode,code:m.error.code,message:m.error.message,data:null})}).post("/me/identifiers",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{identifiers:p}=T;if(!Array.isArray(p)||p.length===0)throw _("identifiers is required and must be a non-empty array");if(p.length>z)throw _(`identifiers must not exceed ${z} entries`);let R=p.map((E,v)=>He(E,v)),y=await k(m.id,R);return y.success?(s.info(h(n,"auth.identifiers.created",{userId:m.id,count:y.identifiers.length,duration_ms:Date.now()-I})),O(w,201,"Identifiers added successfully",{identifiers:y.identifiers})):(s.warn(h(n,"auth.identifiers.create_failure",{userId:m.id,errorCode:y.error.code,duration_ms:Date.now()-I})),w.status=y.error.statusCode,{error:true,statusCode:y.error.statusCode,code:y.error.code,message:y.error.message,data:null})}).put("/me/identifiers",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{identifiers:p}=T;if(!Array.isArray(p)||p.length===0)throw _("identifiers is required and must be a non-empty array");if(p.length>z)throw _(`identifiers must not exceed ${z} entries`);let R=p.map((E,v)=>{if(typeof E!="object"||E===null||Array.isArray(E))throw _(`identifiers[${v}] must be an object`);let U=E;if(typeof U.id!="string"||U.id.trim().length===0)throw _(`identifiers[${v}].id is required and must be a non-empty string`);let{type:Z,value:b}=He(E,v);return {id:U.id,type:Z,value:b}}),y=await H(m.id,R);return y.success?(s.info(h(n,"auth.identifiers.updated",{userId:m.id,count:y.identifiers.length,duration_ms:Date.now()-I})),O(w,200,"Identifiers updated successfully",{identifiers:y.identifiers})):(s.warn(h(n,"auth.identifiers.update_failure",{userId:m.id,errorCode:y.error.code,duration_ms:Date.now()-I})),w.status=y.error.statusCode,{error:true,statusCode:y.error.statusCode,code:y.error.code,message:y.error.message,data:null})}).delete("/me/identifiers",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{ids:p}=T;if(!Array.isArray(p)||p.length===0)throw _("ids is required and must be a non-empty array of strings");if(!p.every(y=>typeof y=="string"))throw _("each id must be a string");let R=await ae(m.id,p);return R.success?(s.info(h(n,"auth.identifiers.deleted",{userId:m.id,duration_ms:Date.now()-I})),O(w,200,"Identifiers deleted successfully",{identifiers:R.identifiers})):(s.warn(h(n,"auth.identifiers.delete_failure",{userId:m.id,errorCode:R.error.code,duration_ms:Date.now()-I})),w.status=R.error.statusCode,{error:true,statusCode:R.error.statusCode,code:R.error.code,message:R.error.message,data:null})}).patch("/me/password",async({body:l,request:f,set:w})=>{let I=Date.now(),m=await F(f,w,r),T=K(l),{currentPassword:p,newPassword:R}=T;if(typeof p!="string"||p.length===0)throw _("currentPassword is required");if(typeof R!="string"||R.length<ke)throw _(`newPassword must be at least ${ke} characters`);if(R.length>j)throw _(`newPassword must not exceed ${j} characters`);if(p===R)throw _("newPassword must be different from currentPassword");let y=await N(m.id,p,R);return y.success?(s.info(h(n,"auth.password.changed",{userId:m.id,duration_ms:Date.now()-I})),O(w,200,"Password updated successfully. All sessions have been revoked.",null)):(s.warn(h(n,"auth.password.change_failure",{userId:m.id,errorCode:y.error.code,duration_ms:Date.now()-I})),w.status=y.error.statusCode,{error:true,statusCode:y.error.statusCode,code:y.error.code,message:y.error.message,data:null})}).post("/users/:userId/roles",async({body:l,request:f,set:w,params:I})=>{let m=Date.now();if(!(await F(f,w,r)).roles.includes("admin"))throw new a("FORBIDDEN","Requires one of roles: admin");let p=K(l),{roles:R}=p,{userId:y}=I;if(!y)throw _("userId is required");if(!Array.isArray(R)||R.length===0)throw _("roles must be a non-empty array of strings");if(!R.every(v=>typeof v=="string"))throw _("each role must be a string");let E=await C(y,R);return E.success?(s.info(h(n,"auth.roles.assigned",{targetUserId:y,roles:R,duration_ms:Date.now()-m})),O(w,200,"Roles assigned successfully",{user:E.user})):(s.warn(h(n,"auth.roles.assign_failure",{targetUserId:y,errorCode:E.error.code,duration_ms:Date.now()-m})),w.status=E.error.statusCode,{error:true,statusCode:E.error.statusCode,code:E.error.code,message:E.error.message,data:null})}),Ae}o(Cr,"createAuthRouter");function Ls(e){if(e.mode==="client"){let i={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};ze(i);let g=i.logger??S,x=i.loggerService??"sentri",C=Ue(g,x),N=Le(g,x);return {protect:o(()=>be(i),"protect"),authorize:o((...k)=>C(...k),"authorize"),permit:o(k=>N(k),"permit"),errorHandler:o(k=>ie(k),"errorHandler")}}let{privateKey:r}=generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),t={mode:"server",dialect:e.dialect,secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??S,n=t.loggerService??"sentri",u=A(t),d=Ue(s,n),c=Le(s,n);return {protect:o(()=>be(t),"protect"),authorize:o((...i)=>d(...i),"authorize"),permit:o(i=>c(i),"permit"),errorHandler:o(i=>ie(i),"errorHandler"),router:o(()=>Cr(t),"router"),migrate:o(()=>Xe(D(t.dialect)),"migrate"),getCurrentAccessToken:o(i=>q(i,t),"getCurrentAccessToken"),hashPassword:o(i=>W(i,u.saltRounds),"hashPassword"),verifyPassword:o((i,g)=>J(i,g),"verifyPassword"),signAccessToken:o(i=>Q(i,t),"signAccessToken"),signRefreshToken:o(i=>ee(i,t),"signRefreshToken"),verifyAccessToken:o(i=>V(i,t),"verifyAccessToken"),verifyRefreshToken:o(i=>re(i,t),"verifyRefreshToken"),register:o(i=>de(i,t),"register"),login:o(i=>le(i,t),"login"),refresh:o(i=>M(i,t),"refresh"),logout:o(i=>fe(i,t),"logout"),logoutAll:o(i=>he(i,t),"logoutAll"),getUser:o(i=>me(i,t),"getUser"),changePassword:o((i,g,x)=>pe(i,g,x,t),"changePassword"),assignRoles:o((i,g)=>we(i,g,t),"assignRoles"),bulkCreateIdentifiers:o((i,g)=>ge(i,g,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:o((i,g)=>ye(i,g,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:o((i,g)=>Ie(i,g,t),"bulkDeleteIdentifiers")}}o(Ls,"createAuthElysia");export{$e as SENTRI_ERROR_STATUS,a as SentriError,Hr as authorize,Ls as createAuthElysia,Cr as createAuthRouter,Ue as createAuthorize,ie as createErrorHandler,Le as createPermit,q as getCurrentAccessToken,Vr as permit,be as protect};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var crypto=require('crypto'),kysely=require('kysely'),Ze=require('bcrypt'),Q=require('jsonwebtoken'),ioredis=require('ioredis'),express=require('express');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var Ze__default=/*#__PURE__*/_interopDefault(Ze);var Q__default=/*#__PURE__*/_interopDefault(Q);var Kr=Object.defineProperty;var n=(e,r)=>Kr(e,"name",{value:r,configurable:true});var Me=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),u=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??Me[r]??500;}};var je=new WeakMap,Ve=32,Be=10,Xe=31;function Je(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new u("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new u("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Ve)throw new u("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Be||s>Xe)throw new u("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Be} and ${Xe}`);if(!e.validRoles||e.validRoles.length===0)throw new u("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new u("CONFIGURATION_ERROR","dialect is required in server mode")}n(Je,"validateConfig");function T(e){let r=je.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return je.set(e,t),t}n(T,"resolveServerConfig");var $r=/^(\d+)([smhdw])$/,Hr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},ze=new Map;function V(e){if(typeof e=="number")return e*1e3;let r=ze.get(e);if(r!==void 0)return r;let t=$r.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Hr[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let o=parseInt(t[1],10)*s;return ze.set(e,o),o}n(V,"parseExpiry");var U={info:n(()=>{},"info"),warn:n(()=>{},"warn"),error:n(()=>{},"error")};function p(e,r,t){return {service:e,event:r,...t}}n(p,"buildLogData");async function Ge(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Ge,"runMigrations");var We=new Map;function D(e){let r=We.get(e);return r||(r=new kysely.Kysely({dialect:e}),We.set(e,r)),r}n(D,"getDatabase");async function Y(e,r=12){return Ze__default.default.hash(e,r)}n(Y,"hashPassword");async function q(e,r){return Ze__default.default.compare(e,r)}n(q,"verifyPassword");var Ye=new Map,qe=new Map,Br=3600*1e3;function er(e){let r=Ye.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),o=s.export({format:"jwk"}),l=crypto.createHash("sha256").update(JSON.stringify({e:o.e,kty:o.kty,n:o.n})).digest("base64url"),d={...o,use:"sig",kid:l};r={kid:l,publicKey:s,jwk:d},Ye.set(e,r);}return r}n(er,"getOrBuildKey");function rr(e){let{jwk:r}=er(e);return {keys:[r]}}n(rr,"buildJwks");function tr(e){return er(e).publicKey}n(tr,"getPublicKeyFromPrivate");async function sr(e){let r=Date.now(),t=qe.get(e);if(t&&r-t.fetchedAt<Br)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new u("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let o=await s.json();if(!o.keys||o.keys.length===0)throw new u("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let a=o.keys[0],l=crypto.createPublicKey({key:a,format:"jwk"});return qe.set(e,{publicKey:l,fetchedAt:r}),l}n(sr,"fetchPublicKey");var nr=new Map,or=new Map,ir=new Map;function ar(e){return e.startsWith("RS")||e.startsWith("PS")}n(ar,"isRSA");function dr(e){let r=nr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},nr.set(e,r)),r}n(dr,"getHsSecrets");function zr(e){let r=or.get(e);return r||(r=crypto.createPrivateKey(e),or.set(e,r)),r}n(zr,"getRsPrivateKey");function cr(e){let r=T(e);if(ar(r.algorithm)){let o=zr(e.secret);return {accessKey:o,refreshKey:o}}let{access:t,refresh:s}=dr(e.secret);return {accessKey:t,refreshKey:s}}n(cr,"getSigningKeys");function ur(e,r){let t=T(e);if(ar(t.algorithm))return tr(e.secret);let{access:s,refresh:o}=dr(e.secret);return r==="access"?s:o}n(ur,"getVerifyKey");function lr(e,r,t,s){let o=`${t}:${s}`,a=ir.get(o);return a||(a={expiresIn:t,algorithm:s},ir.set(o,a)),Q__default.default.sign(e,r,a)}n(lr,"sign");function fr(e,r,t){try{let s=Q__default.default.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new u("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof u?s:s instanceof Q__default.default.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verify");function ee(e,r){let t=T(r),{accessKey:s}=cr(r);return lr(e,s,t.accessExpiresIn,t.algorithm)}n(ee,"signAccessToken");function re(e,r){let t=T(r),{refreshKey:s}=cr(r);return lr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(re,"signRefreshToken");function le(e,r){let t=T(r),s=ur(r,"access");return fr(e,s,t.algorithm)}n(le,"verifyAccessToken");function te(e,r){let t=T(r),s=ur(r,"refresh");return fr(e,s,t.algorithm)}n(te,"verifyRefreshToken");function hr(e,r){try{let t=Q__default.default.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new u("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof u?t:t instanceof Q__default.default.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(hr,"verifyTokenWithPublicKey");function Oe(e){try{return JSON.parse(e)}catch{return []}}n(Oe,"parseRoles");function Jr(e){return JSON.stringify(e)}n(Jr,"serializeRoles");function pr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(pr,"mapIdentifier");async function se(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(se,"findUserByIdentifierValue");async function fe(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(fe,"findUserById");async function wr(e,r,t){let s=t.map(o=>({id:crypto.randomUUID(),user_id:r,type:o.type,value:o.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(o=>({id:o.id,userId:o.user_id,type:o.type,value:o.value,createdAt:new Date}))}n(wr,"createIdentifiers");async function ne(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(pr)}n(ne,"findIdentifiersByUserId");async function Ue(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?pr(s):null}n(Ue,"findIdentifierById");async function yr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}n(yr,"countIdentifiersByUserId");async function Ir(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}n(Ir,"updateIdentifier");async function _r(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(_r,"deleteIdentifiers");async function gr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(gr,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:Jr(t)}).where("id","=",r).execute();}n(Rr,"updateUserRoles");async function Le(e,r){let t=crypto.randomUUID();return await e.insertInto("sentri_sessions").values({id:t,user_id:r.userId,expires_at:r.expiresAt.toISOString(),ip_address:r.ipAddress??null,user_agent:r.userAgent??null}).execute(),{id:t}}n(Le,"createSession");async function he(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:Oe(t.roles)}}:null}n(he,"findSessionById");async function me(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(me,"deleteSession");async function kr(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(kr,"markSessionReplaced");async function pe(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(pe,"deleteAllSessionsForUser");async function Ar(e,r){return (await e.selectFrom("sentri_sessions").selectAll().where("user_id","=",r).where("replaced_by","is",null).where("expires_at",">",new Date).execute()).map(s=>({id:s.id,userId:s.user_id,expiresAt:new Date(s.expires_at),ipAddress:s.ip_address,userAgent:s.user_agent,replacedBy:s.replaced_by,createdAt:new Date(s.created_at)}))}n(Ar,"findSessionsByUserId");async function we(e,r,t){let s=T(r),o=D(r.dialect),a=e.roles??[],l=a.filter(k=>!s.validRolesSet.has(k));if(l.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${l.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let d=e.identifiers.map(k=>({type:k.type.trim(),value:k.value.trim()}));if(new Set(d.map(k=>k.value)).size!==d.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let k of d)if(await se(o,k.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${k.value}`)};let m=await Y(e.password,s.saltRounds),{userId:_,identifierRows:b}=await o.transaction().execute(async k=>{let L=crypto.randomUUID();await k.insertInto("sentri_users").values({id:L,password_hash:m,roles:JSON.stringify(a)}).execute();let K=d.map($=>({id:crypto.randomUUID(),user_id:L,type:$.type,value:$.value}));return await k.insertInto("sentri_identifiers").values(K).execute(),{userId:L,identifierRows:K}}),v={success:true,user:{id:_,roles:a,identifiers:b.map(k=>({id:k.id,type:k.type,value:k.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(v.user),v}n(we,"register");async function ye(e,r,t){let s=T(r),o=D(r.dialect),a=await se(o,e.identifier.trim());if(!a){let v=new u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,v,{ip:t?.ip??""}),{success:false,error:v}}if(!await q(e.password,a.passwordHash)){let v=new u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,v,{ip:t?.ip??""}),{success:false,error:v}}let d=new Date(Date.now()+V(s.refreshExpiresIn)),i=await Le(o,{userId:a.id,expiresAt:d,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),m={id:a.id,roles:a.roles},_=ee({id:a.id,roles:a.roles,sessionId:i.id},r),b=re(i.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(m,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:_,refreshToken:b,user:m}}n(ye,"login");async function B(e,r,t){let s=T(r),o=D(r.dialect),a;try{({sessionId:a}=te(e,r));}catch(v){return v instanceof u?{success:false,error:v}:{success:false,error:new u("TOKEN_INVALID","Invalid refresh token")}}let l=await he(o,a);if(!l)return {success:false,error:new u("UNAUTHORIZED","Session not found or revoked")};if(l.replacedBy)return await pe(o,l.userId),{success:false,error:new u("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(l.expiresAt.getTime()<Date.now())return await me(o,a),{success:false,error:new u("TOKEN_EXPIRED","Session has expired")};let d=new Date(Date.now()+V(s.refreshExpiresIn)),i=await Le(o,{userId:l.userId,expiresAt:d,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await kr(o,a,i.id);let m={id:l.user.id,roles:l.user.roles},_=ee({...m,sessionId:i.id},r),b=re(i.id,r);return {success:true,accessToken:_,refreshToken:b,user:m}}n(B,"refresh");async function Ie(e,r){let t=D(r.dialect),s;try{({sessionId:s}=te(e,r));}catch{return}let o=await he(t,s);o&&(await me(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(o.userId));}n(Ie,"logout");async function _e(e,r){let t=D(r.dialect);await pe(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(_e,"logoutAll");async function ge(e,r){let t=D(r.dialect),s=await fe(t,e);if(!s)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let o=await ne(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:o.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(ge,"getUser");async function Re(e,r,t,s){let o=T(s),a=D(s.dialect),l=await fe(a,e);if(!l)return {success:false,error:new u("USER_NOT_FOUND","User not found")};if(!await q(r,l.passwordHash))return {success:false,error:new u("INVALID_CREDENTIALS","Invalid credentials")};let i=await Y(t,o.saltRounds);return await gr(a,e,i),await pe(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function ke(e,r,t){let s=T(t),o=D(t.dialect),a=r.filter(m=>!s.validRolesSet.has(m));if(a.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let l=await fe(o,e);if(!l)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let d=new Set(l.roles);for(let m of r)d.add(m);let i=Array.from(d);return await Rr(o,e,i),{success:true,user:{id:l.id,roles:i}}}n(ke,"assignRoles");async function Ae(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(d=>({type:d.type.trim(),value:d.value.trim()}));if(new Set(o.map(d=>d.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let d of o)if(await se(s,d.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${d.value}`)};return await wr(s,e,o),{success:true,identifiers:(await ne(s,e)).map(d=>({id:d.id,type:d.type,value:d.value}))}}n(Ae,"bulkCreateIdentifiers");async function ve(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one update is required")};let o=r.map(d=>({id:d.id,type:d.type.trim(),value:d.value.trim()}));if(new Set(o.map(d=>d.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let d of o){let i=await Ue(s,d.id,e);if(!i)return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${d.id}`)};if(i.value!==d.value&&await se(s,d.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${d.value}`)}}return await s.transaction().execute(async d=>{for(let i of o)await Ir(d,i.id,e,{type:i.type,value:i.value});}),{success:true,identifiers:(await ne(s,e)).map(d=>({id:d.id,type:d.type,value:d.value}))}}n(ve,"bulkUpdateIdentifiers");async function Ee(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one ID is required")};let o=Array.from(new Set(r));for(let d of o)if(!await Ue(s,d,e))return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${d}`)};return await yr(s,e)-o.length<1?{success:false,error:new u("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await _r(s,e,o),{success:true,identifiers:(await ne(s,e)).map(d=>({id:d.id,type:d.type,value:d.value}))})}n(Ee,"bulkDeleteIdentifiers");async function Er(e,r){let t=D(r.dialect);return (await Ar(t,e)).map(o=>({id:o.id,ipAddress:o.ipAddress,userAgent:o.userAgent,createdAt:o.createdAt,expiresAt:o.expiresAt}))}n(Er,"getSessions");async function Tr(e,r,t){let s=D(t.dialect),o=await he(s,r);o&&o.userId===e&&await me(s,r);}n(Tr,"revokeSession");function P(e){return T(e).cookieName}n(P,"getCookieName");function Te(e){return T(e).accessCookieName}n(Te,"getAccessCookieName");function H(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let o=e.indexOf(";",s),a=o===-1?e.length:o;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(H,"readCookie");function oe(e,r,t){let s=t.cookie??{},o=V(T(t).refreshExpiresIn);e.cookie(P(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:o});}n(oe,"setCookieFromConfig");function xe(e,r){let t=r.cookie??{};e.clearCookie(P(r),{path:t.path??"/"});}n(xe,"clearCookieFromConfig");function ie(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,o=V(T(t).accessExpiresIn);e.cookie(Te(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:o});}n(ie,"setAccessCookieFromConfig");function Fe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(Te(r),{path:t.path??"/"});}n(Fe,"clearAccessCookieFromConfig");function Ne(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):H(e.headers.cookie,Te(r))}n(Ne,"getCurrentAccessToken");function C(e){let r=e.logger??U,t=e.loggerService??"sentri";return e.mode==="client"?Gr(e.keyUri,r,t):Wr(e,r,t)}n(C,"protect");function Gr(e,r,t){return async(s,o,a)=>{let l=s.headers.authorization,d=l?.startsWith("Bearer ")?l.slice(7):void 0,i=s.requestId;if(!d)return r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...i!==void 0&&{requestId:i}})),a(new u("UNAUTHORIZED","Missing or malformed Authorization header"));try{let m=await sr(e),_=hr(d,m);s.user={id:_.id,roles:_.roles},r.info(p(t,"auth.protect.success",{mode:"client",userId:_.id,...i!==void 0&&{requestId:i}})),a();}catch(m){let _=m instanceof u?m.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:_,...i!==void 0&&{requestId:i}})),a(m);}}}n(Gr,"protectClient");function Wr(e,r,t){return async(s,o,a)=>{let l=Ne(s,e),d=s.requestId;if(!l)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),a(new u("UNAUTHORIZED","Missing or malformed Authorization header"));try{let i=le(l,e);if(e.isTokenRevoked&&await e.isTokenRevoked(i.sessionId))return r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:i.id,...d!==void 0&&{requestId:d}})),a(new u("UNAUTHORIZED","Token has been revoked"));s.user={id:i.id,roles:i.roles},r.info(p(t,"auth.protect.success",{mode:"server",userId:i.id,...d!==void 0&&{requestId:d}})),a();}catch(i){if(i instanceof u&&i.code==="TOKEN_EXPIRED"){let m=H(s.headers.cookie,P(e));if(!m)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),a(new u("UNAUTHORIZED","Token expired. Please login again."));try{let _=await B(m,e);if(!_.success)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:_.error.code,...d!==void 0&&{requestId:d}})),a(new u("UNAUTHORIZED","Session expired. Please login again."));oe(o,_.refreshToken,e),ie(o,_.accessToken,e),o.setHeader("X-New-Access-Token",_.accessToken),s.user=_.user,r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:_.user.id,...d!==void 0&&{requestId:d}})),a();}catch{r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),a(new u("UNAUTHORIZED","Session expired. Please login again."));}}else {let m=i instanceof u?i.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:m,...d!==void 0&&{requestId:d}})),a(i);}}}}n(Wr,"protectServer");function xr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return (o,a,l)=>{let d=o.requestId;if(!o.user)return r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...d!==void 0&&{requestId:d}})),l(new u("UNAUTHORIZED","Not authenticated"));let i=o.user.roles;if(!e.some(m=>i.includes(m)))return r.warn(p(t,"auth.authorize.denied",{userId:o.user.id,userRoles:[...i],requiredRoles:e,...d!==void 0&&{requestId:d}})),l(new u("FORBIDDEN",s));r.info(p(t,"auth.authorize.passed",{userId:o.user.id,userRoles:[...i],requiredRoles:e,...d!==void 0&&{requestId:d}})),l();}}n(xr,"createAuthorizeHandler");function ae(e,r){return n(function(...s){return xr(s,e,r)},"authorize")}n(ae,"createAuthorize");function Zr(...e){return xr(e,U,"sentri")}n(Zr,"authorize");var Yr=new u("FORBIDDEN","You do not have permission to perform this action");function Nr(e,r,t){return async(s,o,a)=>{let l=s.requestId;if(!s.user)return r.warn(p(t,"auth.permit.unauthenticated",{...l!==void 0&&{requestId:l}})),a(new u("UNAUTHORIZED","Not authenticated"));let d=s.user.id;if(e.roles&&e.roles.length>0){let i=s.user.roles;if(e.roles.some(m=>i.includes(m)))return r.info(p(t,"auth.permit.role_bypass",{userId:d,bypassedByRole:true,...l!==void 0&&{requestId:l}})),a()}try{let i=e.check(s);(i instanceof Promise?await i:i)?(r.info(p(t,"auth.permit.passed",{userId:d,...l!==void 0&&{requestId:l}})),a()):(r.warn(p(t,"auth.permit.denied",{userId:d,...l!==void 0&&{requestId:l}})),a(Yr));}catch(i){a(i);}}}n(Nr,"createPermitHandler");function de(e,r){return n(function(s){return Nr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(de,"createPermit");function qr(e){return Nr(typeof e=="function"?{check:e}:e,U,"sentri")}n(qr,"permit");function ce(e){return (r,t,s,o)=>{if(r instanceof u){s.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.status(500).json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}n(ce,"createErrorHandler");var Dr=new Map;function Cr(e){let r=Dr.get(e);return r||(r=new ioredis.Redis(e,{lazyConnect:true,enableOfflineQueue:false}),r.on("error",t=>{console.error("Sentri Redis Client Error:",t.message);}),Dr.set(e,r)),r}n(Cr,"getRedisClient");function Sr(e){let r=e?.ttl??3e5,t=(e?.header??"X-Idempotency-Key").toLowerCase(),s=new Set((e?.methods??["POST","PUT","PATCH"]).map(a=>a.toUpperCase())),o=e?.redisUrl;return o?et(o,r,t,s):rt(r,t,s,e?.maxSize??1e4)}n(Sr,"createIdempotencyMiddleware");function et(e,r,t,s){let o=Cr(e),a="sentri:idempotency:";return async(l,d,i)=>{let m=l.headers[t];if(!m||typeof m!="string"||!s.has(l.method))return i();l.requestId=m,d.setHeader("X-Request-Id",m);let _=await o.get(`${a}${m}`);if(_){let v=JSON.parse(_);return d.setHeader("X-Idempotent-Replayed","true"),d.status(v.statusCode).json(v.body)}let b=d.json.bind(d);d.json=n(function(k){if(d.statusCode>=200&&d.statusCode<300){let L={statusCode:d.statusCode,body:k,expiresAt:Date.now()+r};o.set(`${a}${m}`,JSON.stringify(L),"PX",r).catch(()=>{});}return b(k)},"idempotentJson"),i();}}n(et,"buildRedisMiddleware");function rt(e,r,t,s){let o=Math.max(e,5e3),a=new Map,l=setInterval(()=>{let d=Date.now();for(let[i,m]of a)m.expiresAt<=d&&a.delete(i);},o);return typeof l=="object"&&l!==null&&"unref"in l&&l.unref(),(d,i,m)=>{let _=d.headers[r];if(!_||typeof _!="string"||!t.has(d.method))return m();d.requestId=_,i.setHeader("X-Request-Id",_);let b=Date.now(),v=a.get(_);if(v&&v.expiresAt>b)return i.setHeader("X-Idempotent-Replayed","true"),i.status(v.statusCode).json(v.body);let k=i.json.bind(i);i.json=n(function(K){if(i.statusCode>=200&&i.statusCode<300){if(a.size>=s){let $=a.keys().next().value;$!==void 0&&a.delete($);}a.set(_,{statusCode:i.statusCode,body:K,expiresAt:Date.now()+e});}return k(K)},"idempotentJson"),m();}}n(rt,"buildMemoryMiddleware");var Pe=class{static{n(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let o=Date.now(),a=this.store.get(r);if((!a||a.expiresAt<o)&&(a={count:0,expiresAt:o+s},this.store.set(r,a)),a.count>t){let l=Math.ceil((a.expiresAt-o)/1e3);throw new Error(`Rate limit exceeded. Try again in ${l} seconds.`)}a.count++;}},Ke=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let o=`sentri:rl:${r}`,a=Math.ceil(s/1e3);try{let l=await this.redis.multi().incr(o).expire(o,a,"NX").exec();if(!l||l.length===0)return;if(l[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(l){if(l instanceof Error&&l.message.includes("Rate limit exceeded"))throw l;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:l});}}},X=null;async function br(e,r){if(X)return X;if(e)try{let{Redis:t}=await import('ioredis'),s=new t(e,{lazyConnect:!0,maxRetriesPerRequest:1});return await s.connect(),r?.info({msg:"Connected to Redis for Rate Limiting"}),X=new Ke(s,r??U),X}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return X=new Pe,X}n(br,"getRateLimiter");var De=8,z=72,Ce=255,Or=100,J=50;function R(e){return new u("VALIDATION_ERROR",e)}n(R,"badRequest");function S(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}n(S,"ok");function F(e,r){e.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}n(F,"fail");function M(e){if(e==null||typeof e!="object"||Array.isArray(e))throw new u("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you apply express.json()?");return e}n(M,"parseBody");function st(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new u("UNAUTHORIZED","Invalid or missing API key")}n(st,"validateApiKey");function $e(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n($e,"fireHook");function nt(e){return e.startsWith("RS")||e.startsWith("PS")}n(nt,"isRSA");function He(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw R(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw R(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Or)throw R(`identifiers[${r}].type must not exceed ${Or} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw R(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Ce)throw R(`identifiers[${r}].value must not exceed ${Ce} characters`);return {type:t.type,value:t.value}}n(He,"validateIdentifierInput");function E(e){return e.requestId!==void 0?{requestId:e.requestId}:{}}n(E,"reqId");function Ur(e){let r=express.Router(),t=e,s=T(t),o=e.logger??U,a=e.loggerService??"sentri",l=ae(o,a),d=de(o,a),i=br(e.redisUrl,o),m=e.router?.register??(c=>we(c,t)),_=e.router?.login??(c=>ye(c,t)),b=e.router?.refresh??(c=>B(c,t)),v=e.router?.logout??(c=>c!==void 0?Ie(c,t):Promise.resolve()),k=e.router?.logoutAll??(c=>_e(c,t)),L=e.router?.getUser??(c=>ge(c,t)),K=e.router?.assignRoles??((c,h)=>ke(c,h,t)),$=e.router?.changePassword??((c,h,A)=>Re(c,h,A,t)),Lr=e.router?.bulkCreateIdentifiers??((c,h)=>Ae(c,h,t)),Fr=e.router?.bulkUpdateIdentifiers??((c,h)=>ve(c,h,t)),Pr=e.router?.bulkDeleteIdentifiers??((c,h)=>Ee(c,h,t));nt(s.algorithm)&&r.get("/keys",(c,h)=>{h.setHeader("Cache-Control","public, max-age=3600"),h.json(rr(e.secret));}),r.post("/register",async(c,h,A)=>{let I=Date.now();try{st(c,e);let f=M(c.body),{identifiers:y,password:g,roles:w}=f;if(!Array.isArray(y)||y.length===0)throw R("identifiers is required and must be a non-empty array");if(y.length>J)throw R(`identifiers must not exceed ${J} entries`);let x=y.map((Z,Se)=>He(Z,Se));if(typeof g!="string"||g.length<De)throw R(`password is required and must be at least ${De} characters`);if(g.length>z)throw R(`password must not exceed ${z} characters`);if(w!==void 0&&!Array.isArray(w))throw R("roles must be an array of strings when provided");if(Array.isArray(w)&&!w.every(Z=>typeof Z=="string"))throw R("each role must be a string");let O=Array.isArray(w)?w:void 0,N=O!==void 0?{identifiers:x,password:g,roles:O}:{identifiers:x,password:g},j=c.ip||"127.0.0.1",G=c.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let Z=await i,Se=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await Z.consume(`register:${j}`,Se,900*1e3);}let W=await m(N,{ip:j,userAgent:G});if(!W.success){o.warn(p(a,"auth.register.failure",{errorCode:W.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,W.error);return}o.info(p(a,"auth.register.success",{userId:W.user.id,duration_ms:Date.now()-I,...E(c)})),S(h,201,"User registered successfully",{user:W.user});}catch(f){A(f);}}),r.post("/login",async(c,h,A)=>{let I=Date.now();try{let f=M(c.body),{identifier:y,password:g}=f;if(typeof y!="string"||y.trim().length===0)throw R("identifier is required and must be a non-empty string");if(y.length>Ce)throw R(`identifier must not exceed ${Ce} characters`);if(typeof g!="string"||g.length===0)throw R("password is required");if(g.length>z)throw R(`password must not exceed ${z} characters`);let w=y.trim(),x=c.ip||"127.0.0.1",O=c.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let j=await i,G=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await j.consume(`login:${x}`,G,900*1e3);}let N=await _({identifier:w,password:g},{ip:x,userAgent:O});if(!N.success){$e(()=>e.hooks?.onLoginFailed?.(w,N.error,{ip:x})),o.warn(p(a,"auth.login.failure",{errorCode:N.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,N.error);return}$e(()=>e.hooks?.onLoginSuccess?.(N.user,{ip:x,userAgent:O})),oe(h,N.refreshToken,e),ie(h,N.accessToken,e),o.info(p(a,"auth.login.success",{userId:N.user.id,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Login successful",{accessToken:N.accessToken,user:N.user});}catch(f){A(f);}}),r.post("/refresh",async(c,h,A)=>{let I=Date.now();try{let f=H(c.headers.cookie,P(e));if(!f)throw new u("UNAUTHORIZED","Refresh token cookie is missing");let y=c.ip||"127.0.0.1",g=c.get("user-agent")||"Unknown",w=await b(f,{ip:y,userAgent:g});if(!w.success){xe(h,e),o.warn(p(a,"auth.refresh.failure",{errorCode:w.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,w.error);return}oe(h,w.refreshToken,e),ie(h,w.accessToken,e),o.info(p(a,"auth.refresh.success",{userId:w.user.id,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Token refreshed",{accessToken:w.accessToken});}catch(f){A(f);}}),r.post("/logout",async(c,h,A)=>{let I=Date.now();try{let f=H(c.headers.cookie,P(e));await v(f),xe(h,e),Fe(h,e),o.info(p(a,"auth.logout",{duration_ms:Date.now()-I,...E(c)})),S(h,200,"Logged out",null);}catch(f){A(f);}}),r.post("/logout-all",C(e),async(c,h,A)=>{let I=Date.now();try{let f=c.user.id;await k(f),$e(()=>e.hooks?.onLogout?.(f)),xe(h,e),Fe(h,e),o.info(p(a,"auth.logout_all",{userId:f,duration_ms:Date.now()-I,...E(c)})),S(h,200,"All sessions revoked",null);}catch(f){A(f);}}),r.get("/me",C(e),async(c,h,A)=>{let I=Date.now();try{let f=await L(c.user.id);if(!f.success){o.warn(p(a,"auth.me.failure",{userId:c.user.id,errorCode:f.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,f.error);return}o.info(p(a,"auth.me.success",{userId:f.user.id,duration_ms:Date.now()-I,...E(c)})),S(h,200,"OK",f.user);}catch(f){A(f);}}),r.get("/me/identifiers",C(e),async(c,h,A)=>{let I=Date.now();try{let f=await L(c.user.id);if(!f.success){o.warn(p(a,"auth.me.identifiers.failure",{userId:c.user.id,errorCode:f.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,f.error);return}o.info(p(a,"auth.me.identifiers.success",{userId:f.user.id,count:f.user.identifiers?.length??0,duration_ms:Date.now()-I,...E(c)})),S(h,200,"OK",{identifiers:f.user.identifiers??[]});}catch(f){A(f);}});let ue=d(c=>!!c.user);return r.post("/me/identifiers",C(e),ue,async(c,h,A)=>{let I=Date.now();try{let f=M(c.body),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw R("identifiers is required and must be a non-empty array");if(y.length>J)throw R(`identifiers must not exceed ${J} entries`);let g=y.map((x,O)=>He(x,O)),w=await Lr(c.user.id,g);if(!w.success){o.warn(p(a,"auth.identifiers.create_failure",{userId:c.user.id,errorCode:w.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,w.error);return}o.info(p(a,"auth.identifiers.created",{userId:c.user.id,count:w.identifiers.length,duration_ms:Date.now()-I,...E(c)})),S(h,201,"Identifiers added successfully",{identifiers:w.identifiers});}catch(f){A(f);}}),r.put("/me/identifiers",C(e),ue,async(c,h,A)=>{let I=Date.now();try{let f=M(c.body),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw R("identifiers is required and must be a non-empty array");if(y.length>J)throw R(`identifiers must not exceed ${J} entries`);let g=y.map((x,O)=>{if(typeof x!="object"||x===null||Array.isArray(x))throw R(`identifiers[${O}] must be an object`);let N=x;if(typeof N.id!="string"||N.id.trim().length===0)throw R(`identifiers[${O}].id is required and must be a non-empty string`);let{type:j,value:G}=He(x,O);return {id:N.id,type:j,value:G}}),w=await Fr(c.user.id,g);if(!w.success){o.warn(p(a,"auth.identifiers.update_failure",{userId:c.user.id,errorCode:w.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,w.error);return}o.info(p(a,"auth.identifiers.updated",{userId:c.user.id,count:w.identifiers.length,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Identifiers updated successfully",{identifiers:w.identifiers});}catch(f){A(f);}}),r.delete("/me/identifiers",C(e),ue,async(c,h,A)=>{let I=Date.now();try{let f=M(c.body),{ids:y}=f;if(!Array.isArray(y)||y.length===0)throw R("ids is required and must be a non-empty array of strings");if(!y.every(w=>typeof w=="string"))throw R("each id must be a string");let g=await Pr(c.user.id,y);if(!g.success){o.warn(p(a,"auth.identifiers.delete_failure",{userId:c.user.id,errorCode:g.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,g.error);return}o.info(p(a,"auth.identifiers.deleted",{userId:c.user.id,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Identifiers deleted successfully",{identifiers:g.identifiers});}catch(f){A(f);}}),r.patch("/me/password",C(e),ue,async(c,h,A)=>{let I=Date.now();try{let f=M(c.body),{currentPassword:y,newPassword:g}=f;if(typeof y!="string"||y.length===0)throw R("currentPassword is required");if(typeof g!="string"||g.length<De)throw R(`newPassword must be at least ${De} characters`);if(g.length>z)throw R(`newPassword must not exceed ${z} characters`);if(y===g)throw R("newPassword must be different from currentPassword");let w=await $(c.user.id,y,g);if(!w.success){o.warn(p(a,"auth.password.change_failure",{userId:c.user.id,errorCode:w.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,w.error);return}o.info(p(a,"auth.password.changed",{userId:c.user.id,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Password updated successfully. All sessions have been revoked.",null);}catch(f){A(f);}}),r.post("/users/:userId/roles",C(e),l("admin"),async(c,h,A)=>{let I=Date.now();try{let f=M(c.body),{roles:y}=f,g=c.params.userId,w=typeof g=="string"?g:void 0;if(!w)throw R("userId is required");if(!Array.isArray(y)||y.length===0)throw R("roles must be a non-empty array of strings");if(!y.every(O=>typeof O=="string"))throw R("each role must be a string");let x=await K(w,y);if(!x.success){o.warn(p(a,"auth.roles.assign_failure",{targetUserId:w,errorCode:x.error.code,duration_ms:Date.now()-I,...E(c)})),F(h,x.error);return}o.info(p(a,"auth.roles.assigned",{targetUserId:w,roles:y,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Roles assigned successfully",{user:x.user});}catch(f){A(f);}}),r.use(ce()),r.get("/sessions",C(e),async(c,h,A)=>{let I=Date.now();try{let f=c.user.id,g=await(e.router?.getSessions??(w=>Er(w,t)))(f);o.info(p(a,"auth.sessions.get",{userId:f,duration_ms:Date.now()-I,...E(c)})),S(h,200,"OK",{sessions:g});}catch(f){A(f);}}),r.delete("/sessions/:id",C(e),async(c,h,A)=>{let I=Date.now();try{let f=c.user.id,y=c.params.id;await(e.router?.revokeSession??((w,x)=>Tr(w,x,t)))(f,y),o.info(p(a,"auth.sessions.revoke",{userId:f,sessionId:y,duration_ms:Date.now()-I,...E(c)})),S(h,200,"Session revoked",null);}catch(f){A(f);}}),r}n(Ur,"createAuthRouter");function tn(e){if(e.mode==="client"){let i={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};Je(i);let m=i.logger??U,_=i.loggerService??"sentri",b=ae(m,_),v=de(m,_);return {protect:n(()=>C(i),"protect"),authorize:n((...k)=>b(...k),"authorize"),permit:n(k=>v(k),"permit"),errorHandler:n(k=>ce(k),"errorHandler")}}let{privateKey:r}=crypto.generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),t={mode:"server",dialect:e.dialect,secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??U,o=t.loggerService??"sentri",a=T(t),l=ae(s,o),d=de(s,o);return {protect:n(()=>C(t),"protect"),authorize:n((...i)=>l(...i),"authorize"),permit:n(i=>d(i),"permit"),errorHandler:n(i=>ce(i),"errorHandler"),hashPassword:n(i=>Y(i,a.saltRounds),"hashPassword"),verifyPassword:n((i,m)=>q(i,m),"verifyPassword"),signAccessToken:n(i=>ee(i,t),"signAccessToken"),signRefreshToken:n(i=>re(i,t),"signRefreshToken"),verifyAccessToken:n(i=>le(i,t),"verifyAccessToken"),verifyRefreshToken:n(i=>te(i,t),"verifyRefreshToken"),getCurrentAccessToken:n(i=>Ne(i,t),"getCurrentAccessToken"),router:n(()=>Ur(t),"router"),migrate:n(()=>Ge(D(t.dialect)),"migrate"),idempotencyMiddleware:n(i=>Sr({...i,...t.redisUrl!==void 0&&{redisUrl:t.redisUrl}}),"idempotencyMiddleware"),register:n(i=>we(i,t),"register"),login:n(i=>ye(i,t),"login"),refresh:n(i=>B(i,t),"refresh"),logout:n(i=>Ie(i,t),"logout"),logoutAll:n(i=>_e(i,t),"logoutAll"),getUser:n(i=>ge(i,t),"getUser"),changePassword:n((i,m,_)=>Re(i,m,_,t),"changePassword"),assignRoles:n((i,m)=>ke(i,m,t),"assignRoles"),bulkCreateIdentifiers:n((i,m)=>Ae(i,m,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((i,m)=>ve(i,m,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((i,m)=>Ee(i,m,t),"bulkDeleteIdentifiers")}}n(tn,"createAuthExpress");exports.SENTRI_ERROR_STATUS=Me;exports.SentriError=u;exports.authorize=Zr;exports.createAuthExpress=tn;exports.createAuthRouter=Ur;exports.createAuthorize=ae;exports.createErrorHandler=ce;exports.createIdempotencyMiddleware=Sr;exports.createPermit=de;exports.getCurrentAccessToken=Ne;exports.permit=qr;exports.protect=C;
|