sentri 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/elysia/index.cjs +1 -1
- package/dist/adapters/elysia/index.d.cts +15 -0
- package/dist/adapters/elysia/index.d.ts +15 -0
- package/dist/adapters/elysia/index.js +1 -1
- package/dist/adapters/express/index.cjs +1 -1
- package/dist/adapters/express/index.d.cts +12 -0
- package/dist/adapters/express/index.d.ts +12 -0
- package/dist/adapters/express/index.js +1 -1
- package/dist/adapters/fastify/index.cjs +1 -1
- package/dist/adapters/fastify/index.d.cts +15 -0
- package/dist/adapters/fastify/index.d.ts +15 -0
- package/dist/adapters/fastify/index.js +1 -1
- package/dist/adapters/hono/index.cjs +1 -1
- package/dist/adapters/hono/index.d.cts +15 -0
- package/dist/adapters/hono/index.d.ts +15 -0
- package/dist/adapters/hono/index.js +1 -1
- package/dist/adapters/koa/index.cjs +1 -1
- package/dist/adapters/koa/index.d.cts +15 -0
- package/dist/adapters/koa/index.d.ts +15 -0
- package/dist/adapters/koa/index.js +1 -1
- package/dist/cli.cjs +16 -1
- package/dist/cli.js +16 -1
- package/dist/core/index.d.cts +35 -0
- package/dist/core/index.d.ts +35 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var crypto=require('crypto'),kysely=require('kysely'),We=require('bcrypt'),Z=require('jsonwebtoken');require('@fastify/cookie');var stream=require('stream');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var We__default=/*#__PURE__*/_interopDefault(We);var Z__default=/*#__PURE__*/_interopDefault(Z);var Lr=Object.defineProperty;var n=(e,r)=>Lr(e,"name",{value:r,configurable:true});var He=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),u=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??He[r]??500;}};var $e=new WeakMap,Ve=32,Me=10,Be=31;function ze(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new u("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new u("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Ve)throw new u("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Me||s>Be)throw new u("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Me} and ${Be}`);if(!e.validRoles||e.validRoles.length===0)throw new u("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new u("CONFIGURATION_ERROR","dialect is required in server mode")}n(ze,"validateConfig");function k(e){let r=$e.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return $e.set(e,t),t}n(k,"resolveServerConfig");var Fr=/^(\d+)([smhdw])$/,Pr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},je=new Map;function K(e){if(typeof e=="number")return e*1e3;let r=je.get(e);if(r!==void 0)return r;let t=Fr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Pr[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let o=parseInt(t[1],10)*s;return je.set(e,o),o}n(K,"parseExpiry");var D={info:n(()=>{},"info"),warn:n(()=>{},"warn"),error:n(()=>{},"error")};function m(e,r,t){return {service:e,event:r,...t}}n(m,"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 E(e){let r=Ge.get(e);return r||(r=new kysely.Kysely({dialect:e}),Ge.set(e,r)),r}n(E,"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,Vr=3600*1e3;function qe(e){let r=Ze.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),o=s.export({format:"jwk"}),c=crypto.createHash("sha256").update(JSON.stringify({e:o.e,kty:o.kty,n:o.n})).digest("base64url"),l={...o,use:"sig",kid:c};r={kid:c,publicKey:s,jwk:l},Ze.set(e,r);}return r}n(qe,"getOrBuildKey");function Qe(e){let{jwk:r}=qe(e);return {keys:[r]}}n(Qe,"buildJwks");function er(e){return qe(e).publicKey}n(er,"getPublicKeyFromPrivate");async function rr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Vr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new u("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let o=await s.json();if(!o.keys||o.keys.length===0)throw new u("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let a=o.keys[0],c=crypto.createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:c,fetchedAt:r}),c}n(rr,"fetchPublicKey");var tr=new Map,sr=new Map,nr=new Map;function or(e){return e.startsWith("RS")||e.startsWith("PS")}n(or,"isRSA");function ir(e){let r=tr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},tr.set(e,r)),r}n(ir,"getHsSecrets");function Br(e){let r=sr.get(e);return r||(r=crypto.createPrivateKey(e),sr.set(e,r)),r}n(Br,"getRsPrivateKey");function ar(e){let r=k(e);if(or(r.algorithm)){let o=Br(e.secret);return {accessKey:o,refreshKey:o}}let{access:t,refresh:s}=ir(e.secret);return {accessKey:t,refreshKey:s}}n(ar,"getSigningKeys");function dr(e,r){let t=k(e);if(or(t.algorithm))return er(e.secret);let{access:s,refresh:o}=ir(e.secret);return r==="access"?s:o}n(dr,"getVerifyKey");function cr(e,r,t,s){let o=`${t}:${s}`,a=nr.get(o);return a||(a={expiresIn:t,algorithm:s},nr.set(o,a)),Z__default.default.sign(e,r,a)}n(cr,"sign");function ur(e,r,t){try{let s=Z__default.default.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new u("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof u?s:s instanceof Z__default.default.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(ur,"verify");function J(e,r){let t=k(r),{accessKey:s}=ar(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=k(r),{refreshKey:s}=ar(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function de(e,r){let t=k(r),s=dr(r,"access");return ur(e,s,t.algorithm)}n(de,"verifyAccessToken");function q(e,r){let t=k(r),s=dr(r,"refresh");return ur(e,s,t.algorithm)}n(q,"verifyRefreshToken");function lr(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 u("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof u?t:t instanceof Z__default.default.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verifyTokenWithPublicKey");function Se(e){try{return JSON.parse(e)}catch{return []}}n(Se,"parseRoles");function jr(e){return JSON.stringify(e)}n(jr,"serializeRoles");function hr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(hr,"mapIdentifier");async function Q(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:Se(t.roles)}:null}n(Q,"findUserByIdentifierValue");async function ce(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:Se(t.roles)}:null}n(ce,"findUserById");async function mr(e,r,t){let s=t.map(o=>({id:crypto.randomUUID(),user_id:r,type:o.type,value:o.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(o=>({id:o.id,userId:o.user_id,type:o.type,value:o.value,createdAt:new Date}))}n(mr,"createIdentifiers");async function ee(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(hr)}n(ee,"findIdentifiersByUserId");async function Ce(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?hr(s):null}n(Ce,"findIdentifierById");async function pr(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(pr,"countIdentifiersByUserId");async function wr(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(wr,"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 yr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(yr,"updateUserPassword");async function gr(e,r,t){await e.updateTable("sentri_users").set({roles:jr(t)}).where("id","=",r).execute();}n(gr,"updateUserRoles");async function be(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(be,"createSession");async function ue(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:Se(t.roles)}}:null}n(ue,"findSessionById");async function le(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(le,"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 fe(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(fe,"deleteAllSessionsForUser");async function Rr(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(Rr,"findSessionsByUserId");async function he(e,r,t){let s=k(r),o=E(r.dialect),a=e.roles??[],c=a.filter(R=>!s.validRolesSet.has(R));if(c.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${c.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let l=e.identifiers.map(R=>({type:R.type.trim(),value:R.value.trim()}));if(new Set(l.map(R=>R.value)).size!==l.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let R of l)if(await Q(o,R.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${R.value}`)};let g=await G(e.password,s.saltRounds),{userId:T,identifierRows:b}=await o.transaction().execute(async R=>{let F=crypto.randomUUID();await R.insertInto("sentri_users").values({id:F,password_hash:g,roles:JSON.stringify(a)}).execute();let ae=l.map(B=>({id:crypto.randomUUID(),user_id:F,type:B.type,value:B.value}));return await R.insertInto("sentri_identifiers").values(ae).execute(),{userId:F,identifierRows:ae}}),x={success:true,user:{id:T,roles:a,identifiers:b.map(R=>({id:R.id,type:R.type,value:R.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(x.user),x}n(he,"register");async function me(e,r,t){let s=k(r),o=E(r.dialect),a=await Q(o,e.identifier.trim());if(!a){let x=new u("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 u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let l=new Date(Date.now()+K(s.refreshExpiresIn)),d=await be(o,{userId:a.id,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),g={id:a.id,roles:a.roles},T=J({id:a.id,roles:a.roles,sessionId:d.id},r),b=Y(d.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(g,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:T,refreshToken:b,user:g}}n(me,"login");async function H(e,r,t){let s=k(r),o=E(r.dialect),a;try{({sessionId:a}=q(e,r));}catch(x){return x instanceof u?{success:false,error:x}:{success:false,error:new u("TOKEN_INVALID","Invalid refresh token")}}let c=await ue(o,a);if(!c)return {success:false,error:new u("UNAUTHORIZED","Session not found or revoked")};if(c.replacedBy)return await fe(o,c.userId),{success:false,error:new u("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(c.expiresAt.getTime()<Date.now())return await le(o,a),{success:false,error:new u("TOKEN_EXPIRED","Session has expired")};let l=new Date(Date.now()+K(s.refreshExpiresIn)),d=await be(o,{userId:c.userId,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(o,a,d.id);let g={id:c.user.id,roles:c.user.roles},T=J({...g,sessionId:d.id},r),b=Y(d.id,r);return {success:true,accessToken:T,refreshToken:b,user:g}}n(H,"refresh");async function pe(e,r){let t=E(r.dialect),s;try{({sessionId:s}=q(e,r));}catch{return}let o=await ue(t,s);o&&(await le(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(o.userId));}n(pe,"logout");async function we(e,r){let t=E(r.dialect);await fe(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(we,"logoutAll");async function Ie(e,r){let t=E(r.dialect),s=await ce(t,e);if(!s)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let o=await ee(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:o.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(Ie,"getUser");async function ye(e,r,t,s){let o=k(s),a=E(s.dialect),c=await ce(a,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};if(!await W(r,c.passwordHash))return {success:false,error:new u("INVALID_CREDENTIALS","Invalid credentials")};let d=await G(t,o.saltRounds);return await yr(a,e,d),await fe(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(ye,"changePassword");async function ge(e,r,t){let s=k(t),o=E(t.dialect),a=r.filter(g=>!s.validRolesSet.has(g));if(a.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let c=await ce(o,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let l=new Set(c.roles);for(let g of r)l.add(g);let d=Array.from(l);return await gr(o,e,d),{success:true,user:{id:c.id,roles:d}}}n(ge,"assignRoles");async function _e(e,r,t){let s=E(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(l=>({type:l.type.trim(),value:l.value.trim()}));if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o)if(await Q(s,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)};return await mr(s,e,o),{success:true,identifiers:(await ee(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(_e,"bulkCreateIdentifiers");async function Re(e,r,t){let s=E(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one update is required")};let o=r.map(l=>({id:l.id,type:l.type.trim(),value:l.value.trim()}));if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o){let d=await Ce(s,l.id,e);if(!d)return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l.id}`)};if(d.value!==l.value&&await Q(s,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)}}return await s.transaction().execute(async l=>{for(let d of o)await wr(l,d.id,e,{type:d.type,value:d.value});}),{success:true,identifiers:(await ee(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(Re,"bulkUpdateIdentifiers");async function ke(e,r,t){let s=E(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one ID is required")};let o=Array.from(new Set(r));for(let l of o)if(!await Ce(s,l,e))return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l}`)};return await pr(s,e)-o.length<1?{success:false,error:new u("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,o),{success:true,identifiers:(await ee(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))})}n(ke,"bulkDeleteIdentifiers");async function Ar(e,r){let t=E(r.dialect);return (await Rr(t,e)).map(o=>({id:o.id,ipAddress:o.ipAddress,userAgent:o.userAgent,createdAt:o.createdAt,expiresAt:o.expiresAt}))}n(Ar,"getSessions");async function vr(e,r,t){let s=E(t.dialect),o=await ue(s,r);o&&o.userId===e&&await le(s,r);}n(vr,"revokeSession");function O(e){return k(e).cookieName}n(O,"getCookieName");function Ae(e){return k(e).accessCookieName}n(Ae,"getAccessCookieName");function U(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let o=e.indexOf(";",s),a=o===-1?e.length:o;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(U,"readCookie");function re(e,r,t){let s=t.cookie??{},o=K(k(t).refreshExpiresIn)/1e3;e.setCookie(O(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:o});}n(re,"setCookieFromConfig");function ve(e,r){let t=r.cookie??{};e.clearCookie(O(r),{path:t.path??"/"});}n(ve,"clearCookieFromConfig");function te(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,o=K(k(t).accessExpiresIn)/1e3;e.setCookie(Ae(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:o});}n(te,"setAccessCookieFromConfig");function Oe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(Ae(r),{path:t.path??"/"});}n(Oe,"clearAccessCookieFromConfig");function Ee(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):U(e.headers.cookie,Ae(r))}n(Ee,"getCurrentAccessToken");function se(e){let r=e.logger??D,t=e.loggerService??"sentri";return e.mode==="client"?zr(e.keyUri,r,t):Xr(e,r,t)}n(se,"protect");function zr(e,r,t){return async s=>{let o=s.headers.authorization,a=o?.startsWith("Bearer ")?o.slice(7):void 0;if(!a)throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=await rr(e),l=lr(a,c);s.user={id:l.id,roles:l.roles},r.info(m(t,"auth.protect.success",{mode:"client",userId:l.id,requestId:s.id}));}catch(c){let l=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:l,requestId:s.id})),c}}}n(zr,"protectClient");function Xr(e,r,t){return async(s,o)=>{let a=Ee(s,e);if(!a)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=de(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(c.sessionId))throw r.warn(m(t,"auth.protect.token_revoked",{mode:"server",userId:c.id,requestId:s.id})),new u("UNAUTHORIZED","Token has been revoked");s.user={id:c.id,roles:c.roles},r.info(m(t,"auth.protect.success",{mode:"server",userId:c.id,requestId:s.id}));}catch(c){if(c instanceof u&&c.code==="TOKEN_EXPIRED"){let l=U(s.headers.cookie,O(e));if(!l)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Token expired. Please login again.");let d;try{d=await H(l,e);}catch{throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.")}if(!d.success)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:d.error.code,requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.");re(o,d.refreshToken,e),te(o,d.accessToken,e),o.header("X-New-Access-Token",d.accessToken),s.user=d.user,r.info(m(t,"auth.protect.auto_refresh",{mode:"server",userId:d.user.id,requestId:s.id}));}else {let l=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:l,requestId:s.id})),c}}}}n(Xr,"protectServer");function Er(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async o=>{if(!o.user)throw r.warn(m(t,"auth.authorize.unauthenticated",{requiredRoles:e,requestId:o.id})),new u("UNAUTHORIZED","Not authenticated");let a=o.user.roles;if(!e.some(c=>a.includes(c)))throw r.warn(m(t,"auth.authorize.denied",{userId:o.user.id,userRoles:[...a],requiredRoles:e,requestId:o.id})),new u("FORBIDDEN",s);r.info(m(t,"auth.authorize.passed",{userId:o.user.id,userRoles:[...a],requiredRoles:e,requestId:o.id}));}}n(Er,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Er(s,e,r)},"authorize")}n(ne,"createAuthorize");function Gr(...e){return Er(e,D,"sentri")}n(Gr,"authorize");var Wr=new u("FORBIDDEN","You do not have permission to perform this action");function Tr(e,r,t){return async s=>{if(!s.user)throw r.warn(m(t,"auth.permit.unauthenticated",{requestId:s.id})),new u("UNAUTHORIZED","Not authenticated");let o=s.user.id;if(e.roles&&e.roles.length>0){let l=s.user.roles;if(e.roles.some(d=>l.includes(d))){r.info(m(t,"auth.permit.role_bypass",{userId:o,bypassedByRole:true,requestId:s.id}));return}}let a=e.check(s);if(a instanceof Promise?await a:a)r.info(m(t,"auth.permit.passed",{userId:o,requestId:s.id}));else throw r.warn(m(t,"auth.permit.denied",{userId:o,requestId:s.id})),Wr}}n(Tr,"createPermitHandler");function oe(e,r){return n(function(s){return Tr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Zr(e){return Tr(typeof e=="function"?{check:e}:e,D,"sentri")}n(Zr,"permit");var Ue=class{static{n(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let o=Date.now(),a=this.store.get(r);if((!a||a.expiresAt<o)&&(a={count:0,expiresAt:o+s},this.store.set(r,a)),a.count>t){let c=Math.ceil((a.expiresAt-o)/1e3);throw new Error(`Rate limit exceeded. Try again in ${c} seconds.`)}a.count++;}},Le=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let o=`sentri:rl:${r}`,a=Math.ceil(s/1e3);try{let c=await this.redis.multi().incr(o).expire(o,a,"NX").exec();if(!c||c.length===0)return;if(c[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(c){if(c instanceof Error&&c.message.includes("Rate limit exceeded"))throw c;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:c});}}},$=null;async function xr(e,r){if($)return $;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"}),$=new Le(s,r??D),$}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return $=new Ue,$}n(xr,"getRateLimiter");function ie(e){return (r,t,s)=>{if(r instanceof u){s.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.code(500).send({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}n(ie,"createErrorHandler");var Te=8,V=72,xe=255,Nr=100,M=50;function _(e){return new u("VALIDATION_ERROR",e)}n(_,"badRequest");function N(e,r,t,s){e.code(r).send({error:false,statusCode:r,message:t,data:s});}n(N,"ok");function C(e,r){e.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}n(C,"fail");function L(e){let r=e.body;if(r==null||typeof r!="object"||Array.isArray(r))throw new u("VALIDATION_ERROR","Request body is missing or not a JSON object.");return r}n(L,"parseBody");function Jr(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new u("UNAUTHORIZED","Invalid or missing API key")}n(Jr,"validateApiKey");function Pe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Pe,"fireHook");function Yr(e){return e.startsWith("RS")||e.startsWith("PS")}n(Yr,"isRSA");function Ke(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw _(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw _(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Nr)throw _(`identifiers[${r}].type must not exceed ${Nr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw _(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>xe)throw _(`identifiers[${r}].value must not exceed ${xe} characters`);return {type:t.type,value:t.value}}n(Ke,"validateIdentifierInput");function Dr(e){return async r=>{let t=e,s=k(t),o=e.logger??D,a=e.loggerService??"sentri",c=se(e),l=ne(o,a),d=oe(o,a),g=xr(e.redisUrl,o),T=d(i=>!!i.user);r.setErrorHandler(ie()),r.addHook("preParsing",function(i,f,p,h){if(!i.headers["content-type"]?.includes("application/json")){h(null,p);return}if(p==null){h(null,stream.Readable.from("{}"));return}let w=[];p.on("data",y=>w.push(y)),p.on("end",()=>{let y=Buffer.concat(w);h(null,y.length===0?stream.Readable.from("{}"):stream.Readable.from(y));}),p.on("error",y=>h(y));});let b=e.router?.register??(i=>he(i,t)),x=e.router?.login??(i=>me(i,t)),R=e.router?.refresh??(i=>H(i,t)),F=e.router?.logout??(i=>i!==void 0?pe(i,t):Promise.resolve()),ae=e.router?.logoutAll??(i=>we(i,t)),B=e.router?.getUser??(i=>Ie(i,t)),Sr=e.router?.assignRoles??((i,f)=>ge(i,f,t)),Cr=e.router?.changePassword??((i,f,p)=>ye(i,f,p,t)),br=e.router?.bulkCreateIdentifiers??((i,f)=>_e(i,f,t)),Or=e.router?.bulkUpdateIdentifiers??((i,f)=>Re(i,f,t)),Ur=e.router?.bulkDeleteIdentifiers??((i,f)=>ke(i,f,t));Yr(s.algorithm)&&r.get("/keys",async(i,f)=>{f.header("Cache-Control","public, max-age=3600").send(Qe(e.secret));}),r.post("/register",async(i,f)=>{let p=Date.now();Jr(i,e);let h=L(i),{identifiers:w,password:y,roles:I}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>M)throw _(`identifiers must not exceed ${M} entries`);let A=w.map((X,Ne)=>Ke(X,Ne));if(typeof y!="string"||y.length<Te)throw _(`password is required and must be at least ${Te} characters`);if(y.length>V)throw _(`password must not exceed ${V} characters`);if(I!==void 0&&!Array.isArray(I))throw _("roles must be an array of strings when provided");if(Array.isArray(I)&&!I.every(X=>typeof X=="string"))throw _("each role must be a string");let S=Array.isArray(I)?I:void 0,v=S!==void 0?{identifiers:A,password:y,roles:S}:{identifiers:A,password:y},P=i.ip||"127.0.0.1",j=i.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let X=await g,Ne=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${P}`,Ne,900*1e3);}let z=await b(v,{ip:P,userAgent:j});if(!z.success){o.warn(m(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,z.error);return}o.info(m(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,201,"User registered successfully",{user:z.user});}),r.post("/login",async(i,f)=>{let p=Date.now(),h=L(i),{identifier:w,password:y}=h;if(typeof w!="string"||w.trim().length===0)throw _("identifier is required and must be a non-empty string");if(w.length>xe)throw _(`identifier must not exceed ${xe} characters`);if(typeof y!="string"||y.length===0)throw _("password is required");if(y.length>V)throw _(`password must not exceed ${V} characters`);let I=w.trim(),A=i.ip||"127.0.0.1",S=i.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let P=await g,j=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await P.consume(`login:${A}`,j,900*1e3);}let v=await x({identifier:I,password:y},{ip:A,userAgent:S});if(!v.success){Pe(()=>e.hooks?.onLoginFailed?.(I,v.error,{ip:A})),o.warn(m(a,"auth.login.failure",{errorCode:v.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,v.error);return}Pe(()=>e.hooks?.onLoginSuccess?.(v.user,{ip:A,userAgent:S})),re(f,v.refreshToken,e),te(f,v.accessToken,e),o.info(m(a,"auth.login.success",{userId:v.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Login successful",{accessToken:v.accessToken,user:v.user});}),r.post("/refresh",async(i,f)=>{let p=Date.now(),h=U(i.headers.cookie,O(e));if(!h)throw new u("UNAUTHORIZED","Refresh token cookie is missing");let w=i.ip||"127.0.0.1",y=i.headers["user-agent"]||"Unknown",I=await R(h,{ip:w,userAgent:y});if(!I.success){ve(f,e),o.warn(m(a,"auth.refresh.failure",{errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}re(f,I.refreshToken,e),te(f,I.accessToken,e),o.info(m(a,"auth.refresh.success",{userId:I.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Token refreshed",{accessToken:I.accessToken});}),r.post("/logout",async(i,f)=>{let p=Date.now(),h=U(i.headers.cookie,O(e));await F(h),ve(f,e),Oe(f,e),o.info(m(a,"auth.logout",{duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Logged out",null);}),r.post("/logout-all",{preHandler:c},async(i,f)=>{let p=Date.now(),h=i.user.id;await ae(h),Pe(()=>e.hooks?.onLogout?.(h)),ve(f,e),Oe(f,e),o.info(m(a,"auth.logout_all",{userId:h,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"All sessions revoked",null);}),r.get("/me",{preHandler:c},async(i,f)=>{let p=Date.now(),h=await B(i.user.id);if(!h.success){o.warn(m(a,"auth.me.failure",{userId:i.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,h.error);return}o.info(m(a,"auth.me.success",{userId:h.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"OK",h.user);}),r.get("/me/identifiers",{preHandler:c},async(i,f)=>{let p=Date.now(),h=await B(i.user.id);if(!h.success){o.warn(m(a,"auth.me.identifiers.failure",{userId:i.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,h.error);return}o.info(m(a,"auth.me.identifiers.success",{userId:h.user.id,count:h.user.identifiers?.length??0,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"OK",{identifiers:h.user.identifiers??[]});}),r.post("/me/identifiers",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>M)throw _(`identifiers must not exceed ${M} entries`);let y=w.map((A,S)=>Ke(A,S)),I=await br(i.user.id,y);if(!I.success){o.warn(m(a,"auth.identifiers.create_failure",{userId:i.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.identifiers.created",{userId:i.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:i.id})),N(f,201,"Identifiers added successfully",{identifiers:I.identifiers});}),r.put("/me/identifiers",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>M)throw _(`identifiers must not exceed ${M} entries`);let y=w.map((A,S)=>{if(typeof A!="object"||A===null||Array.isArray(A))throw _(`identifiers[${S}] must be an object`);let v=A;if(typeof v.id!="string"||v.id.trim().length===0)throw _(`identifiers[${S}].id is required and must be a non-empty string`);let{type:P,value:j}=Ke(A,S);return {id:v.id,type:P,value:j}}),I=await Or(i.user.id,y);if(!I.success){o.warn(m(a,"auth.identifiers.update_failure",{userId:i.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.identifiers.updated",{userId:i.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Identifiers updated successfully",{identifiers:I.identifiers});}),r.delete("/me/identifiers",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{ids:w}=h;if(!Array.isArray(w)||w.length===0)throw _("ids is required and must be a non-empty array of strings");if(!w.every(I=>typeof I=="string"))throw _("each id must be a string");let y=await Ur(i.user.id,w);if(!y.success){o.warn(m(a,"auth.identifiers.delete_failure",{userId:i.user.id,errorCode:y.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,y.error);return}o.info(m(a,"auth.identifiers.deleted",{userId:i.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Identifiers deleted successfully",{identifiers:y.identifiers});}),r.patch("/me/password",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{currentPassword:w,newPassword:y}=h;if(typeof w!="string"||w.length===0)throw _("currentPassword is required");if(typeof y!="string"||y.length<Te)throw _(`newPassword must be at least ${Te} characters`);if(y.length>V)throw _(`newPassword must not exceed ${V} characters`);if(w===y)throw _("newPassword must be different from currentPassword");let I=await Cr(i.user.id,w,y);if(!I.success){o.warn(m(a,"auth.password.change_failure",{userId:i.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.password.changed",{userId:i.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Password updated successfully. All sessions have been revoked.",null);}),r.post("/users/:userId/roles",{preHandler:[c,l("admin")]},async(i,f)=>{let p=Date.now(),h=L(i),{roles:w}=h,y=i.params.userId;if(!y)throw _("userId is required");if(!Array.isArray(w)||w.length===0)throw _("roles must be a non-empty array of strings");if(!w.every(A=>typeof A=="string"))throw _("each role must be a string");let I=await Sr(y,w);if(!I.success){o.warn(m(a,"auth.roles.assign_failure",{targetUserId:y,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.roles.assigned",{targetUserId:y,roles:w,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Roles assigned successfully",{user:I.user});}),r.get("/sessions",{preHandler:c},async(i,f)=>{let p=Date.now(),h=i.user.id,y=await(e.router?.getSessions??(I=>Ar(I,t)))(h);o.info(m(a,"auth.sessions.get",{userId:h,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"OK",{sessions:y});}),r.delete("/sessions/:id",{preHandler:c},async(i,f)=>{let p=Date.now(),h=i.user.id,w=i.params.id;await(e.router?.revokeSession??((I,A)=>vr(I,A,t)))(h,w),o.info(m(a,"auth.sessions.revoke",{userId:h,sessionId:w,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Session revoked",null);});}}n(Dr,"createAuthPlugin");function Bs(e){if(e.mode==="client"){let d={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(d);let g=d.logger??D,T=d.loggerService??"sentri",b=ne(g,T),x=oe(g,T);return {protect:n(()=>se(d),"protect"),authorize:n((...R)=>b(...R),"authorize"),permit:n(R=>x(R),"permit"),errorHandler:n(R=>ie(R),"errorHandler")}}let{privateKey:r}=crypto.generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),t={mode:"server",dialect:e.dialect,secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??D,o=t.loggerService??"sentri",a=k(t),c=ne(s,o),l=oe(s,o);return {protect:n(()=>se(t),"protect"),authorize:n((...d)=>c(...d),"authorize"),permit:n(d=>l(d),"permit"),plugin:n(()=>Dr(t),"plugin"),errorHandler:n(d=>ie(d),"errorHandler"),migrate:n(()=>Xe(E(t.dialect)),"migrate"),getCurrentAccessToken:n(d=>Ee(d,t),"getCurrentAccessToken"),hashPassword:n(d=>G(d,a.saltRounds),"hashPassword"),verifyPassword:n((d,g)=>W(d,g),"verifyPassword"),signAccessToken:n(d=>J(d,t),"signAccessToken"),signRefreshToken:n(d=>Y(d,t),"signRefreshToken"),verifyAccessToken:n(d=>de(d,t),"verifyAccessToken"),verifyRefreshToken:n(d=>q(d,t),"verifyRefreshToken"),register:n(d=>he(d,t),"register"),login:n(d=>me(d,t),"login"),refresh:n(d=>H(d,t),"refresh"),logout:n(d=>pe(d,t),"logout"),logoutAll:n(d=>we(d,t),"logoutAll"),getUser:n(d=>Ie(d,t),"getUser"),changePassword:n((d,g,T)=>ye(d,g,T,t),"changePassword"),assignRoles:n((d,g)=>ge(d,g,t),"assignRoles"),bulkCreateIdentifiers:n((d,g)=>_e(d,g,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((d,g)=>Re(d,g,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((d,g)=>ke(d,g,t),"bulkDeleteIdentifiers")}}n(Bs,"createAuthFastify");exports.SENTRI_ERROR_STATUS=He;exports.SentriError=u;exports.authorize=Gr;exports.createAuthFastify=Bs;exports.createAuthPlugin=Dr;exports.createAuthorize=ne;exports.createErrorHandler=ie;exports.createPermit=oe;exports.getCurrentAccessToken=Ee;exports.permit=Zr;exports.protect=se;
|
|
1
|
+
'use strict';var crypto=require('crypto'),kysely=require('kysely'),Ze=require('bcrypt'),J=require('jsonwebtoken');require('@fastify/cookie');var stream=require('stream');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var Ze__default=/*#__PURE__*/_interopDefault(Ze);var J__default=/*#__PURE__*/_interopDefault(J);var Lr=Object.defineProperty;var n=(e,r)=>Lr(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}),u=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??$e[r]??500;}};var Ve=new WeakMap,Me=32,Be=10,je=31;function Xe(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new u("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new u("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Me)throw new u("CONFIGURATION_ERROR",`secret must be at least ${Me} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Be||s>je)throw new u("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Be} and ${je}`);if(!e.validRoles||e.validRoles.length===0)throw new u("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new u("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new u("CONFIGURATION_ERROR","dialect is required in server mode")}n(Xe,"validateConfig");function g(e){let r=Ve.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),validIdentifiers:e.validIdentifiers??["email","username"],validIdentifiersSet:new Set(e.validIdentifiers??["email","username"]),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}n(g,"resolveServerConfig");var Fr=/^(\d+)([smhdw])$/,Pr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},ze=new Map;function H(e){if(typeof e=="number")return e*1e3;let r=ze.get(e);if(r!==void 0)return r;let t=Fr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Pr[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 ze.set(e,i),i}n(H,"parseExpiry");var D={info:n(()=>{},"info"),warn:n(()=>{},"warn"),error:n(()=>{},"error")};function m(e,r,t){return {service:e,event:r,...t}}n(m,"buildLogData");async function Ge(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(kysely.sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Ge,"runMigrations");var We=new Map;function T(e){let r=We.get(e);return r||(r=new kysely.Kysely({dialect:e}),We.set(e,r)),r}n(T,"getDatabase");async function W(e,r=12){return Ze__default.default.hash(e,r)}n(W,"hashPassword");async function Z(e,r){return Ze__default.default.compare(e,r)}n(Z,"verifyPassword");var Je=new Map,Ye=new Map,Vr=3600*1e3;function Qe(e){let r=Je.get(e);if(!r){let t=crypto.createPrivateKey(e),s=crypto.createPublicKey(t),i=s.export({format:"jwk"}),c=crypto.createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),y={...i,use:"sig",kid:c};r={kid:c,publicKey:s,jwk:y},Je.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=Ye.get(e);if(t&&r-t.fetchedAt<Vr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new u("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new u("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let o=i.keys[0],c=crypto.createPublicKey({key:o,format:"jwk"});return Ye.set(e,{publicKey:c,fetchedAt:r}),c}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,ir=new Map;function or(e){return e.startsWith("RS")||e.startsWith("PS")}n(or,"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 dr(e){let r=g(e);if(or(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(dr,"getSigningKeys");function cr(e,r){let t=g(e);if(or(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(cr,"getVerifyKey");function ur(e,r,t,s){let i=`${t}:${s}`,o=ir.get(i);return o||(o={expiresIn:t,algorithm:s},ir.set(i,o)),J__default.default.sign(e,r,o)}n(ur,"sign");function lr(e,r,t){try{let s=J__default.default.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new u("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof u?s:s instanceof J__default.default.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function Y(e,r){let t=g(r),{accessKey:s}=dr(r);return ur(e,s,t.accessExpiresIn,t.algorithm)}n(Y,"signAccessToken");function q(e,r){let t=g(r),{refreshKey:s}=dr(r);return ur({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(q,"signRefreshToken");function ce(e,r){let t=g(r),s=cr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=g(r),s=cr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=J__default.default.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new u("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof u?t:t instanceof J__default.default.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Ce(e){try{return JSON.parse(e)}catch{return []}}n(Ce,"parseRoles");function jr(e){return JSON.stringify(e)}n(jr,"serializeRoles");function mr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(mr,"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:Ce(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function ue(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Ce(t.roles)}:null}n(ue,"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(mr)}n(re,"findIdentifiersByUserId");async function Oe(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?mr(s):null}n(Oe,"findIdentifierById");async function wr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}n(wr,"countIdentifiersByUserId");async function Ir(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}n(Ir,"updateIdentifier");async function yr(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(yr,"deleteIdentifiers");async function Rr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(Rr,"updateUserPassword");async function _r(e,r,t){await e.updateTable("sentri_users").set({roles:jr(t)}).where("id","=",r).execute();}n(_r,"updateUserRoles");async function be(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(be,"createSession");async function le(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:Ce(t.roles)}}:null}n(le,"findSessionById");async function fe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(fe,"deleteSession");async function gr(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(gr,"markSessionReplaced");async function he(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(he,"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 me(e,r,t){let s=g(r),i=T(r.dialect),o=e.roles??[],c=o.filter(A=>!s.validRolesSet.has(A));if(c.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${c.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let y=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),d=y.filter(A=>!s.validIdentifiersSet.has(A.type));if(d.length>0)return {success:false,error:new u("VALIDATION_ERROR",`Invalid identifier types: ${d.map(A=>A.type).join(", ")}`)};if(new Set(y.map(A=>A.value)).size!==y.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of y)if(await ee(i,A.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let v=await W(e.password,s.saltRounds),{userId:C,identifierRows:x}=await i.transaction().execute(async A=>{let P=crypto.randomUUID();await A.insertInto("sentri_users").values({id:P,password_hash:v,roles:JSON.stringify(o)}).execute();let j=y.map(de=>({id:crypto.randomUUID(),user_id:P,type:de.type,value:de.value}));return await A.insertInto("sentri_identifiers").values(j).execute(),{userId:P,identifierRows:j}}),O={success:true,user:{id:C,roles:o,identifiers:x.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(O.user),O}n(me,"register");async function pe(e,r,t){let s=g(r),i=T(r.dialect),o=await ee(i,e.identifier.trim());if(!o){let x=new u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}if(!await Z(e.password,o.passwordHash)){let x=new u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let y=new Date(Date.now()+H(s.refreshExpiresIn)),d=await be(i,{userId:o.id,expiresAt:y,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),l={id:o.id,roles:o.roles},v=Y({id:o.id,roles:o.roles,sessionId:d.id},r),C=q(d.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(l,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:v,refreshToken:C,user:l}}n(pe,"login");async function $(e,r,t){let s=g(r),i=T(r.dialect),o;try{({sessionId:o}=Q(e,r));}catch(x){return x instanceof u?{success:false,error:x}:{success:false,error:new u("TOKEN_INVALID","Invalid refresh token")}}let c=await le(i,o);if(!c)return {success:false,error:new u("UNAUTHORIZED","Session not found or revoked")};if(c.replacedBy)return await he(i,c.userId),{success:false,error:new u("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(c.expiresAt.getTime()<Date.now())return await fe(i,o),{success:false,error:new u("TOKEN_EXPIRED","Session has expired")};let y=new Date(Date.now()+H(s.refreshExpiresIn)),d=await be(i,{userId:c.userId,expiresAt:y,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await gr(i,o,d.id);let l={id:c.user.id,roles:c.user.roles},v=Y({...l,sessionId:d.id},r),C=q(d.id,r);return {success:true,accessToken:v,refreshToken:C,user:l}}n($,"refresh");async function we(e,r){let t=T(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await le(t,s);i&&(await fe(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(we,"logout");async function Ie(e,r){let t=T(r.dialect);await he(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function ye(e,r){let t=T(r.dialect),s=await ue(t,e);if(!s)return {success:false,error:new u("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(o=>({id:o.id,type:o.type,value:o.value}))}}}n(ye,"getUser");async function Re(e,r,t,s){let i=g(s),o=T(s.dialect),c=await ue(o,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};if(!await Z(r,c.passwordHash))return {success:false,error:new u("INVALID_CREDENTIALS","Invalid credentials")};let d=await W(t,i.saltRounds);return await Rr(o,e,d),await he(o,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function _e(e,r,t){let s=g(t),i=T(t.dialect),o=r.filter(l=>!s.validRolesSet.has(l));if(o.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${o.join(", ")}`)};let c=await ue(i,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let y=new Set(c.roles);for(let l of r)y.add(l);let d=Array.from(y);return await _r(i,e,d),{success:true,user:{id:c.id,roles:d}}}n(_e,"assignRoles");async function ge(e,r,t){let s=g(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(l=>({type:l.type.trim(),value:l.value.trim()})),c=o.filter(l=>!s.validIdentifiersSet.has(l.type));if(c.length>0)return {success:false,error:new u("VALIDATION_ERROR",`Invalid identifier types: ${c.map(l=>l.type).join(", ")}`)};if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o)if(await ee(i,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)};return await pr(i,e,o),{success:true,identifiers:(await re(i,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(ge,"bulkCreateIdentifiers");async function ve(e,r,t){let s=g(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one update is required")};let o=r.map(l=>({id:l.id,type:l.type.trim(),value:l.value.trim()})),c=o.filter(l=>!s.validIdentifiersSet.has(l.type));if(c.length>0)return {success:false,error:new u("VALIDATION_ERROR",`Invalid identifier types: ${c.map(l=>l.type).join(", ")}`)};if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o){let v=await Oe(i,l.id,e);if(!v)return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l.id}`)};if(v.value!==l.value&&await ee(i,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)}}return await i.transaction().execute(async l=>{for(let v of o)await Ir(l,v.id,e,{type:v.type,value:v.value});}),{success:true,identifiers:(await re(i,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(ve,"bulkUpdateIdentifiers");async function Ae(e,r,t){let s=T(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let y of i)if(!await Oe(s,y,e))return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${y}`)};return await wr(s,e)-i.length<1?{success:false,error:new u("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await yr(s,e,i),{success:true,identifiers:(await re(s,e)).map(y=>({id:y.id,type:y.type,value:y.value}))})}n(Ae,"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 le(s,r);i&&i.userId===e&&await fe(s,r);}n(Er,"revokeSession");function U(e){return g(e).cookieName}n(U,"getCookieName");function ke(e){return g(e).accessCookieName}n(ke,"getAccessCookieName");function L(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let i=e.indexOf(";",s),o=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,o);s=o+1;}}n(L,"readCookie");function te(e,r,t){let s=t.cookie??{},i=H(g(t).refreshExpiresIn)/1e3;e.setCookie(U(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Ee(e,r){let t=r.cookie??{};e.clearCookie(U(r),{path:t.path??"/"});}n(Ee,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=H(g(t).accessExpiresIn)/1e3;e.setCookie(ke(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function Ue(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(ke(r),{path:t.path??"/"});}n(Ue,"clearAccessCookieFromConfig");function Te(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):L(e.headers.cookie,ke(r))}n(Te,"getCurrentAccessToken");function ne(e){let r=e.logger??D,t=e.loggerService??"sentri";return e.mode==="client"?zr(e.keyUri,r,t):Xr(e,r,t)}n(ne,"protect");function zr(e,r,t){return async s=>{let i=s.headers.authorization,o=i?.startsWith("Bearer ")?i.slice(7):void 0;if(!o)throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=await tr(e),y=fr(o,c);s.user={id:y.id,roles:y.roles},r.info(m(t,"auth.protect.success",{mode:"client",userId:y.id,requestId:s.id}));}catch(c){let y=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:y,requestId:s.id})),c}}}n(zr,"protectClient");function Xr(e,r,t){return async(s,i)=>{let o=Te(s,e);if(!o)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=ce(o,e);if(e.isTokenRevoked&&await e.isTokenRevoked(c.sessionId))throw r.warn(m(t,"auth.protect.token_revoked",{mode:"server",userId:c.id,requestId:s.id})),new u("UNAUTHORIZED","Token has been revoked");s.user={id:c.id,roles:c.roles},r.info(m(t,"auth.protect.success",{mode:"server",userId:c.id,requestId:s.id}));}catch(c){if(c instanceof u&&c.code==="TOKEN_EXPIRED"){let y=L(s.headers.cookie,U(e));if(!y)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Token expired. Please login again.");let d;try{d=await $(y,e);}catch{throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.")}if(!d.success)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:d.error.code,requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.");te(i,d.refreshToken,e),se(i,d.accessToken,e),i.header("X-New-Access-Token",d.accessToken),s.user=d.user,r.info(m(t,"auth.protect.auto_refresh",{mode:"server",userId:d.user.id,requestId:s.id}));}else {let y=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:y,requestId:s.id})),c}}}}n(Xr,"protectServer");function Tr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async i=>{if(!i.user)throw r.warn(m(t,"auth.authorize.unauthenticated",{requiredRoles:e,requestId:i.id})),new u("UNAUTHORIZED","Not authenticated");let o=i.user.roles;if(!e.some(c=>o.includes(c)))throw r.warn(m(t,"auth.authorize.denied",{userId:i.user.id,userRoles:[...o],requiredRoles:e,requestId:i.id})),new u("FORBIDDEN",s);r.info(m(t,"auth.authorize.passed",{userId:i.user.id,userRoles:[...o],requiredRoles:e,requestId:i.id}));}}n(Tr,"createAuthorizeHandler");function ie(e,r){return n(function(...s){return Tr(s,e,r)},"authorize")}n(ie,"createAuthorize");function Gr(...e){return Tr(e,D,"sentri")}n(Gr,"authorize");var Wr=new u("FORBIDDEN","You do not have permission to perform this action");function xr(e,r,t){return async s=>{if(!s.user)throw r.warn(m(t,"auth.permit.unauthenticated",{requestId:s.id})),new u("UNAUTHORIZED","Not authenticated");let i=s.user.id;if(e.roles&&e.roles.length>0){let y=s.user.roles;if(e.roles.some(d=>y.includes(d))){r.info(m(t,"auth.permit.role_bypass",{userId:i,bypassedByRole:true,requestId:s.id}));return}}let o=e.check(s);if(o instanceof Promise?await o:o)r.info(m(t,"auth.permit.passed",{userId:i,requestId:s.id}));else throw r.warn(m(t,"auth.permit.denied",{userId:i,requestId:s.id})),Wr}}n(xr,"createPermitHandler");function oe(e,r){return n(function(s){return xr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Zr(e){return xr(typeof e=="function"?{check:e}:e,D,"sentri")}n(Zr,"permit");var Le=class{static{n(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let i=Date.now(),o=this.store.get(r);if((!o||o.expiresAt<i)&&(o={count:0,expiresAt:i+s},this.store.set(r,o)),o.count>t){let c=Math.ceil((o.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${c} seconds.`)}o.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}`,o=Math.ceil(s/1e3);try{let c=await this.redis.multi().incr(i).expire(i,o,"NX").exec();if(!c||c.length===0)return;if(c[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(c){if(c instanceof Error&&c.message.includes("Rate limit exceeded"))throw c;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:c});}}},V=null;async function Nr(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 Le,V}n(Nr,"getRateLimiter");function ae(e){return (r,t,s)=>{if(r instanceof u){s.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.code(500).send({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}n(ae,"createErrorHandler");var xe=8,M=72,Ne=255,Dr=100,B=50;function _(e){return new u("VALIDATION_ERROR",e)}n(_,"badRequest");function N(e,r,t,s){e.code(r).send({error:false,statusCode:r,message:t,data:s});}n(N,"ok");function b(e,r){e.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}n(b,"fail");function F(e){let r=e.body;if(r==null||typeof r!="object"||Array.isArray(r))throw new u("VALIDATION_ERROR","Request body is missing or not a JSON object.");return r}n(F,"parseBody");function Jr(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new u("UNAUTHORIZED","Invalid or missing API key")}n(Jr,"validateApiKey");function Ke(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Ke,"fireHook");function Yr(e){return e.startsWith("RS")||e.startsWith("PS")}n(Yr,"isRSA");function He(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw _(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw _(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Dr)throw _(`identifiers[${r}].type must not exceed ${Dr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw _(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Ne)throw _(`identifiers[${r}].value must not exceed ${Ne} characters`);return {type:t.type,value:t.value}}n(He,"validateIdentifierInput");function Sr(e){return async r=>{let t=e,s=g(t),i=e.logger??D,o=e.loggerService??"sentri",c=ne(e),y=ie(i,o),d=oe(i,o),l=Nr(e.redisUrl,i),v=d(a=>!!a.user);r.setErrorHandler(ae()),r.addHook("preParsing",function(a,f,p,h){if(!a.headers["content-type"]?.includes("application/json")){h(null,p);return}if(p==null){h(null,stream.Readable.from("{}"));return}let w=[];p.on("data",R=>w.push(R)),p.on("end",()=>{let R=Buffer.concat(w);h(null,R.length===0?stream.Readable.from("{}"):stream.Readable.from(R));}),p.on("error",R=>h(R));});let C=e.router?.register??(a=>me(a,t)),x=e.router?.login??(a=>pe(a,t)),O=e.router?.refresh??(a=>$(a,t)),A=e.router?.logout??(a=>a!==void 0?we(a,t):Promise.resolve()),P=e.router?.logoutAll??(a=>Ie(a,t)),j=e.router?.getUser??(a=>ye(a,t)),de=e.router?.assignRoles??((a,f)=>_e(a,f,t)),Cr=e.router?.changePassword??((a,f,p)=>Re(a,f,p,t)),Or=e.router?.bulkCreateIdentifiers??((a,f)=>ge(a,f,t)),br=e.router?.bulkUpdateIdentifiers??((a,f)=>ve(a,f,t)),Ur=e.router?.bulkDeleteIdentifiers??((a,f)=>Ae(a,f,t));Yr(s.algorithm)&&r.get("/keys",async(a,f)=>{f.header("Cache-Control","public, max-age=3600").send(er(e.secret));}),r.post("/register",async(a,f)=>{let p=Date.now();Jr(a,e);let h=F(a),{identifiers:w,password:R,roles:I}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>B)throw _(`identifiers must not exceed ${B} entries`);let k=w.map((G,De)=>He(G,De));if(typeof R!="string"||R.length<xe)throw _(`password is required and must be at least ${xe} characters`);if(R.length>M)throw _(`password must not exceed ${M} characters`);if(I!==void 0&&!Array.isArray(I))throw _("roles must be an array of strings when provided");if(Array.isArray(I)&&!I.every(G=>typeof G=="string"))throw _("each role must be a string");let S=Array.isArray(I)?I:void 0,E=S!==void 0?{identifiers:k,password:R,roles:S}:{identifiers:k,password:R},K=a.ip||"127.0.0.1",z=a.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let G=await l,De=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await G.consume(`register:${K}`,De,900*1e3);}let X=await C(E,{ip:K,userAgent:z});if(!X.success){i.warn(m(o,"auth.register.failure",{errorCode:X.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,X.error);return}i.info(m(o,"auth.register.success",{userId:X.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,201,"User registered successfully",{user:X.user});}),r.post("/login",async(a,f)=>{let p=Date.now(),h=F(a),{identifier:w,password:R}=h;if(typeof w!="string"||w.trim().length===0)throw _("identifier is required and must be a non-empty string");if(w.length>Ne)throw _(`identifier must not exceed ${Ne} characters`);if(typeof R!="string"||R.length===0)throw _("password is required");if(R.length>M)throw _(`password must not exceed ${M} characters`);let I=w.trim(),k=a.ip||"127.0.0.1",S=a.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let K=await l,z=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${k}`,z,900*1e3);}let E=await x({identifier:I,password:R},{ip:k,userAgent:S});if(!E.success){Ke(()=>e.hooks?.onLoginFailed?.(I,E.error,{ip:k})),i.warn(m(o,"auth.login.failure",{errorCode:E.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,E.error);return}Ke(()=>e.hooks?.onLoginSuccess?.(E.user,{ip:k,userAgent:S})),te(f,E.refreshToken,e),se(f,E.accessToken,e),i.info(m(o,"auth.login.success",{userId:E.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Login successful",{accessToken:E.accessToken,user:E.user});}),r.post("/refresh",async(a,f)=>{let p=Date.now(),h=L(a.headers.cookie,U(e));if(!h)throw new u("UNAUTHORIZED","Refresh token cookie is missing");let w=a.ip||"127.0.0.1",R=a.headers["user-agent"]||"Unknown",I=await O(h,{ip:w,userAgent:R});if(!I.success){Ee(f,e),i.warn(m(o,"auth.refresh.failure",{errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}te(f,I.refreshToken,e),se(f,I.accessToken,e),i.info(m(o,"auth.refresh.success",{userId:I.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Token refreshed",{accessToken:I.accessToken});}),r.post("/logout",async(a,f)=>{let p=Date.now(),h=L(a.headers.cookie,U(e));await A(h),Ee(f,e),Ue(f,e),i.info(m(o,"auth.logout",{duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Logged out",null);}),r.post("/logout-all",{preHandler:c},async(a,f)=>{let p=Date.now(),h=a.user.id;await P(h),Ke(()=>e.hooks?.onLogout?.(h)),Ee(f,e),Ue(f,e),i.info(m(o,"auth.logout_all",{userId:h,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"All sessions revoked",null);}),r.get("/me",{preHandler:c},async(a,f)=>{let p=Date.now(),h=await j(a.user.id);if(!h.success){i.warn(m(o,"auth.me.failure",{userId:a.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,h.error);return}i.info(m(o,"auth.me.success",{userId:h.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"OK",h.user);}),r.get("/me/identifiers",{preHandler:c},async(a,f)=>{let p=Date.now(),h=await j(a.user.id);if(!h.success){i.warn(m(o,"auth.me.identifiers.failure",{userId:a.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,h.error);return}i.info(m(o,"auth.me.identifiers.success",{userId:h.user.id,count:h.user.identifiers?.length??0,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"OK",{identifiers:h.user.identifiers??[]});}),r.post("/me/identifiers",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>B)throw _(`identifiers must not exceed ${B} entries`);let R=w.map((k,S)=>He(k,S)),I=await Or(a.user.id,R);if(!I.success){i.warn(m(o,"auth.identifiers.create_failure",{userId:a.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.identifiers.created",{userId:a.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:a.id})),N(f,201,"Identifiers added successfully",{identifiers:I.identifiers});}),r.put("/me/identifiers",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>B)throw _(`identifiers must not exceed ${B} entries`);let R=w.map((k,S)=>{if(typeof k!="object"||k===null||Array.isArray(k))throw _(`identifiers[${S}] must be an object`);let E=k;if(typeof E.id!="string"||E.id.trim().length===0)throw _(`identifiers[${S}].id is required and must be a non-empty string`);let{type:K,value:z}=He(k,S);return {id:E.id,type:K,value:z}}),I=await br(a.user.id,R);if(!I.success){i.warn(m(o,"auth.identifiers.update_failure",{userId:a.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.identifiers.updated",{userId:a.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Identifiers updated successfully",{identifiers:I.identifiers});}),r.delete("/me/identifiers",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{ids:w}=h;if(!Array.isArray(w)||w.length===0)throw _("ids is required and must be a non-empty array of strings");if(!w.every(I=>typeof I=="string"))throw _("each id must be a string");let R=await Ur(a.user.id,w);if(!R.success){i.warn(m(o,"auth.identifiers.delete_failure",{userId:a.user.id,errorCode:R.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,R.error);return}i.info(m(o,"auth.identifiers.deleted",{userId:a.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Identifiers deleted successfully",{identifiers:R.identifiers});}),r.patch("/me/password",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{currentPassword:w,newPassword:R}=h;if(typeof w!="string"||w.length===0)throw _("currentPassword is required");if(typeof R!="string"||R.length<xe)throw _(`newPassword must be at least ${xe} characters`);if(R.length>M)throw _(`newPassword must not exceed ${M} characters`);if(w===R)throw _("newPassword must be different from currentPassword");let I=await Cr(a.user.id,w,R);if(!I.success){i.warn(m(o,"auth.password.change_failure",{userId:a.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.password.changed",{userId:a.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Password updated successfully. All sessions have been revoked.",null);}),r.post("/users/:userId/roles",{preHandler:[c,y("admin")]},async(a,f)=>{let p=Date.now(),h=F(a),{roles:w}=h,R=a.params.userId;if(!R)throw _("userId is required");if(!Array.isArray(w)||w.length===0)throw _("roles must be a non-empty array of strings");if(!w.every(k=>typeof k=="string"))throw _("each role must be a string");let I=await de(R,w);if(!I.success){i.warn(m(o,"auth.roles.assign_failure",{targetUserId:R,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.roles.assigned",{targetUserId:R,roles:w,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Roles assigned successfully",{user:I.user});}),r.get("/sessions",{preHandler:c},async(a,f)=>{let p=Date.now(),h=a.user.id,R=await(e.router?.getSessions??(I=>kr(I,t)))(h);i.info(m(o,"auth.sessions.get",{userId:h,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"OK",{sessions:R});}),r.delete("/sessions/:id",{preHandler:c},async(a,f)=>{let p=Date.now(),h=a.user.id,w=a.params.id;await(e.router?.revokeSession??((I,k)=>Er(I,k,t)))(h,w),i.info(m(o,"auth.sessions.revoke",{userId:h,sessionId:w,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Session revoked",null);});}}n(Sr,"createAuthPlugin");function Bs(e){if(e.mode==="client"){let d={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}};Xe(d);let l=d.logger??D,v=d.loggerService??"sentri",C=ie(l,v),x=oe(l,v);return {protect:n(()=>ne(d),"protect"),authorize:n((...O)=>C(...O),"authorize"),permit:n(O=>x(O),"permit"),errorHandler:n(O=>ae(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",o=g(t),c=ie(s,i),y=oe(s,i);return {protect:n(()=>ne(t),"protect"),authorize:n((...d)=>c(...d),"authorize"),permit:n(d=>y(d),"permit"),plugin:n(()=>Sr(t),"plugin"),errorHandler:n(d=>ae(d),"errorHandler"),migrate:n(()=>Ge(T(t.dialect)),"migrate"),getCurrentAccessToken:n(d=>Te(d,t),"getCurrentAccessToken"),hashPassword:n(d=>W(d,o.saltRounds),"hashPassword"),verifyPassword:n((d,l)=>Z(d,l),"verifyPassword"),signAccessToken:n(d=>Y(d,t),"signAccessToken"),signRefreshToken:n(d=>q(d,t),"signRefreshToken"),verifyAccessToken:n(d=>ce(d,t),"verifyAccessToken"),verifyRefreshToken:n(d=>Q(d,t),"verifyRefreshToken"),register:n(d=>me(d,t),"register"),login:n(d=>pe(d,t),"login"),refresh:n(d=>$(d,t),"refresh"),logout:n(d=>we(d,t),"logout"),logoutAll:n(d=>Ie(d,t),"logoutAll"),getUser:n(d=>ye(d,t),"getUser"),changePassword:n((d,l,v)=>Re(d,l,v,t),"changePassword"),assignRoles:n((d,l)=>_e(d,l,t),"assignRoles"),bulkCreateIdentifiers:n((d,l)=>ge(d,l,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((d,l)=>ve(d,l,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((d,l)=>Ae(d,l,t),"bulkDeleteIdentifiers")}}n(Bs,"createAuthFastify");exports.SENTRI_ERROR_STATUS=$e;exports.SentriError=u;exports.authorize=Gr;exports.createAuthFastify=Bs;exports.createAuthPlugin=Sr;exports.createAuthorize=ie;exports.createErrorHandler=ae;exports.createPermit=oe;exports.getCurrentAccessToken=Te;exports.permit=Zr;exports.protect=ne;
|
|
@@ -35,6 +35,7 @@ declare function getCurrentAccessToken(request: FastifyRequest, config: ServerAu
|
|
|
35
35
|
interface CreateFastifyServerOptions<TRole extends string = string> {
|
|
36
36
|
mode: 'server';
|
|
37
37
|
validRoles: readonly TRole[];
|
|
38
|
+
validIdentifiers?: readonly string[];
|
|
38
39
|
dialect: kysely.Dialect;
|
|
39
40
|
accessExpiresIn?: string | number;
|
|
40
41
|
refreshExpiresIn?: string | number;
|
|
@@ -57,14 +58,20 @@ interface CreateFastifyClientOptions<TRole extends string = string> {
|
|
|
57
58
|
loggerService?: string;
|
|
58
59
|
}
|
|
59
60
|
interface FastifyAuthClient<TRole extends string = string> {
|
|
61
|
+
/** Hook that ensures the request has a valid access token. Attaches `request.user`. */
|
|
60
62
|
protect(): SentriFastifyHook;
|
|
63
|
+
/** Hook that ensures the user has AT LEAST ONE of the required roles. */
|
|
61
64
|
authorize(...roles: TRole[]): SentriFastifyHook;
|
|
65
|
+
/** Hook that evaluates a custom authorization condition or ABAC rules. */
|
|
62
66
|
permit(check: FastifyPermitCheck): SentriFastifyHook;
|
|
63
67
|
permit(options: FastifyPermitOptions<TRole>): SentriFastifyHook;
|
|
68
|
+
/** Fastify error handler that converts Sentri errors to standardized JSON responses. */
|
|
64
69
|
errorHandler(options?: FastifyErrorHandlerOptions): FastifyErrorHandler;
|
|
65
70
|
}
|
|
66
71
|
interface FastifyServerAuthClient<TRole extends string = string> extends FastifyAuthClient<TRole> {
|
|
72
|
+
/** Returns a Fastify plugin containing all auth endpoints (e.g. POST /register). */
|
|
67
73
|
plugin(): FastifyPluginAsync;
|
|
74
|
+
/** Runs Kysely database migrations for Sentri tables. */
|
|
68
75
|
migrate(): Promise<void>;
|
|
69
76
|
getCurrentAccessToken(request: FastifyRequest): string | undefined;
|
|
70
77
|
hashPassword(plain: string): Promise<string>;
|
|
@@ -92,7 +99,15 @@ interface FastifyServerAuthClient<TRole extends string = string> extends Fastify
|
|
|
92
99
|
bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
|
|
93
100
|
}
|
|
94
101
|
type FastifyClientAuthClient<TRole extends string = string> = FastifyAuthClient<TRole>;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a Fastify authentication client (Server Mode).
|
|
104
|
+
* Configures database connections, handles registration, issues tokens, and mounts endpoints.
|
|
105
|
+
*/
|
|
95
106
|
declare function createAuthFastify<TRole extends string = string>(options: CreateFastifyServerOptions<TRole>): FastifyServerAuthClient<TRole>;
|
|
107
|
+
/**
|
|
108
|
+
* Creates a Fastify authentication client (Client Mode).
|
|
109
|
+
* Used for microservices that only verify tokens issued by a separate auth server.
|
|
110
|
+
*/
|
|
96
111
|
declare function createAuthFastify<TRole extends string = string>(options: CreateFastifyClientOptions<TRole>): FastifyClientAuthClient<TRole>;
|
|
97
112
|
|
|
98
113
|
export { type CreateFastifyClientOptions, type CreateFastifyServerOptions, type FastifyAuthClient, type FastifyClientAuthClient, type FastifyErrorHandler, type FastifyErrorHandlerOptions, type FastifyPermitCheck, type FastifyPermitOptions, type FastifyServerAuthClient, type SentriFastifyHook, SentriLogger, authorize, createAuthFastify, createAuthPlugin, createAuthorize, createErrorHandler, createPermit, getCurrentAccessToken, permit, protect };
|
|
@@ -35,6 +35,7 @@ declare function getCurrentAccessToken(request: FastifyRequest, config: ServerAu
|
|
|
35
35
|
interface CreateFastifyServerOptions<TRole extends string = string> {
|
|
36
36
|
mode: 'server';
|
|
37
37
|
validRoles: readonly TRole[];
|
|
38
|
+
validIdentifiers?: readonly string[];
|
|
38
39
|
dialect: kysely.Dialect;
|
|
39
40
|
accessExpiresIn?: string | number;
|
|
40
41
|
refreshExpiresIn?: string | number;
|
|
@@ -57,14 +58,20 @@ interface CreateFastifyClientOptions<TRole extends string = string> {
|
|
|
57
58
|
loggerService?: string;
|
|
58
59
|
}
|
|
59
60
|
interface FastifyAuthClient<TRole extends string = string> {
|
|
61
|
+
/** Hook that ensures the request has a valid access token. Attaches `request.user`. */
|
|
60
62
|
protect(): SentriFastifyHook;
|
|
63
|
+
/** Hook that ensures the user has AT LEAST ONE of the required roles. */
|
|
61
64
|
authorize(...roles: TRole[]): SentriFastifyHook;
|
|
65
|
+
/** Hook that evaluates a custom authorization condition or ABAC rules. */
|
|
62
66
|
permit(check: FastifyPermitCheck): SentriFastifyHook;
|
|
63
67
|
permit(options: FastifyPermitOptions<TRole>): SentriFastifyHook;
|
|
68
|
+
/** Fastify error handler that converts Sentri errors to standardized JSON responses. */
|
|
64
69
|
errorHandler(options?: FastifyErrorHandlerOptions): FastifyErrorHandler;
|
|
65
70
|
}
|
|
66
71
|
interface FastifyServerAuthClient<TRole extends string = string> extends FastifyAuthClient<TRole> {
|
|
72
|
+
/** Returns a Fastify plugin containing all auth endpoints (e.g. POST /register). */
|
|
67
73
|
plugin(): FastifyPluginAsync;
|
|
74
|
+
/** Runs Kysely database migrations for Sentri tables. */
|
|
68
75
|
migrate(): Promise<void>;
|
|
69
76
|
getCurrentAccessToken(request: FastifyRequest): string | undefined;
|
|
70
77
|
hashPassword(plain: string): Promise<string>;
|
|
@@ -92,7 +99,15 @@ interface FastifyServerAuthClient<TRole extends string = string> extends Fastify
|
|
|
92
99
|
bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
|
|
93
100
|
}
|
|
94
101
|
type FastifyClientAuthClient<TRole extends string = string> = FastifyAuthClient<TRole>;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a Fastify authentication client (Server Mode).
|
|
104
|
+
* Configures database connections, handles registration, issues tokens, and mounts endpoints.
|
|
105
|
+
*/
|
|
95
106
|
declare function createAuthFastify<TRole extends string = string>(options: CreateFastifyServerOptions<TRole>): FastifyServerAuthClient<TRole>;
|
|
107
|
+
/**
|
|
108
|
+
* Creates a Fastify authentication client (Client Mode).
|
|
109
|
+
* Used for microservices that only verify tokens issued by a separate auth server.
|
|
110
|
+
*/
|
|
96
111
|
declare function createAuthFastify<TRole extends string = string>(options: CreateFastifyClientOptions<TRole>): FastifyClientAuthClient<TRole>;
|
|
97
112
|
|
|
98
113
|
export { type CreateFastifyClientOptions, type CreateFastifyServerOptions, type FastifyAuthClient, type FastifyClientAuthClient, type FastifyErrorHandler, type FastifyErrorHandlerOptions, type FastifyPermitCheck, type FastifyPermitOptions, type FastifyServerAuthClient, type SentriFastifyHook, SentriLogger, authorize, createAuthFastify, createAuthPlugin, createAuthorize, createErrorHandler, createPermit, getCurrentAccessToken, permit, protect };
|
|
@@ -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'@fastify/cookie';import {Readable}from'stream';var Lr=Object.defineProperty;var n=(e,r)=>Lr(e,"name",{value:r,configurable:true});var He=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500,TOKEN_REUSE:401}),u=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??He[r]??500;}};var $e=new WeakMap,Ve=32,Me=10,Be=31;function ze(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new u("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new u("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Ve)throw new u("CONFIGURATION_ERROR",`secret must be at least ${Ve} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Me||s>Be)throw new u("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Me} and ${Be}`);if(!e.validRoles||e.validRoles.length===0)throw new u("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new u("CONFIGURATION_ERROR","dialect is required in server mode")}n(ze,"validateConfig");function k(e){let r=$e.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return $e.set(e,t),t}n(k,"resolveServerConfig");var Fr=/^(\d+)([smhdw])$/,Pr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},je=new Map;function K(e){if(typeof e=="number")return e*1e3;let r=je.get(e);if(r!==void 0)return r;let t=Fr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Pr[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let o=parseInt(t[1],10)*s;return je.set(e,o),o}n(K,"parseExpiry");var D={info:n(()=>{},"info"),warn:n(()=>{},"warn"),error:n(()=>{},"error")};function m(e,r,t){return {service:e,event:r,...t}}n(m,"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 E(e){let r=Ge.get(e);return r||(r=new Kysely({dialect:e}),Ge.set(e,r)),r}n(E,"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,Vr=3600*1e3;function qe(e){let r=Ze.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),o=s.export({format:"jwk"}),c=createHash("sha256").update(JSON.stringify({e:o.e,kty:o.kty,n:o.n})).digest("base64url"),l={...o,use:"sig",kid:c};r={kid:c,publicKey:s,jwk:l},Ze.set(e,r);}return r}n(qe,"getOrBuildKey");function Qe(e){let{jwk:r}=qe(e);return {keys:[r]}}n(Qe,"buildJwks");function er(e){return qe(e).publicKey}n(er,"getPublicKeyFromPrivate");async function rr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Vr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new u("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let o=await s.json();if(!o.keys||o.keys.length===0)throw new u("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let a=o.keys[0],c=createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:c,fetchedAt:r}),c}n(rr,"fetchPublicKey");var tr=new Map,sr=new Map,nr=new Map;function or(e){return e.startsWith("RS")||e.startsWith("PS")}n(or,"isRSA");function ir(e){let r=tr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},tr.set(e,r)),r}n(ir,"getHsSecrets");function Br(e){let r=sr.get(e);return r||(r=createPrivateKey(e),sr.set(e,r)),r}n(Br,"getRsPrivateKey");function ar(e){let r=k(e);if(or(r.algorithm)){let o=Br(e.secret);return {accessKey:o,refreshKey:o}}let{access:t,refresh:s}=ir(e.secret);return {accessKey:t,refreshKey:s}}n(ar,"getSigningKeys");function dr(e,r){let t=k(e);if(or(t.algorithm))return er(e.secret);let{access:s,refresh:o}=ir(e.secret);return r==="access"?s:o}n(dr,"getVerifyKey");function cr(e,r,t,s){let o=`${t}:${s}`,a=nr.get(o);return a||(a={expiresIn:t,algorithm:s},nr.set(o,a)),Z.sign(e,r,a)}n(cr,"sign");function ur(e,r,t){try{let s=Z.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new u("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof u?s:s instanceof Z.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(ur,"verify");function J(e,r){let t=k(r),{accessKey:s}=ar(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=k(r),{refreshKey:s}=ar(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function de(e,r){let t=k(r),s=dr(r,"access");return ur(e,s,t.algorithm)}n(de,"verifyAccessToken");function q(e,r){let t=k(r),s=dr(r,"refresh");return ur(e,s,t.algorithm)}n(q,"verifyRefreshToken");function lr(e,r){try{let t=Z.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new u("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof u?t:t instanceof Z.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verifyTokenWithPublicKey");function Se(e){try{return JSON.parse(e)}catch{return []}}n(Se,"parseRoles");function jr(e){return JSON.stringify(e)}n(jr,"serializeRoles");function hr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(hr,"mapIdentifier");async function Q(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:Se(t.roles)}:null}n(Q,"findUserByIdentifierValue");async function ce(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:Se(t.roles)}:null}n(ce,"findUserById");async function mr(e,r,t){let s=t.map(o=>({id:randomUUID(),user_id:r,type:o.type,value:o.value}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(o=>({id:o.id,userId:o.user_id,type:o.type,value:o.value,createdAt:new Date}))}n(mr,"createIdentifiers");async function ee(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(hr)}n(ee,"findIdentifiersByUserId");async function Ce(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?hr(s):null}n(Ce,"findIdentifierById");async function pr(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(pr,"countIdentifiersByUserId");async function wr(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(wr,"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 yr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(yr,"updateUserPassword");async function gr(e,r,t){await e.updateTable("sentri_users").set({roles:jr(t)}).where("id","=",r).execute();}n(gr,"updateUserRoles");async function be(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(be,"createSession");async function ue(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:Se(t.roles)}}:null}n(ue,"findSessionById");async function le(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(le,"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 fe(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(fe,"deleteAllSessionsForUser");async function Rr(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(Rr,"findSessionsByUserId");async function he(e,r,t){let s=k(r),o=E(r.dialect),a=e.roles??[],c=a.filter(R=>!s.validRolesSet.has(R));if(c.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${c.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let l=e.identifiers.map(R=>({type:R.type.trim(),value:R.value.trim()}));if(new Set(l.map(R=>R.value)).size!==l.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let R of l)if(await Q(o,R.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${R.value}`)};let g=await G(e.password,s.saltRounds),{userId:T,identifierRows:b}=await o.transaction().execute(async R=>{let F=randomUUID();await R.insertInto("sentri_users").values({id:F,password_hash:g,roles:JSON.stringify(a)}).execute();let ae=l.map(B=>({id:randomUUID(),user_id:F,type:B.type,value:B.value}));return await R.insertInto("sentri_identifiers").values(ae).execute(),{userId:F,identifierRows:ae}}),x={success:true,user:{id:T,roles:a,identifiers:b.map(R=>({id:R.id,type:R.type,value:R.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(x.user),x}n(he,"register");async function me(e,r,t){let s=k(r),o=E(r.dialect),a=await Q(o,e.identifier.trim());if(!a){let x=new u("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 u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let l=new Date(Date.now()+K(s.refreshExpiresIn)),d=await be(o,{userId:a.id,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),g={id:a.id,roles:a.roles},T=J({id:a.id,roles:a.roles,sessionId:d.id},r),b=Y(d.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(g,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:T,refreshToken:b,user:g}}n(me,"login");async function H(e,r,t){let s=k(r),o=E(r.dialect),a;try{({sessionId:a}=q(e,r));}catch(x){return x instanceof u?{success:false,error:x}:{success:false,error:new u("TOKEN_INVALID","Invalid refresh token")}}let c=await ue(o,a);if(!c)return {success:false,error:new u("UNAUTHORIZED","Session not found or revoked")};if(c.replacedBy)return await fe(o,c.userId),{success:false,error:new u("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(c.expiresAt.getTime()<Date.now())return await le(o,a),{success:false,error:new u("TOKEN_EXPIRED","Session has expired")};let l=new Date(Date.now()+K(s.refreshExpiresIn)),d=await be(o,{userId:c.userId,expiresAt:l,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await _r(o,a,d.id);let g={id:c.user.id,roles:c.user.roles},T=J({...g,sessionId:d.id},r),b=Y(d.id,r);return {success:true,accessToken:T,refreshToken:b,user:g}}n(H,"refresh");async function pe(e,r){let t=E(r.dialect),s;try{({sessionId:s}=q(e,r));}catch{return}let o=await ue(t,s);o&&(await le(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(o.userId));}n(pe,"logout");async function we(e,r){let t=E(r.dialect);await fe(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(we,"logoutAll");async function Ie(e,r){let t=E(r.dialect),s=await ce(t,e);if(!s)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let o=await ee(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:o.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(Ie,"getUser");async function ye(e,r,t,s){let o=k(s),a=E(s.dialect),c=await ce(a,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};if(!await W(r,c.passwordHash))return {success:false,error:new u("INVALID_CREDENTIALS","Invalid credentials")};let d=await G(t,o.saltRounds);return await yr(a,e,d),await fe(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(ye,"changePassword");async function ge(e,r,t){let s=k(t),o=E(t.dialect),a=r.filter(g=>!s.validRolesSet.has(g));if(a.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let c=await ce(o,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let l=new Set(c.roles);for(let g of r)l.add(g);let d=Array.from(l);return await gr(o,e,d),{success:true,user:{id:c.id,roles:d}}}n(ge,"assignRoles");async function _e(e,r,t){let s=E(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(l=>({type:l.type.trim(),value:l.value.trim()}));if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o)if(await Q(s,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)};return await mr(s,e,o),{success:true,identifiers:(await ee(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(_e,"bulkCreateIdentifiers");async function Re(e,r,t){let s=E(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one update is required")};let o=r.map(l=>({id:l.id,type:l.type.trim(),value:l.value.trim()}));if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o){let d=await Ce(s,l.id,e);if(!d)return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l.id}`)};if(d.value!==l.value&&await Q(s,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)}}return await s.transaction().execute(async l=>{for(let d of o)await wr(l,d.id,e,{type:d.type,value:d.value});}),{success:true,identifiers:(await ee(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(Re,"bulkUpdateIdentifiers");async function ke(e,r,t){let s=E(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one ID is required")};let o=Array.from(new Set(r));for(let l of o)if(!await Ce(s,l,e))return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l}`)};return await pr(s,e)-o.length<1?{success:false,error:new u("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,o),{success:true,identifiers:(await ee(s,e)).map(l=>({id:l.id,type:l.type,value:l.value}))})}n(ke,"bulkDeleteIdentifiers");async function Ar(e,r){let t=E(r.dialect);return (await Rr(t,e)).map(o=>({id:o.id,ipAddress:o.ipAddress,userAgent:o.userAgent,createdAt:o.createdAt,expiresAt:o.expiresAt}))}n(Ar,"getSessions");async function vr(e,r,t){let s=E(t.dialect),o=await ue(s,r);o&&o.userId===e&&await le(s,r);}n(vr,"revokeSession");function O(e){return k(e).cookieName}n(O,"getCookieName");function Ae(e){return k(e).accessCookieName}n(Ae,"getAccessCookieName");function U(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let o=e.indexOf(";",s),a=o===-1?e.length:o;if(e.startsWith(t,s))return e.slice(s+t.length,a);s=a+1;}}n(U,"readCookie");function re(e,r,t){let s=t.cookie??{},o=K(k(t).refreshExpiresIn)/1e3;e.setCookie(O(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:o});}n(re,"setCookieFromConfig");function ve(e,r){let t=r.cookie??{};e.clearCookie(O(r),{path:t.path??"/"});}n(ve,"clearCookieFromConfig");function te(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,o=K(k(t).accessExpiresIn)/1e3;e.setCookie(Ae(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:o});}n(te,"setAccessCookieFromConfig");function Oe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(Ae(r),{path:t.path??"/"});}n(Oe,"clearAccessCookieFromConfig");function Ee(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):U(e.headers.cookie,Ae(r))}n(Ee,"getCurrentAccessToken");function se(e){let r=e.logger??D,t=e.loggerService??"sentri";return e.mode==="client"?zr(e.keyUri,r,t):Xr(e,r,t)}n(se,"protect");function zr(e,r,t){return async s=>{let o=s.headers.authorization,a=o?.startsWith("Bearer ")?o.slice(7):void 0;if(!a)throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=await rr(e),l=lr(a,c);s.user={id:l.id,roles:l.roles},r.info(m(t,"auth.protect.success",{mode:"client",userId:l.id,requestId:s.id}));}catch(c){let l=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:l,requestId:s.id})),c}}}n(zr,"protectClient");function Xr(e,r,t){return async(s,o)=>{let a=Ee(s,e);if(!a)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=de(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(c.sessionId))throw r.warn(m(t,"auth.protect.token_revoked",{mode:"server",userId:c.id,requestId:s.id})),new u("UNAUTHORIZED","Token has been revoked");s.user={id:c.id,roles:c.roles},r.info(m(t,"auth.protect.success",{mode:"server",userId:c.id,requestId:s.id}));}catch(c){if(c instanceof u&&c.code==="TOKEN_EXPIRED"){let l=U(s.headers.cookie,O(e));if(!l)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Token expired. Please login again.");let d;try{d=await H(l,e);}catch{throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.")}if(!d.success)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:d.error.code,requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.");re(o,d.refreshToken,e),te(o,d.accessToken,e),o.header("X-New-Access-Token",d.accessToken),s.user=d.user,r.info(m(t,"auth.protect.auto_refresh",{mode:"server",userId:d.user.id,requestId:s.id}));}else {let l=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:l,requestId:s.id})),c}}}}n(Xr,"protectServer");function Er(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async o=>{if(!o.user)throw r.warn(m(t,"auth.authorize.unauthenticated",{requiredRoles:e,requestId:o.id})),new u("UNAUTHORIZED","Not authenticated");let a=o.user.roles;if(!e.some(c=>a.includes(c)))throw r.warn(m(t,"auth.authorize.denied",{userId:o.user.id,userRoles:[...a],requiredRoles:e,requestId:o.id})),new u("FORBIDDEN",s);r.info(m(t,"auth.authorize.passed",{userId:o.user.id,userRoles:[...a],requiredRoles:e,requestId:o.id}));}}n(Er,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Er(s,e,r)},"authorize")}n(ne,"createAuthorize");function Gr(...e){return Er(e,D,"sentri")}n(Gr,"authorize");var Wr=new u("FORBIDDEN","You do not have permission to perform this action");function Tr(e,r,t){return async s=>{if(!s.user)throw r.warn(m(t,"auth.permit.unauthenticated",{requestId:s.id})),new u("UNAUTHORIZED","Not authenticated");let o=s.user.id;if(e.roles&&e.roles.length>0){let l=s.user.roles;if(e.roles.some(d=>l.includes(d))){r.info(m(t,"auth.permit.role_bypass",{userId:o,bypassedByRole:true,requestId:s.id}));return}}let a=e.check(s);if(a instanceof Promise?await a:a)r.info(m(t,"auth.permit.passed",{userId:o,requestId:s.id}));else throw r.warn(m(t,"auth.permit.denied",{userId:o,requestId:s.id})),Wr}}n(Tr,"createPermitHandler");function oe(e,r){return n(function(s){return Tr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Zr(e){return Tr(typeof e=="function"?{check:e}:e,D,"sentri")}n(Zr,"permit");var Ue=class{static{n(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let o=Date.now(),a=this.store.get(r);if((!a||a.expiresAt<o)&&(a={count:0,expiresAt:o+s},this.store.set(r,a)),a.count>t){let c=Math.ceil((a.expiresAt-o)/1e3);throw new Error(`Rate limit exceeded. Try again in ${c} seconds.`)}a.count++;}},Le=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let o=`sentri:rl:${r}`,a=Math.ceil(s/1e3);try{let c=await this.redis.multi().incr(o).expire(o,a,"NX").exec();if(!c||c.length===0)return;if(c[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(c){if(c instanceof Error&&c.message.includes("Rate limit exceeded"))throw c;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:c});}}},$=null;async function xr(e,r){if($)return $;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"}),$=new Le(s,r??D),$}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return $=new Ue,$}n(xr,"getRateLimiter");function ie(e){return (r,t,s)=>{if(r instanceof u){s.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.code(500).send({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}n(ie,"createErrorHandler");var Te=8,V=72,xe=255,Nr=100,M=50;function _(e){return new u("VALIDATION_ERROR",e)}n(_,"badRequest");function N(e,r,t,s){e.code(r).send({error:false,statusCode:r,message:t,data:s});}n(N,"ok");function C(e,r){e.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}n(C,"fail");function L(e){let r=e.body;if(r==null||typeof r!="object"||Array.isArray(r))throw new u("VALIDATION_ERROR","Request body is missing or not a JSON object.");return r}n(L,"parseBody");function Jr(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new u("UNAUTHORIZED","Invalid or missing API key")}n(Jr,"validateApiKey");function Pe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Pe,"fireHook");function Yr(e){return e.startsWith("RS")||e.startsWith("PS")}n(Yr,"isRSA");function Ke(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw _(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw _(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Nr)throw _(`identifiers[${r}].type must not exceed ${Nr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw _(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>xe)throw _(`identifiers[${r}].value must not exceed ${xe} characters`);return {type:t.type,value:t.value}}n(Ke,"validateIdentifierInput");function Dr(e){return async r=>{let t=e,s=k(t),o=e.logger??D,a=e.loggerService??"sentri",c=se(e),l=ne(o,a),d=oe(o,a),g=xr(e.redisUrl,o),T=d(i=>!!i.user);r.setErrorHandler(ie()),r.addHook("preParsing",function(i,f,p,h){if(!i.headers["content-type"]?.includes("application/json")){h(null,p);return}if(p==null){h(null,Readable.from("{}"));return}let w=[];p.on("data",y=>w.push(y)),p.on("end",()=>{let y=Buffer.concat(w);h(null,y.length===0?Readable.from("{}"):Readable.from(y));}),p.on("error",y=>h(y));});let b=e.router?.register??(i=>he(i,t)),x=e.router?.login??(i=>me(i,t)),R=e.router?.refresh??(i=>H(i,t)),F=e.router?.logout??(i=>i!==void 0?pe(i,t):Promise.resolve()),ae=e.router?.logoutAll??(i=>we(i,t)),B=e.router?.getUser??(i=>Ie(i,t)),Sr=e.router?.assignRoles??((i,f)=>ge(i,f,t)),Cr=e.router?.changePassword??((i,f,p)=>ye(i,f,p,t)),br=e.router?.bulkCreateIdentifiers??((i,f)=>_e(i,f,t)),Or=e.router?.bulkUpdateIdentifiers??((i,f)=>Re(i,f,t)),Ur=e.router?.bulkDeleteIdentifiers??((i,f)=>ke(i,f,t));Yr(s.algorithm)&&r.get("/keys",async(i,f)=>{f.header("Cache-Control","public, max-age=3600").send(Qe(e.secret));}),r.post("/register",async(i,f)=>{let p=Date.now();Jr(i,e);let h=L(i),{identifiers:w,password:y,roles:I}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>M)throw _(`identifiers must not exceed ${M} entries`);let A=w.map((X,Ne)=>Ke(X,Ne));if(typeof y!="string"||y.length<Te)throw _(`password is required and must be at least ${Te} characters`);if(y.length>V)throw _(`password must not exceed ${V} characters`);if(I!==void 0&&!Array.isArray(I))throw _("roles must be an array of strings when provided");if(Array.isArray(I)&&!I.every(X=>typeof X=="string"))throw _("each role must be a string");let S=Array.isArray(I)?I:void 0,v=S!==void 0?{identifiers:A,password:y,roles:S}:{identifiers:A,password:y},P=i.ip||"127.0.0.1",j=i.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let X=await g,Ne=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${P}`,Ne,900*1e3);}let z=await b(v,{ip:P,userAgent:j});if(!z.success){o.warn(m(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,z.error);return}o.info(m(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,201,"User registered successfully",{user:z.user});}),r.post("/login",async(i,f)=>{let p=Date.now(),h=L(i),{identifier:w,password:y}=h;if(typeof w!="string"||w.trim().length===0)throw _("identifier is required and must be a non-empty string");if(w.length>xe)throw _(`identifier must not exceed ${xe} characters`);if(typeof y!="string"||y.length===0)throw _("password is required");if(y.length>V)throw _(`password must not exceed ${V} characters`);let I=w.trim(),A=i.ip||"127.0.0.1",S=i.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let P=await g,j=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await P.consume(`login:${A}`,j,900*1e3);}let v=await x({identifier:I,password:y},{ip:A,userAgent:S});if(!v.success){Pe(()=>e.hooks?.onLoginFailed?.(I,v.error,{ip:A})),o.warn(m(a,"auth.login.failure",{errorCode:v.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,v.error);return}Pe(()=>e.hooks?.onLoginSuccess?.(v.user,{ip:A,userAgent:S})),re(f,v.refreshToken,e),te(f,v.accessToken,e),o.info(m(a,"auth.login.success",{userId:v.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Login successful",{accessToken:v.accessToken,user:v.user});}),r.post("/refresh",async(i,f)=>{let p=Date.now(),h=U(i.headers.cookie,O(e));if(!h)throw new u("UNAUTHORIZED","Refresh token cookie is missing");let w=i.ip||"127.0.0.1",y=i.headers["user-agent"]||"Unknown",I=await R(h,{ip:w,userAgent:y});if(!I.success){ve(f,e),o.warn(m(a,"auth.refresh.failure",{errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}re(f,I.refreshToken,e),te(f,I.accessToken,e),o.info(m(a,"auth.refresh.success",{userId:I.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Token refreshed",{accessToken:I.accessToken});}),r.post("/logout",async(i,f)=>{let p=Date.now(),h=U(i.headers.cookie,O(e));await F(h),ve(f,e),Oe(f,e),o.info(m(a,"auth.logout",{duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Logged out",null);}),r.post("/logout-all",{preHandler:c},async(i,f)=>{let p=Date.now(),h=i.user.id;await ae(h),Pe(()=>e.hooks?.onLogout?.(h)),ve(f,e),Oe(f,e),o.info(m(a,"auth.logout_all",{userId:h,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"All sessions revoked",null);}),r.get("/me",{preHandler:c},async(i,f)=>{let p=Date.now(),h=await B(i.user.id);if(!h.success){o.warn(m(a,"auth.me.failure",{userId:i.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,h.error);return}o.info(m(a,"auth.me.success",{userId:h.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"OK",h.user);}),r.get("/me/identifiers",{preHandler:c},async(i,f)=>{let p=Date.now(),h=await B(i.user.id);if(!h.success){o.warn(m(a,"auth.me.identifiers.failure",{userId:i.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,h.error);return}o.info(m(a,"auth.me.identifiers.success",{userId:h.user.id,count:h.user.identifiers?.length??0,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"OK",{identifiers:h.user.identifiers??[]});}),r.post("/me/identifiers",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>M)throw _(`identifiers must not exceed ${M} entries`);let y=w.map((A,S)=>Ke(A,S)),I=await br(i.user.id,y);if(!I.success){o.warn(m(a,"auth.identifiers.create_failure",{userId:i.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.identifiers.created",{userId:i.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:i.id})),N(f,201,"Identifiers added successfully",{identifiers:I.identifiers});}),r.put("/me/identifiers",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>M)throw _(`identifiers must not exceed ${M} entries`);let y=w.map((A,S)=>{if(typeof A!="object"||A===null||Array.isArray(A))throw _(`identifiers[${S}] must be an object`);let v=A;if(typeof v.id!="string"||v.id.trim().length===0)throw _(`identifiers[${S}].id is required and must be a non-empty string`);let{type:P,value:j}=Ke(A,S);return {id:v.id,type:P,value:j}}),I=await Or(i.user.id,y);if(!I.success){o.warn(m(a,"auth.identifiers.update_failure",{userId:i.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.identifiers.updated",{userId:i.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Identifiers updated successfully",{identifiers:I.identifiers});}),r.delete("/me/identifiers",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{ids:w}=h;if(!Array.isArray(w)||w.length===0)throw _("ids is required and must be a non-empty array of strings");if(!w.every(I=>typeof I=="string"))throw _("each id must be a string");let y=await Ur(i.user.id,w);if(!y.success){o.warn(m(a,"auth.identifiers.delete_failure",{userId:i.user.id,errorCode:y.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,y.error);return}o.info(m(a,"auth.identifiers.deleted",{userId:i.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Identifiers deleted successfully",{identifiers:y.identifiers});}),r.patch("/me/password",{preHandler:[c,T]},async(i,f)=>{let p=Date.now(),h=L(i),{currentPassword:w,newPassword:y}=h;if(typeof w!="string"||w.length===0)throw _("currentPassword is required");if(typeof y!="string"||y.length<Te)throw _(`newPassword must be at least ${Te} characters`);if(y.length>V)throw _(`newPassword must not exceed ${V} characters`);if(w===y)throw _("newPassword must be different from currentPassword");let I=await Cr(i.user.id,w,y);if(!I.success){o.warn(m(a,"auth.password.change_failure",{userId:i.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.password.changed",{userId:i.user.id,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Password updated successfully. All sessions have been revoked.",null);}),r.post("/users/:userId/roles",{preHandler:[c,l("admin")]},async(i,f)=>{let p=Date.now(),h=L(i),{roles:w}=h,y=i.params.userId;if(!y)throw _("userId is required");if(!Array.isArray(w)||w.length===0)throw _("roles must be a non-empty array of strings");if(!w.every(A=>typeof A=="string"))throw _("each role must be a string");let I=await Sr(y,w);if(!I.success){o.warn(m(a,"auth.roles.assign_failure",{targetUserId:y,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:i.id})),C(f,I.error);return}o.info(m(a,"auth.roles.assigned",{targetUserId:y,roles:w,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Roles assigned successfully",{user:I.user});}),r.get("/sessions",{preHandler:c},async(i,f)=>{let p=Date.now(),h=i.user.id,y=await(e.router?.getSessions??(I=>Ar(I,t)))(h);o.info(m(a,"auth.sessions.get",{userId:h,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"OK",{sessions:y});}),r.delete("/sessions/:id",{preHandler:c},async(i,f)=>{let p=Date.now(),h=i.user.id,w=i.params.id;await(e.router?.revokeSession??((I,A)=>vr(I,A,t)))(h,w),o.info(m(a,"auth.sessions.revoke",{userId:h,sessionId:w,duration_ms:Date.now()-p,requestId:i.id})),N(f,200,"Session revoked",null);});}}n(Dr,"createAuthPlugin");function Bs(e){if(e.mode==="client"){let d={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(d);let g=d.logger??D,T=d.loggerService??"sentri",b=ne(g,T),x=oe(g,T);return {protect:n(()=>se(d),"protect"),authorize:n((...R)=>b(...R),"authorize"),permit:n(R=>x(R),"permit"),errorHandler:n(R=>ie(R),"errorHandler")}}let{privateKey:r}=generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),t={mode:"server",dialect:e.dialect,secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}},s=t.logger??D,o=t.loggerService??"sentri",a=k(t),c=ne(s,o),l=oe(s,o);return {protect:n(()=>se(t),"protect"),authorize:n((...d)=>c(...d),"authorize"),permit:n(d=>l(d),"permit"),plugin:n(()=>Dr(t),"plugin"),errorHandler:n(d=>ie(d),"errorHandler"),migrate:n(()=>Xe(E(t.dialect)),"migrate"),getCurrentAccessToken:n(d=>Ee(d,t),"getCurrentAccessToken"),hashPassword:n(d=>G(d,a.saltRounds),"hashPassword"),verifyPassword:n((d,g)=>W(d,g),"verifyPassword"),signAccessToken:n(d=>J(d,t),"signAccessToken"),signRefreshToken:n(d=>Y(d,t),"signRefreshToken"),verifyAccessToken:n(d=>de(d,t),"verifyAccessToken"),verifyRefreshToken:n(d=>q(d,t),"verifyRefreshToken"),register:n(d=>he(d,t),"register"),login:n(d=>me(d,t),"login"),refresh:n(d=>H(d,t),"refresh"),logout:n(d=>pe(d,t),"logout"),logoutAll:n(d=>we(d,t),"logoutAll"),getUser:n(d=>Ie(d,t),"getUser"),changePassword:n((d,g,T)=>ye(d,g,T,t),"changePassword"),assignRoles:n((d,g)=>ge(d,g,t),"assignRoles"),bulkCreateIdentifiers:n((d,g)=>_e(d,g,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((d,g)=>Re(d,g,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((d,g)=>ke(d,g,t),"bulkDeleteIdentifiers")}}n(Bs,"createAuthFastify");export{He as SENTRI_ERROR_STATUS,u as SentriError,Gr as authorize,Bs as createAuthFastify,Dr as createAuthPlugin,ne as createAuthorize,ie as createErrorHandler,oe as createPermit,Ee as getCurrentAccessToken,Zr as permit,se as protect};
|
|
1
|
+
import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import Ze from'bcrypt';import J from'jsonwebtoken';import'@fastify/cookie';import {Readable}from'stream';var Lr=Object.defineProperty;var n=(e,r)=>Lr(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}),u=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??$e[r]??500;}};var Ve=new WeakMap,Me=32,Be=10,je=31;function Xe(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new u("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new u("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Me)throw new u("CONFIGURATION_ERROR",`secret must be at least ${Me} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Be||s>je)throw new u("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Be} and ${je}`);if(!e.validRoles||e.validRoles.length===0)throw new u("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new u("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new u("CONFIGURATION_ERROR","dialect is required in server mode")}n(Xe,"validateConfig");function g(e){let r=Ve.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),validIdentifiers:e.validIdentifiers??["email","username"],validIdentifiersSet:new Set(e.validIdentifiers??["email","username"]),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Ve.set(e,t),t}n(g,"resolveServerConfig");var Fr=/^(\d+)([smhdw])$/,Pr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},ze=new Map;function H(e){if(typeof e=="number")return e*1e3;let r=ze.get(e);if(r!==void 0)return r;let t=Fr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Pr[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 ze.set(e,i),i}n(H,"parseExpiry");var D={info:n(()=>{},"info"),warn:n(()=>{},"warn"),error:n(()=>{},"error")};function m(e,r,t){return {service:e,event:r,...t}}n(m,"buildLogData");async function Ge(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(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(Ge,"runMigrations");var We=new Map;function T(e){let r=We.get(e);return r||(r=new Kysely({dialect:e}),We.set(e,r)),r}n(T,"getDatabase");async function W(e,r=12){return Ze.hash(e,r)}n(W,"hashPassword");async function Z(e,r){return Ze.compare(e,r)}n(Z,"verifyPassword");var Je=new Map,Ye=new Map,Vr=3600*1e3;function Qe(e){let r=Je.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),i=s.export({format:"jwk"}),c=createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),y={...i,use:"sig",kid:c};r={kid:c,publicKey:s,jwk:y},Je.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=Ye.get(e);if(t&&r-t.fetchedAt<Vr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new u("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new u("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let o=i.keys[0],c=createPublicKey({key:o,format:"jwk"});return Ye.set(e,{publicKey:c,fetchedAt:r}),c}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,ir=new Map;function or(e){return e.startsWith("RS")||e.startsWith("PS")}n(or,"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 dr(e){let r=g(e);if(or(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(dr,"getSigningKeys");function cr(e,r){let t=g(e);if(or(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(cr,"getVerifyKey");function ur(e,r,t,s){let i=`${t}:${s}`,o=ir.get(i);return o||(o={expiresIn:t,algorithm:s},ir.set(i,o)),J.sign(e,r,o)}n(ur,"sign");function lr(e,r,t){try{let s=J.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new u("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof u?s:s instanceof J.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function Y(e,r){let t=g(r),{accessKey:s}=dr(r);return ur(e,s,t.accessExpiresIn,t.algorithm)}n(Y,"signAccessToken");function q(e,r){let t=g(r),{refreshKey:s}=dr(r);return ur({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(q,"signRefreshToken");function ce(e,r){let t=g(r),s=cr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=g(r),s=cr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=J.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new u("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof u?t:t instanceof J.TokenExpiredError?new u("TOKEN_EXPIRED","Token has expired"):new u("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Ce(e){try{return JSON.parse(e)}catch{return []}}n(Ce,"parseRoles");function jr(e){return JSON.stringify(e)}n(jr,"serializeRoles");function mr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(mr,"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:Ce(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function ue(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Ce(t.roles)}:null}n(ue,"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(mr)}n(re,"findIdentifiersByUserId");async function Oe(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?mr(s):null}n(Oe,"findIdentifierById");async function wr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}n(wr,"countIdentifiersByUserId");async function Ir(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}n(Ir,"updateIdentifier");async function yr(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(yr,"deleteIdentifiers");async function Rr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}n(Rr,"updateUserPassword");async function _r(e,r,t){await e.updateTable("sentri_users").set({roles:jr(t)}).where("id","=",r).execute();}n(_r,"updateUserRoles");async function be(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(be,"createSession");async function le(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:Ce(t.roles)}}:null}n(le,"findSessionById");async function fe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(fe,"deleteSession");async function gr(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(gr,"markSessionReplaced");async function he(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(he,"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 me(e,r,t){let s=g(r),i=T(r.dialect),o=e.roles??[],c=o.filter(A=>!s.validRolesSet.has(A));if(c.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${c.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let y=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),d=y.filter(A=>!s.validIdentifiersSet.has(A.type));if(d.length>0)return {success:false,error:new u("VALIDATION_ERROR",`Invalid identifier types: ${d.map(A=>A.type).join(", ")}`)};if(new Set(y.map(A=>A.value)).size!==y.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of y)if(await ee(i,A.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let v=await W(e.password,s.saltRounds),{userId:C,identifierRows:x}=await i.transaction().execute(async A=>{let P=randomUUID();await A.insertInto("sentri_users").values({id:P,password_hash:v,roles:JSON.stringify(o)}).execute();let j=y.map(de=>({id:randomUUID(),user_id:P,type:de.type,value:de.value}));return await A.insertInto("sentri_identifiers").values(j).execute(),{userId:P,identifierRows:j}}),O={success:true,user:{id:C,roles:o,identifiers:x.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(O.user),O}n(me,"register");async function pe(e,r,t){let s=g(r),i=T(r.dialect),o=await ee(i,e.identifier.trim());if(!o){let x=new u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}if(!await Z(e.password,o.passwordHash)){let x=new u("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,x,{ip:t?.ip??""}),{success:false,error:x}}let y=new Date(Date.now()+H(s.refreshExpiresIn)),d=await be(i,{userId:o.id,expiresAt:y,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),l={id:o.id,roles:o.roles},v=Y({id:o.id,roles:o.roles,sessionId:d.id},r),C=q(d.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(l,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:v,refreshToken:C,user:l}}n(pe,"login");async function $(e,r,t){let s=g(r),i=T(r.dialect),o;try{({sessionId:o}=Q(e,r));}catch(x){return x instanceof u?{success:false,error:x}:{success:false,error:new u("TOKEN_INVALID","Invalid refresh token")}}let c=await le(i,o);if(!c)return {success:false,error:new u("UNAUTHORIZED","Session not found or revoked")};if(c.replacedBy)return await he(i,c.userId),{success:false,error:new u("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(c.expiresAt.getTime()<Date.now())return await fe(i,o),{success:false,error:new u("TOKEN_EXPIRED","Session has expired")};let y=new Date(Date.now()+H(s.refreshExpiresIn)),d=await be(i,{userId:c.userId,expiresAt:y,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await gr(i,o,d.id);let l={id:c.user.id,roles:c.user.roles},v=Y({...l,sessionId:d.id},r),C=q(d.id,r);return {success:true,accessToken:v,refreshToken:C,user:l}}n($,"refresh");async function we(e,r){let t=T(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await le(t,s);i&&(await fe(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(we,"logout");async function Ie(e,r){let t=T(r.dialect);await he(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function ye(e,r){let t=T(r.dialect),s=await ue(t,e);if(!s)return {success:false,error:new u("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(o=>({id:o.id,type:o.type,value:o.value}))}}}n(ye,"getUser");async function Re(e,r,t,s){let i=g(s),o=T(s.dialect),c=await ue(o,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};if(!await Z(r,c.passwordHash))return {success:false,error:new u("INVALID_CREDENTIALS","Invalid credentials")};let d=await W(t,i.saltRounds);return await Rr(o,e,d),await he(o,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(Re,"changePassword");async function _e(e,r,t){let s=g(t),i=T(t.dialect),o=r.filter(l=>!s.validRolesSet.has(l));if(o.length>0)return {success:false,error:new u("INVALID_ROLE",`Invalid roles: ${o.join(", ")}`)};let c=await ue(i,e);if(!c)return {success:false,error:new u("USER_NOT_FOUND","User not found")};let y=new Set(c.roles);for(let l of r)y.add(l);let d=Array.from(y);return await _r(i,e,d),{success:true,user:{id:c.id,roles:d}}}n(_e,"assignRoles");async function ge(e,r,t){let s=g(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one identifier is required")};let o=r.map(l=>({type:l.type.trim(),value:l.value.trim()})),c=o.filter(l=>!s.validIdentifiersSet.has(l.type));if(c.length>0)return {success:false,error:new u("VALIDATION_ERROR",`Invalid identifier types: ${c.map(l=>l.type).join(", ")}`)};if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o)if(await ee(i,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)};return await pr(i,e,o),{success:true,identifiers:(await re(i,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(ge,"bulkCreateIdentifiers");async function ve(e,r,t){let s=g(t),i=T(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one update is required")};let o=r.map(l=>({id:l.id,type:l.type.trim(),value:l.value.trim()})),c=o.filter(l=>!s.validIdentifiersSet.has(l.type));if(c.length>0)return {success:false,error:new u("VALIDATION_ERROR",`Invalid identifier types: ${c.map(l=>l.type).join(", ")}`)};if(new Set(o.map(l=>l.value)).size!==o.length)return {success:false,error:new u("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of o){let v=await Oe(i,l.id,e);if(!v)return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${l.id}`)};if(v.value!==l.value&&await ee(i,l.value))return {success:false,error:new u("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)}}return await i.transaction().execute(async l=>{for(let v of o)await Ir(l,v.id,e,{type:v.type,value:v.value});}),{success:true,identifiers:(await re(i,e)).map(l=>({id:l.id,type:l.type,value:l.value}))}}n(ve,"bulkUpdateIdentifiers");async function Ae(e,r,t){let s=T(t.dialect);if(r.length===0)return {success:false,error:new u("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let y of i)if(!await Oe(s,y,e))return {success:false,error:new u("IDENTIFIER_NOT_FOUND",`Identifier not found: ${y}`)};return await wr(s,e)-i.length<1?{success:false,error:new u("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await yr(s,e,i),{success:true,identifiers:(await re(s,e)).map(y=>({id:y.id,type:y.type,value:y.value}))})}n(Ae,"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 le(s,r);i&&i.userId===e&&await fe(s,r);}n(Er,"revokeSession");function U(e){return g(e).cookieName}n(U,"getCookieName");function ke(e){return g(e).accessCookieName}n(ke,"getAccessCookieName");function L(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let i=e.indexOf(";",s),o=i===-1?e.length:i;if(e.startsWith(t,s))return e.slice(s+t.length,o);s=o+1;}}n(L,"readCookie");function te(e,r,t){let s=t.cookie??{},i=H(g(t).refreshExpiresIn)/1e3;e.setCookie(U(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Ee(e,r){let t=r.cookie??{};e.clearCookie(U(r),{path:t.path??"/"});}n(Ee,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=H(g(t).accessExpiresIn)/1e3;e.setCookie(ke(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function Ue(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(ke(r),{path:t.path??"/"});}n(Ue,"clearAccessCookieFromConfig");function Te(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):L(e.headers.cookie,ke(r))}n(Te,"getCurrentAccessToken");function ne(e){let r=e.logger??D,t=e.loggerService??"sentri";return e.mode==="client"?zr(e.keyUri,r,t):Xr(e,r,t)}n(ne,"protect");function zr(e,r,t){return async s=>{let i=s.headers.authorization,o=i?.startsWith("Bearer ")?i.slice(7):void 0;if(!o)throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=await tr(e),y=fr(o,c);s.user={id:y.id,roles:y.roles},r.info(m(t,"auth.protect.success",{mode:"client",userId:y.id,requestId:s.id}));}catch(c){let y=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:y,requestId:s.id})),c}}}n(zr,"protectClient");function Xr(e,r,t){return async(s,i)=>{let o=Te(s,e);if(!o)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",requestId:s.id})),new u("UNAUTHORIZED","Missing or malformed Authorization header");try{let c=ce(o,e);if(e.isTokenRevoked&&await e.isTokenRevoked(c.sessionId))throw r.warn(m(t,"auth.protect.token_revoked",{mode:"server",userId:c.id,requestId:s.id})),new u("UNAUTHORIZED","Token has been revoked");s.user={id:c.id,roles:c.roles},r.info(m(t,"auth.protect.success",{mode:"server",userId:c.id,requestId:s.id}));}catch(c){if(c instanceof u&&c.code==="TOKEN_EXPIRED"){let y=L(s.headers.cookie,U(e));if(!y)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Token expired. Please login again.");let d;try{d=await $(y,e);}catch{throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.")}if(!d.success)throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:d.error.code,requestId:s.id})),new u("UNAUTHORIZED","Session expired. Please login again.");te(i,d.refreshToken,e),se(i,d.accessToken,e),i.header("X-New-Access-Token",d.accessToken),s.user=d.user,r.info(m(t,"auth.protect.auto_refresh",{mode:"server",userId:d.user.id,requestId:s.id}));}else {let y=c instanceof u?c.code:"TOKEN_INVALID";throw r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:y,requestId:s.id})),c}}}}n(Xr,"protectServer");function Tr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async i=>{if(!i.user)throw r.warn(m(t,"auth.authorize.unauthenticated",{requiredRoles:e,requestId:i.id})),new u("UNAUTHORIZED","Not authenticated");let o=i.user.roles;if(!e.some(c=>o.includes(c)))throw r.warn(m(t,"auth.authorize.denied",{userId:i.user.id,userRoles:[...o],requiredRoles:e,requestId:i.id})),new u("FORBIDDEN",s);r.info(m(t,"auth.authorize.passed",{userId:i.user.id,userRoles:[...o],requiredRoles:e,requestId:i.id}));}}n(Tr,"createAuthorizeHandler");function ie(e,r){return n(function(...s){return Tr(s,e,r)},"authorize")}n(ie,"createAuthorize");function Gr(...e){return Tr(e,D,"sentri")}n(Gr,"authorize");var Wr=new u("FORBIDDEN","You do not have permission to perform this action");function xr(e,r,t){return async s=>{if(!s.user)throw r.warn(m(t,"auth.permit.unauthenticated",{requestId:s.id})),new u("UNAUTHORIZED","Not authenticated");let i=s.user.id;if(e.roles&&e.roles.length>0){let y=s.user.roles;if(e.roles.some(d=>y.includes(d))){r.info(m(t,"auth.permit.role_bypass",{userId:i,bypassedByRole:true,requestId:s.id}));return}}let o=e.check(s);if(o instanceof Promise?await o:o)r.info(m(t,"auth.permit.passed",{userId:i,requestId:s.id}));else throw r.warn(m(t,"auth.permit.denied",{userId:i,requestId:s.id})),Wr}}n(xr,"createPermitHandler");function oe(e,r){return n(function(s){return xr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Zr(e){return xr(typeof e=="function"?{check:e}:e,D,"sentri")}n(Zr,"permit");var Le=class{static{n(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let i=Date.now(),o=this.store.get(r);if((!o||o.expiresAt<i)&&(o={count:0,expiresAt:i+s},this.store.set(r,o)),o.count>t){let c=Math.ceil((o.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${c} seconds.`)}o.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}`,o=Math.ceil(s/1e3);try{let c=await this.redis.multi().incr(i).expire(i,o,"NX").exec();if(!c||c.length===0)return;if(c[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(c){if(c instanceof Error&&c.message.includes("Rate limit exceeded"))throw c;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:c});}}},V=null;async function Nr(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 Le,V}n(Nr,"getRateLimiter");function ae(e){return (r,t,s)=>{if(r instanceof u){s.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.code(500).send({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}n(ae,"createErrorHandler");var xe=8,M=72,Ne=255,Dr=100,B=50;function _(e){return new u("VALIDATION_ERROR",e)}n(_,"badRequest");function N(e,r,t,s){e.code(r).send({error:false,statusCode:r,message:t,data:s});}n(N,"ok");function b(e,r){e.code(r.statusCode).send({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}n(b,"fail");function F(e){let r=e.body;if(r==null||typeof r!="object"||Array.isArray(r))throw new u("VALIDATION_ERROR","Request body is missing or not a JSON object.");return r}n(F,"parseBody");function Jr(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new u("UNAUTHORIZED","Invalid or missing API key")}n(Jr,"validateApiKey");function Ke(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Ke,"fireHook");function Yr(e){return e.startsWith("RS")||e.startsWith("PS")}n(Yr,"isRSA");function He(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw _(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw _(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Dr)throw _(`identifiers[${r}].type must not exceed ${Dr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw _(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Ne)throw _(`identifiers[${r}].value must not exceed ${Ne} characters`);return {type:t.type,value:t.value}}n(He,"validateIdentifierInput");function Sr(e){return async r=>{let t=e,s=g(t),i=e.logger??D,o=e.loggerService??"sentri",c=ne(e),y=ie(i,o),d=oe(i,o),l=Nr(e.redisUrl,i),v=d(a=>!!a.user);r.setErrorHandler(ae()),r.addHook("preParsing",function(a,f,p,h){if(!a.headers["content-type"]?.includes("application/json")){h(null,p);return}if(p==null){h(null,Readable.from("{}"));return}let w=[];p.on("data",R=>w.push(R)),p.on("end",()=>{let R=Buffer.concat(w);h(null,R.length===0?Readable.from("{}"):Readable.from(R));}),p.on("error",R=>h(R));});let C=e.router?.register??(a=>me(a,t)),x=e.router?.login??(a=>pe(a,t)),O=e.router?.refresh??(a=>$(a,t)),A=e.router?.logout??(a=>a!==void 0?we(a,t):Promise.resolve()),P=e.router?.logoutAll??(a=>Ie(a,t)),j=e.router?.getUser??(a=>ye(a,t)),de=e.router?.assignRoles??((a,f)=>_e(a,f,t)),Cr=e.router?.changePassword??((a,f,p)=>Re(a,f,p,t)),Or=e.router?.bulkCreateIdentifiers??((a,f)=>ge(a,f,t)),br=e.router?.bulkUpdateIdentifiers??((a,f)=>ve(a,f,t)),Ur=e.router?.bulkDeleteIdentifiers??((a,f)=>Ae(a,f,t));Yr(s.algorithm)&&r.get("/keys",async(a,f)=>{f.header("Cache-Control","public, max-age=3600").send(er(e.secret));}),r.post("/register",async(a,f)=>{let p=Date.now();Jr(a,e);let h=F(a),{identifiers:w,password:R,roles:I}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>B)throw _(`identifiers must not exceed ${B} entries`);let k=w.map((G,De)=>He(G,De));if(typeof R!="string"||R.length<xe)throw _(`password is required and must be at least ${xe} characters`);if(R.length>M)throw _(`password must not exceed ${M} characters`);if(I!==void 0&&!Array.isArray(I))throw _("roles must be an array of strings when provided");if(Array.isArray(I)&&!I.every(G=>typeof G=="string"))throw _("each role must be a string");let S=Array.isArray(I)?I:void 0,E=S!==void 0?{identifiers:k,password:R,roles:S}:{identifiers:k,password:R},K=a.ip||"127.0.0.1",z=a.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let G=await l,De=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await G.consume(`register:${K}`,De,900*1e3);}let X=await C(E,{ip:K,userAgent:z});if(!X.success){i.warn(m(o,"auth.register.failure",{errorCode:X.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,X.error);return}i.info(m(o,"auth.register.success",{userId:X.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,201,"User registered successfully",{user:X.user});}),r.post("/login",async(a,f)=>{let p=Date.now(),h=F(a),{identifier:w,password:R}=h;if(typeof w!="string"||w.trim().length===0)throw _("identifier is required and must be a non-empty string");if(w.length>Ne)throw _(`identifier must not exceed ${Ne} characters`);if(typeof R!="string"||R.length===0)throw _("password is required");if(R.length>M)throw _(`password must not exceed ${M} characters`);let I=w.trim(),k=a.ip||"127.0.0.1",S=a.headers["user-agent"]||"Unknown";if(e.rateLimit!==false){let K=await l,z=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${k}`,z,900*1e3);}let E=await x({identifier:I,password:R},{ip:k,userAgent:S});if(!E.success){Ke(()=>e.hooks?.onLoginFailed?.(I,E.error,{ip:k})),i.warn(m(o,"auth.login.failure",{errorCode:E.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,E.error);return}Ke(()=>e.hooks?.onLoginSuccess?.(E.user,{ip:k,userAgent:S})),te(f,E.refreshToken,e),se(f,E.accessToken,e),i.info(m(o,"auth.login.success",{userId:E.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Login successful",{accessToken:E.accessToken,user:E.user});}),r.post("/refresh",async(a,f)=>{let p=Date.now(),h=L(a.headers.cookie,U(e));if(!h)throw new u("UNAUTHORIZED","Refresh token cookie is missing");let w=a.ip||"127.0.0.1",R=a.headers["user-agent"]||"Unknown",I=await O(h,{ip:w,userAgent:R});if(!I.success){Ee(f,e),i.warn(m(o,"auth.refresh.failure",{errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}te(f,I.refreshToken,e),se(f,I.accessToken,e),i.info(m(o,"auth.refresh.success",{userId:I.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Token refreshed",{accessToken:I.accessToken});}),r.post("/logout",async(a,f)=>{let p=Date.now(),h=L(a.headers.cookie,U(e));await A(h),Ee(f,e),Ue(f,e),i.info(m(o,"auth.logout",{duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Logged out",null);}),r.post("/logout-all",{preHandler:c},async(a,f)=>{let p=Date.now(),h=a.user.id;await P(h),Ke(()=>e.hooks?.onLogout?.(h)),Ee(f,e),Ue(f,e),i.info(m(o,"auth.logout_all",{userId:h,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"All sessions revoked",null);}),r.get("/me",{preHandler:c},async(a,f)=>{let p=Date.now(),h=await j(a.user.id);if(!h.success){i.warn(m(o,"auth.me.failure",{userId:a.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,h.error);return}i.info(m(o,"auth.me.success",{userId:h.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"OK",h.user);}),r.get("/me/identifiers",{preHandler:c},async(a,f)=>{let p=Date.now(),h=await j(a.user.id);if(!h.success){i.warn(m(o,"auth.me.identifiers.failure",{userId:a.user.id,errorCode:h.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,h.error);return}i.info(m(o,"auth.me.identifiers.success",{userId:h.user.id,count:h.user.identifiers?.length??0,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"OK",{identifiers:h.user.identifiers??[]});}),r.post("/me/identifiers",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>B)throw _(`identifiers must not exceed ${B} entries`);let R=w.map((k,S)=>He(k,S)),I=await Or(a.user.id,R);if(!I.success){i.warn(m(o,"auth.identifiers.create_failure",{userId:a.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.identifiers.created",{userId:a.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:a.id})),N(f,201,"Identifiers added successfully",{identifiers:I.identifiers});}),r.put("/me/identifiers",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{identifiers:w}=h;if(!Array.isArray(w)||w.length===0)throw _("identifiers is required and must be a non-empty array");if(w.length>B)throw _(`identifiers must not exceed ${B} entries`);let R=w.map((k,S)=>{if(typeof k!="object"||k===null||Array.isArray(k))throw _(`identifiers[${S}] must be an object`);let E=k;if(typeof E.id!="string"||E.id.trim().length===0)throw _(`identifiers[${S}].id is required and must be a non-empty string`);let{type:K,value:z}=He(k,S);return {id:E.id,type:K,value:z}}),I=await br(a.user.id,R);if(!I.success){i.warn(m(o,"auth.identifiers.update_failure",{userId:a.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.identifiers.updated",{userId:a.user.id,count:I.identifiers.length,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Identifiers updated successfully",{identifiers:I.identifiers});}),r.delete("/me/identifiers",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{ids:w}=h;if(!Array.isArray(w)||w.length===0)throw _("ids is required and must be a non-empty array of strings");if(!w.every(I=>typeof I=="string"))throw _("each id must be a string");let R=await Ur(a.user.id,w);if(!R.success){i.warn(m(o,"auth.identifiers.delete_failure",{userId:a.user.id,errorCode:R.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,R.error);return}i.info(m(o,"auth.identifiers.deleted",{userId:a.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Identifiers deleted successfully",{identifiers:R.identifiers});}),r.patch("/me/password",{preHandler:[c,v]},async(a,f)=>{let p=Date.now(),h=F(a),{currentPassword:w,newPassword:R}=h;if(typeof w!="string"||w.length===0)throw _("currentPassword is required");if(typeof R!="string"||R.length<xe)throw _(`newPassword must be at least ${xe} characters`);if(R.length>M)throw _(`newPassword must not exceed ${M} characters`);if(w===R)throw _("newPassword must be different from currentPassword");let I=await Cr(a.user.id,w,R);if(!I.success){i.warn(m(o,"auth.password.change_failure",{userId:a.user.id,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.password.changed",{userId:a.user.id,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Password updated successfully. All sessions have been revoked.",null);}),r.post("/users/:userId/roles",{preHandler:[c,y("admin")]},async(a,f)=>{let p=Date.now(),h=F(a),{roles:w}=h,R=a.params.userId;if(!R)throw _("userId is required");if(!Array.isArray(w)||w.length===0)throw _("roles must be a non-empty array of strings");if(!w.every(k=>typeof k=="string"))throw _("each role must be a string");let I=await de(R,w);if(!I.success){i.warn(m(o,"auth.roles.assign_failure",{targetUserId:R,errorCode:I.error.code,duration_ms:Date.now()-p,requestId:a.id})),b(f,I.error);return}i.info(m(o,"auth.roles.assigned",{targetUserId:R,roles:w,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Roles assigned successfully",{user:I.user});}),r.get("/sessions",{preHandler:c},async(a,f)=>{let p=Date.now(),h=a.user.id,R=await(e.router?.getSessions??(I=>kr(I,t)))(h);i.info(m(o,"auth.sessions.get",{userId:h,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"OK",{sessions:R});}),r.delete("/sessions/:id",{preHandler:c},async(a,f)=>{let p=Date.now(),h=a.user.id,w=a.params.id;await(e.router?.revokeSession??((I,k)=>Er(I,k,t)))(h,w),i.info(m(o,"auth.sessions.revoke",{userId:h,sessionId:w,duration_ms:Date.now()-p,requestId:a.id})),N(f,200,"Session revoked",null);});}}n(Sr,"createAuthPlugin");function Bs(e){if(e.mode==="client"){let d={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}};Xe(d);let l=d.logger??D,v=d.loggerService??"sentri",C=ie(l,v),x=oe(l,v);return {protect:n(()=>ne(d),"protect"),authorize:n((...O)=>C(...O),"authorize"),permit:n(O=>x(O),"permit"),errorHandler:n(O=>ae(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",o=g(t),c=ie(s,i),y=oe(s,i);return {protect:n(()=>ne(t),"protect"),authorize:n((...d)=>c(...d),"authorize"),permit:n(d=>y(d),"permit"),plugin:n(()=>Sr(t),"plugin"),errorHandler:n(d=>ae(d),"errorHandler"),migrate:n(()=>Ge(T(t.dialect)),"migrate"),getCurrentAccessToken:n(d=>Te(d,t),"getCurrentAccessToken"),hashPassword:n(d=>W(d,o.saltRounds),"hashPassword"),verifyPassword:n((d,l)=>Z(d,l),"verifyPassword"),signAccessToken:n(d=>Y(d,t),"signAccessToken"),signRefreshToken:n(d=>q(d,t),"signRefreshToken"),verifyAccessToken:n(d=>ce(d,t),"verifyAccessToken"),verifyRefreshToken:n(d=>Q(d,t),"verifyRefreshToken"),register:n(d=>me(d,t),"register"),login:n(d=>pe(d,t),"login"),refresh:n(d=>$(d,t),"refresh"),logout:n(d=>we(d,t),"logout"),logoutAll:n(d=>Ie(d,t),"logoutAll"),getUser:n(d=>ye(d,t),"getUser"),changePassword:n((d,l,v)=>Re(d,l,v,t),"changePassword"),assignRoles:n((d,l)=>_e(d,l,t),"assignRoles"),bulkCreateIdentifiers:n((d,l)=>ge(d,l,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((d,l)=>ve(d,l,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((d,l)=>Ae(d,l,t),"bulkDeleteIdentifiers")}}n(Bs,"createAuthFastify");export{$e as SENTRI_ERROR_STATUS,u as SentriError,Gr as authorize,Bs as createAuthFastify,Sr as createAuthPlugin,ie as createAuthorize,ae as createErrorHandler,oe as createPermit,Te as getCurrentAccessToken,Zr as permit,ne as protect};
|