sentri 5.0.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 'use strict';var crypto=require('crypto'),kysely=require('kysely'),Ye=require('bcrypt'),Q=require('jsonwebtoken'),ioredis=require('ioredis'),express=require('express');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var Ye__default=/*#__PURE__*/_interopDefault(Ye);var Q__default=/*#__PURE__*/_interopDefault(Q);var Kr=Object.defineProperty;var n=(e,r)=>Kr(e,"name",{value:r,configurable:true});var je=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}),l=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??je[r]??500;}};var Ve=new WeakMap,Be=32,Xe=10,ze=31;function Ge(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new l("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new l("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Be)throw new l("CONFIGURATION_ERROR",`secret must be at least ${Be} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Xe||s>ze)throw new l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Xe} and ${ze}`);if(!e.validRoles||e.validRoles.length===0)throw new l("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new l("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new l("CONFIGURATION_ERROR","dialect is required in server mode")}n(Ge,"validateConfig");function k(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),validIdentifiers:e.validIdentifiers??["email","username"],validIdentifiersSet:new Set(e.validIdentifiers??["email","username"]),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}n(k,"resolveServerConfig");var $r=/^(\d+)([smhdw])$/,Hr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Je=new Map;function V(e){if(typeof e=="number")return e*1e3;let r=Je.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 i=parseInt(t[1],10)*s;return Je.set(e,i),i}n(V,"parseExpiry");var L={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 We(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(We,"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}n(D,"getDatabase");async function Y(e,r=12){return Ye__default.default.hash(e,r)}n(Y,"hashPassword");async function q(e,r){return Ye__default.default.compare(e,r)}n(q,"verifyPassword");var qe=new Map,Qe=new Map,Br=3600*1e3;function rr(e){let r=qe.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),i=s.export({format:"jwk"}),f=crypto.createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),c={...i,use:"sig",kid:f};r={kid:f,publicKey:s,jwk:c},qe.set(e,r);}return r}n(rr,"getOrBuildKey");function tr(e){let{jwk:r}=rr(e);return {keys:[r]}}n(tr,"buildJwks");function sr(e){return rr(e).publicKey}n(sr,"getPublicKeyFromPrivate");async function nr(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 l("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new l("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let o=i.keys[0],f=crypto.createPublicKey({key:o,format:"jwk"});return Qe.set(e,{publicKey:f,fetchedAt:r}),f}n(nr,"fetchPublicKey");var ir=new Map,or=new Map,ar=new Map;function dr(e){return e.startsWith("RS")||e.startsWith("PS")}n(dr,"isRSA");function ur(e){let r=ir.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},ir.set(e,r)),r}n(ur,"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=k(e);if(dr(r.algorithm)){let i=zr(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ur(e.secret);return {accessKey:t,refreshKey:s}}n(cr,"getSigningKeys");function lr(e,r){let t=k(e);if(dr(t.algorithm))return sr(e.secret);let{access:s,refresh:i}=ur(e.secret);return r==="access"?s:i}n(lr,"getVerifyKey");function fr(e,r,t,s){let i=`${t}:${s}`,o=ar.get(i);return o||(o={expiresIn:t,algorithm:s},ar.set(i,o)),Q__default.default.sign(e,r,o)}n(fr,"sign");function hr(e,r,t){try{let s=Q__default.default.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new l("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof l?s:s instanceof Q__default.default.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(hr,"verify");function ee(e,r){let t=k(r),{accessKey:s}=cr(r);return fr(e,s,t.accessExpiresIn,t.algorithm)}n(ee,"signAccessToken");function re(e,r){let t=k(r),{refreshKey:s}=cr(r);return fr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(re,"signRefreshToken");function fe(e,r){let t=k(r),s=lr(r,"access");return hr(e,s,t.algorithm)}n(fe,"verifyAccessToken");function te(e,r){let t=k(r),s=lr(r,"refresh");return hr(e,s,t.algorithm)}n(te,"verifyRefreshToken");function mr(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 l("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof l?t:t instanceof Q__default.default.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(mr,"verifyTokenWithPublicKey");function Ue(e){try{return JSON.parse(e)}catch{return []}}n(Ue,"parseRoles");function Jr(e){return JSON.stringify(e)}n(Jr,"serializeRoles");function wr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(wr,"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:Ue(t.roles)}:null}n(se,"findUserByIdentifierValue");async function he(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:Ue(t.roles)}:null}n(he,"findUserById");async function yr(e,r,t){let s=t.map(i=>({id:crypto.randomUUID(),user_id:r,type:i.type,value:i.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(i=>({id:i.id,userId:i.user_id,type:i.type,value:i.value,createdAt:new Date}))}n(yr,"createIdentifiers");async function ne(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(ne,"findIdentifiersByUserId");async function Le(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?wr(s):null}n(Le,"findIdentifierById");async function Ir(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(Ir,"countIdentifiersByUserId");async function Rr(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(Rr,"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 vr(e,r,t){await e.updateTable("sentri_users").set({roles:Jr(t)}).where("id","=",r).execute();}n(vr,"updateUserRoles");async function Fe(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(Fe,"createSession");async function me(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:Ue(t.roles)}}:null}n(me,"findSessionById");async function pe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(pe,"deleteSession");async function Ar(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(Ar,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function kr(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(kr,"findSessionsByUserId");async function ye(e,r,t){let s=k(r),i=D(r.dialect),o=e.roles??[],f=o.filter(A=>!s.validRolesSet.has(A));if(f.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${f.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let c=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),a=c.filter(A=>!s.validIdentifiersSet.has(A.type));if(a.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${a.map(A=>A.type).join(", ")}`)};if(new Set(c.map(A=>A.value)).size!==c.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of c)if(await se(i,A.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let w=await Y(e.password,s.saltRounds),{userId:C,identifierRows:T}=await i.transaction().execute(async A=>{let P=crypto.randomUUID();await A.insertInto("sentri_users").values({id:P,password_hash:w,roles:JSON.stringify(o)}).execute();let $=c.map(ce=>({id:crypto.randomUUID(),user_id:P,type:ce.type,value:ce.value}));return await A.insertInto("sentri_identifiers").values($).execute(),{userId:P,identifierRows:$}}),S={success:true,user:{id:C,roles:o,identifiers:T.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(S.user),S}n(ye,"register");async function Ie(e,r,t){let s=k(r),i=D(r.dialect),o=await se(i,e.identifier.trim());if(!o){let T=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}if(!await q(e.password,o.passwordHash)){let T=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}let c=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:o.id,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),u={id:o.id,roles:o.roles},w=ee({id:o.id,roles:o.roles,sessionId:a.id},r),C=re(a.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(u,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:w,refreshToken:C,user:u}}n(Ie,"login");async function B(e,r,t){let s=k(r),i=D(r.dialect),o;try{({sessionId:o}=te(e,r));}catch(T){return T instanceof l?{success:false,error:T}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let f=await me(i,o);if(!f)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(f.replacedBy)return await we(i,f.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(f.expiresAt.getTime()<Date.now())return await pe(i,o),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let c=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:f.userId,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await Ar(i,o,a.id);let u={id:f.user.id,roles:f.user.roles},w=ee({...u,sessionId:a.id},r),C=re(a.id,r);return {success:true,accessToken:w,refreshToken:C,user:u}}n(B,"refresh");async function Re(e,r){let t=D(r.dialect),s;try{({sessionId:s}=te(e,r));}catch{return}let i=await me(t,s);i&&(await pe(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(Re,"logout");async function _e(e,r){let t=D(r.dialect);await we(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 he(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await ne(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(o=>({id:o.id,type:o.type,value:o.value}))}}}n(ge,"getUser");async function ve(e,r,t,s){let i=k(s),o=D(s.dialect),f=await he(o,e);if(!f)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await q(r,f.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let a=await Y(t,i.saltRounds);return await gr(o,e,a),await we(o,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(ve,"changePassword");async function Ae(e,r,t){let s=k(t),i=D(t.dialect),o=r.filter(u=>!s.validRolesSet.has(u));if(o.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${o.join(", ")}`)};let f=await he(i,e);if(!f)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let c=new Set(f.roles);for(let u of r)c.add(u);let a=Array.from(c);return await vr(i,e,a),{success:true,user:{id:f.id,roles:a}}}n(Ae,"assignRoles");async function ke(e,r,t){let s=k(t),i=D(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(u=>({type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o)if(await se(i,u.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)};return await yr(i,e,o),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(ke,"bulkCreateIdentifiers");async function Ee(e,r,t){let s=k(t),i=D(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let o=r.map(u=>({id:u.id,type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o){let w=await Le(i,u.id,e);if(!w)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${u.id}`)};if(w.value!==u.value&&await se(i,u.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)}}return await i.transaction().execute(async u=>{for(let w of o)await Rr(u,w.id,e,{type:w.type,value:w.value});}),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(Ee,"bulkUpdateIdentifiers");async function Te(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let c of i)if(!await Le(s,c,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c}`)};return await Ir(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await _r(s,e,i),{success:true,identifiers:(await ne(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))})}n(Te,"bulkDeleteIdentifiers");async function Tr(e,r){let t=D(r.dialect);return (await kr(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(Tr,"getSessions");async function xr(e,r,t){let s=D(t.dialect),i=await me(s,r);i&&i.userId===e&&await pe(s,r);}n(xr,"revokeSession");function K(e){return k(e).cookieName}n(K,"getCookieName");function xe(e){return k(e).accessCookieName}n(xe,"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 i=e.indexOf(";",s),o=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,o);s=o+1;}}n(H,"readCookie");function ie(e,r,t){let s=t.cookie??{},i=V(k(t).refreshExpiresIn);e.cookie(K(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(ie,"setCookieFromConfig");function Ne(e,r){let t=r.cookie??{};e.clearCookie(K(r),{path:t.path??"/"});}n(Ne,"clearCookieFromConfig");function oe(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=V(k(t).accessExpiresIn);e.cookie(xe(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(oe,"setAccessCookieFromConfig");function Pe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(xe(r),{path:t.path??"/"});}n(Pe,"clearAccessCookieFromConfig");function De(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):H(e.headers.cookie,xe(r))}n(De,"getCurrentAccessToken");function O(e){let r=e.logger??L,t=e.loggerService??"sentri";return e.mode==="client"?Gr(e.keyUri,r,t):Wr(e,r,t)}n(O,"protect");function Gr(e,r,t){return async(s,i,o)=>{let f=s.headers.authorization,c=f?.startsWith("Bearer ")?f.slice(7):void 0,a=s.requestId;if(!c)return r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...a!==void 0&&{requestId:a}})),o(new l("UNAUTHORIZED","Missing or malformed Authorization header"));try{let u=await nr(e),w=mr(c,u);s.user={id:w.id,roles:w.roles},r.info(p(t,"auth.protect.success",{mode:"client",userId:w.id,...a!==void 0&&{requestId:a}})),o();}catch(u){let w=u instanceof l?u.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:w,...a!==void 0&&{requestId:a}})),o(u);}}}n(Gr,"protectClient");function Wr(e,r,t){return async(s,i,o)=>{let f=De(s,e),c=s.requestId;if(!f)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Missing or malformed Authorization header"));try{let a=fe(f,e);if(e.isTokenRevoked&&await e.isTokenRevoked(a.sessionId))return r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:a.id,...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Token has been revoked"));s.user={id:a.id,roles:a.roles},r.info(p(t,"auth.protect.success",{mode:"server",userId:a.id,...c!==void 0&&{requestId:c}})),o();}catch(a){if(a instanceof l&&a.code==="TOKEN_EXPIRED"){let u=H(s.headers.cookie,K(e));if(!u)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Token expired. Please login again."));try{let w=await B(u,e);if(!w.success)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:w.error.code,...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Session expired. Please login again."));ie(i,w.refreshToken,e),oe(i,w.accessToken,e),i.setHeader("X-New-Access-Token",w.accessToken),s.user=w.user,r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:w.user.id,...c!==void 0&&{requestId:c}})),o();}catch{r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Session expired. Please login again."));}}else {let u=a instanceof l?a.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...c!==void 0&&{requestId:c}})),o(a);}}}}n(Wr,"protectServer");function Nr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return (i,o,f)=>{let c=i.requestId;if(!i.user)return r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...c!==void 0&&{requestId:c}})),f(new l("UNAUTHORIZED","Not authenticated"));let a=i.user.roles;if(!e.some(u=>a.includes(u)))return r.warn(p(t,"auth.authorize.denied",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...c!==void 0&&{requestId:c}})),f(new l("FORBIDDEN",s));r.info(p(t,"auth.authorize.passed",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...c!==void 0&&{requestId:c}})),f();}}n(Nr,"createAuthorizeHandler");function ae(e,r){return n(function(...s){return Nr(s,e,r)},"authorize")}n(ae,"createAuthorize");function Zr(...e){return Nr(e,L,"sentri")}n(Zr,"authorize");var Yr=new l("FORBIDDEN","You do not have permission to perform this action");function Dr(e,r,t){return async(s,i,o)=>{let f=s.requestId;if(!s.user)return r.warn(p(t,"auth.permit.unauthenticated",{...f!==void 0&&{requestId:f}})),o(new l("UNAUTHORIZED","Not authenticated"));let c=s.user.id;if(e.roles&&e.roles.length>0){let a=s.user.roles;if(e.roles.some(u=>a.includes(u)))return r.info(p(t,"auth.permit.role_bypass",{userId:c,bypassedByRole:true,...f!==void 0&&{requestId:f}})),o()}try{let a=e.check(s);(a instanceof Promise?await a:a)?(r.info(p(t,"auth.permit.passed",{userId:c,...f!==void 0&&{requestId:f}})),o()):(r.warn(p(t,"auth.permit.denied",{userId:c,...f!==void 0&&{requestId:f}})),o(Yr));}catch(a){o(a);}}}n(Dr,"createPermitHandler");function de(e,r){return n(function(s){return Dr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(de,"createPermit");function qr(e){return Dr(typeof e=="function"?{check:e}:e,L,"sentri")}n(qr,"permit");function ue(e){return (r,t,s,i)=>{if(r instanceof l){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(ue,"createErrorHandler");var Sr=new Map;function Cr(e){let r=Sr.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);}),Sr.set(e,r)),r}n(Cr,"getRedisClient");function Or(e){let r=e?.ttl??3e5,t=(e?.header??"X-Idempotency-Key").toLowerCase(),s=new Set((e?.methods??["POST","PUT","PATCH"]).map(o=>o.toUpperCase())),i=e?.redisUrl;return i?et(i,r,t,s):rt(r,t,s,e?.maxSize??1e4)}n(Or,"createIdempotencyMiddleware");function et(e,r,t,s){let i=Cr(e),o="sentri:idempotency:";return async(f,c,a)=>{let u=f.headers[t];if(!u||typeof u!="string"||!s.has(f.method))return a();f.requestId=u,c.setHeader("X-Request-Id",u);let w=await i.get(`${o}${u}`);if(w){let T=JSON.parse(w);return c.setHeader("X-Idempotent-Replayed","true"),c.status(T.statusCode).json(T.body)}let C=c.json.bind(c);c.json=n(function(S){if(c.statusCode>=200&&c.statusCode<300){let A={statusCode:c.statusCode,body:S,expiresAt:Date.now()+r};i.set(`${o}${u}`,JSON.stringify(A),"PX",r).catch(()=>{});}return C(S)},"idempotentJson"),a();}}n(et,"buildRedisMiddleware");function rt(e,r,t,s){let i=Math.max(e,5e3),o=new Map,f=setInterval(()=>{let c=Date.now();for(let[a,u]of o)u.expiresAt<=c&&o.delete(a);},i);return typeof f=="object"&&f!==null&&"unref"in f&&f.unref(),(c,a,u)=>{let w=c.headers[r];if(!w||typeof w!="string"||!t.has(c.method))return u();c.requestId=w,a.setHeader("X-Request-Id",w);let C=Date.now(),T=o.get(w);if(T&&T.expiresAt>C)return a.setHeader("X-Idempotent-Replayed","true"),a.status(T.statusCode).json(T.body);let S=a.json.bind(a);a.json=n(function(P){if(a.statusCode>=200&&a.statusCode<300){if(o.size>=s){let $=o.keys().next().value;$!==void 0&&o.delete($);}o.set(w,{statusCode:a.statusCode,body:P,expiresAt:Date.now()+e});}return S(P)},"idempotentJson"),u();}}n(rt,"buildMemoryMiddleware");var Ke=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 i=Date.now(),o=this.store.get(r);if((!o||o.expiresAt<i)&&(o={count:0,expiresAt:i+s},this.store.set(r,o)),o.count>t){let f=Math.ceil((o.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${f} seconds.`)}o.count++;}},$e=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let i=`sentri:rl:${r}`,o=Math.ceil(s/1e3);try{let f=await this.redis.multi().incr(i).expire(i,o,"NX").exec();if(!f||f.length===0)return;if(f[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(f){if(f instanceof Error&&f.message.includes("Rate limit exceeded"))throw f;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:f});}}},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 $e(s,r??L),X}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return X=new Ke,X}n(br,"getRateLimiter");var Se=8,z=72,Ce=255,Ur=100,J=50;function g(e){return new l("VALIDATION_ERROR",e)}n(g,"badRequest");function b(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}n(b,"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 l("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 l("UNAUTHORIZED","Invalid or missing API key")}n(st,"validateApiKey");function He(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(He,"fireHook");function nt(e){return e.startsWith("RS")||e.startsWith("PS")}n(nt,"isRSA");function Me(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw g(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw g(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Ur)throw g(`identifiers[${r}].type must not exceed ${Ur} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw g(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Ce)throw g(`identifiers[${r}].value must not exceed ${Ce} characters`);return {type:t.type,value:t.value}}n(Me,"validateIdentifierInput");function E(e){return e.requestId!==void 0?{requestId:e.requestId}:{}}n(E,"reqId");function Lr(e){let r=express.Router(),t=e,s=k(t),i=e.logger??L,o=e.loggerService??"sentri",f=ae(i,o),c=de(i,o),a=br(e.redisUrl,i),u=e.router?.register??(d=>ye(d,t)),w=e.router?.login??(d=>Ie(d,t)),C=e.router?.refresh??(d=>B(d,t)),T=e.router?.logout??(d=>d!==void 0?Re(d,t):Promise.resolve()),S=e.router?.logoutAll??(d=>_e(d,t)),A=e.router?.getUser??(d=>ge(d,t)),P=e.router?.assignRoles??((d,m)=>Ae(d,m,t)),$=e.router?.changePassword??((d,m,v)=>ve(d,m,v,t)),ce=e.router?.bulkCreateIdentifiers??((d,m)=>ke(d,m,t)),Fr=e.router?.bulkUpdateIdentifiers??((d,m)=>Ee(d,m,t)),Pr=e.router?.bulkDeleteIdentifiers??((d,m)=>Te(d,m,t));nt(s.algorithm)&&r.get("/keys",(d,m)=>{m.setHeader("Cache-Control","public, max-age=3600"),m.json(tr(e.secret));}),r.post("/register",async(d,m,v)=>{let R=Date.now();try{st(d,e);let h=M(d.body),{identifiers:I,password:_,roles:y}=h;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let x=I.map((Z,Oe)=>Me(Z,Oe));if(typeof _!="string"||_.length<Se)throw g(`password is required and must be at least ${Se} characters`);if(_.length>z)throw g(`password must not exceed ${z} characters`);if(y!==void 0&&!Array.isArray(y))throw g("roles must be an array of strings when provided");if(Array.isArray(y)&&!y.every(Z=>typeof Z=="string"))throw g("each role must be a string");let U=Array.isArray(y)?y:void 0,N=U!==void 0?{identifiers:x,password:_,roles:U}:{identifiers:x,password:_},j=d.ip||"127.0.0.1",G=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let Z=await a,Oe=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await Z.consume(`register:${j}`,Oe,900*1e3);}let W=await u(N,{ip:j,userAgent:G});if(!W.success){i.warn(p(o,"auth.register.failure",{errorCode:W.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,W.error);return}i.info(p(o,"auth.register.success",{userId:W.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,201,"User registered successfully",{user:W.user});}catch(h){v(h);}}),r.post("/login",async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{identifier:I,password:_}=h;if(typeof I!="string"||I.trim().length===0)throw g("identifier is required and must be a non-empty string");if(I.length>Ce)throw g(`identifier must not exceed ${Ce} characters`);if(typeof _!="string"||_.length===0)throw g("password is required");if(_.length>z)throw g(`password must not exceed ${z} characters`);let y=I.trim(),x=d.ip||"127.0.0.1",U=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let j=await a,G=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await j.consume(`login:${x}`,G,900*1e3);}let N=await w({identifier:y,password:_},{ip:x,userAgent:U});if(!N.success){He(()=>e.hooks?.onLoginFailed?.(y,N.error,{ip:x})),i.warn(p(o,"auth.login.failure",{errorCode:N.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,N.error);return}He(()=>e.hooks?.onLoginSuccess?.(N.user,{ip:x,userAgent:U})),ie(m,N.refreshToken,e),oe(m,N.accessToken,e),i.info(p(o,"auth.login.success",{userId:N.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Login successful",{accessToken:N.accessToken,user:N.user});}catch(h){v(h);}}),r.post("/refresh",async(d,m,v)=>{let R=Date.now();try{let h=H(d.headers.cookie,K(e));if(!h)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let I=d.ip||"127.0.0.1",_=d.get("user-agent")||"Unknown",y=await C(h,{ip:I,userAgent:_});if(!y.success){Ne(m,e),i.warn(p(o,"auth.refresh.failure",{errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}ie(m,y.refreshToken,e),oe(m,y.accessToken,e),i.info(p(o,"auth.refresh.success",{userId:y.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Token refreshed",{accessToken:y.accessToken});}catch(h){v(h);}}),r.post("/logout",async(d,m,v)=>{let R=Date.now();try{let h=H(d.headers.cookie,K(e));await T(h),Ne(m,e),Pe(m,e),i.info(p(o,"auth.logout",{duration_ms:Date.now()-R,...E(d)})),b(m,200,"Logged out",null);}catch(h){v(h);}}),r.post("/logout-all",O(e),async(d,m,v)=>{let R=Date.now();try{let h=d.user.id;await S(h),He(()=>e.hooks?.onLogout?.(h)),Ne(m,e),Pe(m,e),i.info(p(o,"auth.logout_all",{userId:h,duration_ms:Date.now()-R,...E(d)})),b(m,200,"All sessions revoked",null);}catch(h){v(h);}}),r.get("/me",O(e),async(d,m,v)=>{let R=Date.now();try{let h=await A(d.user.id);if(!h.success){i.warn(p(o,"auth.me.failure",{userId:d.user.id,errorCode:h.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,h.error);return}i.info(p(o,"auth.me.success",{userId:h.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"OK",h.user);}catch(h){v(h);}}),r.get("/me/identifiers",O(e),async(d,m,v)=>{let R=Date.now();try{let h=await A(d.user.id);if(!h.success){i.warn(p(o,"auth.me.identifiers.failure",{userId:d.user.id,errorCode:h.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,h.error);return}i.info(p(o,"auth.me.identifiers.success",{userId:h.user.id,count:h.user.identifiers?.length??0,duration_ms:Date.now()-R,...E(d)})),b(m,200,"OK",{identifiers:h.user.identifiers??[]});}catch(h){v(h);}});let le=c(d=>!!d.user);return r.post("/me/identifiers",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{identifiers:I}=h;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((x,U)=>Me(x,U)),y=await ce(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.create_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}i.info(p(o,"auth.identifiers.created",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...E(d)})),b(m,201,"Identifiers added successfully",{identifiers:y.identifiers});}catch(h){v(h);}}),r.put("/me/identifiers",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{identifiers:I}=h;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((x,U)=>{if(typeof x!="object"||x===null||Array.isArray(x))throw g(`identifiers[${U}] must be an object`);let N=x;if(typeof N.id!="string"||N.id.trim().length===0)throw g(`identifiers[${U}].id is required and must be a non-empty string`);let{type:j,value:G}=Me(x,U);return {id:N.id,type:j,value:G}}),y=await Fr(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.update_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}i.info(p(o,"auth.identifiers.updated",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Identifiers updated successfully",{identifiers:y.identifiers});}catch(h){v(h);}}),r.delete("/me/identifiers",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{ids:I}=h;if(!Array.isArray(I)||I.length===0)throw g("ids is required and must be a non-empty array of strings");if(!I.every(y=>typeof y=="string"))throw g("each id must be a string");let _=await Pr(d.user.id,I);if(!_.success){i.warn(p(o,"auth.identifiers.delete_failure",{userId:d.user.id,errorCode:_.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,_.error);return}i.info(p(o,"auth.identifiers.deleted",{userId:d.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Identifiers deleted successfully",{identifiers:_.identifiers});}catch(h){v(h);}}),r.patch("/me/password",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{currentPassword:I,newPassword:_}=h;if(typeof I!="string"||I.length===0)throw g("currentPassword is required");if(typeof _!="string"||_.length<Se)throw g(`newPassword must be at least ${Se} characters`);if(_.length>z)throw g(`newPassword must not exceed ${z} characters`);if(I===_)throw g("newPassword must be different from currentPassword");let y=await $(d.user.id,I,_);if(!y.success){i.warn(p(o,"auth.password.change_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}i.info(p(o,"auth.password.changed",{userId:d.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Password updated successfully. All sessions have been revoked.",null);}catch(h){v(h);}}),r.post("/users/:userId/roles",O(e),f("admin"),async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{roles:I}=h,_=d.params.userId,y=typeof _=="string"?_:void 0;if(!y)throw g("userId is required");if(!Array.isArray(I)||I.length===0)throw g("roles must be a non-empty array of strings");if(!I.every(U=>typeof U=="string"))throw g("each role must be a string");let x=await P(y,I);if(!x.success){i.warn(p(o,"auth.roles.assign_failure",{targetUserId:y,errorCode:x.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,x.error);return}i.info(p(o,"auth.roles.assigned",{targetUserId:y,roles:I,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Roles assigned successfully",{user:x.user});}catch(h){v(h);}}),r.use(ue()),r.get("/sessions",O(e),async(d,m,v)=>{let R=Date.now();try{let h=d.user.id,_=await(e.router?.getSessions??(y=>Tr(y,t)))(h);i.info(p(o,"auth.sessions.get",{userId:h,duration_ms:Date.now()-R,...E(d)})),b(m,200,"OK",{sessions:_});}catch(h){v(h);}}),r.delete("/sessions/:id",O(e),async(d,m,v)=>{let R=Date.now();try{let h=d.user.id,I=d.params.id;await(e.router?.revokeSession??((y,x)=>xr(y,x,t)))(h,I),i.info(p(o,"auth.sessions.revoke",{userId:h,sessionId:I,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Session revoked",null);}catch(h){v(h);}}),r}n(Lr,"createAuthRouter");function tn(e){if(e.mode==="client"){let a={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}};Ge(a);let u=a.logger??L,w=a.loggerService??"sentri",C=ae(u,w),T=de(u,w);return {protect:n(()=>O(a),"protect"),authorize:n((...S)=>C(...S),"authorize"),permit:n(S=>T(S),"permit"),errorHandler:n(S=>ue(S),"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.validIdentifiers!==void 0&&{validIdentifiers:e.validIdentifiers},...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??L,i=t.loggerService??"sentri",o=k(t),f=ae(s,i),c=de(s,i);return {protect:n(()=>O(t),"protect"),authorize:n((...a)=>f(...a),"authorize"),permit:n(a=>c(a),"permit"),errorHandler:n(a=>ue(a),"errorHandler"),hashPassword:n(a=>Y(a,o.saltRounds),"hashPassword"),verifyPassword:n((a,u)=>q(a,u),"verifyPassword"),signAccessToken:n(a=>ee(a,t),"signAccessToken"),signRefreshToken:n(a=>re(a,t),"signRefreshToken"),verifyAccessToken:n(a=>fe(a,t),"verifyAccessToken"),verifyRefreshToken:n(a=>te(a,t),"verifyRefreshToken"),getCurrentAccessToken:n(a=>De(a,t),"getCurrentAccessToken"),router:n(()=>Lr(t),"router"),migrate:n(()=>We(D(t.dialect)),"migrate"),idempotencyMiddleware:n(a=>Or({...a,...t.redisUrl!==void 0&&{redisUrl:t.redisUrl}}),"idempotencyMiddleware"),register:n(a=>ye(a,t),"register"),login:n(a=>Ie(a,t),"login"),refresh:n(a=>B(a,t),"refresh"),logout:n(a=>Re(a,t),"logout"),logoutAll:n(a=>_e(a,t),"logoutAll"),getUser:n(a=>ge(a,t),"getUser"),changePassword:n((a,u,w)=>ve(a,u,w,t),"changePassword"),assignRoles:n((a,u)=>Ae(a,u,t),"assignRoles"),bulkCreateIdentifiers:n((a,u)=>ke(a,u,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((a,u)=>Ee(a,u,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((a,u)=>Te(a,u,t),"bulkDeleteIdentifiers")}}n(tn,"createAuthExpress");exports.SENTRI_ERROR_STATUS=je;exports.SentriError=l;exports.authorize=Zr;exports.createAuthExpress=tn;exports.createAuthRouter=Lr;exports.createAuthorize=ae;exports.createErrorHandler=ue;exports.createIdempotencyMiddleware=Or;exports.createPermit=de;exports.getCurrentAccessToken=De;exports.permit=qr;exports.protect=O;
1
+ 'use strict';var crypto=require('crypto'),kysely=require('kysely'),Ye=require('bcrypt'),Q=require('jsonwebtoken'),ioredis=require('ioredis'),express=require('express');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var Ye__default=/*#__PURE__*/_interopDefault(Ye);var Q__default=/*#__PURE__*/_interopDefault(Q);var Kr=Object.defineProperty;var n=(e,r)=>Kr(e,"name",{value:r,configurable:true});var je=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,RATE_LIMIT_EXCEEDED:429}),c=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??je[r]??500;}};var Ve=new WeakMap,Xe=32,Be=10,ze=31;function Ge(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new c("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new c("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Xe)throw new c("CONFIGURATION_ERROR",`secret must be at least ${Xe} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Be||s>ze)throw new c("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Be} and ${ze}`);if(!e.validRoles||e.validRoles.length===0)throw new c("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new c("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new c("CONFIGURATION_ERROR","dialect is required in server mode")}n(Ge,"validateConfig");function E(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),validIdentifiers:e.validIdentifiers??["email","username"],validIdentifiersSet:new Set(e.validIdentifiers??["email","username"]),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}n(E,"resolveServerConfig");var $r=/^(\d+)([smhdw])$/,Hr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Je=new Map;function V(e){if(typeof e=="number")return e*1e3;let r=Je.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 i=parseInt(t[1],10)*s;return Je.set(e,i),i}n(V,"parseExpiry");var L={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 We(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(We,"runMigrations");var Ze=new Map;function N(e){let r=Ze.get(e);return r||(r=new kysely.Kysely({dialect:e}),Ze.set(e,r)),r}n(N,"getDatabase");async function Y(e,r=12){return Ye__default.default.hash(e,r)}n(Y,"hashPassword");async function q(e,r){return Ye__default.default.compare(e,r)}n(q,"verifyPassword");var qe=new Map,Qe=new Map,Xr=3600*1e3;function rr(e){let r=qe.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),i=s.export({format:"jwk"}),f=crypto.createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),l={...i,use:"sig",kid:f};r={kid:f,publicKey:s,jwk:l},qe.set(e,r);}return r}n(rr,"getOrBuildKey");function tr(e){let{jwk:r}=rr(e);return {keys:[r]}}n(tr,"buildJwks");function sr(e){return rr(e).publicKey}n(sr,"getPublicKeyFromPrivate");async function nr(e){let r=Date.now(),t=Qe.get(e);if(t&&r-t.fetchedAt<Xr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new c("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new c("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let o=i.keys[0],f=crypto.createPublicKey({key:o,format:"jwk"});return Qe.set(e,{publicKey:f,fetchedAt:r}),f}n(nr,"fetchPublicKey");var ir=new Map,or=new Map,ar=new Map;function dr(e){return e.startsWith("RS")||e.startsWith("PS")}n(dr,"isRSA");function ur(e){let r=ir.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},ir.set(e,r)),r}n(ur,"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=E(e);if(dr(r.algorithm)){let i=zr(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ur(e.secret);return {accessKey:t,refreshKey:s}}n(cr,"getSigningKeys");function lr(e,r){let t=E(e);if(dr(t.algorithm))return sr(e.secret);let{access:s,refresh:i}=ur(e.secret);return r==="access"?s:i}n(lr,"getVerifyKey");function fr(e,r,t,s){let i=`${t}:${s}`,o=ar.get(i);return o||(o={expiresIn:t,algorithm:s},ar.set(i,o)),Q__default.default.sign(e,r,o)}n(fr,"sign");function mr(e,r,t){try{let s=Q__default.default.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new c("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof c?s:s instanceof Q__default.default.TokenExpiredError?new c("TOKEN_EXPIRED","Token has expired"):new c("TOKEN_INVALID","Token is invalid or malformed")}}n(mr,"verify");function ee(e,r){let t=E(r),{accessKey:s}=cr(r);return fr(e,s,t.accessExpiresIn,t.algorithm)}n(ee,"signAccessToken");function re(e,r){let t=E(r),{refreshKey:s}=cr(r);return fr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(re,"signRefreshToken");function fe(e,r){let t=E(r),s=lr(r,"access");return mr(e,s,t.algorithm)}n(fe,"verifyAccessToken");function te(e,r){let t=E(r),s=lr(r,"refresh");return mr(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 c("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof c?t:t instanceof Q__default.default.TokenExpiredError?new c("TOKEN_EXPIRED","Token has expired"):new c("TOKEN_INVALID","Token is invalid or malformed")}}n(hr,"verifyTokenWithPublicKey");function Ue(e){try{return JSON.parse(e)}catch{return []}}n(Ue,"parseRoles");function Jr(e){return JSON.stringify(e)}n(Jr,"serializeRoles");function wr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(wr,"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:Ue(t.roles)}:null}n(se,"findUserByIdentifierValue");async function me(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:Ue(t.roles)}:null}n(me,"findUserById");async function yr(e,r,t){let s=t.map(i=>({id:crypto.randomUUID(),user_id:r,type:i.type,value:i.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(i=>({id:i.id,userId:i.user_id,type:i.type,value:i.value,createdAt:new Date}))}n(yr,"createIdentifiers");async function ne(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(ne,"findIdentifiersByUserId");async function Le(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?wr(s):null}n(Le,"findIdentifierById");async function Ir(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(Ir,"countIdentifiersByUserId");async function Rr(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(Rr,"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 vr(e,r,t){await e.updateTable("sentri_users").set({roles:Jr(t)}).where("id","=",r).execute();}n(vr,"updateUserRoles");async function Fe(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(Fe,"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:Ue(t.roles)}}:null}n(he,"findSessionById");async function pe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(pe,"deleteSession");async function Ar(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(Ar,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function Er(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(Er,"findSessionsByUserId");async function ye(e,r,t){let s=E(r),i=N(r.dialect),o=e.roles??[],f=o.filter(A=>!s.validRolesSet.has(A));if(f.length>0)return {success:false,error:new c("INVALID_ROLE",`Invalid roles: ${f.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one identifier is required")};let l=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),a=l.filter(A=>!s.validIdentifiersSet.has(A.type));if(a.length>0)return {success:false,error:new c("VALIDATION_ERROR",`Invalid identifier types: ${a.map(A=>A.type).join(", ")}`)};if(new Set(l.map(A=>A.value)).size!==l.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of l)if(await se(i,A.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let w=await Y(e.password,s.saltRounds),{userId:S,identifierRows:T}=await i.transaction().execute(async A=>{let P=crypto.randomUUID();await A.insertInto("sentri_users").values({id:P,password_hash:w,roles:JSON.stringify(o)}).execute();let $=l.map(ce=>({id:crypto.randomUUID(),user_id:P,type:ce.type,value:ce.value}));return await A.insertInto("sentri_identifiers").values($).execute(),{userId:P,identifierRows:$}}),C={success:true,user:{id:S,roles:o,identifiers:T.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(C.user),C}n(ye,"register");async function Ie(e,r,t){let s=E(r),i=N(r.dialect),o=await se(i,e.identifier.trim());if(!o){let T=new c("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}if(!await q(e.password,o.passwordHash)){let T=new c("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}let l=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:o.id,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),u={id:o.id,roles:o.roles},w=ee({id:o.id,roles:o.roles,sessionId:a.id},r),S=re(a.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(u,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:w,refreshToken:S,user:u}}n(Ie,"login");async function X(e,r,t){let s=E(r),i=N(r.dialect),o;try{({sessionId:o}=te(e,r));}catch(T){return T instanceof c?{success:false,error:T}:{success:false,error:new c("TOKEN_INVALID","Invalid refresh token")}}let f=await he(i,o);if(!f)return {success:false,error:new c("UNAUTHORIZED","Session not found or revoked")};if(f.replacedBy)return await we(i,f.userId),{success:false,error:new c("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(f.expiresAt.getTime()<Date.now())return await pe(i,o),{success:false,error:new c("TOKEN_EXPIRED","Session has expired")};let l=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:f.userId,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await Ar(i,o,a.id);let u={id:f.user.id,roles:f.user.roles},w=ee({...u,sessionId:a.id},r),S=re(a.id,r);return {success:true,accessToken:w,refreshToken:S,user:u}}n(X,"refresh");async function Re(e,r){let t=N(r.dialect),s;try{({sessionId:s}=te(e,r));}catch{return}let i=await he(t,s);i&&(await pe(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(Re,"logout");async function _e(e,r){let t=N(r.dialect);await we(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(_e,"logoutAll");async function ge(e,r){let t=N(r.dialect),s=await me(t,e);if(!s)return {success:false,error:new c("USER_NOT_FOUND","User not found")};let i=await ne(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(o=>({id:o.id,type:o.type,value:o.value}))}}}n(ge,"getUser");async function ve(e,r,t,s){let i=E(s),o=N(s.dialect),f=await me(o,e);if(!f)return {success:false,error:new c("USER_NOT_FOUND","User not found")};if(!await q(r,f.passwordHash))return {success:false,error:new c("INVALID_CREDENTIALS","Invalid credentials")};let a=await Y(t,i.saltRounds);return await gr(o,e,a),await we(o,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(ve,"changePassword");async function Ae(e,r,t){let s=E(t),i=N(t.dialect),o=r.filter(u=>!s.validRolesSet.has(u));if(o.length>0)return {success:false,error:new c("INVALID_ROLE",`Invalid roles: ${o.join(", ")}`)};let f=await me(i,e);if(!f)return {success:false,error:new c("USER_NOT_FOUND","User not found")};let l=new Set(f.roles);for(let u of r)l.add(u);let a=Array.from(l);return await vr(i,e,a),{success:true,user:{id:f.id,roles:a}}}n(Ae,"assignRoles");async function Ee(e,r,t){let s=E(t),i=N(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(u=>({type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new c("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o)if(await se(i,u.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)};return await yr(i,e,o),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(Ee,"bulkCreateIdentifiers");async function ke(e,r,t){let s=E(t),i=N(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one update is required")};let o=r.map(u=>({id:u.id,type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new c("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o){let w=await Le(i,u.id,e);if(!w)return {success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${u.id}`)};if(w.value!==u.value&&await se(i,u.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)}}return await i.transaction().execute(async u=>{for(let w of o)await Rr(u,w.id,e,{type:w.type,value:w.value});}),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(ke,"bulkUpdateIdentifiers");async function Te(e,r,t){let s=N(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let l of i)if(!await Le(s,l,e))return {success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l}`)};return await Ir(s,e)-i.length<1?{success:false,error:new c("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await _r(s,e,i),{success:true,identifiers:(await ne(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))})}n(Te,"bulkDeleteIdentifiers");async function Tr(e,r){let t=N(r.dialect);return (await Er(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(Tr,"getSessions");async function Dr(e,r,t){let s=N(t.dialect),i=await he(s,r);i&&i.userId===e&&await pe(s,r);}n(Dr,"revokeSession");function K(e){return E(e).cookieName}n(K,"getCookieName");function De(e){return E(e).accessCookieName}n(De,"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 i=e.indexOf(";",s),o=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,o);s=o+1;}}n(H,"readCookie");function ie(e,r,t){let s=t.cookie??{},i=V(E(t).refreshExpiresIn);e.cookie(K(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(ie,"setCookieFromConfig");function xe(e,r){let t=r.cookie??{};e.clearCookie(K(r),{path:t.path??"/"});}n(xe,"clearCookieFromConfig");function oe(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=V(E(t).accessExpiresIn);e.cookie(De(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(oe,"setAccessCookieFromConfig");function Pe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(De(r),{path:t.path??"/"});}n(Pe,"clearAccessCookieFromConfig");function Ne(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):H(e.headers.cookie,De(r))}n(Ne,"getCurrentAccessToken");function O(e){let r=e.logger??L,t=e.loggerService??"sentri";return e.mode==="client"?Gr(e.keyUri,r,t):Wr(e,r,t)}n(O,"protect");function Gr(e,r,t){return async(s,i,o)=>{let f=s.headers.authorization,l=f?.startsWith("Bearer ")?f.slice(7):void 0,a=s.requestId;if(!l)return r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...a!==void 0&&{requestId:a}})),o(new c("UNAUTHORIZED","Missing or malformed Authorization header"));try{let u=await nr(e),w=hr(l,u);s.user={id:w.id,roles:w.roles},r.info(p(t,"auth.protect.success",{mode:"client",userId:w.id,...a!==void 0&&{requestId:a}})),o();}catch(u){let w=u instanceof c?u.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:w,...a!==void 0&&{requestId:a}})),o(u);}}}n(Gr,"protectClient");function Wr(e,r,t){return async(s,i,o)=>{let f=Ne(s,e),l=s.requestId;if(!f)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Missing or malformed Authorization header"));try{let a=fe(f,e);if(e.isTokenRevoked&&await e.isTokenRevoked(a.sessionId))return r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:a.id,...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Token has been revoked"));s.user={id:a.id,roles:a.roles},r.info(p(t,"auth.protect.success",{mode:"server",userId:a.id,...l!==void 0&&{requestId:l}})),o();}catch(a){if(a instanceof c&&a.code==="TOKEN_EXPIRED"){let u=H(s.headers.cookie,K(e));if(!u)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Token expired. Please login again."));try{let w=await X(u,e);if(!w.success)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:w.error.code,...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Session expired. Please login again."));ie(i,w.refreshToken,e),oe(i,w.accessToken,e),i.setHeader("X-New-Access-Token",w.accessToken),s.user=w.user,r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:w.user.id,...l!==void 0&&{requestId:l}})),o();}catch{r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Session expired. Please login again."));}}else {let u=a instanceof c?a.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...l!==void 0&&{requestId:l}})),o(a);}}}}n(Wr,"protectServer");function xr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return (i,o,f)=>{let l=i.requestId;if(!i.user)return r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...l!==void 0&&{requestId:l}})),f(new c("UNAUTHORIZED","Not authenticated"));let a=i.user.roles;if(!e.some(u=>a.includes(u)))return r.warn(p(t,"auth.authorize.denied",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...l!==void 0&&{requestId:l}})),f(new c("FORBIDDEN",s));r.info(p(t,"auth.authorize.passed",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...l!==void 0&&{requestId:l}})),f();}}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,L,"sentri")}n(Zr,"authorize");var Yr=new c("FORBIDDEN","You do not have permission to perform this action");function Nr(e,r,t){return async(s,i,o)=>{let f=s.requestId;if(!s.user)return r.warn(p(t,"auth.permit.unauthenticated",{...f!==void 0&&{requestId:f}})),o(new c("UNAUTHORIZED","Not authenticated"));let l=s.user.id;if(e.roles&&e.roles.length>0){let a=s.user.roles;if(e.roles.some(u=>a.includes(u)))return r.info(p(t,"auth.permit.role_bypass",{userId:l,bypassedByRole:true,...f!==void 0&&{requestId:f}})),o()}try{let a=e.check(s);(a instanceof Promise?await a:a)?(r.info(p(t,"auth.permit.passed",{userId:l,...f!==void 0&&{requestId:f}})),o()):(r.warn(p(t,"auth.permit.denied",{userId:l,...f!==void 0&&{requestId:f}})),o(Yr));}catch(a){o(a);}}}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,L,"sentri")}n(qr,"permit");function ue(e){return (r,t,s,i)=>{if(r instanceof c){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(ue,"createErrorHandler");var Cr=new Map;function Sr(e){let r=Cr.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);}),Cr.set(e,r)),r}n(Sr,"getRedisClient");function Or(e){let r=e?.ttl??3e5,t=(e?.header??"X-Idempotency-Key").toLowerCase(),s=new Set((e?.methods??["POST","PUT","PATCH"]).map(o=>o.toUpperCase())),i=e?.redisUrl;return i?et(i,r,t,s):rt(r,t,s,e?.maxSize??1e4)}n(Or,"createIdempotencyMiddleware");function et(e,r,t,s){let i=Sr(e),o="sentri:idempotency:";return async(f,l,a)=>{let u=f.headers[t];if(!u||typeof u!="string"||!s.has(f.method))return a();f.requestId=u,l.setHeader("X-Request-Id",u);let w=await i.get(`${o}${u}`);if(w){let T=JSON.parse(w);return l.setHeader("X-Idempotent-Replayed","true"),l.status(T.statusCode).json(T.body)}let S=l.json.bind(l);l.json=n(function(C){if(l.statusCode>=200&&l.statusCode<300){let A={statusCode:l.statusCode,body:C,expiresAt:Date.now()+r};i.set(`${o}${u}`,JSON.stringify(A),"PX",r).catch(()=>{});}return S(C)},"idempotentJson"),a();}}n(et,"buildRedisMiddleware");function rt(e,r,t,s){let i=Math.max(e,5e3),o=new Map,f=setInterval(()=>{let l=Date.now();for(let[a,u]of o)u.expiresAt<=l&&o.delete(a);},i);return typeof f=="object"&&f!==null&&"unref"in f&&f.unref(),(l,a,u)=>{let w=l.headers[r];if(!w||typeof w!="string"||!t.has(l.method))return u();l.requestId=w,a.setHeader("X-Request-Id",w);let S=Date.now(),T=o.get(w);if(T&&T.expiresAt>S)return a.setHeader("X-Idempotent-Replayed","true"),a.status(T.statusCode).json(T.body);let C=a.json.bind(a);a.json=n(function(P){if(a.statusCode>=200&&a.statusCode<300){if(o.size>=s){let $=o.keys().next().value;$!==void 0&&o.delete($);}o.set(w,{statusCode:a.statusCode,body:P,expiresAt:Date.now()+e});}return C(P)},"idempotentJson"),u();}}n(rt,"buildMemoryMiddleware");var Ke=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 i=Date.now(),o=this.store.get(r);if((!o||o.expiresAt<i)&&(o={count:0,expiresAt:i+s},this.store.set(r,o)),o.count>=t){let f=Math.ceil((o.expiresAt-i)/1e3);throw new c("RATE_LIMIT_EXCEEDED",`Rate limit exceeded. Try again in ${f} seconds.`)}o.count++;}},$e=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let i=`sentri:rl:${r}`,o=Math.ceil(s/1e3);try{let f=await this.redis.multi().incr(i).expire(i,o,"NX").exec();if(!f||f.length===0)return;if(f[0][1]>t)throw new c("RATE_LIMIT_EXCEEDED","Rate limit exceeded. Try again later.")}catch(f){if(f instanceof c&&f.code==="RATE_LIMIT_EXCEEDED")throw f;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:f});}}},B=null;async function br(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 $e(s,r??L),B}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return B=new Ke,B}n(br,"getRateLimiter");var Ce=8,z=72,Se=255,Ur=100,J=50;function g(e){return new c("VALIDATION_ERROR",e)}n(g,"badRequest");function b(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}n(b,"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 c("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 c("UNAUTHORIZED","Invalid or missing API key")}n(st,"validateApiKey");function He(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(He,"fireHook");function nt(e){return e.startsWith("RS")||e.startsWith("PS")}n(nt,"isRSA");function Me(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw g(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw g(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Ur)throw g(`identifiers[${r}].type must not exceed ${Ur} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw g(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Se)throw g(`identifiers[${r}].value must not exceed ${Se} characters`);return {type:t.type,value:t.value}}n(Me,"validateIdentifierInput");function k(e){return e.requestId!==void 0?{requestId:e.requestId}:{}}n(k,"reqId");function Lr(e){let r=express.Router(),t=e,s=E(t),i=e.logger??L,o=e.loggerService??"sentri",f=ae(i,o),l=de(i,o),a=br(e.redisUrl,i),u=e.router?.register??(d=>ye(d,t)),w=e.router?.login??(d=>Ie(d,t)),S=e.router?.refresh??(d=>X(d,t)),T=e.router?.logout??(d=>d!==void 0?Re(d,t):Promise.resolve()),C=e.router?.logoutAll??(d=>_e(d,t)),A=e.router?.getUser??(d=>ge(d,t)),P=e.router?.assignRoles??((d,h)=>Ae(d,h,t)),$=e.router?.changePassword??((d,h,v)=>ve(d,h,v,t)),ce=e.router?.bulkCreateIdentifiers??((d,h)=>Ee(d,h,t)),Fr=e.router?.bulkUpdateIdentifiers??((d,h)=>ke(d,h,t)),Pr=e.router?.bulkDeleteIdentifiers??((d,h)=>Te(d,h,t));nt(s.algorithm)&&r.get("/keys",(d,h)=>{h.setHeader("Cache-Control","public, max-age=3600"),h.json(tr(e.secret));}),r.post("/register",async(d,h,v)=>{let R=Date.now();try{st(d,e);let m=M(d.body),{identifiers:I,password:_,roles:y}=m;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let D=I.map((Z,Oe)=>Me(Z,Oe));if(typeof _!="string"||_.length<Ce)throw g(`password is required and must be at least ${Ce} characters`);if(_.length>z)throw g(`password must not exceed ${z} characters`);if(y!==void 0&&!Array.isArray(y))throw g("roles must be an array of strings when provided");if(Array.isArray(y)&&!y.every(Z=>typeof Z=="string"))throw g("each role must be a string");let U=Array.isArray(y)?y:void 0,x=U!==void 0?{identifiers:D,password:_,roles:U}:{identifiers:D,password:_},j=d.ip||"127.0.0.1",G=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let Z=await a,Oe=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await Z.consume(`register:${j}`,Oe,900*1e3);}let W=await u(x,{ip:j,userAgent:G});if(!W.success){i.warn(p(o,"auth.register.failure",{errorCode:W.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,W.error);return}i.info(p(o,"auth.register.success",{userId:W.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,201,"User registered successfully",{user:W.user});}catch(m){v(m);}}),r.post("/login",async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{identifier:I,password:_}=m;if(typeof I!="string"||I.trim().length===0)throw g("identifier is required and must be a non-empty string");if(I.length>Se)throw g(`identifier must not exceed ${Se} characters`);if(typeof _!="string"||_.length===0)throw g("password is required");if(_.length>z)throw g(`password must not exceed ${z} characters`);let y=I.trim(),D=d.ip||"127.0.0.1",U=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let j=await a,G=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await j.consume(`login:${D}`,G,900*1e3);}let x=await w({identifier:y,password:_},{ip:D,userAgent:U});if(!x.success){He(()=>e.hooks?.onLoginFailed?.(y,x.error,{ip:D})),i.warn(p(o,"auth.login.failure",{errorCode:x.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,x.error);return}He(()=>e.hooks?.onLoginSuccess?.(x.user,{ip:D,userAgent:U})),ie(h,x.refreshToken,e),oe(h,x.accessToken,e),i.info(p(o,"auth.login.success",{userId:x.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Login successful",{accessToken:x.accessToken,user:x.user});}catch(m){v(m);}}),r.post("/refresh",async(d,h,v)=>{let R=Date.now();try{let m=H(d.headers.cookie,K(e));if(!m)throw new c("UNAUTHORIZED","Refresh token cookie is missing");let I=d.ip||"127.0.0.1",_=d.get("user-agent")||"Unknown",y=await S(m,{ip:I,userAgent:_});if(!y.success){xe(h,e),i.warn(p(o,"auth.refresh.failure",{errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}ie(h,y.refreshToken,e),oe(h,y.accessToken,e),i.info(p(o,"auth.refresh.success",{userId:y.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Token refreshed",{accessToken:y.accessToken});}catch(m){v(m);}}),r.post("/logout",async(d,h,v)=>{let R=Date.now();try{let m=H(d.headers.cookie,K(e));await T(m),xe(h,e),Pe(h,e),i.info(p(o,"auth.logout",{duration_ms:Date.now()-R,...k(d)})),b(h,200,"Logged out",null);}catch(m){v(m);}}),r.post("/logout-all",O(e),async(d,h,v)=>{let R=Date.now();try{let m=d.user.id;await C(m),He(()=>e.hooks?.onLogout?.(m)),xe(h,e),Pe(h,e),i.info(p(o,"auth.logout_all",{userId:m,duration_ms:Date.now()-R,...k(d)})),b(h,200,"All sessions revoked",null);}catch(m){v(m);}}),r.get("/me",O(e),async(d,h,v)=>{let R=Date.now();try{let m=await A(d.user.id);if(!m.success){i.warn(p(o,"auth.me.failure",{userId:d.user.id,errorCode:m.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,m.error);return}i.info(p(o,"auth.me.success",{userId:m.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"OK",m.user);}catch(m){v(m);}}),r.get("/me/identifiers",O(e),async(d,h,v)=>{let R=Date.now();try{let m=await A(d.user.id);if(!m.success){i.warn(p(o,"auth.me.identifiers.failure",{userId:d.user.id,errorCode:m.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,m.error);return}i.info(p(o,"auth.me.identifiers.success",{userId:m.user.id,count:m.user.identifiers?.length??0,duration_ms:Date.now()-R,...k(d)})),b(h,200,"OK",{identifiers:m.user.identifiers??[]});}catch(m){v(m);}});let le=l(d=>!!d.user);return r.post("/me/identifiers",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{identifiers:I}=m;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((D,U)=>Me(D,U)),y=await ce(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.create_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}i.info(p(o,"auth.identifiers.created",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...k(d)})),b(h,201,"Identifiers added successfully",{identifiers:y.identifiers});}catch(m){v(m);}}),r.put("/me/identifiers",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{identifiers:I}=m;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((D,U)=>{if(typeof D!="object"||D===null||Array.isArray(D))throw g(`identifiers[${U}] must be an object`);let x=D;if(typeof x.id!="string"||x.id.trim().length===0)throw g(`identifiers[${U}].id is required and must be a non-empty string`);let{type:j,value:G}=Me(D,U);return {id:x.id,type:j,value:G}}),y=await Fr(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.update_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}i.info(p(o,"auth.identifiers.updated",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Identifiers updated successfully",{identifiers:y.identifiers});}catch(m){v(m);}}),r.delete("/me/identifiers",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{ids:I}=m;if(!Array.isArray(I)||I.length===0)throw g("ids is required and must be a non-empty array of strings");if(!I.every(y=>typeof y=="string"))throw g("each id must be a string");let _=await Pr(d.user.id,I);if(!_.success){i.warn(p(o,"auth.identifiers.delete_failure",{userId:d.user.id,errorCode:_.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,_.error);return}i.info(p(o,"auth.identifiers.deleted",{userId:d.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Identifiers deleted successfully",{identifiers:_.identifiers});}catch(m){v(m);}}),r.patch("/me/password",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{currentPassword:I,newPassword:_}=m;if(typeof I!="string"||I.length===0)throw g("currentPassword is required");if(typeof _!="string"||_.length<Ce)throw g(`newPassword must be at least ${Ce} characters`);if(_.length>z)throw g(`newPassword must not exceed ${z} characters`);if(I===_)throw g("newPassword must be different from currentPassword");let y=await $(d.user.id,I,_);if(!y.success){i.warn(p(o,"auth.password.change_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}i.info(p(o,"auth.password.changed",{userId:d.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Password updated successfully. All sessions have been revoked.",null);}catch(m){v(m);}}),r.post("/users/:userId/roles",O(e),f("admin"),async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{roles:I}=m,_=d.params.userId,y=typeof _=="string"?_:void 0;if(!y)throw g("userId is required");if(!Array.isArray(I)||I.length===0)throw g("roles must be a non-empty array of strings");if(!I.every(U=>typeof U=="string"))throw g("each role must be a string");let D=await P(y,I);if(!D.success){i.warn(p(o,"auth.roles.assign_failure",{targetUserId:y,errorCode:D.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,D.error);return}i.info(p(o,"auth.roles.assigned",{targetUserId:y,roles:I,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Roles assigned successfully",{user:D.user});}catch(m){v(m);}}),r.use(ue()),r.get("/sessions",O(e),async(d,h,v)=>{let R=Date.now();try{let m=d.user.id,_=await(e.router?.getSessions??(y=>Tr(y,t)))(m);i.info(p(o,"auth.sessions.get",{userId:m,duration_ms:Date.now()-R,...k(d)})),b(h,200,"OK",{sessions:_});}catch(m){v(m);}}),r.delete("/sessions/:id",O(e),async(d,h,v)=>{let R=Date.now();try{let m=d.user.id,I=d.params.id;await(e.router?.revokeSession??((y,D)=>Dr(y,D,t)))(m,I),i.info(p(o,"auth.sessions.revoke",{userId:m,sessionId:I,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Session revoked",null);}catch(m){v(m);}}),r}n(Lr,"createAuthRouter");function sn(e){if(e.mode==="client"){let a={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}};Ge(a);let u=a.logger??L,w=a.loggerService??"sentri",S=ae(u,w),T=de(u,w);return {protect:n(()=>O(a),"protect"),authorize:n((...C)=>S(...C),"authorize"),permit:n(C=>T(C),"permit"),errorHandler:n(C=>ue(C),"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.validIdentifiers!==void 0&&{validIdentifiers:e.validIdentifiers},...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.rateLimit!==void 0&&{rateLimit:e.rateLimit},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??L,i=t.loggerService??"sentri",o=E(t),f=ae(s,i),l=de(s,i);return {protect:n(()=>O(t),"protect"),authorize:n((...a)=>f(...a),"authorize"),permit:n(a=>l(a),"permit"),errorHandler:n(a=>ue(a),"errorHandler"),hashPassword:n(a=>Y(a,o.saltRounds),"hashPassword"),verifyPassword:n((a,u)=>q(a,u),"verifyPassword"),signAccessToken:n(a=>ee(a,t),"signAccessToken"),signRefreshToken:n(a=>re(a,t),"signRefreshToken"),verifyAccessToken:n(a=>fe(a,t),"verifyAccessToken"),verifyRefreshToken:n(a=>te(a,t),"verifyRefreshToken"),getCurrentAccessToken:n(a=>Ne(a,t),"getCurrentAccessToken"),router:n(()=>Lr(t),"router"),migrate:n(()=>We(N(t.dialect)),"migrate"),idempotencyMiddleware:n(a=>Or({...a,...t.redisUrl!==void 0&&{redisUrl:t.redisUrl}}),"idempotencyMiddleware"),register:n(a=>ye(a,t),"register"),login:n(a=>Ie(a,t),"login"),refresh:n(a=>X(a,t),"refresh"),logout:n(a=>Re(a,t),"logout"),logoutAll:n(a=>_e(a,t),"logoutAll"),getUser:n(a=>ge(a,t),"getUser"),changePassword:n((a,u,w)=>ve(a,u,w,t),"changePassword"),assignRoles:n((a,u)=>Ae(a,u,t),"assignRoles"),bulkCreateIdentifiers:n((a,u)=>Ee(a,u,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((a,u)=>ke(a,u,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((a,u)=>Te(a,u,t),"bulkDeleteIdentifiers")}}n(sn,"createAuthExpress");exports.SENTRI_ERROR_STATUS=je;exports.SentriError=c;exports.authorize=Zr;exports.createAuthExpress=sn;exports.createAuthRouter=Lr;exports.createAuthorize=ae;exports.createErrorHandler=ue;exports.createIdempotencyMiddleware=Or;exports.createPermit=de;exports.getCurrentAccessToken=Ne;exports.permit=qr;exports.protect=O;
@@ -1,7 +1,7 @@
1
+ import { a as AuthConfig, S as SentriLogger, b as ServerAuthConfig, A as AuthUser, C as CookieConfig, c as AccessCookieConfig, d as AuthHooks, R as RouterHandlers, e as RateLimitOptions, f as RegisterInput, g as RegisterResult, L as LoginInput, h as AuthResult, i as RefreshResult, G as GetUserResult, j as ChangePasswordResult, k as AssignRolesResult, I as IdentifierInput, B as BulkIdentifiersResult } from '../../index-CVb3-EnK.cjs';
2
+ export { n as ApiResponse, o as ClientAuthConfig, p as IdentifierRecord, l as SENTRI_ERROR_STATUS, m as SentriError, q as SentriErrorCode } from '../../index-CVb3-EnK.cjs';
1
3
  import * as kysely from 'kysely';
2
4
  import { RequestHandler, Request, ErrorRequestHandler, Router } from 'express';
3
- import { AuthConfig, SentriLogger, ServerAuthConfig, AuthUser, CookieConfig, AccessCookieConfig, AuthHooks, RouterHandlers, RegisterInput, RegisterResult, LoginInput, AuthResult, RefreshResult, GetUserResult, ChangePasswordResult, AssignRolesResult, IdentifierInput, BulkIdentifiersResult } from '../../core/index.cjs';
4
- export { ApiResponse, ClientAuthConfig, IdentifierRecord, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode } from '../../core/index.cjs';
5
5
 
6
6
  declare function protect(config: AuthConfig): RequestHandler;
7
7
 
@@ -255,6 +255,7 @@ interface CreateExpressServerOptions<TRole extends string = string> {
255
255
  hooks?: AuthHooks;
256
256
  router?: RouterHandlers;
257
257
  isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
258
+ rateLimit?: RateLimitOptions | boolean;
258
259
  redisUrl?: string;
259
260
  logger?: SentriLogger;
260
261
  loggerService?: string;
@@ -1,7 +1,7 @@
1
+ import { a as AuthConfig, S as SentriLogger, b as ServerAuthConfig, A as AuthUser, C as CookieConfig, c as AccessCookieConfig, d as AuthHooks, R as RouterHandlers, e as RateLimitOptions, f as RegisterInput, g as RegisterResult, L as LoginInput, h as AuthResult, i as RefreshResult, G as GetUserResult, j as ChangePasswordResult, k as AssignRolesResult, I as IdentifierInput, B as BulkIdentifiersResult } from '../../index-CVb3-EnK.js';
2
+ export { n as ApiResponse, o as ClientAuthConfig, p as IdentifierRecord, l as SENTRI_ERROR_STATUS, m as SentriError, q as SentriErrorCode } from '../../index-CVb3-EnK.js';
1
3
  import * as kysely from 'kysely';
2
4
  import { RequestHandler, Request, ErrorRequestHandler, Router } from 'express';
3
- import { AuthConfig, SentriLogger, ServerAuthConfig, AuthUser, CookieConfig, AccessCookieConfig, AuthHooks, RouterHandlers, RegisterInput, RegisterResult, LoginInput, AuthResult, RefreshResult, GetUserResult, ChangePasswordResult, AssignRolesResult, IdentifierInput, BulkIdentifiersResult } from '../../core/index.js';
4
- export { ApiResponse, ClientAuthConfig, IdentifierRecord, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode } from '../../core/index.js';
5
5
 
6
6
  declare function protect(config: AuthConfig): RequestHandler;
7
7
 
@@ -255,6 +255,7 @@ interface CreateExpressServerOptions<TRole extends string = string> {
255
255
  hooks?: AuthHooks;
256
256
  router?: RouterHandlers;
257
257
  isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
258
+ rateLimit?: RateLimitOptions | boolean;
258
259
  redisUrl?: string;
259
260
  logger?: SentriLogger;
260
261
  loggerService?: string;
@@ -1 +1 @@
1
- import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import Ye from'bcrypt';import Q from'jsonwebtoken';import {Redis}from'ioredis';import {Router}from'express';var Kr=Object.defineProperty;var n=(e,r)=>Kr(e,"name",{value:r,configurable:true});var je=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}),l=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??je[r]??500;}};var Ve=new WeakMap,Be=32,Xe=10,ze=31;function Ge(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new l("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new l("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Be)throw new l("CONFIGURATION_ERROR",`secret must be at least ${Be} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Xe||s>ze)throw new l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Xe} and ${ze}`);if(!e.validRoles||e.validRoles.length===0)throw new l("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new l("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new l("CONFIGURATION_ERROR","dialect is required in server mode")}n(Ge,"validateConfig");function k(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),validIdentifiers:e.validIdentifiers??["email","username"],validIdentifiersSet:new Set(e.validIdentifiers??["email","username"]),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}n(k,"resolveServerConfig");var $r=/^(\d+)([smhdw])$/,Hr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Je=new Map;function V(e){if(typeof e=="number")return e*1e3;let r=Je.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 i=parseInt(t[1],10)*s;return Je.set(e,i),i}n(V,"parseExpiry");var L={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 We(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();}n(We,"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}n(D,"getDatabase");async function Y(e,r=12){return Ye.hash(e,r)}n(Y,"hashPassword");async function q(e,r){return Ye.compare(e,r)}n(q,"verifyPassword");var qe=new Map,Qe=new Map,Br=3600*1e3;function rr(e){let r=qe.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),i=s.export({format:"jwk"}),f=createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),c={...i,use:"sig",kid:f};r={kid:f,publicKey:s,jwk:c},qe.set(e,r);}return r}n(rr,"getOrBuildKey");function tr(e){let{jwk:r}=rr(e);return {keys:[r]}}n(tr,"buildJwks");function sr(e){return rr(e).publicKey}n(sr,"getPublicKeyFromPrivate");async function nr(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 l("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new l("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let o=i.keys[0],f=createPublicKey({key:o,format:"jwk"});return Qe.set(e,{publicKey:f,fetchedAt:r}),f}n(nr,"fetchPublicKey");var ir=new Map,or=new Map,ar=new Map;function dr(e){return e.startsWith("RS")||e.startsWith("PS")}n(dr,"isRSA");function ur(e){let r=ir.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},ir.set(e,r)),r}n(ur,"getHsSecrets");function zr(e){let r=or.get(e);return r||(r=createPrivateKey(e),or.set(e,r)),r}n(zr,"getRsPrivateKey");function cr(e){let r=k(e);if(dr(r.algorithm)){let i=zr(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ur(e.secret);return {accessKey:t,refreshKey:s}}n(cr,"getSigningKeys");function lr(e,r){let t=k(e);if(dr(t.algorithm))return sr(e.secret);let{access:s,refresh:i}=ur(e.secret);return r==="access"?s:i}n(lr,"getVerifyKey");function fr(e,r,t,s){let i=`${t}:${s}`,o=ar.get(i);return o||(o={expiresIn:t,algorithm:s},ar.set(i,o)),Q.sign(e,r,o)}n(fr,"sign");function hr(e,r,t){try{let s=Q.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new l("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof l?s:s instanceof Q.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(hr,"verify");function ee(e,r){let t=k(r),{accessKey:s}=cr(r);return fr(e,s,t.accessExpiresIn,t.algorithm)}n(ee,"signAccessToken");function re(e,r){let t=k(r),{refreshKey:s}=cr(r);return fr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(re,"signRefreshToken");function fe(e,r){let t=k(r),s=lr(r,"access");return hr(e,s,t.algorithm)}n(fe,"verifyAccessToken");function te(e,r){let t=k(r),s=lr(r,"refresh");return hr(e,s,t.algorithm)}n(te,"verifyRefreshToken");function mr(e,r){try{let t=Q.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new l("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof l?t:t instanceof Q.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(mr,"verifyTokenWithPublicKey");function Ue(e){try{return JSON.parse(e)}catch{return []}}n(Ue,"parseRoles");function Jr(e){return JSON.stringify(e)}n(Jr,"serializeRoles");function wr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(wr,"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:Ue(t.roles)}:null}n(se,"findUserByIdentifierValue");async function he(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:Ue(t.roles)}:null}n(he,"findUserById");async function yr(e,r,t){let s=t.map(i=>({id:randomUUID(),user_id:r,type:i.type,value:i.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(i=>({id:i.id,userId:i.user_id,type:i.type,value:i.value,createdAt:new Date}))}n(yr,"createIdentifiers");async function ne(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(ne,"findIdentifiersByUserId");async function Le(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?wr(s):null}n(Le,"findIdentifierById");async function Ir(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(Ir,"countIdentifiersByUserId");async function Rr(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(Rr,"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 vr(e,r,t){await e.updateTable("sentri_users").set({roles:Jr(t)}).where("id","=",r).execute();}n(vr,"updateUserRoles");async function Fe(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}}n(Fe,"createSession");async function me(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:Ue(t.roles)}}:null}n(me,"findSessionById");async function pe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(pe,"deleteSession");async function Ar(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(Ar,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function kr(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(kr,"findSessionsByUserId");async function ye(e,r,t){let s=k(r),i=D(r.dialect),o=e.roles??[],f=o.filter(A=>!s.validRolesSet.has(A));if(f.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${f.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let c=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),a=c.filter(A=>!s.validIdentifiersSet.has(A.type));if(a.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${a.map(A=>A.type).join(", ")}`)};if(new Set(c.map(A=>A.value)).size!==c.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of c)if(await se(i,A.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let w=await Y(e.password,s.saltRounds),{userId:C,identifierRows:T}=await i.transaction().execute(async A=>{let P=randomUUID();await A.insertInto("sentri_users").values({id:P,password_hash:w,roles:JSON.stringify(o)}).execute();let $=c.map(ce=>({id:randomUUID(),user_id:P,type:ce.type,value:ce.value}));return await A.insertInto("sentri_identifiers").values($).execute(),{userId:P,identifierRows:$}}),S={success:true,user:{id:C,roles:o,identifiers:T.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(S.user),S}n(ye,"register");async function Ie(e,r,t){let s=k(r),i=D(r.dialect),o=await se(i,e.identifier.trim());if(!o){let T=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}if(!await q(e.password,o.passwordHash)){let T=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}let c=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:o.id,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),u={id:o.id,roles:o.roles},w=ee({id:o.id,roles:o.roles,sessionId:a.id},r),C=re(a.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(u,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:w,refreshToken:C,user:u}}n(Ie,"login");async function B(e,r,t){let s=k(r),i=D(r.dialect),o;try{({sessionId:o}=te(e,r));}catch(T){return T instanceof l?{success:false,error:T}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let f=await me(i,o);if(!f)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(f.replacedBy)return await we(i,f.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(f.expiresAt.getTime()<Date.now())return await pe(i,o),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let c=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:f.userId,expiresAt:c,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await Ar(i,o,a.id);let u={id:f.user.id,roles:f.user.roles},w=ee({...u,sessionId:a.id},r),C=re(a.id,r);return {success:true,accessToken:w,refreshToken:C,user:u}}n(B,"refresh");async function Re(e,r){let t=D(r.dialect),s;try{({sessionId:s}=te(e,r));}catch{return}let i=await me(t,s);i&&(await pe(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(Re,"logout");async function _e(e,r){let t=D(r.dialect);await we(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 he(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await ne(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(o=>({id:o.id,type:o.type,value:o.value}))}}}n(ge,"getUser");async function ve(e,r,t,s){let i=k(s),o=D(s.dialect),f=await he(o,e);if(!f)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await q(r,f.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let a=await Y(t,i.saltRounds);return await gr(o,e,a),await we(o,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(ve,"changePassword");async function Ae(e,r,t){let s=k(t),i=D(t.dialect),o=r.filter(u=>!s.validRolesSet.has(u));if(o.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${o.join(", ")}`)};let f=await he(i,e);if(!f)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let c=new Set(f.roles);for(let u of r)c.add(u);let a=Array.from(c);return await vr(i,e,a),{success:true,user:{id:f.id,roles:a}}}n(Ae,"assignRoles");async function ke(e,r,t){let s=k(t),i=D(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(u=>({type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o)if(await se(i,u.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)};return await yr(i,e,o),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(ke,"bulkCreateIdentifiers");async function Ee(e,r,t){let s=k(t),i=D(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let o=r.map(u=>({id:u.id,type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o){let w=await Le(i,u.id,e);if(!w)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${u.id}`)};if(w.value!==u.value&&await se(i,u.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)}}return await i.transaction().execute(async u=>{for(let w of o)await Rr(u,w.id,e,{type:w.type,value:w.value});}),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(Ee,"bulkUpdateIdentifiers");async function Te(e,r,t){let s=D(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let c of i)if(!await Le(s,c,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c}`)};return await Ir(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await _r(s,e,i),{success:true,identifiers:(await ne(s,e)).map(c=>({id:c.id,type:c.type,value:c.value}))})}n(Te,"bulkDeleteIdentifiers");async function Tr(e,r){let t=D(r.dialect);return (await kr(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(Tr,"getSessions");async function xr(e,r,t){let s=D(t.dialect),i=await me(s,r);i&&i.userId===e&&await pe(s,r);}n(xr,"revokeSession");function K(e){return k(e).cookieName}n(K,"getCookieName");function xe(e){return k(e).accessCookieName}n(xe,"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 i=e.indexOf(";",s),o=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,o);s=o+1;}}n(H,"readCookie");function ie(e,r,t){let s=t.cookie??{},i=V(k(t).refreshExpiresIn);e.cookie(K(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(ie,"setCookieFromConfig");function Ne(e,r){let t=r.cookie??{};e.clearCookie(K(r),{path:t.path??"/"});}n(Ne,"clearCookieFromConfig");function oe(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=V(k(t).accessExpiresIn);e.cookie(xe(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(oe,"setAccessCookieFromConfig");function Pe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(xe(r),{path:t.path??"/"});}n(Pe,"clearAccessCookieFromConfig");function De(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):H(e.headers.cookie,xe(r))}n(De,"getCurrentAccessToken");function O(e){let r=e.logger??L,t=e.loggerService??"sentri";return e.mode==="client"?Gr(e.keyUri,r,t):Wr(e,r,t)}n(O,"protect");function Gr(e,r,t){return async(s,i,o)=>{let f=s.headers.authorization,c=f?.startsWith("Bearer ")?f.slice(7):void 0,a=s.requestId;if(!c)return r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...a!==void 0&&{requestId:a}})),o(new l("UNAUTHORIZED","Missing or malformed Authorization header"));try{let u=await nr(e),w=mr(c,u);s.user={id:w.id,roles:w.roles},r.info(p(t,"auth.protect.success",{mode:"client",userId:w.id,...a!==void 0&&{requestId:a}})),o();}catch(u){let w=u instanceof l?u.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:w,...a!==void 0&&{requestId:a}})),o(u);}}}n(Gr,"protectClient");function Wr(e,r,t){return async(s,i,o)=>{let f=De(s,e),c=s.requestId;if(!f)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Missing or malformed Authorization header"));try{let a=fe(f,e);if(e.isTokenRevoked&&await e.isTokenRevoked(a.sessionId))return r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:a.id,...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Token has been revoked"));s.user={id:a.id,roles:a.roles},r.info(p(t,"auth.protect.success",{mode:"server",userId:a.id,...c!==void 0&&{requestId:c}})),o();}catch(a){if(a instanceof l&&a.code==="TOKEN_EXPIRED"){let u=H(s.headers.cookie,K(e));if(!u)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Token expired. Please login again."));try{let w=await B(u,e);if(!w.success)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:w.error.code,...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Session expired. Please login again."));ie(i,w.refreshToken,e),oe(i,w.accessToken,e),i.setHeader("X-New-Access-Token",w.accessToken),s.user=w.user,r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:w.user.id,...c!==void 0&&{requestId:c}})),o();}catch{r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...c!==void 0&&{requestId:c}})),o(new l("UNAUTHORIZED","Session expired. Please login again."));}}else {let u=a instanceof l?a.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...c!==void 0&&{requestId:c}})),o(a);}}}}n(Wr,"protectServer");function Nr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return (i,o,f)=>{let c=i.requestId;if(!i.user)return r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...c!==void 0&&{requestId:c}})),f(new l("UNAUTHORIZED","Not authenticated"));let a=i.user.roles;if(!e.some(u=>a.includes(u)))return r.warn(p(t,"auth.authorize.denied",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...c!==void 0&&{requestId:c}})),f(new l("FORBIDDEN",s));r.info(p(t,"auth.authorize.passed",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...c!==void 0&&{requestId:c}})),f();}}n(Nr,"createAuthorizeHandler");function ae(e,r){return n(function(...s){return Nr(s,e,r)},"authorize")}n(ae,"createAuthorize");function Zr(...e){return Nr(e,L,"sentri")}n(Zr,"authorize");var Yr=new l("FORBIDDEN","You do not have permission to perform this action");function Dr(e,r,t){return async(s,i,o)=>{let f=s.requestId;if(!s.user)return r.warn(p(t,"auth.permit.unauthenticated",{...f!==void 0&&{requestId:f}})),o(new l("UNAUTHORIZED","Not authenticated"));let c=s.user.id;if(e.roles&&e.roles.length>0){let a=s.user.roles;if(e.roles.some(u=>a.includes(u)))return r.info(p(t,"auth.permit.role_bypass",{userId:c,bypassedByRole:true,...f!==void 0&&{requestId:f}})),o()}try{let a=e.check(s);(a instanceof Promise?await a:a)?(r.info(p(t,"auth.permit.passed",{userId:c,...f!==void 0&&{requestId:f}})),o()):(r.warn(p(t,"auth.permit.denied",{userId:c,...f!==void 0&&{requestId:f}})),o(Yr));}catch(a){o(a);}}}n(Dr,"createPermitHandler");function de(e,r){return n(function(s){return Dr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(de,"createPermit");function qr(e){return Dr(typeof e=="function"?{check:e}:e,L,"sentri")}n(qr,"permit");function ue(e){return (r,t,s,i)=>{if(r instanceof l){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(ue,"createErrorHandler");var Sr=new Map;function Cr(e){let r=Sr.get(e);return r||(r=new Redis(e,{lazyConnect:true,enableOfflineQueue:false}),r.on("error",t=>{console.error("Sentri Redis Client Error:",t.message);}),Sr.set(e,r)),r}n(Cr,"getRedisClient");function Or(e){let r=e?.ttl??3e5,t=(e?.header??"X-Idempotency-Key").toLowerCase(),s=new Set((e?.methods??["POST","PUT","PATCH"]).map(o=>o.toUpperCase())),i=e?.redisUrl;return i?et(i,r,t,s):rt(r,t,s,e?.maxSize??1e4)}n(Or,"createIdempotencyMiddleware");function et(e,r,t,s){let i=Cr(e),o="sentri:idempotency:";return async(f,c,a)=>{let u=f.headers[t];if(!u||typeof u!="string"||!s.has(f.method))return a();f.requestId=u,c.setHeader("X-Request-Id",u);let w=await i.get(`${o}${u}`);if(w){let T=JSON.parse(w);return c.setHeader("X-Idempotent-Replayed","true"),c.status(T.statusCode).json(T.body)}let C=c.json.bind(c);c.json=n(function(S){if(c.statusCode>=200&&c.statusCode<300){let A={statusCode:c.statusCode,body:S,expiresAt:Date.now()+r};i.set(`${o}${u}`,JSON.stringify(A),"PX",r).catch(()=>{});}return C(S)},"idempotentJson"),a();}}n(et,"buildRedisMiddleware");function rt(e,r,t,s){let i=Math.max(e,5e3),o=new Map,f=setInterval(()=>{let c=Date.now();for(let[a,u]of o)u.expiresAt<=c&&o.delete(a);},i);return typeof f=="object"&&f!==null&&"unref"in f&&f.unref(),(c,a,u)=>{let w=c.headers[r];if(!w||typeof w!="string"||!t.has(c.method))return u();c.requestId=w,a.setHeader("X-Request-Id",w);let C=Date.now(),T=o.get(w);if(T&&T.expiresAt>C)return a.setHeader("X-Idempotent-Replayed","true"),a.status(T.statusCode).json(T.body);let S=a.json.bind(a);a.json=n(function(P){if(a.statusCode>=200&&a.statusCode<300){if(o.size>=s){let $=o.keys().next().value;$!==void 0&&o.delete($);}o.set(w,{statusCode:a.statusCode,body:P,expiresAt:Date.now()+e});}return S(P)},"idempotentJson"),u();}}n(rt,"buildMemoryMiddleware");var Ke=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 i=Date.now(),o=this.store.get(r);if((!o||o.expiresAt<i)&&(o={count:0,expiresAt:i+s},this.store.set(r,o)),o.count>t){let f=Math.ceil((o.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${f} seconds.`)}o.count++;}},$e=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let i=`sentri:rl:${r}`,o=Math.ceil(s/1e3);try{let f=await this.redis.multi().incr(i).expire(i,o,"NX").exec();if(!f||f.length===0)return;if(f[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(f){if(f instanceof Error&&f.message.includes("Rate limit exceeded"))throw f;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:f});}}},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 $e(s,r??L),X}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return X=new Ke,X}n(br,"getRateLimiter");var Se=8,z=72,Ce=255,Ur=100,J=50;function g(e){return new l("VALIDATION_ERROR",e)}n(g,"badRequest");function b(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}n(b,"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 l("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 l("UNAUTHORIZED","Invalid or missing API key")}n(st,"validateApiKey");function He(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(He,"fireHook");function nt(e){return e.startsWith("RS")||e.startsWith("PS")}n(nt,"isRSA");function Me(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw g(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw g(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Ur)throw g(`identifiers[${r}].type must not exceed ${Ur} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw g(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Ce)throw g(`identifiers[${r}].value must not exceed ${Ce} characters`);return {type:t.type,value:t.value}}n(Me,"validateIdentifierInput");function E(e){return e.requestId!==void 0?{requestId:e.requestId}:{}}n(E,"reqId");function Lr(e){let r=Router(),t=e,s=k(t),i=e.logger??L,o=e.loggerService??"sentri",f=ae(i,o),c=de(i,o),a=br(e.redisUrl,i),u=e.router?.register??(d=>ye(d,t)),w=e.router?.login??(d=>Ie(d,t)),C=e.router?.refresh??(d=>B(d,t)),T=e.router?.logout??(d=>d!==void 0?Re(d,t):Promise.resolve()),S=e.router?.logoutAll??(d=>_e(d,t)),A=e.router?.getUser??(d=>ge(d,t)),P=e.router?.assignRoles??((d,m)=>Ae(d,m,t)),$=e.router?.changePassword??((d,m,v)=>ve(d,m,v,t)),ce=e.router?.bulkCreateIdentifiers??((d,m)=>ke(d,m,t)),Fr=e.router?.bulkUpdateIdentifiers??((d,m)=>Ee(d,m,t)),Pr=e.router?.bulkDeleteIdentifiers??((d,m)=>Te(d,m,t));nt(s.algorithm)&&r.get("/keys",(d,m)=>{m.setHeader("Cache-Control","public, max-age=3600"),m.json(tr(e.secret));}),r.post("/register",async(d,m,v)=>{let R=Date.now();try{st(d,e);let h=M(d.body),{identifiers:I,password:_,roles:y}=h;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let x=I.map((Z,Oe)=>Me(Z,Oe));if(typeof _!="string"||_.length<Se)throw g(`password is required and must be at least ${Se} characters`);if(_.length>z)throw g(`password must not exceed ${z} characters`);if(y!==void 0&&!Array.isArray(y))throw g("roles must be an array of strings when provided");if(Array.isArray(y)&&!y.every(Z=>typeof Z=="string"))throw g("each role must be a string");let U=Array.isArray(y)?y:void 0,N=U!==void 0?{identifiers:x,password:_,roles:U}:{identifiers:x,password:_},j=d.ip||"127.0.0.1",G=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let Z=await a,Oe=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await Z.consume(`register:${j}`,Oe,900*1e3);}let W=await u(N,{ip:j,userAgent:G});if(!W.success){i.warn(p(o,"auth.register.failure",{errorCode:W.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,W.error);return}i.info(p(o,"auth.register.success",{userId:W.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,201,"User registered successfully",{user:W.user});}catch(h){v(h);}}),r.post("/login",async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{identifier:I,password:_}=h;if(typeof I!="string"||I.trim().length===0)throw g("identifier is required and must be a non-empty string");if(I.length>Ce)throw g(`identifier must not exceed ${Ce} characters`);if(typeof _!="string"||_.length===0)throw g("password is required");if(_.length>z)throw g(`password must not exceed ${z} characters`);let y=I.trim(),x=d.ip||"127.0.0.1",U=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let j=await a,G=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await j.consume(`login:${x}`,G,900*1e3);}let N=await w({identifier:y,password:_},{ip:x,userAgent:U});if(!N.success){He(()=>e.hooks?.onLoginFailed?.(y,N.error,{ip:x})),i.warn(p(o,"auth.login.failure",{errorCode:N.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,N.error);return}He(()=>e.hooks?.onLoginSuccess?.(N.user,{ip:x,userAgent:U})),ie(m,N.refreshToken,e),oe(m,N.accessToken,e),i.info(p(o,"auth.login.success",{userId:N.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Login successful",{accessToken:N.accessToken,user:N.user});}catch(h){v(h);}}),r.post("/refresh",async(d,m,v)=>{let R=Date.now();try{let h=H(d.headers.cookie,K(e));if(!h)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let I=d.ip||"127.0.0.1",_=d.get("user-agent")||"Unknown",y=await C(h,{ip:I,userAgent:_});if(!y.success){Ne(m,e),i.warn(p(o,"auth.refresh.failure",{errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}ie(m,y.refreshToken,e),oe(m,y.accessToken,e),i.info(p(o,"auth.refresh.success",{userId:y.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Token refreshed",{accessToken:y.accessToken});}catch(h){v(h);}}),r.post("/logout",async(d,m,v)=>{let R=Date.now();try{let h=H(d.headers.cookie,K(e));await T(h),Ne(m,e),Pe(m,e),i.info(p(o,"auth.logout",{duration_ms:Date.now()-R,...E(d)})),b(m,200,"Logged out",null);}catch(h){v(h);}}),r.post("/logout-all",O(e),async(d,m,v)=>{let R=Date.now();try{let h=d.user.id;await S(h),He(()=>e.hooks?.onLogout?.(h)),Ne(m,e),Pe(m,e),i.info(p(o,"auth.logout_all",{userId:h,duration_ms:Date.now()-R,...E(d)})),b(m,200,"All sessions revoked",null);}catch(h){v(h);}}),r.get("/me",O(e),async(d,m,v)=>{let R=Date.now();try{let h=await A(d.user.id);if(!h.success){i.warn(p(o,"auth.me.failure",{userId:d.user.id,errorCode:h.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,h.error);return}i.info(p(o,"auth.me.success",{userId:h.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"OK",h.user);}catch(h){v(h);}}),r.get("/me/identifiers",O(e),async(d,m,v)=>{let R=Date.now();try{let h=await A(d.user.id);if(!h.success){i.warn(p(o,"auth.me.identifiers.failure",{userId:d.user.id,errorCode:h.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,h.error);return}i.info(p(o,"auth.me.identifiers.success",{userId:h.user.id,count:h.user.identifiers?.length??0,duration_ms:Date.now()-R,...E(d)})),b(m,200,"OK",{identifiers:h.user.identifiers??[]});}catch(h){v(h);}});let le=c(d=>!!d.user);return r.post("/me/identifiers",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{identifiers:I}=h;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((x,U)=>Me(x,U)),y=await ce(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.create_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}i.info(p(o,"auth.identifiers.created",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...E(d)})),b(m,201,"Identifiers added successfully",{identifiers:y.identifiers});}catch(h){v(h);}}),r.put("/me/identifiers",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{identifiers:I}=h;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((x,U)=>{if(typeof x!="object"||x===null||Array.isArray(x))throw g(`identifiers[${U}] must be an object`);let N=x;if(typeof N.id!="string"||N.id.trim().length===0)throw g(`identifiers[${U}].id is required and must be a non-empty string`);let{type:j,value:G}=Me(x,U);return {id:N.id,type:j,value:G}}),y=await Fr(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.update_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}i.info(p(o,"auth.identifiers.updated",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Identifiers updated successfully",{identifiers:y.identifiers});}catch(h){v(h);}}),r.delete("/me/identifiers",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{ids:I}=h;if(!Array.isArray(I)||I.length===0)throw g("ids is required and must be a non-empty array of strings");if(!I.every(y=>typeof y=="string"))throw g("each id must be a string");let _=await Pr(d.user.id,I);if(!_.success){i.warn(p(o,"auth.identifiers.delete_failure",{userId:d.user.id,errorCode:_.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,_.error);return}i.info(p(o,"auth.identifiers.deleted",{userId:d.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Identifiers deleted successfully",{identifiers:_.identifiers});}catch(h){v(h);}}),r.patch("/me/password",O(e),le,async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{currentPassword:I,newPassword:_}=h;if(typeof I!="string"||I.length===0)throw g("currentPassword is required");if(typeof _!="string"||_.length<Se)throw g(`newPassword must be at least ${Se} characters`);if(_.length>z)throw g(`newPassword must not exceed ${z} characters`);if(I===_)throw g("newPassword must be different from currentPassword");let y=await $(d.user.id,I,_);if(!y.success){i.warn(p(o,"auth.password.change_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,y.error);return}i.info(p(o,"auth.password.changed",{userId:d.user.id,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Password updated successfully. All sessions have been revoked.",null);}catch(h){v(h);}}),r.post("/users/:userId/roles",O(e),f("admin"),async(d,m,v)=>{let R=Date.now();try{let h=M(d.body),{roles:I}=h,_=d.params.userId,y=typeof _=="string"?_:void 0;if(!y)throw g("userId is required");if(!Array.isArray(I)||I.length===0)throw g("roles must be a non-empty array of strings");if(!I.every(U=>typeof U=="string"))throw g("each role must be a string");let x=await P(y,I);if(!x.success){i.warn(p(o,"auth.roles.assign_failure",{targetUserId:y,errorCode:x.error.code,duration_ms:Date.now()-R,...E(d)})),F(m,x.error);return}i.info(p(o,"auth.roles.assigned",{targetUserId:y,roles:I,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Roles assigned successfully",{user:x.user});}catch(h){v(h);}}),r.use(ue()),r.get("/sessions",O(e),async(d,m,v)=>{let R=Date.now();try{let h=d.user.id,_=await(e.router?.getSessions??(y=>Tr(y,t)))(h);i.info(p(o,"auth.sessions.get",{userId:h,duration_ms:Date.now()-R,...E(d)})),b(m,200,"OK",{sessions:_});}catch(h){v(h);}}),r.delete("/sessions/:id",O(e),async(d,m,v)=>{let R=Date.now();try{let h=d.user.id,I=d.params.id;await(e.router?.revokeSession??((y,x)=>xr(y,x,t)))(h,I),i.info(p(o,"auth.sessions.revoke",{userId:h,sessionId:I,duration_ms:Date.now()-R,...E(d)})),b(m,200,"Session revoked",null);}catch(h){v(h);}}),r}n(Lr,"createAuthRouter");function tn(e){if(e.mode==="client"){let a={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}};Ge(a);let u=a.logger??L,w=a.loggerService??"sentri",C=ae(u,w),T=de(u,w);return {protect:n(()=>O(a),"protect"),authorize:n((...S)=>C(...S),"authorize"),permit:n(S=>T(S),"permit"),errorHandler:n(S=>ue(S),"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.validIdentifiers!==void 0&&{validIdentifiers:e.validIdentifiers},...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??L,i=t.loggerService??"sentri",o=k(t),f=ae(s,i),c=de(s,i);return {protect:n(()=>O(t),"protect"),authorize:n((...a)=>f(...a),"authorize"),permit:n(a=>c(a),"permit"),errorHandler:n(a=>ue(a),"errorHandler"),hashPassword:n(a=>Y(a,o.saltRounds),"hashPassword"),verifyPassword:n((a,u)=>q(a,u),"verifyPassword"),signAccessToken:n(a=>ee(a,t),"signAccessToken"),signRefreshToken:n(a=>re(a,t),"signRefreshToken"),verifyAccessToken:n(a=>fe(a,t),"verifyAccessToken"),verifyRefreshToken:n(a=>te(a,t),"verifyRefreshToken"),getCurrentAccessToken:n(a=>De(a,t),"getCurrentAccessToken"),router:n(()=>Lr(t),"router"),migrate:n(()=>We(D(t.dialect)),"migrate"),idempotencyMiddleware:n(a=>Or({...a,...t.redisUrl!==void 0&&{redisUrl:t.redisUrl}}),"idempotencyMiddleware"),register:n(a=>ye(a,t),"register"),login:n(a=>Ie(a,t),"login"),refresh:n(a=>B(a,t),"refresh"),logout:n(a=>Re(a,t),"logout"),logoutAll:n(a=>_e(a,t),"logoutAll"),getUser:n(a=>ge(a,t),"getUser"),changePassword:n((a,u,w)=>ve(a,u,w,t),"changePassword"),assignRoles:n((a,u)=>Ae(a,u,t),"assignRoles"),bulkCreateIdentifiers:n((a,u)=>ke(a,u,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((a,u)=>Ee(a,u,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((a,u)=>Te(a,u,t),"bulkDeleteIdentifiers")}}n(tn,"createAuthExpress");export{je as SENTRI_ERROR_STATUS,l as SentriError,Zr as authorize,tn as createAuthExpress,Lr as createAuthRouter,ae as createAuthorize,ue as createErrorHandler,Or as createIdempotencyMiddleware,de as createPermit,De as getCurrentAccessToken,qr as permit,O as protect};
1
+ import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import Ye from'bcrypt';import Q from'jsonwebtoken';import {Redis}from'ioredis';import {Router}from'express';var Kr=Object.defineProperty;var n=(e,r)=>Kr(e,"name",{value:r,configurable:true});var je=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,RATE_LIMIT_EXCEEDED:429}),c=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??je[r]??500;}};var Ve=new WeakMap,Xe=32,Be=10,ze=31;function Ge(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new c("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new c("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Xe)throw new c("CONFIGURATION_ERROR",`secret must be at least ${Xe} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Be||s>ze)throw new c("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Be} and ${ze}`);if(!e.validRoles||e.validRoles.length===0)throw new c("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new c("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new c("CONFIGURATION_ERROR","dialect is required in server mode")}n(Ge,"validateConfig");function E(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),validIdentifiers:e.validIdentifiers??["email","username"],validIdentifiersSet:new Set(e.validIdentifiers??["email","username"]),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}n(E,"resolveServerConfig");var $r=/^(\d+)([smhdw])$/,Hr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Je=new Map;function V(e){if(typeof e=="number")return e*1e3;let r=Je.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 i=parseInt(t[1],10)*s;return Je.set(e,i),i}n(V,"parseExpiry");var L={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 We(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();}n(We,"runMigrations");var Ze=new Map;function N(e){let r=Ze.get(e);return r||(r=new Kysely({dialect:e}),Ze.set(e,r)),r}n(N,"getDatabase");async function Y(e,r=12){return Ye.hash(e,r)}n(Y,"hashPassword");async function q(e,r){return Ye.compare(e,r)}n(q,"verifyPassword");var qe=new Map,Qe=new Map,Xr=3600*1e3;function rr(e){let r=qe.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),i=s.export({format:"jwk"}),f=createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),l={...i,use:"sig",kid:f};r={kid:f,publicKey:s,jwk:l},qe.set(e,r);}return r}n(rr,"getOrBuildKey");function tr(e){let{jwk:r}=rr(e);return {keys:[r]}}n(tr,"buildJwks");function sr(e){return rr(e).publicKey}n(sr,"getPublicKeyFromPrivate");async function nr(e){let r=Date.now(),t=Qe.get(e);if(t&&r-t.fetchedAt<Xr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new c("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new c("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let o=i.keys[0],f=createPublicKey({key:o,format:"jwk"});return Qe.set(e,{publicKey:f,fetchedAt:r}),f}n(nr,"fetchPublicKey");var ir=new Map,or=new Map,ar=new Map;function dr(e){return e.startsWith("RS")||e.startsWith("PS")}n(dr,"isRSA");function ur(e){let r=ir.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},ir.set(e,r)),r}n(ur,"getHsSecrets");function zr(e){let r=or.get(e);return r||(r=createPrivateKey(e),or.set(e,r)),r}n(zr,"getRsPrivateKey");function cr(e){let r=E(e);if(dr(r.algorithm)){let i=zr(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ur(e.secret);return {accessKey:t,refreshKey:s}}n(cr,"getSigningKeys");function lr(e,r){let t=E(e);if(dr(t.algorithm))return sr(e.secret);let{access:s,refresh:i}=ur(e.secret);return r==="access"?s:i}n(lr,"getVerifyKey");function fr(e,r,t,s){let i=`${t}:${s}`,o=ar.get(i);return o||(o={expiresIn:t,algorithm:s},ar.set(i,o)),Q.sign(e,r,o)}n(fr,"sign");function mr(e,r,t){try{let s=Q.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new c("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof c?s:s instanceof Q.TokenExpiredError?new c("TOKEN_EXPIRED","Token has expired"):new c("TOKEN_INVALID","Token is invalid or malformed")}}n(mr,"verify");function ee(e,r){let t=E(r),{accessKey:s}=cr(r);return fr(e,s,t.accessExpiresIn,t.algorithm)}n(ee,"signAccessToken");function re(e,r){let t=E(r),{refreshKey:s}=cr(r);return fr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(re,"signRefreshToken");function fe(e,r){let t=E(r),s=lr(r,"access");return mr(e,s,t.algorithm)}n(fe,"verifyAccessToken");function te(e,r){let t=E(r),s=lr(r,"refresh");return mr(e,s,t.algorithm)}n(te,"verifyRefreshToken");function hr(e,r){try{let t=Q.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new c("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof c?t:t instanceof Q.TokenExpiredError?new c("TOKEN_EXPIRED","Token has expired"):new c("TOKEN_INVALID","Token is invalid or malformed")}}n(hr,"verifyTokenWithPublicKey");function Ue(e){try{return JSON.parse(e)}catch{return []}}n(Ue,"parseRoles");function Jr(e){return JSON.stringify(e)}n(Jr,"serializeRoles");function wr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(wr,"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:Ue(t.roles)}:null}n(se,"findUserByIdentifierValue");async function me(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:Ue(t.roles)}:null}n(me,"findUserById");async function yr(e,r,t){let s=t.map(i=>({id:randomUUID(),user_id:r,type:i.type,value:i.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(i=>({id:i.id,userId:i.user_id,type:i.type,value:i.value,createdAt:new Date}))}n(yr,"createIdentifiers");async function ne(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(ne,"findIdentifiersByUserId");async function Le(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?wr(s):null}n(Le,"findIdentifierById");async function Ir(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(Ir,"countIdentifiersByUserId");async function Rr(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(Rr,"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 vr(e,r,t){await e.updateTable("sentri_users").set({roles:Jr(t)}).where("id","=",r).execute();}n(vr,"updateUserRoles");async function Fe(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}}n(Fe,"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:Ue(t.roles)}}:null}n(he,"findSessionById");async function pe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(pe,"deleteSession");async function Ar(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(Ar,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function Er(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(Er,"findSessionsByUserId");async function ye(e,r,t){let s=E(r),i=N(r.dialect),o=e.roles??[],f=o.filter(A=>!s.validRolesSet.has(A));if(f.length>0)return {success:false,error:new c("INVALID_ROLE",`Invalid roles: ${f.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one identifier is required")};let l=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),a=l.filter(A=>!s.validIdentifiersSet.has(A.type));if(a.length>0)return {success:false,error:new c("VALIDATION_ERROR",`Invalid identifier types: ${a.map(A=>A.type).join(", ")}`)};if(new Set(l.map(A=>A.value)).size!==l.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of l)if(await se(i,A.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let w=await Y(e.password,s.saltRounds),{userId:S,identifierRows:T}=await i.transaction().execute(async A=>{let P=randomUUID();await A.insertInto("sentri_users").values({id:P,password_hash:w,roles:JSON.stringify(o)}).execute();let $=l.map(ce=>({id:randomUUID(),user_id:P,type:ce.type,value:ce.value}));return await A.insertInto("sentri_identifiers").values($).execute(),{userId:P,identifierRows:$}}),C={success:true,user:{id:S,roles:o,identifiers:T.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(C.user),C}n(ye,"register");async function Ie(e,r,t){let s=E(r),i=N(r.dialect),o=await se(i,e.identifier.trim());if(!o){let T=new c("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}if(!await q(e.password,o.passwordHash)){let T=new c("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,T,{ip:t?.ip??""}),{success:false,error:T}}let l=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:o.id,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),u={id:o.id,roles:o.roles},w=ee({id:o.id,roles:o.roles,sessionId:a.id},r),S=re(a.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(u,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:w,refreshToken:S,user:u}}n(Ie,"login");async function X(e,r,t){let s=E(r),i=N(r.dialect),o;try{({sessionId:o}=te(e,r));}catch(T){return T instanceof c?{success:false,error:T}:{success:false,error:new c("TOKEN_INVALID","Invalid refresh token")}}let f=await he(i,o);if(!f)return {success:false,error:new c("UNAUTHORIZED","Session not found or revoked")};if(f.replacedBy)return await we(i,f.userId),{success:false,error:new c("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(f.expiresAt.getTime()<Date.now())return await pe(i,o),{success:false,error:new c("TOKEN_EXPIRED","Session has expired")};let l=new Date(Date.now()+V(s.refreshExpiresIn)),a=await Fe(i,{userId:f.userId,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await Ar(i,o,a.id);let u={id:f.user.id,roles:f.user.roles},w=ee({...u,sessionId:a.id},r),S=re(a.id,r);return {success:true,accessToken:w,refreshToken:S,user:u}}n(X,"refresh");async function Re(e,r){let t=N(r.dialect),s;try{({sessionId:s}=te(e,r));}catch{return}let i=await he(t,s);i&&(await pe(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(Re,"logout");async function _e(e,r){let t=N(r.dialect);await we(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(_e,"logoutAll");async function ge(e,r){let t=N(r.dialect),s=await me(t,e);if(!s)return {success:false,error:new c("USER_NOT_FOUND","User not found")};let i=await ne(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(o=>({id:o.id,type:o.type,value:o.value}))}}}n(ge,"getUser");async function ve(e,r,t,s){let i=E(s),o=N(s.dialect),f=await me(o,e);if(!f)return {success:false,error:new c("USER_NOT_FOUND","User not found")};if(!await q(r,f.passwordHash))return {success:false,error:new c("INVALID_CREDENTIALS","Invalid credentials")};let a=await Y(t,i.saltRounds);return await gr(o,e,a),await we(o,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(ve,"changePassword");async function Ae(e,r,t){let s=E(t),i=N(t.dialect),o=r.filter(u=>!s.validRolesSet.has(u));if(o.length>0)return {success:false,error:new c("INVALID_ROLE",`Invalid roles: ${o.join(", ")}`)};let f=await me(i,e);if(!f)return {success:false,error:new c("USER_NOT_FOUND","User not found")};let l=new Set(f.roles);for(let u of r)l.add(u);let a=Array.from(l);return await vr(i,e,a),{success:true,user:{id:f.id,roles:a}}}n(Ae,"assignRoles");async function Ee(e,r,t){let s=E(t),i=N(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(u=>({type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new c("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o)if(await se(i,u.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)};return await yr(i,e,o),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(Ee,"bulkCreateIdentifiers");async function ke(e,r,t){let s=E(t),i=N(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one update is required")};let o=r.map(u=>({id:u.id,type:u.type.trim(),value:u.value.trim()})),f=o.filter(u=>!s.validIdentifiersSet.has(u.type));if(f.length>0)return {success:false,error:new c("VALIDATION_ERROR",`Invalid identifier types: ${f.map(u=>u.type).join(", ")}`)};if(new Set(o.map(u=>u.value)).size!==o.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let u of o){let w=await Le(i,u.id,e);if(!w)return {success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${u.id}`)};if(w.value!==u.value&&await se(i,u.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${u.value}`)}}return await i.transaction().execute(async u=>{for(let w of o)await Rr(u,w.id,e,{type:w.type,value:w.value});}),{success:true,identifiers:(await ne(i,e)).map(u=>({id:u.id,type:u.type,value:u.value}))}}n(ke,"bulkUpdateIdentifiers");async function Te(e,r,t){let s=N(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let l of i)if(!await Le(s,l,e))return {success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l}`)};return await Ir(s,e)-i.length<1?{success:false,error:new c("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await _r(s,e,i),{success:true,identifiers:(await ne(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))})}n(Te,"bulkDeleteIdentifiers");async function Tr(e,r){let t=N(r.dialect);return (await Er(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(Tr,"getSessions");async function Dr(e,r,t){let s=N(t.dialect),i=await he(s,r);i&&i.userId===e&&await pe(s,r);}n(Dr,"revokeSession");function K(e){return E(e).cookieName}n(K,"getCookieName");function De(e){return E(e).accessCookieName}n(De,"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 i=e.indexOf(";",s),o=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,o);s=o+1;}}n(H,"readCookie");function ie(e,r,t){let s=t.cookie??{},i=V(E(t).refreshExpiresIn);e.cookie(K(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(ie,"setCookieFromConfig");function xe(e,r){let t=r.cookie??{};e.clearCookie(K(r),{path:t.path??"/"});}n(xe,"clearCookieFromConfig");function oe(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=V(E(t).accessExpiresIn);e.cookie(De(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(oe,"setAccessCookieFromConfig");function Pe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(De(r),{path:t.path??"/"});}n(Pe,"clearAccessCookieFromConfig");function Ne(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):H(e.headers.cookie,De(r))}n(Ne,"getCurrentAccessToken");function O(e){let r=e.logger??L,t=e.loggerService??"sentri";return e.mode==="client"?Gr(e.keyUri,r,t):Wr(e,r,t)}n(O,"protect");function Gr(e,r,t){return async(s,i,o)=>{let f=s.headers.authorization,l=f?.startsWith("Bearer ")?f.slice(7):void 0,a=s.requestId;if(!l)return r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...a!==void 0&&{requestId:a}})),o(new c("UNAUTHORIZED","Missing or malformed Authorization header"));try{let u=await nr(e),w=hr(l,u);s.user={id:w.id,roles:w.roles},r.info(p(t,"auth.protect.success",{mode:"client",userId:w.id,...a!==void 0&&{requestId:a}})),o();}catch(u){let w=u instanceof c?u.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:w,...a!==void 0&&{requestId:a}})),o(u);}}}n(Gr,"protectClient");function Wr(e,r,t){return async(s,i,o)=>{let f=Ne(s,e),l=s.requestId;if(!f)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Missing or malformed Authorization header"));try{let a=fe(f,e);if(e.isTokenRevoked&&await e.isTokenRevoked(a.sessionId))return r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:a.id,...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Token has been revoked"));s.user={id:a.id,roles:a.roles},r.info(p(t,"auth.protect.success",{mode:"server",userId:a.id,...l!==void 0&&{requestId:l}})),o();}catch(a){if(a instanceof c&&a.code==="TOKEN_EXPIRED"){let u=H(s.headers.cookie,K(e));if(!u)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Token expired. Please login again."));try{let w=await X(u,e);if(!w.success)return r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:w.error.code,...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Session expired. Please login again."));ie(i,w.refreshToken,e),oe(i,w.accessToken,e),i.setHeader("X-New-Access-Token",w.accessToken),s.user=w.user,r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:w.user.id,...l!==void 0&&{requestId:l}})),o();}catch{r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...l!==void 0&&{requestId:l}})),o(new c("UNAUTHORIZED","Session expired. Please login again."));}}else {let u=a instanceof c?a.code:"TOKEN_INVALID";r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...l!==void 0&&{requestId:l}})),o(a);}}}}n(Wr,"protectServer");function xr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return (i,o,f)=>{let l=i.requestId;if(!i.user)return r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...l!==void 0&&{requestId:l}})),f(new c("UNAUTHORIZED","Not authenticated"));let a=i.user.roles;if(!e.some(u=>a.includes(u)))return r.warn(p(t,"auth.authorize.denied",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...l!==void 0&&{requestId:l}})),f(new c("FORBIDDEN",s));r.info(p(t,"auth.authorize.passed",{userId:i.user.id,userRoles:[...a],requiredRoles:e,...l!==void 0&&{requestId:l}})),f();}}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,L,"sentri")}n(Zr,"authorize");var Yr=new c("FORBIDDEN","You do not have permission to perform this action");function Nr(e,r,t){return async(s,i,o)=>{let f=s.requestId;if(!s.user)return r.warn(p(t,"auth.permit.unauthenticated",{...f!==void 0&&{requestId:f}})),o(new c("UNAUTHORIZED","Not authenticated"));let l=s.user.id;if(e.roles&&e.roles.length>0){let a=s.user.roles;if(e.roles.some(u=>a.includes(u)))return r.info(p(t,"auth.permit.role_bypass",{userId:l,bypassedByRole:true,...f!==void 0&&{requestId:f}})),o()}try{let a=e.check(s);(a instanceof Promise?await a:a)?(r.info(p(t,"auth.permit.passed",{userId:l,...f!==void 0&&{requestId:f}})),o()):(r.warn(p(t,"auth.permit.denied",{userId:l,...f!==void 0&&{requestId:f}})),o(Yr));}catch(a){o(a);}}}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,L,"sentri")}n(qr,"permit");function ue(e){return (r,t,s,i)=>{if(r instanceof c){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(ue,"createErrorHandler");var Cr=new Map;function Sr(e){let r=Cr.get(e);return r||(r=new Redis(e,{lazyConnect:true,enableOfflineQueue:false}),r.on("error",t=>{console.error("Sentri Redis Client Error:",t.message);}),Cr.set(e,r)),r}n(Sr,"getRedisClient");function Or(e){let r=e?.ttl??3e5,t=(e?.header??"X-Idempotency-Key").toLowerCase(),s=new Set((e?.methods??["POST","PUT","PATCH"]).map(o=>o.toUpperCase())),i=e?.redisUrl;return i?et(i,r,t,s):rt(r,t,s,e?.maxSize??1e4)}n(Or,"createIdempotencyMiddleware");function et(e,r,t,s){let i=Sr(e),o="sentri:idempotency:";return async(f,l,a)=>{let u=f.headers[t];if(!u||typeof u!="string"||!s.has(f.method))return a();f.requestId=u,l.setHeader("X-Request-Id",u);let w=await i.get(`${o}${u}`);if(w){let T=JSON.parse(w);return l.setHeader("X-Idempotent-Replayed","true"),l.status(T.statusCode).json(T.body)}let S=l.json.bind(l);l.json=n(function(C){if(l.statusCode>=200&&l.statusCode<300){let A={statusCode:l.statusCode,body:C,expiresAt:Date.now()+r};i.set(`${o}${u}`,JSON.stringify(A),"PX",r).catch(()=>{});}return S(C)},"idempotentJson"),a();}}n(et,"buildRedisMiddleware");function rt(e,r,t,s){let i=Math.max(e,5e3),o=new Map,f=setInterval(()=>{let l=Date.now();for(let[a,u]of o)u.expiresAt<=l&&o.delete(a);},i);return typeof f=="object"&&f!==null&&"unref"in f&&f.unref(),(l,a,u)=>{let w=l.headers[r];if(!w||typeof w!="string"||!t.has(l.method))return u();l.requestId=w,a.setHeader("X-Request-Id",w);let S=Date.now(),T=o.get(w);if(T&&T.expiresAt>S)return a.setHeader("X-Idempotent-Replayed","true"),a.status(T.statusCode).json(T.body);let C=a.json.bind(a);a.json=n(function(P){if(a.statusCode>=200&&a.statusCode<300){if(o.size>=s){let $=o.keys().next().value;$!==void 0&&o.delete($);}o.set(w,{statusCode:a.statusCode,body:P,expiresAt:Date.now()+e});}return C(P)},"idempotentJson"),u();}}n(rt,"buildMemoryMiddleware");var Ke=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 i=Date.now(),o=this.store.get(r);if((!o||o.expiresAt<i)&&(o={count:0,expiresAt:i+s},this.store.set(r,o)),o.count>=t){let f=Math.ceil((o.expiresAt-i)/1e3);throw new c("RATE_LIMIT_EXCEEDED",`Rate limit exceeded. Try again in ${f} seconds.`)}o.count++;}},$e=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let i=`sentri:rl:${r}`,o=Math.ceil(s/1e3);try{let f=await this.redis.multi().incr(i).expire(i,o,"NX").exec();if(!f||f.length===0)return;if(f[0][1]>t)throw new c("RATE_LIMIT_EXCEEDED","Rate limit exceeded. Try again later.")}catch(f){if(f instanceof c&&f.code==="RATE_LIMIT_EXCEEDED")throw f;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:f});}}},B=null;async function br(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 $e(s,r??L),B}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return B=new Ke,B}n(br,"getRateLimiter");var Ce=8,z=72,Se=255,Ur=100,J=50;function g(e){return new c("VALIDATION_ERROR",e)}n(g,"badRequest");function b(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}n(b,"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 c("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 c("UNAUTHORIZED","Invalid or missing API key")}n(st,"validateApiKey");function He(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(He,"fireHook");function nt(e){return e.startsWith("RS")||e.startsWith("PS")}n(nt,"isRSA");function Me(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw g(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw g(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Ur)throw g(`identifiers[${r}].type must not exceed ${Ur} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw g(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Se)throw g(`identifiers[${r}].value must not exceed ${Se} characters`);return {type:t.type,value:t.value}}n(Me,"validateIdentifierInput");function k(e){return e.requestId!==void 0?{requestId:e.requestId}:{}}n(k,"reqId");function Lr(e){let r=Router(),t=e,s=E(t),i=e.logger??L,o=e.loggerService??"sentri",f=ae(i,o),l=de(i,o),a=br(e.redisUrl,i),u=e.router?.register??(d=>ye(d,t)),w=e.router?.login??(d=>Ie(d,t)),S=e.router?.refresh??(d=>X(d,t)),T=e.router?.logout??(d=>d!==void 0?Re(d,t):Promise.resolve()),C=e.router?.logoutAll??(d=>_e(d,t)),A=e.router?.getUser??(d=>ge(d,t)),P=e.router?.assignRoles??((d,h)=>Ae(d,h,t)),$=e.router?.changePassword??((d,h,v)=>ve(d,h,v,t)),ce=e.router?.bulkCreateIdentifiers??((d,h)=>Ee(d,h,t)),Fr=e.router?.bulkUpdateIdentifiers??((d,h)=>ke(d,h,t)),Pr=e.router?.bulkDeleteIdentifiers??((d,h)=>Te(d,h,t));nt(s.algorithm)&&r.get("/keys",(d,h)=>{h.setHeader("Cache-Control","public, max-age=3600"),h.json(tr(e.secret));}),r.post("/register",async(d,h,v)=>{let R=Date.now();try{st(d,e);let m=M(d.body),{identifiers:I,password:_,roles:y}=m;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let D=I.map((Z,Oe)=>Me(Z,Oe));if(typeof _!="string"||_.length<Ce)throw g(`password is required and must be at least ${Ce} characters`);if(_.length>z)throw g(`password must not exceed ${z} characters`);if(y!==void 0&&!Array.isArray(y))throw g("roles must be an array of strings when provided");if(Array.isArray(y)&&!y.every(Z=>typeof Z=="string"))throw g("each role must be a string");let U=Array.isArray(y)?y:void 0,x=U!==void 0?{identifiers:D,password:_,roles:U}:{identifiers:D,password:_},j=d.ip||"127.0.0.1",G=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let Z=await a,Oe=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await Z.consume(`register:${j}`,Oe,900*1e3);}let W=await u(x,{ip:j,userAgent:G});if(!W.success){i.warn(p(o,"auth.register.failure",{errorCode:W.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,W.error);return}i.info(p(o,"auth.register.success",{userId:W.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,201,"User registered successfully",{user:W.user});}catch(m){v(m);}}),r.post("/login",async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{identifier:I,password:_}=m;if(typeof I!="string"||I.trim().length===0)throw g("identifier is required and must be a non-empty string");if(I.length>Se)throw g(`identifier must not exceed ${Se} characters`);if(typeof _!="string"||_.length===0)throw g("password is required");if(_.length>z)throw g(`password must not exceed ${z} characters`);let y=I.trim(),D=d.ip||"127.0.0.1",U=d.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let j=await a,G=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await j.consume(`login:${D}`,G,900*1e3);}let x=await w({identifier:y,password:_},{ip:D,userAgent:U});if(!x.success){He(()=>e.hooks?.onLoginFailed?.(y,x.error,{ip:D})),i.warn(p(o,"auth.login.failure",{errorCode:x.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,x.error);return}He(()=>e.hooks?.onLoginSuccess?.(x.user,{ip:D,userAgent:U})),ie(h,x.refreshToken,e),oe(h,x.accessToken,e),i.info(p(o,"auth.login.success",{userId:x.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Login successful",{accessToken:x.accessToken,user:x.user});}catch(m){v(m);}}),r.post("/refresh",async(d,h,v)=>{let R=Date.now();try{let m=H(d.headers.cookie,K(e));if(!m)throw new c("UNAUTHORIZED","Refresh token cookie is missing");let I=d.ip||"127.0.0.1",_=d.get("user-agent")||"Unknown",y=await S(m,{ip:I,userAgent:_});if(!y.success){xe(h,e),i.warn(p(o,"auth.refresh.failure",{errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}ie(h,y.refreshToken,e),oe(h,y.accessToken,e),i.info(p(o,"auth.refresh.success",{userId:y.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Token refreshed",{accessToken:y.accessToken});}catch(m){v(m);}}),r.post("/logout",async(d,h,v)=>{let R=Date.now();try{let m=H(d.headers.cookie,K(e));await T(m),xe(h,e),Pe(h,e),i.info(p(o,"auth.logout",{duration_ms:Date.now()-R,...k(d)})),b(h,200,"Logged out",null);}catch(m){v(m);}}),r.post("/logout-all",O(e),async(d,h,v)=>{let R=Date.now();try{let m=d.user.id;await C(m),He(()=>e.hooks?.onLogout?.(m)),xe(h,e),Pe(h,e),i.info(p(o,"auth.logout_all",{userId:m,duration_ms:Date.now()-R,...k(d)})),b(h,200,"All sessions revoked",null);}catch(m){v(m);}}),r.get("/me",O(e),async(d,h,v)=>{let R=Date.now();try{let m=await A(d.user.id);if(!m.success){i.warn(p(o,"auth.me.failure",{userId:d.user.id,errorCode:m.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,m.error);return}i.info(p(o,"auth.me.success",{userId:m.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"OK",m.user);}catch(m){v(m);}}),r.get("/me/identifiers",O(e),async(d,h,v)=>{let R=Date.now();try{let m=await A(d.user.id);if(!m.success){i.warn(p(o,"auth.me.identifiers.failure",{userId:d.user.id,errorCode:m.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,m.error);return}i.info(p(o,"auth.me.identifiers.success",{userId:m.user.id,count:m.user.identifiers?.length??0,duration_ms:Date.now()-R,...k(d)})),b(h,200,"OK",{identifiers:m.user.identifiers??[]});}catch(m){v(m);}});let le=l(d=>!!d.user);return r.post("/me/identifiers",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{identifiers:I}=m;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((D,U)=>Me(D,U)),y=await ce(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.create_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}i.info(p(o,"auth.identifiers.created",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...k(d)})),b(h,201,"Identifiers added successfully",{identifiers:y.identifiers});}catch(m){v(m);}}),r.put("/me/identifiers",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{identifiers:I}=m;if(!Array.isArray(I)||I.length===0)throw g("identifiers is required and must be a non-empty array");if(I.length>J)throw g(`identifiers must not exceed ${J} entries`);let _=I.map((D,U)=>{if(typeof D!="object"||D===null||Array.isArray(D))throw g(`identifiers[${U}] must be an object`);let x=D;if(typeof x.id!="string"||x.id.trim().length===0)throw g(`identifiers[${U}].id is required and must be a non-empty string`);let{type:j,value:G}=Me(D,U);return {id:x.id,type:j,value:G}}),y=await Fr(d.user.id,_);if(!y.success){i.warn(p(o,"auth.identifiers.update_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}i.info(p(o,"auth.identifiers.updated",{userId:d.user.id,count:y.identifiers.length,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Identifiers updated successfully",{identifiers:y.identifiers});}catch(m){v(m);}}),r.delete("/me/identifiers",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{ids:I}=m;if(!Array.isArray(I)||I.length===0)throw g("ids is required and must be a non-empty array of strings");if(!I.every(y=>typeof y=="string"))throw g("each id must be a string");let _=await Pr(d.user.id,I);if(!_.success){i.warn(p(o,"auth.identifiers.delete_failure",{userId:d.user.id,errorCode:_.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,_.error);return}i.info(p(o,"auth.identifiers.deleted",{userId:d.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Identifiers deleted successfully",{identifiers:_.identifiers});}catch(m){v(m);}}),r.patch("/me/password",O(e),le,async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{currentPassword:I,newPassword:_}=m;if(typeof I!="string"||I.length===0)throw g("currentPassword is required");if(typeof _!="string"||_.length<Ce)throw g(`newPassword must be at least ${Ce} characters`);if(_.length>z)throw g(`newPassword must not exceed ${z} characters`);if(I===_)throw g("newPassword must be different from currentPassword");let y=await $(d.user.id,I,_);if(!y.success){i.warn(p(o,"auth.password.change_failure",{userId:d.user.id,errorCode:y.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,y.error);return}i.info(p(o,"auth.password.changed",{userId:d.user.id,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Password updated successfully. All sessions have been revoked.",null);}catch(m){v(m);}}),r.post("/users/:userId/roles",O(e),f("admin"),async(d,h,v)=>{let R=Date.now();try{let m=M(d.body),{roles:I}=m,_=d.params.userId,y=typeof _=="string"?_:void 0;if(!y)throw g("userId is required");if(!Array.isArray(I)||I.length===0)throw g("roles must be a non-empty array of strings");if(!I.every(U=>typeof U=="string"))throw g("each role must be a string");let D=await P(y,I);if(!D.success){i.warn(p(o,"auth.roles.assign_failure",{targetUserId:y,errorCode:D.error.code,duration_ms:Date.now()-R,...k(d)})),F(h,D.error);return}i.info(p(o,"auth.roles.assigned",{targetUserId:y,roles:I,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Roles assigned successfully",{user:D.user});}catch(m){v(m);}}),r.use(ue()),r.get("/sessions",O(e),async(d,h,v)=>{let R=Date.now();try{let m=d.user.id,_=await(e.router?.getSessions??(y=>Tr(y,t)))(m);i.info(p(o,"auth.sessions.get",{userId:m,duration_ms:Date.now()-R,...k(d)})),b(h,200,"OK",{sessions:_});}catch(m){v(m);}}),r.delete("/sessions/:id",O(e),async(d,h,v)=>{let R=Date.now();try{let m=d.user.id,I=d.params.id;await(e.router?.revokeSession??((y,D)=>Dr(y,D,t)))(m,I),i.info(p(o,"auth.sessions.revoke",{userId:m,sessionId:I,duration_ms:Date.now()-R,...k(d)})),b(h,200,"Session revoked",null);}catch(m){v(m);}}),r}n(Lr,"createAuthRouter");function sn(e){if(e.mode==="client"){let a={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}};Ge(a);let u=a.logger??L,w=a.loggerService??"sentri",S=ae(u,w),T=de(u,w);return {protect:n(()=>O(a),"protect"),authorize:n((...C)=>S(...C),"authorize"),permit:n(C=>T(C),"permit"),errorHandler:n(C=>ue(C),"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.validIdentifiers!==void 0&&{validIdentifiers:e.validIdentifiers},...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.rateLimit!==void 0&&{rateLimit:e.rateLimit},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??L,i=t.loggerService??"sentri",o=E(t),f=ae(s,i),l=de(s,i);return {protect:n(()=>O(t),"protect"),authorize:n((...a)=>f(...a),"authorize"),permit:n(a=>l(a),"permit"),errorHandler:n(a=>ue(a),"errorHandler"),hashPassword:n(a=>Y(a,o.saltRounds),"hashPassword"),verifyPassword:n((a,u)=>q(a,u),"verifyPassword"),signAccessToken:n(a=>ee(a,t),"signAccessToken"),signRefreshToken:n(a=>re(a,t),"signRefreshToken"),verifyAccessToken:n(a=>fe(a,t),"verifyAccessToken"),verifyRefreshToken:n(a=>te(a,t),"verifyRefreshToken"),getCurrentAccessToken:n(a=>Ne(a,t),"getCurrentAccessToken"),router:n(()=>Lr(t),"router"),migrate:n(()=>We(N(t.dialect)),"migrate"),idempotencyMiddleware:n(a=>Or({...a,...t.redisUrl!==void 0&&{redisUrl:t.redisUrl}}),"idempotencyMiddleware"),register:n(a=>ye(a,t),"register"),login:n(a=>Ie(a,t),"login"),refresh:n(a=>X(a,t),"refresh"),logout:n(a=>Re(a,t),"logout"),logoutAll:n(a=>_e(a,t),"logoutAll"),getUser:n(a=>ge(a,t),"getUser"),changePassword:n((a,u,w)=>ve(a,u,w,t),"changePassword"),assignRoles:n((a,u)=>Ae(a,u,t),"assignRoles"),bulkCreateIdentifiers:n((a,u)=>Ee(a,u,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((a,u)=>ke(a,u,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((a,u)=>Te(a,u,t),"bulkDeleteIdentifiers")}}n(sn,"createAuthExpress");export{je as SENTRI_ERROR_STATUS,c as SentriError,Zr as authorize,sn as createAuthExpress,Lr as createAuthRouter,ae as createAuthorize,ue as createErrorHandler,Or as createIdempotencyMiddleware,de as createPermit,Ne as getCurrentAccessToken,qr as permit,O as protect};