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'),We=require('bcrypt'),Z=require('jsonwebtoken'),cookie=require('hono/cookie'),hono=require('hono');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var We__default=/*#__PURE__*/_interopDefault(We);var Z__default=/*#__PURE__*/_interopDefault(Z);var Fr=Object.defineProperty;var n=(e,r)=>Fr(e,"name",{value:r,configurable:true});var $e=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),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??$e[r]??500;}};var He=new WeakMap,Ve=32,je=10,Me=31;function ze(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<Ve)throw new l("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<je||s>Me)throw new l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${je} and ${Me}`);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(ze,"validateConfig");function R(e){let r=He.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 He.set(e,t),t}n(R,"resolveServerConfig");var Pr=/^(\d+)([smhdw])$/,Kr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Be=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=Be.get(e);if(r!==void 0)return r;let t=Pr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Kr[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 Be.set(e,i),i}n($,"parseExpiry");var D={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 Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Xe,"runMigrations");var Ge=new Map;function T(e){let r=Ge.get(e);return r||(r=new kysely.Kysely({dialect:e}),Ge.set(e,r)),r}n(T,"getDatabase");async function G(e,r=12){return We__default.default.hash(e,r)}n(G,"hashPassword");async function W(e,r){return We__default.default.compare(e,r)}n(W,"verifyPassword");var Ze=new Map,Je=new Map,jr=3600*1e3;function Qe(e){let r=Ze.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),i=s.export({format:"jwk"}),d=crypto.createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),h={...i,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:h},Ze.set(e,r);}return r}n(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}n(er,"buildJwks");function rr(e){return Qe(e).publicKey}n(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<jr)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 a=i.keys[0],d=crypto.createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,or=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}n(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}n(ar,"getHsSecrets");function Br(e){let r=nr.get(e);return r||(r=crypto.createPrivateKey(e),nr.set(e,r)),r}n(Br,"getRsPrivateKey");function ur(e){let r=R(e);if(ir(r.algorithm)){let i=Br(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}n(ur,"getSigningKeys");function dr(e,r){let t=R(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(dr,"getVerifyKey");function cr(e,r,t,s){let i=`${t}:${s}`,a=or.get(i);return a||(a={expiresIn:t,algorithm:s},or.set(i,a)),Z__default.default.sign(e,r,a)}n(cr,"sign");function lr(e,r,t){try{let s=Z__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 Z__default.default.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function J(e,r){let t=R(r),{accessKey:s}=ur(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=R(r),{refreshKey:s}=ur(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function ce(e,r){let t=R(r),s=dr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=R(r),s=dr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=Z__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 Z__default.default.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Oe(e){try{return JSON.parse(e)}catch{return []}}n(Oe,"parseRoles");function zr(e){return JSON.stringify(e)}n(zr,"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 ee(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function le(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(le,"findUserById");async function pr(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(pr,"createIdentifiers");async function re(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(re,"findIdentifiersByUserId");async function be(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(be,"findIdentifierById");async function mr(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(mr,"countIdentifiersByUserId");async function yr(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(yr,"updateIdentifier");async function Ir(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(Ir,"deleteIdentifiers");async function gr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(gr,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:zr(t)}).where("id","=",r).execute();}n(Rr,"updateUserRoles");async function Ue(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(Ue,"createSession");async function fe(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:Oe(t.roles)}}:null}n(fe,"findSessionById");async function he(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(he,"deleteSession");async function _r(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(_r,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function vr(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(vr,"findSessionsByUserId");async function pe(e,r,t){let s=R(r),i=T(r.dialect),a=e.roles??[],d=a.filter(v=>!s.validRolesSet.has(v));if(d.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let h=e.identifiers.map(v=>({type:v.type.trim(),value:v.value.trim()})),u=h.filter(v=>!s.validIdentifiersSet.has(v.type));if(u.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${u.map(v=>v.type).join(", ")}`)};if(new Set(h.map(v=>v.value)).size!==h.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let v of h)if(await ee(i,v.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${v.value}`)};let A=await G(e.password,s.saltRounds),{userId:C,identifierRows:x}=await i.transaction().execute(async v=>{let L=crypto.randomUUID();await v.insertInto("sentri_users").values({id:L,password_hash:A,roles:JSON.stringify(a)}).execute();let ae=h.map(ue=>({id:crypto.randomUUID(),user_id:L,type:ue.type,value:ue.value}));return await v.insertInto("sentri_identifiers").values(ae).execute(),{userId:L,identifierRows:ae}}),O={success:true,user:{id:C,roles:a,identifiers:x.map(v=>({id:v.id,type:v.type,value:v.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(O.user),O}n(pe,"register");async function me(e,r,t){let s=R(r),i=T(r.dialect),a=await ee(i,e.identifier.trim());if(!a){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}if(!await W(e.password,a.passwordHash)){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:a.id,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),c={id:a.id,roles:a.roles},A=J({id:a.id,roles:a.roles,sessionId:u.id},r),C=Y(u.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(c,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:A,refreshToken:C,user:c}}n(me,"login");async function H(e,r,t){let s=R(r),i=T(r.dialect),a;try{({sessionId:a}=Q(e,r));}catch(x){return x instanceof l?{success:false,error:x}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let d=await fe(i,a);if(!d)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await we(i,d.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await he(i,a),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:d.userId,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(i,a,u.id);let c={id:d.user.id,roles:d.user.roles},A=J({...c,sessionId:u.id},r),C=Y(u.id,r);return {success:true,accessToken:A,refreshToken:C,user:c}}n(H,"refresh");async function ye(e,r){let t=T(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await fe(t,s);i&&(await he(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(ye,"logout");async function Ie(e,r){let t=T(r.dialect);await we(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function ge(e,r){let t=T(r.dialect),s=await le(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await re(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(ge,"getUser");async function Re(e,r,t,s){let i=R(s),a=T(s.dialect),d=await le(a,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await W(r,d.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let u=await G(t,i.saltRounds);return await gr(a,e,u),await we(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function _e(e,r,t){let s=R(t),i=T(t.dialect),a=r.filter(c=>!s.validRolesSet.has(c));if(a.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let d=await le(i,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let h=new Set(d.roles);for(let c of r)h.add(c);let u=Array.from(h);return await Rr(i,e,u),{success:true,user:{id:d.id,roles:u}}}n(_e,"assignRoles");async function ve(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let a=r.map(c=>({type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a)if(await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await pr(i,e,a),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(ve,"bulkCreateIdentifiers");async function Ae(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let a=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a){let A=await be(i,c.id,e);if(!A)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(A.value!==c.value&&await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await i.transaction().execute(async c=>{for(let A of a)await yr(c,A.id,e,{type:A.type,value:A.value});}),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(Ae,"bulkUpdateIdentifiers");async function ke(e,r,t){let s=T(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 h of i)if(!await be(s,h,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${h}`)};return await mr(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,i),{success:true,identifiers:(await re(s,e)).map(h=>({id:h.id,type:h.type,value:h.value}))})}n(ke,"bulkDeleteIdentifiers");async function kr(e,r){let t=T(r.dialect);return (await vr(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(kr,"getSessions");async function Er(e,r,t){let s=T(t.dialect),i=await fe(s,r);i&&i.userId===e&&await he(s,r);}n(Er,"revokeSession");function U(e){return R(e).cookieName}n(U,"getCookieName");function Ee(e){return R(e).accessCookieName}n(Ee,"getAccessCookieName");function q(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),a=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(q,"readCookie");function Nr(e){let r=(e??"strict").toLowerCase();return r==="lax"?"Lax":r==="none"?"None":"Strict"}n(Nr,"normalizeSameSite");function te(e,r,t){let s=t.cookie??{},i=$(R(t).refreshExpiresIn)/1e3;cookie.setCookie(e,U(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:Nr(s.sameSite),path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Te(e,r){let t=r.cookie??{};cookie.deleteCookie(e,U(r),{path:t.path??"/"});}n(Te,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=$(R(t).accessExpiresIn)/1e3;cookie.setCookie(e,Ee(t),r,{httpOnly:false,secure:s.secure??false,sameSite:Nr(s.sameSite),path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function Le(e,r){if(!r.accessCookie)return;let t=r.accessCookie;cookie.deleteCookie(e,Ee(r),{path:t.path??"/"});}n(Le,"clearAccessCookieFromConfig");function xe(e,r){let t=e.req.header("authorization");return t?.startsWith("Bearer ")?t.slice(7):q(e.req.header("cookie"),Ee(r))}n(xe,"getCurrentAccessToken");function F(e){let r=e.logger??D,t=e.loggerService??"sentri";return e.mode==="client"?Xr(e.keyUri,r,t):Gr(e,r,t)}n(F,"protect");function Xr(e,r,t){return async(s,i)=>{let a=s.req.header("authorization"),d=a?.startsWith("Bearer ")?a.slice(7):void 0,h=s.get("requestId");if(!d)throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let u=await tr(e),c=fr(d,u);s.set("user",{id:c.id,roles:c.roles}),r.info(p(t,"auth.protect.success",{mode:"client",userId:c.id,...h!==void 0&&{requestId:h}})),await i();}catch(u){let c=u instanceof l?u.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:c,...h!==void 0&&{requestId:h}})),u}}}n(Xr,"protectClient");function Gr(e,r,t){return async(s,i)=>{let a=xe(s,e),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let h=ce(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(h.sessionId))throw r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token has been revoked");s.set("user",{id:h.id,roles:h.roles}),r.info(p(t,"auth.protect.success",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),await i();}catch(h){if(h instanceof l&&h.code==="TOKEN_EXPIRED"){let u=q(s.req.header("cookie"),U(e));if(!u)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token expired. Please login again.");let c;try{c=await H(u,e);}catch{throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.")}if(!c.success)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:c.error.code,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.");te(s,c.refreshToken,e),se(s,c.accessToken,e),s.header("X-New-Access-Token",c.accessToken),s.set("user",c.user),r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:c.user.id,...d!==void 0&&{requestId:d}})),await i();}else {let u=h instanceof l?h.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...d!==void 0&&{requestId:d}})),h}}}}n(Gr,"protectServer");function Dr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async(i,a)=>{let d=i.get("user"),h=i.get("requestId");if(!d)throw r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Not authenticated");let u=d.roles;if(!e.some(c=>u.includes(c)))throw r.warn(p(t,"auth.authorize.denied",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("FORBIDDEN",s);r.info(p(t,"auth.authorize.passed",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),await a();}}n(Dr,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Dr(s,e,r)},"authorize")}n(ne,"createAuthorize");function Wr(...e){return Dr(e,D,"sentri")}n(Wr,"authorize");var Zr=new l("FORBIDDEN","You do not have permission to perform this action");function Sr(e,r,t){return async(s,i)=>{let a=s.get("user"),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.permit.unauthenticated",{...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Not authenticated");let h=a.id;if(e.roles&&e.roles.length>0){let u=a.roles;if(e.roles.some(c=>u.includes(c))){r.info(p(t,"auth.permit.role_bypass",{userId:h,bypassedByRole:true,...d!==void 0&&{requestId:d}})),await i();return}}try{let u=e.check(s);if(u instanceof Promise?await u:u)r.info(p(t,"auth.permit.passed",{userId:h,...d!==void 0&&{requestId:d}})),await i();else throw r.warn(p(t,"auth.permit.denied",{userId:h,...d!==void 0&&{requestId:d}})),Zr}catch(u){throw u}}}n(Sr,"createPermitHandler");function oe(e,r){return n(function(s){return Sr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Jr(e){return Sr(typeof e=="function"?{check:e}:e,D,"sentri")}n(Jr,"permit");function ie(e){return (r,t)=>r instanceof l?t.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode):(e?.onUnhandled?.(r),t.json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null},500))}n(ie,"createErrorHandler");var qe=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(),a=this.store.get(r);if((!a||a.expiresAt<i)&&(a={count:0,expiresAt:i+s},this.store.set(r,a)),a.count>t){let d=Math.ceil((a.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${d} seconds.`)}a.count++;}},Fe=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}`,a=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(i).expire(i,a,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(d){if(d instanceof Error&&d.message.includes("Rate limit exceeded"))throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},V=null;async function Cr(e,r){if(V)return V;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"}),V=new Fe(s,r??D),V}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return V=new qe,V}n(Cr,"getRateLimiter");var Ne=8,j=72,De=255,Or=100,M=50;function g(e){return new l("VALIDATION_ERROR",e)}n(g,"badRequest");function N(e,r,t,s){return e.json({error:false,statusCode:r,message:t,data:s},r)}n(N,"ok");function b(e,r){return e.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode)}n(b,"fail");async function P(e){try{let r=await e.req.json();if(r==null||typeof r!="object"||Array.isArray(r))throw new l("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you set Content-Type: application/json?");return r}catch(r){throw r instanceof l?r:new l("VALIDATION_ERROR","Invalid JSON body. Did you set Content-Type: application/json?")}}n(P,"parseBody");function Qr(e,r){if(!r.apiKey)return;let t=e.req.header("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new l("UNAUTHORIZED","Invalid or missing API key")}n(Qr,"validateApiKey");function Pe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Pe,"fireHook");function et(e){return e.startsWith("RS")||e.startsWith("PS")}n(et,"isRSA");function Ke(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>Or)throw g(`identifiers[${r}].type must not exceed ${Or} 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>De)throw g(`identifiers[${r}].value must not exceed ${De} characters`);return {type:t.type,value:t.value}}n(Ke,"validateIdentifierInput");function _(e){let r=e.get("requestId");return r!==void 0?{requestId:r}:{}}n(_,"reqId");function br(e){let r=new hono.Hono,t=e,s=R(t),i=e.logger??D,a=e.loggerService??"sentri",d=F(e),h=ne(i,a),u=oe(i,a),c=Cr(e.redisUrl,i),A=e.router?.register??(o=>pe(o,t)),C=e.router?.login??(o=>me(o,t)),x=e.router?.refresh??(o=>H(o,t)),O=e.router?.logout??(o=>o!==void 0?ye(o,t):Promise.resolve()),v=e.router?.logoutAll??(o=>Ie(o,t)),L=e.router?.getUser??(o=>ge(o,t)),ae=e.router?.assignRoles??((o,w)=>_e(o,w,t)),ue=e.router?.changePassword??((o,w,f)=>Re(o,w,f,t)),Ur=e.router?.bulkCreateIdentifiers??((o,w)=>ve(o,w,t)),Lr=e.router?.bulkUpdateIdentifiers??((o,w)=>Ae(o,w,t)),qr=e.router?.bulkDeleteIdentifiers??((o,w)=>ke(o,w,t)),de=u(o=>!!o.get("user"));return r.onError(ie()),et(s.algorithm)&&r.get("/keys",o=>(o.header("Cache-Control","public, max-age=3600"),o.json(er(e.secret)))),r.post("/register",async o=>{let w=Date.now();try{Qr(o,e);let f=await P(o),{identifiers:y,password:I,roles:m}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>M)throw g(`identifiers must not exceed ${M} entries`);let k=y.map((X,Se)=>Ke(X,Se));if(typeof I!="string"||I.length<Ne)throw g(`password is required and must be at least ${Ne} characters`);if(I.length>j)throw g(`password must not exceed ${j} characters`);if(m!==void 0&&!Array.isArray(m))throw g("roles must be an array of strings when provided");if(Array.isArray(m)&&!m.every(X=>typeof X=="string"))throw g("each role must be a string");let S=Array.isArray(m)?m:void 0,E=S!==void 0?{identifiers:k,password:I,roles:S}:{identifiers:k,password:I},K=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",B=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let X=await c,Se=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${K}`,Se,900*1e3);}let z=await A(E,{ip:K,userAgent:B});return z.success?(i.info(p(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,201,"User registered successfully",{user:z.user})):(i.warn(p(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,z.error))}catch(f){throw f}}),r.post("/login",async o=>{let w=Date.now();try{let f=await P(o),{identifier:y,password:I}=f;if(typeof y!="string"||y.trim().length===0)throw g("identifier is required and must be a non-empty string");if(y.length>De)throw g(`identifier must not exceed ${De} characters`);if(typeof I!="string"||I.length===0)throw g("password is required");if(I.length>j)throw g(`password must not exceed ${j} characters`);let m=y.trim(),k=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",S=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let K=await c,B=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${k}`,B,900*1e3);}let E=await C({identifier:m,password:I},{ip:k,userAgent:S});return E.success?(Pe(()=>e.hooks?.onLoginSuccess?.(E.user,{ip:k,userAgent:S})),te(o,E.refreshToken,e),se(o,E.accessToken,e),i.info(p(a,"auth.login.success",{userId:E.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Login successful",{accessToken:E.accessToken,user:E.user})):(Pe(()=>e.hooks?.onLoginFailed?.(m,E.error,{ip:k})),i.warn(p(a,"auth.login.failure",{errorCode:E.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,E.error))}catch(f){throw f}}),r.post("/refresh",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));if(!f)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let y=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",I=o.req.header("user-agent")||"Unknown",m=await x(f,{ip:y,userAgent:I});return m.success?(te(o,m.refreshToken,e),se(o,m.accessToken,e),i.info(p(a,"auth.refresh.success",{userId:m.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Token refreshed",{accessToken:m.accessToken})):(Te(o,e),i.warn(p(a,"auth.refresh.failure",{errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/logout",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));return await O(f),Te(o,e),Le(o,e),i.info(p(a,"auth.logout",{duration_ms:Date.now()-w,..._(o)})),N(o,200,"Logged out",null)}catch(f){throw f}}),r.post("/logout-all",d,async o=>{let w=Date.now();try{let f=o.get("user").id;return await v(f),Pe(()=>e.hooks?.onLogout?.(f)),Te(o,e),Le(o,e),i.info(p(a,"auth.logout_all",{userId:f,duration_ms:Date.now()-w,..._(o)})),N(o,200,"All sessions revoked",null)}catch(f){throw f}}),r.get("/me",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.success",{userId:f.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"OK",f.user)):(i.warn(p(a,"auth.me.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.get("/me/identifiers",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.identifiers.success",{userId:f.user.id,count:f.user.identifiers?.length??0,duration_ms:Date.now()-w,..._(o)})),N(o,200,"OK",{identifiers:f.user.identifiers??[]})):(i.warn(p(a,"auth.me.identifiers.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.post("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>M)throw g(`identifiers must not exceed ${M} entries`);let I=y.map((k,S)=>Ke(k,S)),m=await Ur(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.created",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),N(o,201,"Identifiers added successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.create_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.put("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>M)throw g(`identifiers must not exceed ${M} entries`);let I=y.map((k,S)=>{if(typeof k!="object"||k===null||Array.isArray(k))throw g(`identifiers[${S}] must be an object`);let E=k;if(typeof E.id!="string"||E.id.trim().length===0)throw g(`identifiers[${S}].id is required and must be a non-empty string`);let{type:K,value:B}=Ke(k,S);return {id:E.id,type:K,value:B}}),m=await Lr(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.updated",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Identifiers updated successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.update_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.delete("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{ids:y}=f;if(!Array.isArray(y)||y.length===0)throw g("ids is required and must be a non-empty array of strings");if(!y.every(m=>typeof m=="string"))throw g("each id must be a string");let I=await qr(o.get("user").id,y);return I.success?(i.info(p(a,"auth.identifiers.deleted",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Identifiers deleted successfully",{identifiers:I.identifiers})):(i.warn(p(a,"auth.identifiers.delete_failure",{userId:o.get("user").id,errorCode:I.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,I.error))}catch(f){throw f}}),r.patch("/me/password",d,de,async o=>{let w=Date.now();try{let f=await P(o),{currentPassword:y,newPassword:I}=f;if(typeof y!="string"||y.length===0)throw g("currentPassword is required");if(typeof I!="string"||I.length<Ne)throw g(`newPassword must be at least ${Ne} characters`);if(I.length>j)throw g(`newPassword must not exceed ${j} characters`);if(y===I)throw g("newPassword must be different from currentPassword");let m=await ue(o.get("user").id,y,I);return m.success?(i.info(p(a,"auth.password.changed",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Password updated successfully. All sessions have been revoked.",null)):(i.warn(p(a,"auth.password.change_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/users/:userId/roles",d,h("admin"),async o=>{let w=Date.now();try{let f=await P(o),{roles:y}=f,I=o.req.param("userId");if(!I)throw g("userId is required");if(!Array.isArray(y)||y.length===0)throw g("roles must be a non-empty array of strings");if(!y.every(k=>typeof k=="string"))throw g("each role must be a string");let m=await ae(I,y);return m.success?(i.info(p(a,"auth.roles.assigned",{targetUserId:I,roles:y,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Roles assigned successfully",{user:m.user})):(i.warn(p(a,"auth.roles.assign_failure",{targetUserId:I,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.get("/sessions",F(e),async o=>{let w=Date.now(),f=o.get("user").id,I=await(e.router?.getSessions??(m=>kr(m,t)))(f);return i.info(p(a,"auth.sessions.get",{userId:f,duration_ms:Date.now()-w,..._(o)})),N(o,200,"OK",{sessions:I})}),r.delete("/sessions/:id",F(e),async o=>{let w=Date.now(),f=o.get("user").id,y=o.req.param("id");return await(e.router?.revokeSession??((m,k)=>Er(m,k,t)))(f,y),i.info(p(a,"auth.sessions.revoke",{userId:f,sessionId:y,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Session revoked",null)}),r}n(br,"createAuthRouter");function zs(e){if(e.mode==="client"){let u={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};ze(u);let c=u.logger??D,A=u.loggerService??"sentri",C=ne(c,A),x=oe(c,A);return {protect:n(()=>F(u),"protect"),authorize:n((...O)=>C(...O),"authorize"),permit:n(O=>x(O),"permit"),errorHandler:n(O=>ie(O),"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??D,i=t.loggerService??"sentri",a=R(t),d=ne(s,i),h=oe(s,i);return {protect:n(()=>F(t),"protect"),authorize:n((...u)=>d(...u),"authorize"),permit:n(u=>h(u),"permit"),errorHandler:n(u=>ie(u),"errorHandler"),router:n(()=>br(t),"router"),migrate:n(()=>Xe(T(t.dialect)),"migrate"),getCurrentAccessToken:n(u=>xe(u,t),"getCurrentAccessToken"),hashPassword:n(u=>G(u,a.saltRounds),"hashPassword"),verifyPassword:n((u,c)=>W(u,c),"verifyPassword"),signAccessToken:n(u=>J(u,t),"signAccessToken"),signRefreshToken:n(u=>Y(u,t),"signRefreshToken"),verifyAccessToken:n(u=>ce(u,t),"verifyAccessToken"),verifyRefreshToken:n(u=>Q(u,t),"verifyRefreshToken"),register:n(u=>pe(u,t),"register"),login:n(u=>me(u,t),"login"),refresh:n(u=>H(u,t),"refresh"),logout:n(u=>ye(u,t),"logout"),logoutAll:n(u=>Ie(u,t),"logoutAll"),getUser:n(u=>ge(u,t),"getUser"),changePassword:n((u,c,A)=>Re(u,c,A,t),"changePassword"),assignRoles:n((u,c)=>_e(u,c,t),"assignRoles"),bulkCreateIdentifiers:n((u,c)=>ve(u,c,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((u,c)=>Ae(u,c,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((u,c)=>ke(u,c,t),"bulkDeleteIdentifiers")}}n(zs,"createAuthHono");exports.SENTRI_ERROR_STATUS=$e;exports.SentriError=l;exports.authorize=Wr;exports.createAuthHono=zs;exports.createAuthRouter=br;exports.createAuthorize=ne;exports.createErrorHandler=ie;exports.createPermit=oe;exports.getCurrentAccessToken=xe;exports.permit=Jr;exports.protect=F;
1
+ 'use strict';var crypto=require('crypto'),kysely=require('kysely'),We=require('bcrypt'),Z=require('jsonwebtoken'),cookie=require('hono/cookie'),hono=require('hono');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var We__default=/*#__PURE__*/_interopDefault(We);var Z__default=/*#__PURE__*/_interopDefault(Z);var Fr=Object.defineProperty;var n=(e,r)=>Fr(e,"name",{value:r,configurable:true});var $e=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401,RATE_LIMIT_EXCEEDED:429}),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??$e[r]??500;}};var He=new WeakMap,Ve=32,Me=10,je=31;function ze(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<Ve)throw new l("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Me||s>je)throw new l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Me} and ${je}`);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(ze,"validateConfig");function R(e){let r=He.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 He.set(e,t),t}n(R,"resolveServerConfig");var Pr=/^(\d+)([smhdw])$/,Kr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Be=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=Be.get(e);if(r!==void 0)return r;let t=Pr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Kr[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 Be.set(e,i),i}n($,"parseExpiry");var N={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 Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Xe,"runMigrations");var Ge=new Map;function T(e){let r=Ge.get(e);return r||(r=new kysely.Kysely({dialect:e}),Ge.set(e,r)),r}n(T,"getDatabase");async function G(e,r=12){return We__default.default.hash(e,r)}n(G,"hashPassword");async function W(e,r){return We__default.default.compare(e,r)}n(W,"verifyPassword");var Ze=new Map,Je=new Map,Mr=3600*1e3;function Qe(e){let r=Ze.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),i=s.export({format:"jwk"}),d=crypto.createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),h={...i,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:h},Ze.set(e,r);}return r}n(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}n(er,"buildJwks");function rr(e){return Qe(e).publicKey}n(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Mr)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 a=i.keys[0],d=crypto.createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,or=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}n(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}n(ar,"getHsSecrets");function Br(e){let r=nr.get(e);return r||(r=crypto.createPrivateKey(e),nr.set(e,r)),r}n(Br,"getRsPrivateKey");function ur(e){let r=R(e);if(ir(r.algorithm)){let i=Br(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}n(ur,"getSigningKeys");function dr(e,r){let t=R(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(dr,"getVerifyKey");function cr(e,r,t,s){let i=`${t}:${s}`,a=or.get(i);return a||(a={expiresIn:t,algorithm:s},or.set(i,a)),Z__default.default.sign(e,r,a)}n(cr,"sign");function lr(e,r,t){try{let s=Z__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 Z__default.default.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function J(e,r){let t=R(r),{accessKey:s}=ur(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=R(r),{refreshKey:s}=ur(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function ce(e,r){let t=R(r),s=dr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=R(r),s=dr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=Z__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 Z__default.default.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Oe(e){try{return JSON.parse(e)}catch{return []}}n(Oe,"parseRoles");function zr(e){return JSON.stringify(e)}n(zr,"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 ee(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function le(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(le,"findUserById");async function pr(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(pr,"createIdentifiers");async function re(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(re,"findIdentifiersByUserId");async function be(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(be,"findIdentifierById");async function mr(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(mr,"countIdentifiersByUserId");async function yr(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(yr,"updateIdentifier");async function Ir(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(Ir,"deleteIdentifiers");async function gr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(gr,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:zr(t)}).where("id","=",r).execute();}n(Rr,"updateUserRoles");async function Ue(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(Ue,"createSession");async function fe(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:Oe(t.roles)}}:null}n(fe,"findSessionById");async function he(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(he,"deleteSession");async function _r(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(_r,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function vr(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(vr,"findSessionsByUserId");async function pe(e,r,t){let s=R(r),i=T(r.dialect),a=e.roles??[],d=a.filter(v=>!s.validRolesSet.has(v));if(d.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let h=e.identifiers.map(v=>({type:v.type.trim(),value:v.value.trim()})),u=h.filter(v=>!s.validIdentifiersSet.has(v.type));if(u.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${u.map(v=>v.type).join(", ")}`)};if(new Set(h.map(v=>v.value)).size!==h.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let v of h)if(await ee(i,v.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${v.value}`)};let A=await G(e.password,s.saltRounds),{userId:C,identifierRows:x}=await i.transaction().execute(async v=>{let L=crypto.randomUUID();await v.insertInto("sentri_users").values({id:L,password_hash:A,roles:JSON.stringify(a)}).execute();let ae=h.map(ue=>({id:crypto.randomUUID(),user_id:L,type:ue.type,value:ue.value}));return await v.insertInto("sentri_identifiers").values(ae).execute(),{userId:L,identifierRows:ae}}),O={success:true,user:{id:C,roles:a,identifiers:x.map(v=>({id:v.id,type:v.type,value:v.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(O.user),O}n(pe,"register");async function me(e,r,t){let s=R(r),i=T(r.dialect),a=await ee(i,e.identifier.trim());if(!a){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}if(!await W(e.password,a.passwordHash)){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:a.id,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),c={id:a.id,roles:a.roles},A=J({id:a.id,roles:a.roles,sessionId:u.id},r),C=Y(u.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(c,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:A,refreshToken:C,user:c}}n(me,"login");async function H(e,r,t){let s=R(r),i=T(r.dialect),a;try{({sessionId:a}=Q(e,r));}catch(x){return x instanceof l?{success:false,error:x}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let d=await fe(i,a);if(!d)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await we(i,d.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await he(i,a),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:d.userId,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(i,a,u.id);let c={id:d.user.id,roles:d.user.roles},A=J({...c,sessionId:u.id},r),C=Y(u.id,r);return {success:true,accessToken:A,refreshToken:C,user:c}}n(H,"refresh");async function ye(e,r){let t=T(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await fe(t,s);i&&(await he(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(ye,"logout");async function Ie(e,r){let t=T(r.dialect);await we(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function ge(e,r){let t=T(r.dialect),s=await le(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await re(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(ge,"getUser");async function Re(e,r,t,s){let i=R(s),a=T(s.dialect),d=await le(a,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await W(r,d.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let u=await G(t,i.saltRounds);return await gr(a,e,u),await we(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function _e(e,r,t){let s=R(t),i=T(t.dialect),a=r.filter(c=>!s.validRolesSet.has(c));if(a.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let d=await le(i,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let h=new Set(d.roles);for(let c of r)h.add(c);let u=Array.from(h);return await Rr(i,e,u),{success:true,user:{id:d.id,roles:u}}}n(_e,"assignRoles");async function ve(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let a=r.map(c=>({type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a)if(await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await pr(i,e,a),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(ve,"bulkCreateIdentifiers");async function Ae(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let a=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a){let A=await be(i,c.id,e);if(!A)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(A.value!==c.value&&await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await i.transaction().execute(async c=>{for(let A of a)await yr(c,A.id,e,{type:A.type,value:A.value});}),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(Ae,"bulkUpdateIdentifiers");async function Ee(e,r,t){let s=T(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 h of i)if(!await be(s,h,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${h}`)};return await mr(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,i),{success:true,identifiers:(await re(s,e)).map(h=>({id:h.id,type:h.type,value:h.value}))})}n(Ee,"bulkDeleteIdentifiers");async function Er(e,r){let t=T(r.dialect);return (await vr(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(Er,"getSessions");async function kr(e,r,t){let s=T(t.dialect),i=await fe(s,r);i&&i.userId===e&&await he(s,r);}n(kr,"revokeSession");function U(e){return R(e).cookieName}n(U,"getCookieName");function ke(e){return R(e).accessCookieName}n(ke,"getAccessCookieName");function q(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),a=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(q,"readCookie");function Dr(e){let r=(e??"strict").toLowerCase();return r==="lax"?"Lax":r==="none"?"None":"Strict"}n(Dr,"normalizeSameSite");function te(e,r,t){let s=t.cookie??{},i=$(R(t).refreshExpiresIn)/1e3;cookie.setCookie(e,U(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:Dr(s.sameSite),path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Te(e,r){let t=r.cookie??{};cookie.deleteCookie(e,U(r),{path:t.path??"/"});}n(Te,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=$(R(t).accessExpiresIn)/1e3;cookie.setCookie(e,ke(t),r,{httpOnly:false,secure:s.secure??false,sameSite:Dr(s.sameSite),path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function Le(e,r){if(!r.accessCookie)return;let t=r.accessCookie;cookie.deleteCookie(e,ke(r),{path:t.path??"/"});}n(Le,"clearAccessCookieFromConfig");function xe(e,r){let t=e.req.header("authorization");return t?.startsWith("Bearer ")?t.slice(7):q(e.req.header("cookie"),ke(r))}n(xe,"getCurrentAccessToken");function F(e){let r=e.logger??N,t=e.loggerService??"sentri";return e.mode==="client"?Xr(e.keyUri,r,t):Gr(e,r,t)}n(F,"protect");function Xr(e,r,t){return async(s,i)=>{let a=s.req.header("authorization"),d=a?.startsWith("Bearer ")?a.slice(7):void 0,h=s.get("requestId");if(!d)throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let u=await tr(e),c=fr(d,u);s.set("user",{id:c.id,roles:c.roles}),r.info(p(t,"auth.protect.success",{mode:"client",userId:c.id,...h!==void 0&&{requestId:h}})),await i();}catch(u){let c=u instanceof l?u.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:c,...h!==void 0&&{requestId:h}})),u}}}n(Xr,"protectClient");function Gr(e,r,t){return async(s,i)=>{let a=xe(s,e),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let h=ce(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(h.sessionId))throw r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token has been revoked");s.set("user",{id:h.id,roles:h.roles}),r.info(p(t,"auth.protect.success",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),await i();}catch(h){if(h instanceof l&&h.code==="TOKEN_EXPIRED"){let u=q(s.req.header("cookie"),U(e));if(!u)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token expired. Please login again.");let c;try{c=await H(u,e);}catch{throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.")}if(!c.success)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:c.error.code,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.");te(s,c.refreshToken,e),se(s,c.accessToken,e),s.header("X-New-Access-Token",c.accessToken),s.set("user",c.user),r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:c.user.id,...d!==void 0&&{requestId:d}})),await i();}else {let u=h instanceof l?h.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...d!==void 0&&{requestId:d}})),h}}}}n(Gr,"protectServer");function Nr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async(i,a)=>{let d=i.get("user"),h=i.get("requestId");if(!d)throw r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Not authenticated");let u=d.roles;if(!e.some(c=>u.includes(c)))throw r.warn(p(t,"auth.authorize.denied",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("FORBIDDEN",s);r.info(p(t,"auth.authorize.passed",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),await a();}}n(Nr,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Nr(s,e,r)},"authorize")}n(ne,"createAuthorize");function Wr(...e){return Nr(e,N,"sentri")}n(Wr,"authorize");var Zr=new l("FORBIDDEN","You do not have permission to perform this action");function Sr(e,r,t){return async(s,i)=>{let a=s.get("user"),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.permit.unauthenticated",{...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Not authenticated");let h=a.id;if(e.roles&&e.roles.length>0){let u=a.roles;if(e.roles.some(c=>u.includes(c))){r.info(p(t,"auth.permit.role_bypass",{userId:h,bypassedByRole:true,...d!==void 0&&{requestId:d}})),await i();return}}try{let u=e.check(s);if(u instanceof Promise?await u:u)r.info(p(t,"auth.permit.passed",{userId:h,...d!==void 0&&{requestId:d}})),await i();else throw r.warn(p(t,"auth.permit.denied",{userId:h,...d!==void 0&&{requestId:d}})),Zr}catch(u){throw u}}}n(Sr,"createPermitHandler");function oe(e,r){return n(function(s){return Sr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Jr(e){return Sr(typeof e=="function"?{check:e}:e,N,"sentri")}n(Jr,"permit");function ie(e){return (r,t)=>r instanceof l?t.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode):(e?.onUnhandled?.(r),t.json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null},500))}n(ie,"createErrorHandler");var qe=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(),a=this.store.get(r);if((!a||a.expiresAt<i)&&(a={count:0,expiresAt:i+s},this.store.set(r,a)),a.count>=t){let d=Math.ceil((a.expiresAt-i)/1e3);throw new l("RATE_LIMIT_EXCEEDED",`Rate limit exceeded. Try again in ${d} seconds.`)}a.count++;}},Fe=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}`,a=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(i).expire(i,a,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new l("RATE_LIMIT_EXCEEDED","Rate limit exceeded. Try again later.")}catch(d){if(d instanceof l&&d.code==="RATE_LIMIT_EXCEEDED")throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},V=null;async function Cr(e,r){if(V)return V;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"}),V=new Fe(s,r??N),V}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return V=new qe,V}n(Cr,"getRateLimiter");var De=8,M=72,Ne=255,Or=100,j=50;function g(e){return new l("VALIDATION_ERROR",e)}n(g,"badRequest");function D(e,r,t,s){return e.json({error:false,statusCode:r,message:t,data:s},r)}n(D,"ok");function b(e,r){return e.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode)}n(b,"fail");async function P(e){try{let r=await e.req.json();if(r==null||typeof r!="object"||Array.isArray(r))throw new l("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you set Content-Type: application/json?");return r}catch(r){throw r instanceof l?r:new l("VALIDATION_ERROR","Invalid JSON body. Did you set Content-Type: application/json?")}}n(P,"parseBody");function Qr(e,r){if(!r.apiKey)return;let t=e.req.header("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new l("UNAUTHORIZED","Invalid or missing API key")}n(Qr,"validateApiKey");function Pe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Pe,"fireHook");function et(e){return e.startsWith("RS")||e.startsWith("PS")}n(et,"isRSA");function Ke(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>Or)throw g(`identifiers[${r}].type must not exceed ${Or} 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>Ne)throw g(`identifiers[${r}].value must not exceed ${Ne} characters`);return {type:t.type,value:t.value}}n(Ke,"validateIdentifierInput");function _(e){let r=e.get("requestId");return r!==void 0?{requestId:r}:{}}n(_,"reqId");function br(e){let r=new hono.Hono,t=e,s=R(t),i=e.logger??N,a=e.loggerService??"sentri",d=F(e),h=ne(i,a),u=oe(i,a),c=Cr(e.redisUrl,i),A=e.router?.register??(o=>pe(o,t)),C=e.router?.login??(o=>me(o,t)),x=e.router?.refresh??(o=>H(o,t)),O=e.router?.logout??(o=>o!==void 0?ye(o,t):Promise.resolve()),v=e.router?.logoutAll??(o=>Ie(o,t)),L=e.router?.getUser??(o=>ge(o,t)),ae=e.router?.assignRoles??((o,w)=>_e(o,w,t)),ue=e.router?.changePassword??((o,w,f)=>Re(o,w,f,t)),Ur=e.router?.bulkCreateIdentifiers??((o,w)=>ve(o,w,t)),Lr=e.router?.bulkUpdateIdentifiers??((o,w)=>Ae(o,w,t)),qr=e.router?.bulkDeleteIdentifiers??((o,w)=>Ee(o,w,t)),de=u(o=>!!o.get("user"));return r.onError(ie()),et(s.algorithm)&&r.get("/keys",o=>(o.header("Cache-Control","public, max-age=3600"),o.json(er(e.secret)))),r.post("/register",async o=>{let w=Date.now();try{Qr(o,e);let f=await P(o),{identifiers:y,password:I,roles:m}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>j)throw g(`identifiers must not exceed ${j} entries`);let E=y.map((X,Se)=>Ke(X,Se));if(typeof I!="string"||I.length<De)throw g(`password is required and must be at least ${De} characters`);if(I.length>M)throw g(`password must not exceed ${M} characters`);if(m!==void 0&&!Array.isArray(m))throw g("roles must be an array of strings when provided");if(Array.isArray(m)&&!m.every(X=>typeof X=="string"))throw g("each role must be a string");let S=Array.isArray(m)?m:void 0,k=S!==void 0?{identifiers:E,password:I,roles:S}:{identifiers:E,password:I},K=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",B=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let X=await c,Se=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${K}`,Se,900*1e3);}let z=await A(k,{ip:K,userAgent:B});return z.success?(i.info(p(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,201,"User registered successfully",{user:z.user})):(i.warn(p(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,z.error))}catch(f){throw f}}),r.post("/login",async o=>{let w=Date.now();try{let f=await P(o),{identifier:y,password:I}=f;if(typeof y!="string"||y.trim().length===0)throw g("identifier is required and must be a non-empty string");if(y.length>Ne)throw g(`identifier must not exceed ${Ne} characters`);if(typeof I!="string"||I.length===0)throw g("password is required");if(I.length>M)throw g(`password must not exceed ${M} characters`);let m=y.trim(),E=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",S=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let K=await c,B=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${E}`,B,900*1e3);}let k=await C({identifier:m,password:I},{ip:E,userAgent:S});return k.success?(Pe(()=>e.hooks?.onLoginSuccess?.(k.user,{ip:E,userAgent:S})),te(o,k.refreshToken,e),se(o,k.accessToken,e),i.info(p(a,"auth.login.success",{userId:k.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Login successful",{accessToken:k.accessToken,user:k.user})):(Pe(()=>e.hooks?.onLoginFailed?.(m,k.error,{ip:E})),i.warn(p(a,"auth.login.failure",{errorCode:k.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,k.error))}catch(f){throw f}}),r.post("/refresh",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));if(!f)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let y=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",I=o.req.header("user-agent")||"Unknown",m=await x(f,{ip:y,userAgent:I});return m.success?(te(o,m.refreshToken,e),se(o,m.accessToken,e),i.info(p(a,"auth.refresh.success",{userId:m.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Token refreshed",{accessToken:m.accessToken})):(Te(o,e),i.warn(p(a,"auth.refresh.failure",{errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/logout",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));return await O(f),Te(o,e),Le(o,e),i.info(p(a,"auth.logout",{duration_ms:Date.now()-w,..._(o)})),D(o,200,"Logged out",null)}catch(f){throw f}}),r.post("/logout-all",d,async o=>{let w=Date.now();try{let f=o.get("user").id;return await v(f),Pe(()=>e.hooks?.onLogout?.(f)),Te(o,e),Le(o,e),i.info(p(a,"auth.logout_all",{userId:f,duration_ms:Date.now()-w,..._(o)})),D(o,200,"All sessions revoked",null)}catch(f){throw f}}),r.get("/me",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.success",{userId:f.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"OK",f.user)):(i.warn(p(a,"auth.me.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.get("/me/identifiers",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.identifiers.success",{userId:f.user.id,count:f.user.identifiers?.length??0,duration_ms:Date.now()-w,..._(o)})),D(o,200,"OK",{identifiers:f.user.identifiers??[]})):(i.warn(p(a,"auth.me.identifiers.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.post("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>j)throw g(`identifiers must not exceed ${j} entries`);let I=y.map((E,S)=>Ke(E,S)),m=await Ur(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.created",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),D(o,201,"Identifiers added successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.create_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.put("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>j)throw g(`identifiers must not exceed ${j} entries`);let I=y.map((E,S)=>{if(typeof E!="object"||E===null||Array.isArray(E))throw g(`identifiers[${S}] must be an object`);let k=E;if(typeof k.id!="string"||k.id.trim().length===0)throw g(`identifiers[${S}].id is required and must be a non-empty string`);let{type:K,value:B}=Ke(E,S);return {id:k.id,type:K,value:B}}),m=await Lr(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.updated",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Identifiers updated successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.update_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.delete("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{ids:y}=f;if(!Array.isArray(y)||y.length===0)throw g("ids is required and must be a non-empty array of strings");if(!y.every(m=>typeof m=="string"))throw g("each id must be a string");let I=await qr(o.get("user").id,y);return I.success?(i.info(p(a,"auth.identifiers.deleted",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Identifiers deleted successfully",{identifiers:I.identifiers})):(i.warn(p(a,"auth.identifiers.delete_failure",{userId:o.get("user").id,errorCode:I.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,I.error))}catch(f){throw f}}),r.patch("/me/password",d,de,async o=>{let w=Date.now();try{let f=await P(o),{currentPassword:y,newPassword:I}=f;if(typeof y!="string"||y.length===0)throw g("currentPassword is required");if(typeof I!="string"||I.length<De)throw g(`newPassword must be at least ${De} characters`);if(I.length>M)throw g(`newPassword must not exceed ${M} characters`);if(y===I)throw g("newPassword must be different from currentPassword");let m=await ue(o.get("user").id,y,I);return m.success?(i.info(p(a,"auth.password.changed",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Password updated successfully. All sessions have been revoked.",null)):(i.warn(p(a,"auth.password.change_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/users/:userId/roles",d,h("admin"),async o=>{let w=Date.now();try{let f=await P(o),{roles:y}=f,I=o.req.param("userId");if(!I)throw g("userId is required");if(!Array.isArray(y)||y.length===0)throw g("roles must be a non-empty array of strings");if(!y.every(E=>typeof E=="string"))throw g("each role must be a string");let m=await ae(I,y);return m.success?(i.info(p(a,"auth.roles.assigned",{targetUserId:I,roles:y,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Roles assigned successfully",{user:m.user})):(i.warn(p(a,"auth.roles.assign_failure",{targetUserId:I,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.get("/sessions",F(e),async o=>{let w=Date.now(),f=o.get("user").id,I=await(e.router?.getSessions??(m=>Er(m,t)))(f);return i.info(p(a,"auth.sessions.get",{userId:f,duration_ms:Date.now()-w,..._(o)})),D(o,200,"OK",{sessions:I})}),r.delete("/sessions/:id",F(e),async o=>{let w=Date.now(),f=o.get("user").id,y=o.req.param("id");return await(e.router?.revokeSession??((m,E)=>kr(m,E,t)))(f,y),i.info(p(a,"auth.sessions.revoke",{userId:f,sessionId:y,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Session revoked",null)}),r}n(br,"createAuthRouter");function Xs(e){if(e.mode==="client"){let u={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};ze(u);let c=u.logger??N,A=u.loggerService??"sentri",C=ne(c,A),x=oe(c,A);return {protect:n(()=>F(u),"protect"),authorize:n((...O)=>C(...O),"authorize"),permit:n(O=>x(O),"permit"),errorHandler:n(O=>ie(O),"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??N,i=t.loggerService??"sentri",a=R(t),d=ne(s,i),h=oe(s,i);return {protect:n(()=>F(t),"protect"),authorize:n((...u)=>d(...u),"authorize"),permit:n(u=>h(u),"permit"),errorHandler:n(u=>ie(u),"errorHandler"),router:n(()=>br(t),"router"),migrate:n(()=>Xe(T(t.dialect)),"migrate"),getCurrentAccessToken:n(u=>xe(u,t),"getCurrentAccessToken"),hashPassword:n(u=>G(u,a.saltRounds),"hashPassword"),verifyPassword:n((u,c)=>W(u,c),"verifyPassword"),signAccessToken:n(u=>J(u,t),"signAccessToken"),signRefreshToken:n(u=>Y(u,t),"signRefreshToken"),verifyAccessToken:n(u=>ce(u,t),"verifyAccessToken"),verifyRefreshToken:n(u=>Q(u,t),"verifyRefreshToken"),register:n(u=>pe(u,t),"register"),login:n(u=>me(u,t),"login"),refresh:n(u=>H(u,t),"refresh"),logout:n(u=>ye(u,t),"logout"),logoutAll:n(u=>Ie(u,t),"logoutAll"),getUser:n(u=>ge(u,t),"getUser"),changePassword:n((u,c,A)=>Re(u,c,A,t),"changePassword"),assignRoles:n((u,c)=>_e(u,c,t),"assignRoles"),bulkCreateIdentifiers:n((u,c)=>ve(u,c,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((u,c)=>Ae(u,c,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((u,c)=>Ee(u,c,t),"bulkDeleteIdentifiers")}}n(Xs,"createAuthHono");exports.SENTRI_ERROR_STATUS=$e;exports.SentriError=l;exports.authorize=Wr;exports.createAuthHono=Xs;exports.createAuthRouter=br;exports.createAuthorize=ne;exports.createErrorHandler=ie;exports.createPermit=oe;exports.getCurrentAccessToken=xe;exports.permit=Jr;exports.protect=F;
@@ -1,7 +1,7 @@
1
+ import { A as AuthUser, a as AuthConfig, S as SentriLogger, b as ServerAuthConfig, 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 { MiddlewareHandler, Context, Next, Hono } from 'hono';
3
- import { AuthUser, AuthConfig, SentriLogger, ServerAuthConfig, 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
  interface SentriHonoVariables {
7
7
  user: AuthUser | undefined;
@@ -62,6 +62,7 @@ interface CreateHonoServerOptions<TRole extends string = string> {
62
62
  hooks?: AuthHooks;
63
63
  router?: RouterHandlers;
64
64
  isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
65
+ rateLimit?: RateLimitOptions | boolean;
65
66
  redisUrl?: string;
66
67
  logger?: SentriLogger;
67
68
  loggerService?: string;
@@ -1,7 +1,7 @@
1
+ import { A as AuthUser, a as AuthConfig, S as SentriLogger, b as ServerAuthConfig, 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 { MiddlewareHandler, Context, Next, Hono } from 'hono';
3
- import { AuthUser, AuthConfig, SentriLogger, ServerAuthConfig, 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
  interface SentriHonoVariables {
7
7
  user: AuthUser | undefined;
@@ -62,6 +62,7 @@ interface CreateHonoServerOptions<TRole extends string = string> {
62
62
  hooks?: AuthHooks;
63
63
  router?: RouterHandlers;
64
64
  isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
65
+ rateLimit?: RateLimitOptions | boolean;
65
66
  redisUrl?: string;
66
67
  logger?: SentriLogger;
67
68
  loggerService?: string;
@@ -1 +1 @@
1
- import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import We from'bcrypt';import Z from'jsonwebtoken';import {setCookie,deleteCookie}from'hono/cookie';import {Hono}from'hono';var Fr=Object.defineProperty;var n=(e,r)=>Fr(e,"name",{value:r,configurable:true});var $e=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),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??$e[r]??500;}};var He=new WeakMap,Ve=32,je=10,Me=31;function ze(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<Ve)throw new l("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<je||s>Me)throw new l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${je} and ${Me}`);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(ze,"validateConfig");function R(e){let r=He.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 He.set(e,t),t}n(R,"resolveServerConfig");var Pr=/^(\d+)([smhdw])$/,Kr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Be=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=Be.get(e);if(r!==void 0)return r;let t=Pr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Kr[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 Be.set(e,i),i}n($,"parseExpiry");var D={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 Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Xe,"runMigrations");var Ge=new Map;function T(e){let r=Ge.get(e);return r||(r=new Kysely({dialect:e}),Ge.set(e,r)),r}n(T,"getDatabase");async function G(e,r=12){return We.hash(e,r)}n(G,"hashPassword");async function W(e,r){return We.compare(e,r)}n(W,"verifyPassword");var Ze=new Map,Je=new Map,jr=3600*1e3;function Qe(e){let r=Ze.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),i=s.export({format:"jwk"}),d=createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),h={...i,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:h},Ze.set(e,r);}return r}n(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}n(er,"buildJwks");function rr(e){return Qe(e).publicKey}n(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<jr)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 a=i.keys[0],d=createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,or=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}n(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}n(ar,"getHsSecrets");function Br(e){let r=nr.get(e);return r||(r=createPrivateKey(e),nr.set(e,r)),r}n(Br,"getRsPrivateKey");function ur(e){let r=R(e);if(ir(r.algorithm)){let i=Br(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}n(ur,"getSigningKeys");function dr(e,r){let t=R(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(dr,"getVerifyKey");function cr(e,r,t,s){let i=`${t}:${s}`,a=or.get(i);return a||(a={expiresIn:t,algorithm:s},or.set(i,a)),Z.sign(e,r,a)}n(cr,"sign");function lr(e,r,t){try{let s=Z.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 Z.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function J(e,r){let t=R(r),{accessKey:s}=ur(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=R(r),{refreshKey:s}=ur(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function ce(e,r){let t=R(r),s=dr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=R(r),s=dr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=Z.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 Z.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Oe(e){try{return JSON.parse(e)}catch{return []}}n(Oe,"parseRoles");function zr(e){return JSON.stringify(e)}n(zr,"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 ee(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function le(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(le,"findUserById");async function pr(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(pr,"createIdentifiers");async function re(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(re,"findIdentifiersByUserId");async function be(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(be,"findIdentifierById");async function mr(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(mr,"countIdentifiersByUserId");async function yr(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(yr,"updateIdentifier");async function Ir(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(Ir,"deleteIdentifiers");async function gr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(gr,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:zr(t)}).where("id","=",r).execute();}n(Rr,"updateUserRoles");async function Ue(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(Ue,"createSession");async function fe(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:Oe(t.roles)}}:null}n(fe,"findSessionById");async function he(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(he,"deleteSession");async function _r(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(_r,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function vr(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(vr,"findSessionsByUserId");async function pe(e,r,t){let s=R(r),i=T(r.dialect),a=e.roles??[],d=a.filter(v=>!s.validRolesSet.has(v));if(d.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let h=e.identifiers.map(v=>({type:v.type.trim(),value:v.value.trim()})),u=h.filter(v=>!s.validIdentifiersSet.has(v.type));if(u.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${u.map(v=>v.type).join(", ")}`)};if(new Set(h.map(v=>v.value)).size!==h.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let v of h)if(await ee(i,v.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${v.value}`)};let A=await G(e.password,s.saltRounds),{userId:C,identifierRows:x}=await i.transaction().execute(async v=>{let L=randomUUID();await v.insertInto("sentri_users").values({id:L,password_hash:A,roles:JSON.stringify(a)}).execute();let ae=h.map(ue=>({id:randomUUID(),user_id:L,type:ue.type,value:ue.value}));return await v.insertInto("sentri_identifiers").values(ae).execute(),{userId:L,identifierRows:ae}}),O={success:true,user:{id:C,roles:a,identifiers:x.map(v=>({id:v.id,type:v.type,value:v.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(O.user),O}n(pe,"register");async function me(e,r,t){let s=R(r),i=T(r.dialect),a=await ee(i,e.identifier.trim());if(!a){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}if(!await W(e.password,a.passwordHash)){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:a.id,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),c={id:a.id,roles:a.roles},A=J({id:a.id,roles:a.roles,sessionId:u.id},r),C=Y(u.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(c,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:A,refreshToken:C,user:c}}n(me,"login");async function H(e,r,t){let s=R(r),i=T(r.dialect),a;try{({sessionId:a}=Q(e,r));}catch(x){return x instanceof l?{success:false,error:x}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let d=await fe(i,a);if(!d)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await we(i,d.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await he(i,a),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:d.userId,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(i,a,u.id);let c={id:d.user.id,roles:d.user.roles},A=J({...c,sessionId:u.id},r),C=Y(u.id,r);return {success:true,accessToken:A,refreshToken:C,user:c}}n(H,"refresh");async function ye(e,r){let t=T(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await fe(t,s);i&&(await he(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(ye,"logout");async function Ie(e,r){let t=T(r.dialect);await we(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function ge(e,r){let t=T(r.dialect),s=await le(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await re(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(ge,"getUser");async function Re(e,r,t,s){let i=R(s),a=T(s.dialect),d=await le(a,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await W(r,d.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let u=await G(t,i.saltRounds);return await gr(a,e,u),await we(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function _e(e,r,t){let s=R(t),i=T(t.dialect),a=r.filter(c=>!s.validRolesSet.has(c));if(a.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let d=await le(i,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let h=new Set(d.roles);for(let c of r)h.add(c);let u=Array.from(h);return await Rr(i,e,u),{success:true,user:{id:d.id,roles:u}}}n(_e,"assignRoles");async function ve(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let a=r.map(c=>({type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a)if(await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await pr(i,e,a),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(ve,"bulkCreateIdentifiers");async function Ae(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let a=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a){let A=await be(i,c.id,e);if(!A)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(A.value!==c.value&&await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await i.transaction().execute(async c=>{for(let A of a)await yr(c,A.id,e,{type:A.type,value:A.value});}),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(Ae,"bulkUpdateIdentifiers");async function ke(e,r,t){let s=T(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 h of i)if(!await be(s,h,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${h}`)};return await mr(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,i),{success:true,identifiers:(await re(s,e)).map(h=>({id:h.id,type:h.type,value:h.value}))})}n(ke,"bulkDeleteIdentifiers");async function kr(e,r){let t=T(r.dialect);return (await vr(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(kr,"getSessions");async function Er(e,r,t){let s=T(t.dialect),i=await fe(s,r);i&&i.userId===e&&await he(s,r);}n(Er,"revokeSession");function U(e){return R(e).cookieName}n(U,"getCookieName");function Ee(e){return R(e).accessCookieName}n(Ee,"getAccessCookieName");function q(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),a=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(q,"readCookie");function Nr(e){let r=(e??"strict").toLowerCase();return r==="lax"?"Lax":r==="none"?"None":"Strict"}n(Nr,"normalizeSameSite");function te(e,r,t){let s=t.cookie??{},i=$(R(t).refreshExpiresIn)/1e3;setCookie(e,U(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:Nr(s.sameSite),path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Te(e,r){let t=r.cookie??{};deleteCookie(e,U(r),{path:t.path??"/"});}n(Te,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=$(R(t).accessExpiresIn)/1e3;setCookie(e,Ee(t),r,{httpOnly:false,secure:s.secure??false,sameSite:Nr(s.sameSite),path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function Le(e,r){if(!r.accessCookie)return;let t=r.accessCookie;deleteCookie(e,Ee(r),{path:t.path??"/"});}n(Le,"clearAccessCookieFromConfig");function xe(e,r){let t=e.req.header("authorization");return t?.startsWith("Bearer ")?t.slice(7):q(e.req.header("cookie"),Ee(r))}n(xe,"getCurrentAccessToken");function F(e){let r=e.logger??D,t=e.loggerService??"sentri";return e.mode==="client"?Xr(e.keyUri,r,t):Gr(e,r,t)}n(F,"protect");function Xr(e,r,t){return async(s,i)=>{let a=s.req.header("authorization"),d=a?.startsWith("Bearer ")?a.slice(7):void 0,h=s.get("requestId");if(!d)throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let u=await tr(e),c=fr(d,u);s.set("user",{id:c.id,roles:c.roles}),r.info(p(t,"auth.protect.success",{mode:"client",userId:c.id,...h!==void 0&&{requestId:h}})),await i();}catch(u){let c=u instanceof l?u.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:c,...h!==void 0&&{requestId:h}})),u}}}n(Xr,"protectClient");function Gr(e,r,t){return async(s,i)=>{let a=xe(s,e),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let h=ce(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(h.sessionId))throw r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token has been revoked");s.set("user",{id:h.id,roles:h.roles}),r.info(p(t,"auth.protect.success",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),await i();}catch(h){if(h instanceof l&&h.code==="TOKEN_EXPIRED"){let u=q(s.req.header("cookie"),U(e));if(!u)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token expired. Please login again.");let c;try{c=await H(u,e);}catch{throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.")}if(!c.success)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:c.error.code,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.");te(s,c.refreshToken,e),se(s,c.accessToken,e),s.header("X-New-Access-Token",c.accessToken),s.set("user",c.user),r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:c.user.id,...d!==void 0&&{requestId:d}})),await i();}else {let u=h instanceof l?h.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...d!==void 0&&{requestId:d}})),h}}}}n(Gr,"protectServer");function Dr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async(i,a)=>{let d=i.get("user"),h=i.get("requestId");if(!d)throw r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Not authenticated");let u=d.roles;if(!e.some(c=>u.includes(c)))throw r.warn(p(t,"auth.authorize.denied",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("FORBIDDEN",s);r.info(p(t,"auth.authorize.passed",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),await a();}}n(Dr,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Dr(s,e,r)},"authorize")}n(ne,"createAuthorize");function Wr(...e){return Dr(e,D,"sentri")}n(Wr,"authorize");var Zr=new l("FORBIDDEN","You do not have permission to perform this action");function Sr(e,r,t){return async(s,i)=>{let a=s.get("user"),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.permit.unauthenticated",{...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Not authenticated");let h=a.id;if(e.roles&&e.roles.length>0){let u=a.roles;if(e.roles.some(c=>u.includes(c))){r.info(p(t,"auth.permit.role_bypass",{userId:h,bypassedByRole:true,...d!==void 0&&{requestId:d}})),await i();return}}try{let u=e.check(s);if(u instanceof Promise?await u:u)r.info(p(t,"auth.permit.passed",{userId:h,...d!==void 0&&{requestId:d}})),await i();else throw r.warn(p(t,"auth.permit.denied",{userId:h,...d!==void 0&&{requestId:d}})),Zr}catch(u){throw u}}}n(Sr,"createPermitHandler");function oe(e,r){return n(function(s){return Sr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Jr(e){return Sr(typeof e=="function"?{check:e}:e,D,"sentri")}n(Jr,"permit");function ie(e){return (r,t)=>r instanceof l?t.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode):(e?.onUnhandled?.(r),t.json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null},500))}n(ie,"createErrorHandler");var qe=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(),a=this.store.get(r);if((!a||a.expiresAt<i)&&(a={count:0,expiresAt:i+s},this.store.set(r,a)),a.count>t){let d=Math.ceil((a.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${d} seconds.`)}a.count++;}},Fe=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}`,a=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(i).expire(i,a,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(d){if(d instanceof Error&&d.message.includes("Rate limit exceeded"))throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},V=null;async function Cr(e,r){if(V)return V;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"}),V=new Fe(s,r??D),V}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return V=new qe,V}n(Cr,"getRateLimiter");var Ne=8,j=72,De=255,Or=100,M=50;function g(e){return new l("VALIDATION_ERROR",e)}n(g,"badRequest");function N(e,r,t,s){return e.json({error:false,statusCode:r,message:t,data:s},r)}n(N,"ok");function b(e,r){return e.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode)}n(b,"fail");async function P(e){try{let r=await e.req.json();if(r==null||typeof r!="object"||Array.isArray(r))throw new l("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you set Content-Type: application/json?");return r}catch(r){throw r instanceof l?r:new l("VALIDATION_ERROR","Invalid JSON body. Did you set Content-Type: application/json?")}}n(P,"parseBody");function Qr(e,r){if(!r.apiKey)return;let t=e.req.header("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new l("UNAUTHORIZED","Invalid or missing API key")}n(Qr,"validateApiKey");function Pe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Pe,"fireHook");function et(e){return e.startsWith("RS")||e.startsWith("PS")}n(et,"isRSA");function Ke(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>Or)throw g(`identifiers[${r}].type must not exceed ${Or} 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>De)throw g(`identifiers[${r}].value must not exceed ${De} characters`);return {type:t.type,value:t.value}}n(Ke,"validateIdentifierInput");function _(e){let r=e.get("requestId");return r!==void 0?{requestId:r}:{}}n(_,"reqId");function br(e){let r=new Hono,t=e,s=R(t),i=e.logger??D,a=e.loggerService??"sentri",d=F(e),h=ne(i,a),u=oe(i,a),c=Cr(e.redisUrl,i),A=e.router?.register??(o=>pe(o,t)),C=e.router?.login??(o=>me(o,t)),x=e.router?.refresh??(o=>H(o,t)),O=e.router?.logout??(o=>o!==void 0?ye(o,t):Promise.resolve()),v=e.router?.logoutAll??(o=>Ie(o,t)),L=e.router?.getUser??(o=>ge(o,t)),ae=e.router?.assignRoles??((o,w)=>_e(o,w,t)),ue=e.router?.changePassword??((o,w,f)=>Re(o,w,f,t)),Ur=e.router?.bulkCreateIdentifiers??((o,w)=>ve(o,w,t)),Lr=e.router?.bulkUpdateIdentifiers??((o,w)=>Ae(o,w,t)),qr=e.router?.bulkDeleteIdentifiers??((o,w)=>ke(o,w,t)),de=u(o=>!!o.get("user"));return r.onError(ie()),et(s.algorithm)&&r.get("/keys",o=>(o.header("Cache-Control","public, max-age=3600"),o.json(er(e.secret)))),r.post("/register",async o=>{let w=Date.now();try{Qr(o,e);let f=await P(o),{identifiers:y,password:I,roles:m}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>M)throw g(`identifiers must not exceed ${M} entries`);let k=y.map((X,Se)=>Ke(X,Se));if(typeof I!="string"||I.length<Ne)throw g(`password is required and must be at least ${Ne} characters`);if(I.length>j)throw g(`password must not exceed ${j} characters`);if(m!==void 0&&!Array.isArray(m))throw g("roles must be an array of strings when provided");if(Array.isArray(m)&&!m.every(X=>typeof X=="string"))throw g("each role must be a string");let S=Array.isArray(m)?m:void 0,E=S!==void 0?{identifiers:k,password:I,roles:S}:{identifiers:k,password:I},K=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",B=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let X=await c,Se=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${K}`,Se,900*1e3);}let z=await A(E,{ip:K,userAgent:B});return z.success?(i.info(p(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,201,"User registered successfully",{user:z.user})):(i.warn(p(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,z.error))}catch(f){throw f}}),r.post("/login",async o=>{let w=Date.now();try{let f=await P(o),{identifier:y,password:I}=f;if(typeof y!="string"||y.trim().length===0)throw g("identifier is required and must be a non-empty string");if(y.length>De)throw g(`identifier must not exceed ${De} characters`);if(typeof I!="string"||I.length===0)throw g("password is required");if(I.length>j)throw g(`password must not exceed ${j} characters`);let m=y.trim(),k=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",S=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let K=await c,B=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${k}`,B,900*1e3);}let E=await C({identifier:m,password:I},{ip:k,userAgent:S});return E.success?(Pe(()=>e.hooks?.onLoginSuccess?.(E.user,{ip:k,userAgent:S})),te(o,E.refreshToken,e),se(o,E.accessToken,e),i.info(p(a,"auth.login.success",{userId:E.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Login successful",{accessToken:E.accessToken,user:E.user})):(Pe(()=>e.hooks?.onLoginFailed?.(m,E.error,{ip:k})),i.warn(p(a,"auth.login.failure",{errorCode:E.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,E.error))}catch(f){throw f}}),r.post("/refresh",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));if(!f)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let y=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",I=o.req.header("user-agent")||"Unknown",m=await x(f,{ip:y,userAgent:I});return m.success?(te(o,m.refreshToken,e),se(o,m.accessToken,e),i.info(p(a,"auth.refresh.success",{userId:m.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Token refreshed",{accessToken:m.accessToken})):(Te(o,e),i.warn(p(a,"auth.refresh.failure",{errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/logout",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));return await O(f),Te(o,e),Le(o,e),i.info(p(a,"auth.logout",{duration_ms:Date.now()-w,..._(o)})),N(o,200,"Logged out",null)}catch(f){throw f}}),r.post("/logout-all",d,async o=>{let w=Date.now();try{let f=o.get("user").id;return await v(f),Pe(()=>e.hooks?.onLogout?.(f)),Te(o,e),Le(o,e),i.info(p(a,"auth.logout_all",{userId:f,duration_ms:Date.now()-w,..._(o)})),N(o,200,"All sessions revoked",null)}catch(f){throw f}}),r.get("/me",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.success",{userId:f.user.id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"OK",f.user)):(i.warn(p(a,"auth.me.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.get("/me/identifiers",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.identifiers.success",{userId:f.user.id,count:f.user.identifiers?.length??0,duration_ms:Date.now()-w,..._(o)})),N(o,200,"OK",{identifiers:f.user.identifiers??[]})):(i.warn(p(a,"auth.me.identifiers.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.post("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>M)throw g(`identifiers must not exceed ${M} entries`);let I=y.map((k,S)=>Ke(k,S)),m=await Ur(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.created",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),N(o,201,"Identifiers added successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.create_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.put("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>M)throw g(`identifiers must not exceed ${M} entries`);let I=y.map((k,S)=>{if(typeof k!="object"||k===null||Array.isArray(k))throw g(`identifiers[${S}] must be an object`);let E=k;if(typeof E.id!="string"||E.id.trim().length===0)throw g(`identifiers[${S}].id is required and must be a non-empty string`);let{type:K,value:B}=Ke(k,S);return {id:E.id,type:K,value:B}}),m=await Lr(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.updated",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Identifiers updated successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.update_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.delete("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{ids:y}=f;if(!Array.isArray(y)||y.length===0)throw g("ids is required and must be a non-empty array of strings");if(!y.every(m=>typeof m=="string"))throw g("each id must be a string");let I=await qr(o.get("user").id,y);return I.success?(i.info(p(a,"auth.identifiers.deleted",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Identifiers deleted successfully",{identifiers:I.identifiers})):(i.warn(p(a,"auth.identifiers.delete_failure",{userId:o.get("user").id,errorCode:I.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,I.error))}catch(f){throw f}}),r.patch("/me/password",d,de,async o=>{let w=Date.now();try{let f=await P(o),{currentPassword:y,newPassword:I}=f;if(typeof y!="string"||y.length===0)throw g("currentPassword is required");if(typeof I!="string"||I.length<Ne)throw g(`newPassword must be at least ${Ne} characters`);if(I.length>j)throw g(`newPassword must not exceed ${j} characters`);if(y===I)throw g("newPassword must be different from currentPassword");let m=await ue(o.get("user").id,y,I);return m.success?(i.info(p(a,"auth.password.changed",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Password updated successfully. All sessions have been revoked.",null)):(i.warn(p(a,"auth.password.change_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/users/:userId/roles",d,h("admin"),async o=>{let w=Date.now();try{let f=await P(o),{roles:y}=f,I=o.req.param("userId");if(!I)throw g("userId is required");if(!Array.isArray(y)||y.length===0)throw g("roles must be a non-empty array of strings");if(!y.every(k=>typeof k=="string"))throw g("each role must be a string");let m=await ae(I,y);return m.success?(i.info(p(a,"auth.roles.assigned",{targetUserId:I,roles:y,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Roles assigned successfully",{user:m.user})):(i.warn(p(a,"auth.roles.assign_failure",{targetUserId:I,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.get("/sessions",F(e),async o=>{let w=Date.now(),f=o.get("user").id,I=await(e.router?.getSessions??(m=>kr(m,t)))(f);return i.info(p(a,"auth.sessions.get",{userId:f,duration_ms:Date.now()-w,..._(o)})),N(o,200,"OK",{sessions:I})}),r.delete("/sessions/:id",F(e),async o=>{let w=Date.now(),f=o.get("user").id,y=o.req.param("id");return await(e.router?.revokeSession??((m,k)=>Er(m,k,t)))(f,y),i.info(p(a,"auth.sessions.revoke",{userId:f,sessionId:y,duration_ms:Date.now()-w,..._(o)})),N(o,200,"Session revoked",null)}),r}n(br,"createAuthRouter");function zs(e){if(e.mode==="client"){let u={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};ze(u);let c=u.logger??D,A=u.loggerService??"sentri",C=ne(c,A),x=oe(c,A);return {protect:n(()=>F(u),"protect"),authorize:n((...O)=>C(...O),"authorize"),permit:n(O=>x(O),"permit"),errorHandler:n(O=>ie(O),"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??D,i=t.loggerService??"sentri",a=R(t),d=ne(s,i),h=oe(s,i);return {protect:n(()=>F(t),"protect"),authorize:n((...u)=>d(...u),"authorize"),permit:n(u=>h(u),"permit"),errorHandler:n(u=>ie(u),"errorHandler"),router:n(()=>br(t),"router"),migrate:n(()=>Xe(T(t.dialect)),"migrate"),getCurrentAccessToken:n(u=>xe(u,t),"getCurrentAccessToken"),hashPassword:n(u=>G(u,a.saltRounds),"hashPassword"),verifyPassword:n((u,c)=>W(u,c),"verifyPassword"),signAccessToken:n(u=>J(u,t),"signAccessToken"),signRefreshToken:n(u=>Y(u,t),"signRefreshToken"),verifyAccessToken:n(u=>ce(u,t),"verifyAccessToken"),verifyRefreshToken:n(u=>Q(u,t),"verifyRefreshToken"),register:n(u=>pe(u,t),"register"),login:n(u=>me(u,t),"login"),refresh:n(u=>H(u,t),"refresh"),logout:n(u=>ye(u,t),"logout"),logoutAll:n(u=>Ie(u,t),"logoutAll"),getUser:n(u=>ge(u,t),"getUser"),changePassword:n((u,c,A)=>Re(u,c,A,t),"changePassword"),assignRoles:n((u,c)=>_e(u,c,t),"assignRoles"),bulkCreateIdentifiers:n((u,c)=>ve(u,c,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((u,c)=>Ae(u,c,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((u,c)=>ke(u,c,t),"bulkDeleteIdentifiers")}}n(zs,"createAuthHono");export{$e as SENTRI_ERROR_STATUS,l as SentriError,Wr as authorize,zs as createAuthHono,br as createAuthRouter,ne as createAuthorize,ie as createErrorHandler,oe as createPermit,xe as getCurrentAccessToken,Jr as permit,F as protect};
1
+ import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import We from'bcrypt';import Z from'jsonwebtoken';import {setCookie,deleteCookie}from'hono/cookie';import {Hono}from'hono';var Fr=Object.defineProperty;var n=(e,r)=>Fr(e,"name",{value:r,configurable:true});var $e=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401,RATE_LIMIT_EXCEEDED:429}),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??$e[r]??500;}};var He=new WeakMap,Ve=32,Me=10,je=31;function ze(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<Ve)throw new l("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Me||s>je)throw new l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Me} and ${je}`);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(ze,"validateConfig");function R(e){let r=He.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 He.set(e,t),t}n(R,"resolveServerConfig");var Pr=/^(\d+)([smhdw])$/,Kr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Be=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=Be.get(e);if(r!==void 0)return r;let t=Pr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Kr[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 Be.set(e,i),i}n($,"parseExpiry");var N={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 Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Xe,"runMigrations");var Ge=new Map;function T(e){let r=Ge.get(e);return r||(r=new Kysely({dialect:e}),Ge.set(e,r)),r}n(T,"getDatabase");async function G(e,r=12){return We.hash(e,r)}n(G,"hashPassword");async function W(e,r){return We.compare(e,r)}n(W,"verifyPassword");var Ze=new Map,Je=new Map,Mr=3600*1e3;function Qe(e){let r=Ze.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),i=s.export({format:"jwk"}),d=createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),h={...i,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:h},Ze.set(e,r);}return r}n(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}n(er,"buildJwks");function rr(e){return Qe(e).publicKey}n(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Mr)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 a=i.keys[0],d=createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,or=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}n(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}n(ar,"getHsSecrets");function Br(e){let r=nr.get(e);return r||(r=createPrivateKey(e),nr.set(e,r)),r}n(Br,"getRsPrivateKey");function ur(e){let r=R(e);if(ir(r.algorithm)){let i=Br(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}n(ur,"getSigningKeys");function dr(e,r){let t=R(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(dr,"getVerifyKey");function cr(e,r,t,s){let i=`${t}:${s}`,a=or.get(i);return a||(a={expiresIn:t,algorithm:s},or.set(i,a)),Z.sign(e,r,a)}n(cr,"sign");function lr(e,r,t){try{let s=Z.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 Z.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function J(e,r){let t=R(r),{accessKey:s}=ur(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=R(r),{refreshKey:s}=ur(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function ce(e,r){let t=R(r),s=dr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=R(r),s=dr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=Z.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 Z.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Oe(e){try{return JSON.parse(e)}catch{return []}}n(Oe,"parseRoles");function zr(e){return JSON.stringify(e)}n(zr,"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 ee(e,r){let t=await e.selectFrom("sentri_identifiers as i").innerJoin("sentri_users as u","u.id","i.user_id").select(["u.id","u.password_hash","u.roles"]).where("i.value","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function le(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Oe(t.roles)}:null}n(le,"findUserById");async function pr(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(pr,"createIdentifiers");async function re(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(wr)}n(re,"findIdentifiersByUserId");async function be(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(be,"findIdentifierById");async function mr(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(mr,"countIdentifiersByUserId");async function yr(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(yr,"updateIdentifier");async function Ir(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(Ir,"deleteIdentifiers");async function gr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(gr,"updateUserPassword");async function Rr(e,r,t){await e.updateTable("sentri_users").set({roles:zr(t)}).where("id","=",r).execute();}n(Rr,"updateUserRoles");async function Ue(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(Ue,"createSession");async function fe(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:Oe(t.roles)}}:null}n(fe,"findSessionById");async function he(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(he,"deleteSession");async function _r(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(_r,"markSessionReplaced");async function we(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(we,"deleteAllSessionsForUser");async function vr(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(vr,"findSessionsByUserId");async function pe(e,r,t){let s=R(r),i=T(r.dialect),a=e.roles??[],d=a.filter(v=>!s.validRolesSet.has(v));if(d.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let h=e.identifiers.map(v=>({type:v.type.trim(),value:v.value.trim()})),u=h.filter(v=>!s.validIdentifiersSet.has(v.type));if(u.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${u.map(v=>v.type).join(", ")}`)};if(new Set(h.map(v=>v.value)).size!==h.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let v of h)if(await ee(i,v.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${v.value}`)};let A=await G(e.password,s.saltRounds),{userId:C,identifierRows:x}=await i.transaction().execute(async v=>{let L=randomUUID();await v.insertInto("sentri_users").values({id:L,password_hash:A,roles:JSON.stringify(a)}).execute();let ae=h.map(ue=>({id:randomUUID(),user_id:L,type:ue.type,value:ue.value}));return await v.insertInto("sentri_identifiers").values(ae).execute(),{userId:L,identifierRows:ae}}),O={success:true,user:{id:C,roles:a,identifiers:x.map(v=>({id:v.id,type:v.type,value:v.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(O.user),O}n(pe,"register");async function me(e,r,t){let s=R(r),i=T(r.dialect),a=await ee(i,e.identifier.trim());if(!a){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}if(!await W(e.password,a.passwordHash)){let x=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:a.id,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),c={id:a.id,roles:a.roles},A=J({id:a.id,roles:a.roles,sessionId:u.id},r),C=Y(u.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(c,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:A,refreshToken:C,user:c}}n(me,"login");async function H(e,r,t){let s=R(r),i=T(r.dialect),a;try{({sessionId:a}=Q(e,r));}catch(x){return x instanceof l?{success:false,error:x}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let d=await fe(i,a);if(!d)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await we(i,d.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await he(i,a),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Ue(i,{userId:d.userId,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(i,a,u.id);let c={id:d.user.id,roles:d.user.roles},A=J({...c,sessionId:u.id},r),C=Y(u.id,r);return {success:true,accessToken:A,refreshToken:C,user:c}}n(H,"refresh");async function ye(e,r){let t=T(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await fe(t,s);i&&(await he(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(ye,"logout");async function Ie(e,r){let t=T(r.dialect);await we(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function ge(e,r){let t=T(r.dialect),s=await le(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await re(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(ge,"getUser");async function Re(e,r,t,s){let i=R(s),a=T(s.dialect),d=await le(a,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await W(r,d.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let u=await G(t,i.saltRounds);return await gr(a,e,u),await we(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function _e(e,r,t){let s=R(t),i=T(t.dialect),a=r.filter(c=>!s.validRolesSet.has(c));if(a.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let d=await le(i,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let h=new Set(d.roles);for(let c of r)h.add(c);let u=Array.from(h);return await Rr(i,e,u),{success:true,user:{id:d.id,roles:u}}}n(_e,"assignRoles");async function ve(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let a=r.map(c=>({type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a)if(await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await pr(i,e,a),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(ve,"bulkCreateIdentifiers");async function Ae(e,r,t){let s=R(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let a=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a){let A=await be(i,c.id,e);if(!A)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(A.value!==c.value&&await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await i.transaction().execute(async c=>{for(let A of a)await yr(c,A.id,e,{type:A.type,value:A.value});}),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(Ae,"bulkUpdateIdentifiers");async function Ee(e,r,t){let s=T(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 h of i)if(!await be(s,h,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${h}`)};return await mr(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,i),{success:true,identifiers:(await re(s,e)).map(h=>({id:h.id,type:h.type,value:h.value}))})}n(Ee,"bulkDeleteIdentifiers");async function Er(e,r){let t=T(r.dialect);return (await vr(t,e)).map(i=>({id:i.id,ipAddress:i.ipAddress,userAgent:i.userAgent,createdAt:i.createdAt,expiresAt:i.expiresAt}))}n(Er,"getSessions");async function kr(e,r,t){let s=T(t.dialect),i=await fe(s,r);i&&i.userId===e&&await he(s,r);}n(kr,"revokeSession");function U(e){return R(e).cookieName}n(U,"getCookieName");function ke(e){return R(e).accessCookieName}n(ke,"getAccessCookieName");function q(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),a=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(q,"readCookie");function Dr(e){let r=(e??"strict").toLowerCase();return r==="lax"?"Lax":r==="none"?"None":"Strict"}n(Dr,"normalizeSameSite");function te(e,r,t){let s=t.cookie??{},i=$(R(t).refreshExpiresIn)/1e3;setCookie(e,U(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:Dr(s.sameSite),path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Te(e,r){let t=r.cookie??{};deleteCookie(e,U(r),{path:t.path??"/"});}n(Te,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=$(R(t).accessExpiresIn)/1e3;setCookie(e,ke(t),r,{httpOnly:false,secure:s.secure??false,sameSite:Dr(s.sameSite),path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function Le(e,r){if(!r.accessCookie)return;let t=r.accessCookie;deleteCookie(e,ke(r),{path:t.path??"/"});}n(Le,"clearAccessCookieFromConfig");function xe(e,r){let t=e.req.header("authorization");return t?.startsWith("Bearer ")?t.slice(7):q(e.req.header("cookie"),ke(r))}n(xe,"getCurrentAccessToken");function F(e){let r=e.logger??N,t=e.loggerService??"sentri";return e.mode==="client"?Xr(e.keyUri,r,t):Gr(e,r,t)}n(F,"protect");function Xr(e,r,t){return async(s,i)=>{let a=s.req.header("authorization"),d=a?.startsWith("Bearer ")?a.slice(7):void 0,h=s.get("requestId");if(!d)throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let u=await tr(e),c=fr(d,u);s.set("user",{id:c.id,roles:c.roles}),r.info(p(t,"auth.protect.success",{mode:"client",userId:c.id,...h!==void 0&&{requestId:h}})),await i();}catch(u){let c=u instanceof l?u.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:c,...h!==void 0&&{requestId:h}})),u}}}n(Xr,"protectClient");function Gr(e,r,t){return async(s,i)=>{let a=xe(s,e),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let h=ce(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(h.sessionId))throw r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token has been revoked");s.set("user",{id:h.id,roles:h.roles}),r.info(p(t,"auth.protect.success",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),await i();}catch(h){if(h instanceof l&&h.code==="TOKEN_EXPIRED"){let u=q(s.req.header("cookie"),U(e));if(!u)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token expired. Please login again.");let c;try{c=await H(u,e);}catch{throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.")}if(!c.success)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:c.error.code,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.");te(s,c.refreshToken,e),se(s,c.accessToken,e),s.header("X-New-Access-Token",c.accessToken),s.set("user",c.user),r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:c.user.id,...d!==void 0&&{requestId:d}})),await i();}else {let u=h instanceof l?h.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...d!==void 0&&{requestId:d}})),h}}}}n(Gr,"protectServer");function Nr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async(i,a)=>{let d=i.get("user"),h=i.get("requestId");if(!d)throw r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Not authenticated");let u=d.roles;if(!e.some(c=>u.includes(c)))throw r.warn(p(t,"auth.authorize.denied",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),new l("FORBIDDEN",s);r.info(p(t,"auth.authorize.passed",{userId:d.id,userRoles:[...u],requiredRoles:e,...h!==void 0&&{requestId:h}})),await a();}}n(Nr,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Nr(s,e,r)},"authorize")}n(ne,"createAuthorize");function Wr(...e){return Nr(e,N,"sentri")}n(Wr,"authorize");var Zr=new l("FORBIDDEN","You do not have permission to perform this action");function Sr(e,r,t){return async(s,i)=>{let a=s.get("user"),d=s.get("requestId");if(!a)throw r.warn(p(t,"auth.permit.unauthenticated",{...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Not authenticated");let h=a.id;if(e.roles&&e.roles.length>0){let u=a.roles;if(e.roles.some(c=>u.includes(c))){r.info(p(t,"auth.permit.role_bypass",{userId:h,bypassedByRole:true,...d!==void 0&&{requestId:d}})),await i();return}}try{let u=e.check(s);if(u instanceof Promise?await u:u)r.info(p(t,"auth.permit.passed",{userId:h,...d!==void 0&&{requestId:d}})),await i();else throw r.warn(p(t,"auth.permit.denied",{userId:h,...d!==void 0&&{requestId:d}})),Zr}catch(u){throw u}}}n(Sr,"createPermitHandler");function oe(e,r){return n(function(s){return Sr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Jr(e){return Sr(typeof e=="function"?{check:e}:e,N,"sentri")}n(Jr,"permit");function ie(e){return (r,t)=>r instanceof l?t.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode):(e?.onUnhandled?.(r),t.json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null},500))}n(ie,"createErrorHandler");var qe=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(),a=this.store.get(r);if((!a||a.expiresAt<i)&&(a={count:0,expiresAt:i+s},this.store.set(r,a)),a.count>=t){let d=Math.ceil((a.expiresAt-i)/1e3);throw new l("RATE_LIMIT_EXCEEDED",`Rate limit exceeded. Try again in ${d} seconds.`)}a.count++;}},Fe=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}`,a=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(i).expire(i,a,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new l("RATE_LIMIT_EXCEEDED","Rate limit exceeded. Try again later.")}catch(d){if(d instanceof l&&d.code==="RATE_LIMIT_EXCEEDED")throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},V=null;async function Cr(e,r){if(V)return V;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"}),V=new Fe(s,r??N),V}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return V=new qe,V}n(Cr,"getRateLimiter");var De=8,M=72,Ne=255,Or=100,j=50;function g(e){return new l("VALIDATION_ERROR",e)}n(g,"badRequest");function D(e,r,t,s){return e.json({error:false,statusCode:r,message:t,data:s},r)}n(D,"ok");function b(e,r){return e.json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null},r.statusCode)}n(b,"fail");async function P(e){try{let r=await e.req.json();if(r==null||typeof r!="object"||Array.isArray(r))throw new l("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you set Content-Type: application/json?");return r}catch(r){throw r instanceof l?r:new l("VALIDATION_ERROR","Invalid JSON body. Did you set Content-Type: application/json?")}}n(P,"parseBody");function Qr(e,r){if(!r.apiKey)return;let t=e.req.header("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new l("UNAUTHORIZED","Invalid or missing API key")}n(Qr,"validateApiKey");function Pe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Pe,"fireHook");function et(e){return e.startsWith("RS")||e.startsWith("PS")}n(et,"isRSA");function Ke(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>Or)throw g(`identifiers[${r}].type must not exceed ${Or} 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>Ne)throw g(`identifiers[${r}].value must not exceed ${Ne} characters`);return {type:t.type,value:t.value}}n(Ke,"validateIdentifierInput");function _(e){let r=e.get("requestId");return r!==void 0?{requestId:r}:{}}n(_,"reqId");function br(e){let r=new Hono,t=e,s=R(t),i=e.logger??N,a=e.loggerService??"sentri",d=F(e),h=ne(i,a),u=oe(i,a),c=Cr(e.redisUrl,i),A=e.router?.register??(o=>pe(o,t)),C=e.router?.login??(o=>me(o,t)),x=e.router?.refresh??(o=>H(o,t)),O=e.router?.logout??(o=>o!==void 0?ye(o,t):Promise.resolve()),v=e.router?.logoutAll??(o=>Ie(o,t)),L=e.router?.getUser??(o=>ge(o,t)),ae=e.router?.assignRoles??((o,w)=>_e(o,w,t)),ue=e.router?.changePassword??((o,w,f)=>Re(o,w,f,t)),Ur=e.router?.bulkCreateIdentifiers??((o,w)=>ve(o,w,t)),Lr=e.router?.bulkUpdateIdentifiers??((o,w)=>Ae(o,w,t)),qr=e.router?.bulkDeleteIdentifiers??((o,w)=>Ee(o,w,t)),de=u(o=>!!o.get("user"));return r.onError(ie()),et(s.algorithm)&&r.get("/keys",o=>(o.header("Cache-Control","public, max-age=3600"),o.json(er(e.secret)))),r.post("/register",async o=>{let w=Date.now();try{Qr(o,e);let f=await P(o),{identifiers:y,password:I,roles:m}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>j)throw g(`identifiers must not exceed ${j} entries`);let E=y.map((X,Se)=>Ke(X,Se));if(typeof I!="string"||I.length<De)throw g(`password is required and must be at least ${De} characters`);if(I.length>M)throw g(`password must not exceed ${M} characters`);if(m!==void 0&&!Array.isArray(m))throw g("roles must be an array of strings when provided");if(Array.isArray(m)&&!m.every(X=>typeof X=="string"))throw g("each role must be a string");let S=Array.isArray(m)?m:void 0,k=S!==void 0?{identifiers:E,password:I,roles:S}:{identifiers:E,password:I},K=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",B=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let X=await c,Se=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${K}`,Se,900*1e3);}let z=await A(k,{ip:K,userAgent:B});return z.success?(i.info(p(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,201,"User registered successfully",{user:z.user})):(i.warn(p(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,z.error))}catch(f){throw f}}),r.post("/login",async o=>{let w=Date.now();try{let f=await P(o),{identifier:y,password:I}=f;if(typeof y!="string"||y.trim().length===0)throw g("identifier is required and must be a non-empty string");if(y.length>Ne)throw g(`identifier must not exceed ${Ne} characters`);if(typeof I!="string"||I.length===0)throw g("password is required");if(I.length>M)throw g(`password must not exceed ${M} characters`);let m=y.trim(),E=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",S=o.req.header("user-agent")||"Unknown";if(e.rateLimit!==!1){let K=await c,B=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${E}`,B,900*1e3);}let k=await C({identifier:m,password:I},{ip:E,userAgent:S});return k.success?(Pe(()=>e.hooks?.onLoginSuccess?.(k.user,{ip:E,userAgent:S})),te(o,k.refreshToken,e),se(o,k.accessToken,e),i.info(p(a,"auth.login.success",{userId:k.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Login successful",{accessToken:k.accessToken,user:k.user})):(Pe(()=>e.hooks?.onLoginFailed?.(m,k.error,{ip:E})),i.warn(p(a,"auth.login.failure",{errorCode:k.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,k.error))}catch(f){throw f}}),r.post("/refresh",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));if(!f)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let y=o.req.header("x-forwarded-for")||o.env?.incoming?.ip||"127.0.0.1",I=o.req.header("user-agent")||"Unknown",m=await x(f,{ip:y,userAgent:I});return m.success?(te(o,m.refreshToken,e),se(o,m.accessToken,e),i.info(p(a,"auth.refresh.success",{userId:m.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Token refreshed",{accessToken:m.accessToken})):(Te(o,e),i.warn(p(a,"auth.refresh.failure",{errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/logout",async o=>{let w=Date.now();try{let f=q(o.req.header("cookie"),U(e));return await O(f),Te(o,e),Le(o,e),i.info(p(a,"auth.logout",{duration_ms:Date.now()-w,..._(o)})),D(o,200,"Logged out",null)}catch(f){throw f}}),r.post("/logout-all",d,async o=>{let w=Date.now();try{let f=o.get("user").id;return await v(f),Pe(()=>e.hooks?.onLogout?.(f)),Te(o,e),Le(o,e),i.info(p(a,"auth.logout_all",{userId:f,duration_ms:Date.now()-w,..._(o)})),D(o,200,"All sessions revoked",null)}catch(f){throw f}}),r.get("/me",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.success",{userId:f.user.id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"OK",f.user)):(i.warn(p(a,"auth.me.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.get("/me/identifiers",d,async o=>{let w=Date.now();try{let f=await L(o.get("user").id);return f.success?(i.info(p(a,"auth.me.identifiers.success",{userId:f.user.id,count:f.user.identifiers?.length??0,duration_ms:Date.now()-w,..._(o)})),D(o,200,"OK",{identifiers:f.user.identifiers??[]})):(i.warn(p(a,"auth.me.identifiers.failure",{userId:o.get("user").id,errorCode:f.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,f.error))}catch(f){throw f}}),r.post("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>j)throw g(`identifiers must not exceed ${j} entries`);let I=y.map((E,S)=>Ke(E,S)),m=await Ur(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.created",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),D(o,201,"Identifiers added successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.create_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.put("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{identifiers:y}=f;if(!Array.isArray(y)||y.length===0)throw g("identifiers is required and must be a non-empty array");if(y.length>j)throw g(`identifiers must not exceed ${j} entries`);let I=y.map((E,S)=>{if(typeof E!="object"||E===null||Array.isArray(E))throw g(`identifiers[${S}] must be an object`);let k=E;if(typeof k.id!="string"||k.id.trim().length===0)throw g(`identifiers[${S}].id is required and must be a non-empty string`);let{type:K,value:B}=Ke(E,S);return {id:k.id,type:K,value:B}}),m=await Lr(o.get("user").id,I);return m.success?(i.info(p(a,"auth.identifiers.updated",{userId:o.get("user").id,count:m.identifiers.length,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Identifiers updated successfully",{identifiers:m.identifiers})):(i.warn(p(a,"auth.identifiers.update_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.delete("/me/identifiers",d,de,async o=>{let w=Date.now();try{let f=await P(o),{ids:y}=f;if(!Array.isArray(y)||y.length===0)throw g("ids is required and must be a non-empty array of strings");if(!y.every(m=>typeof m=="string"))throw g("each id must be a string");let I=await qr(o.get("user").id,y);return I.success?(i.info(p(a,"auth.identifiers.deleted",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Identifiers deleted successfully",{identifiers:I.identifiers})):(i.warn(p(a,"auth.identifiers.delete_failure",{userId:o.get("user").id,errorCode:I.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,I.error))}catch(f){throw f}}),r.patch("/me/password",d,de,async o=>{let w=Date.now();try{let f=await P(o),{currentPassword:y,newPassword:I}=f;if(typeof y!="string"||y.length===0)throw g("currentPassword is required");if(typeof I!="string"||I.length<De)throw g(`newPassword must be at least ${De} characters`);if(I.length>M)throw g(`newPassword must not exceed ${M} characters`);if(y===I)throw g("newPassword must be different from currentPassword");let m=await ue(o.get("user").id,y,I);return m.success?(i.info(p(a,"auth.password.changed",{userId:o.get("user").id,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Password updated successfully. All sessions have been revoked.",null)):(i.warn(p(a,"auth.password.change_failure",{userId:o.get("user").id,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.post("/users/:userId/roles",d,h("admin"),async o=>{let w=Date.now();try{let f=await P(o),{roles:y}=f,I=o.req.param("userId");if(!I)throw g("userId is required");if(!Array.isArray(y)||y.length===0)throw g("roles must be a non-empty array of strings");if(!y.every(E=>typeof E=="string"))throw g("each role must be a string");let m=await ae(I,y);return m.success?(i.info(p(a,"auth.roles.assigned",{targetUserId:I,roles:y,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Roles assigned successfully",{user:m.user})):(i.warn(p(a,"auth.roles.assign_failure",{targetUserId:I,errorCode:m.error.code,duration_ms:Date.now()-w,..._(o)})),b(o,m.error))}catch(f){throw f}}),r.get("/sessions",F(e),async o=>{let w=Date.now(),f=o.get("user").id,I=await(e.router?.getSessions??(m=>Er(m,t)))(f);return i.info(p(a,"auth.sessions.get",{userId:f,duration_ms:Date.now()-w,..._(o)})),D(o,200,"OK",{sessions:I})}),r.delete("/sessions/:id",F(e),async o=>{let w=Date.now(),f=o.get("user").id,y=o.req.param("id");return await(e.router?.revokeSession??((m,E)=>kr(m,E,t)))(f,y),i.info(p(a,"auth.sessions.revoke",{userId:f,sessionId:y,duration_ms:Date.now()-w,..._(o)})),D(o,200,"Session revoked",null)}),r}n(br,"createAuthRouter");function Xs(e){if(e.mode==="client"){let u={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};ze(u);let c=u.logger??N,A=u.loggerService??"sentri",C=ne(c,A),x=oe(c,A);return {protect:n(()=>F(u),"protect"),authorize:n((...O)=>C(...O),"authorize"),permit:n(O=>x(O),"permit"),errorHandler:n(O=>ie(O),"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??N,i=t.loggerService??"sentri",a=R(t),d=ne(s,i),h=oe(s,i);return {protect:n(()=>F(t),"protect"),authorize:n((...u)=>d(...u),"authorize"),permit:n(u=>h(u),"permit"),errorHandler:n(u=>ie(u),"errorHandler"),router:n(()=>br(t),"router"),migrate:n(()=>Xe(T(t.dialect)),"migrate"),getCurrentAccessToken:n(u=>xe(u,t),"getCurrentAccessToken"),hashPassword:n(u=>G(u,a.saltRounds),"hashPassword"),verifyPassword:n((u,c)=>W(u,c),"verifyPassword"),signAccessToken:n(u=>J(u,t),"signAccessToken"),signRefreshToken:n(u=>Y(u,t),"signRefreshToken"),verifyAccessToken:n(u=>ce(u,t),"verifyAccessToken"),verifyRefreshToken:n(u=>Q(u,t),"verifyRefreshToken"),register:n(u=>pe(u,t),"register"),login:n(u=>me(u,t),"login"),refresh:n(u=>H(u,t),"refresh"),logout:n(u=>ye(u,t),"logout"),logoutAll:n(u=>Ie(u,t),"logoutAll"),getUser:n(u=>ge(u,t),"getUser"),changePassword:n((u,c,A)=>Re(u,c,A,t),"changePassword"),assignRoles:n((u,c)=>_e(u,c,t),"assignRoles"),bulkCreateIdentifiers:n((u,c)=>ve(u,c,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((u,c)=>Ae(u,c,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((u,c)=>Ee(u,c,t),"bulkDeleteIdentifiers")}}n(Xs,"createAuthHono");export{$e as SENTRI_ERROR_STATUS,l as SentriError,Wr as authorize,Xs as createAuthHono,br as createAuthRouter,ne as createAuthorize,ie as createErrorHandler,oe as createPermit,xe as getCurrentAccessToken,Jr as permit,F as protect};