sentri 4.1.1 → 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/README.md +462 -211
- package/dist/adapters/elysia/index.cjs +1 -0
- package/dist/adapters/elysia/index.d.cts +164 -0
- package/dist/adapters/elysia/index.d.ts +164 -0
- package/dist/adapters/elysia/index.js +1 -0
- package/dist/adapters/express/index.cjs +1 -0
- package/dist/adapters/express/index.d.cts +320 -0
- package/dist/adapters/express/index.d.ts +320 -0
- package/dist/adapters/express/index.js +1 -0
- package/dist/adapters/fastify/index.cjs +1 -0
- package/dist/adapters/fastify/index.d.cts +113 -0
- package/dist/adapters/fastify/index.d.ts +113 -0
- package/dist/adapters/fastify/index.js +1 -0
- package/dist/adapters/hono/index.cjs +1 -0
- package/dist/adapters/hono/index.d.cts +129 -0
- package/dist/adapters/hono/index.d.ts +129 -0
- package/dist/adapters/hono/index.js +1 -0
- package/dist/adapters/koa/index.cjs +1 -0
- package/dist/adapters/koa/index.d.cts +105 -0
- package/dist/adapters/koa/index.d.ts +105 -0
- package/dist/adapters/koa/index.js +1 -0
- package/dist/cli.cjs +529 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.js +449 -50
- package/dist/core/index.cjs +1 -0
- package/dist/core/index.d.cts +375 -0
- package/dist/core/index.d.ts +375 -0
- package/dist/core/index.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -671
- package/dist/index.js +1 -1
- package/package.json +78 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {generateKeyPairSync,createPrivateKey,createPublicKey,createHash,randomUUID}from'crypto';import {sql,Kysely}from'kysely';import We from'bcrypt';import Z from'jsonwebtoken';import Gr from'@koa/router';var Ur=Object.defineProperty;var n=(e,r)=>Ur(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}),l=class extends Error{static{n(this,"SentriError");}code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??He[r]??500;}};var Ve=new WeakMap,Me=32,Be=10,je=31;function be(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new l("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new l("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Me)throw new l("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 l("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Be} and ${je}`);if(!e.validRoles||e.validRoles.length===0)throw new l("CONFIGURATION_ERROR","validRoles must contain at least one role");if(e.validIdentifiers!==void 0&&e.validIdentifiers.length===0)throw new l("CONFIGURATION_ERROR","validIdentifiers must contain at least one identifier type");if(!e.dialect)throw new l("CONFIGURATION_ERROR","dialect is required in server mode")}n(be,"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 Lr=/^(\d+)([smhdw])$/,Fr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},ze=new Map;function $(e){if(typeof e=="number")return e*1e3;let r=ze.get(e);if(r!==void 0)return r;let t=Lr.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Fr[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($,"parseExpiry");var O={info:n(()=>{},"info"),warn:n(()=>{},"warn"),error:n(()=>{},"error")};function p(e,r,t){return {service:e,event:r,...t}}n(p,"buildLogData");async function Xe(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("ip_address","varchar(45)").addColumn("user_agent","text").addColumn("replaced_by","varchar(36)").addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createIndex("idx_sentri_sessions_user_id").ifNotExists().on("sentri_sessions").column("user_id").execute(),await e.schema.createIndex("idx_sentri_identifiers_user_id").ifNotExists().on("sentri_identifiers").column("user_id").execute();}n(Xe,"runMigrations");var Ge=new Map;function N(e){let r=Ge.get(e);return r||(r=new Kysely({dialect:e}),Ge.set(e,r)),r}n(N,"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,Kr=3600*1e3;function Qe(e){let r=Ze.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),i=s.export({format:"jwk"}),d=createHash("sha256").update(JSON.stringify({e:i.e,kty:i.kty,n:i.n})).digest("base64url"),h={...i,use:"sig",kid:d};r={kid:d,publicKey:s,jwk:h},Ze.set(e,r);}return r}n(Qe,"getOrBuildKey");function er(e){let{jwk:r}=Qe(e);return {keys:[r]}}n(er,"buildJwks");function rr(e){return Qe(e).publicKey}n(rr,"getPublicKeyFromPrivate");async function tr(e){let r=Date.now(),t=Je.get(e);if(t&&r-t.fetchedAt<Kr)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new l("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let i=await s.json();if(!i.keys||i.keys.length===0)throw new l("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let a=i.keys[0],d=createPublicKey({key:a,format:"jwk"});return Je.set(e,{publicKey:d,fetchedAt:r}),d}n(tr,"fetchPublicKey");var sr=new Map,nr=new Map,or=new Map;function ir(e){return e.startsWith("RS")||e.startsWith("PS")}n(ir,"isRSA");function ar(e){let r=sr.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},sr.set(e,r)),r}n(ar,"getHsSecrets");function Hr(e){let r=nr.get(e);return r||(r=createPrivateKey(e),nr.set(e,r)),r}n(Hr,"getRsPrivateKey");function ur(e){let r=g(e);if(ir(r.algorithm)){let i=Hr(e.secret);return {accessKey:i,refreshKey:i}}let{access:t,refresh:s}=ar(e.secret);return {accessKey:t,refreshKey:s}}n(ur,"getSigningKeys");function dr(e,r){let t=g(e);if(ir(t.algorithm))return rr(e.secret);let{access:s,refresh:i}=ar(e.secret);return r==="access"?s:i}n(dr,"getVerifyKey");function cr(e,r,t,s){let i=`${t}:${s}`,a=or.get(i);return a||(a={expiresIn:t,algorithm:s},or.set(i,a)),Z.sign(e,r,a)}n(cr,"sign");function lr(e,r,t){try{let s=Z.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new l("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof l?s:s instanceof Z.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(lr,"verify");function J(e,r){let t=g(r),{accessKey:s}=ur(r);return cr(e,s,t.accessExpiresIn,t.algorithm)}n(J,"signAccessToken");function Y(e,r){let t=g(r),{refreshKey:s}=ur(r);return cr({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}n(Y,"signRefreshToken");function ce(e,r){let t=g(r),s=dr(r,"access");return lr(e,s,t.algorithm)}n(ce,"verifyAccessToken");function Q(e,r){let t=g(r),s=dr(r,"refresh");return lr(e,s,t.algorithm)}n(Q,"verifyRefreshToken");function fr(e,r){try{let t=Z.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new l("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof l?t:t instanceof Z.TokenExpiredError?new l("TOKEN_EXPIRED","Token has expired"):new l("TOKEN_INVALID","Token is invalid or malformed")}}n(fr,"verifyTokenWithPublicKey");function Ue(e){try{return JSON.parse(e)}catch{return []}}n(Ue,"parseRoles");function Vr(e){return JSON.stringify(e)}n(Vr,"serializeRoles");function pr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,createdAt:new Date(e.created_at)}}n(pr,"mapIdentifier");async function 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:Ue(t.roles)}:null}n(ee,"findUserByIdentifierValue");async function le(e,r){let t=await e.selectFrom("sentri_users").select(["id","password_hash","roles"]).where("id","=",r).executeTakeFirst();return t?{id:t.id,passwordHash:t.password_hash,roles:Ue(t.roles)}:null}n(le,"findUserById");async function wr(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(wr,"createIdentifiers");async function re(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(pr)}n(re,"findIdentifiersByUserId");async function Le(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?pr(s):null}n(Le,"findIdentifierById");async function mr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}n(mr,"countIdentifiersByUserId");async function yr(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}n(yr,"updateIdentifier");async function Ir(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}n(Ir,"deleteIdentifiers");async function 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:Vr(t)}).where("id","=",r).execute();}n(_r,"updateUserRoles");async function Fe(e,r){let t=randomUUID();return await e.insertInto("sentri_sessions").values({id:t,user_id:r.userId,expires_at:r.expiresAt.toISOString(),ip_address:r.ipAddress??null,user_agent:r.userAgent??null}).execute(),{id:t}}n(Fe,"createSession");async function fe(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").select(["s.id as session_id","s.user_id","s.expires_at","s.ip_address","s.user_agent","s.replaced_by","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),ipAddress:t.ip_address,userAgent:t.user_agent,replacedBy:t.replaced_by,createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,passwordHash:t.password_hash,roles:Ue(t.roles)}}:null}n(fe,"findSessionById");async function he(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}n(he,"deleteSession");async function gr(e,r,t){await e.updateTable("sentri_sessions").set({replaced_by:t}).where("id","=",r).execute();}n(gr,"markSessionReplaced");async function pe(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}n(pe,"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 we(e,r,t){let s=g(r),i=N(r.dialect),a=e.roles??[],d=a.filter(A=>!s.validRolesSet.has(A));if(d.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${d.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let h=e.identifiers.map(A=>({type:A.type.trim(),value:A.value.trim()})),u=h.filter(A=>!s.validIdentifiersSet.has(A.type));if(u.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${u.map(A=>A.type).join(", ")}`)};if(new Set(h.map(A=>A.value)).size!==h.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let A of h)if(await ee(i,A.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${A.value}`)};let E=await G(e.password,s.saltRounds),{userId:U,identifierRows:D}=await i.transaction().execute(async A=>{let q=randomUUID();await A.insertInto("sentri_users").values({id:q,password_hash:E,roles:JSON.stringify(a)}).execute();let ae=h.map(ue=>({id:randomUUID(),user_id:q,type:ue.type,value:ue.value}));return await A.insertInto("sentri_identifiers").values(ae).execute(),{userId:q,identifierRows:ae}}),L={success:true,user:{id:U,roles:a,identifiers:D.map(A=>({id:A.id,type:A.type,value:A.value}))}};return r.hooks?.onRegister&&await r.hooks.onRegister(L.user),L}n(we,"register");async function me(e,r,t){let s=g(r),i=N(r.dialect),a=await ee(i,e.identifier.trim());if(!a){let D=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,D,{ip:t?.ip??""}),{success:false,error:D}}if(!await W(e.password,a.passwordHash)){let D=new l("INVALID_CREDENTIALS","Invalid credentials");return r.hooks?.onLoginFailed&&await r.hooks.onLoginFailed(e.identifier,D,{ip:t?.ip??""}),{success:false,error:D}}let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Fe(i,{userId:a.id,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null}),c={id:a.id,roles:a.roles},E=J({id:a.id,roles:a.roles,sessionId:u.id},r),U=Y(u.id,r);return r.hooks?.onLoginSuccess&&await r.hooks.onLoginSuccess(c,{ip:t?.ip??"",userAgent:t?.userAgent??""}),{success:true,accessToken:E,refreshToken:U,user:c}}n(me,"login");async function H(e,r,t){let s=g(r),i=N(r.dialect),a;try{({sessionId:a}=Q(e,r));}catch(D){return D instanceof l?{success:false,error:D}:{success:false,error:new l("TOKEN_INVALID","Invalid refresh token")}}let d=await fe(i,a);if(!d)return {success:false,error:new l("UNAUTHORIZED","Session not found or revoked")};if(d.replacedBy)return await pe(i,d.userId),{success:false,error:new l("TOKEN_REUSE","Token reuse detected. All sessions revoked.")};if(d.expiresAt.getTime()<Date.now())return await he(i,a),{success:false,error:new l("TOKEN_EXPIRED","Session has expired")};let h=new Date(Date.now()+$(s.refreshExpiresIn)),u=await Fe(i,{userId:d.userId,expiresAt:h,ipAddress:t?.ip??null,userAgent:t?.userAgent??null});await gr(i,a,u.id);let c={id:d.user.id,roles:d.user.roles},E=J({...c,sessionId:u.id},r),U=Y(u.id,r);return {success:true,accessToken:E,refreshToken:U,user:c}}n(H,"refresh");async function ye(e,r){let t=N(r.dialect),s;try{({sessionId:s}=Q(e,r));}catch{return}let i=await fe(t,s);i&&(await he(t,s),r.hooks?.onLogout&&await r.hooks.onLogout(i.userId));}n(ye,"logout");async function Ie(e,r){let t=N(r.dialect);await pe(t,e),r.hooks?.onLogout&&await r.hooks.onLogout(e);}n(Ie,"logoutAll");async function Re(e,r){let t=N(r.dialect),s=await le(t,e);if(!s)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let i=await re(t,e);return {success:true,user:{id:s.id,roles:s.roles,identifiers:i.map(a=>({id:a.id,type:a.type,value:a.value}))}}}n(Re,"getUser");async function _e(e,r,t,s){let i=g(s),a=N(s.dialect),d=await le(a,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};if(!await W(r,d.passwordHash))return {success:false,error:new l("INVALID_CREDENTIALS","Invalid credentials")};let u=await G(t,i.saltRounds);return await Rr(a,e,u),await pe(a,e),s.hooks?.onPasswordChanged&&await s.hooks.onPasswordChanged(e),{success:true}}n(_e,"changePassword");async function ge(e,r,t){let s=g(t),i=N(t.dialect),a=r.filter(c=>!s.validRolesSet.has(c));if(a.length>0)return {success:false,error:new l("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let d=await le(i,e);if(!d)return {success:false,error:new l("USER_NOT_FOUND","User not found")};let h=new Set(d.roles);for(let c of r)h.add(c);let u=Array.from(h);return await _r(i,e,u),{success:true,user:{id:d.id,roles:u}}}n(ge,"assignRoles");async function ve(e,r,t){let s=g(t),i=N(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one identifier is required")};let a=r.map(c=>({type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a)if(await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)};return await wr(i,e,a),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(ve,"bulkCreateIdentifiers");async function Ae(e,r,t){let s=g(t),i=N(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one update is required")};let a=r.map(c=>({id:c.id,type:c.type.trim(),value:c.value.trim()})),d=a.filter(c=>!s.validIdentifiersSet.has(c.type));if(d.length>0)return {success:false,error:new l("VALIDATION_ERROR",`Invalid identifier types: ${d.map(c=>c.type).join(", ")}`)};if(new Set(a.map(c=>c.value)).size!==a.length)return {success:false,error:new l("VALIDATION_ERROR","Duplicate identifier values in request")};for(let c of a){let E=await Le(i,c.id,e);if(!E)return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${c.id}`)};if(E.value!==c.value&&await ee(i,c.value))return {success:false,error:new l("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${c.value}`)}}return await i.transaction().execute(async c=>{for(let E of a)await yr(c,E.id,e,{type:E.type,value:E.value});}),{success:true,identifiers:(await re(i,e)).map(c=>({id:c.id,type:c.type,value:c.value}))}}n(Ae,"bulkUpdateIdentifiers");async function ke(e,r,t){let s=N(t.dialect);if(r.length===0)return {success:false,error:new l("VALIDATION_ERROR","At least one ID is required")};let i=Array.from(new Set(r));for(let h of i)if(!await Le(s,h,e))return {success:false,error:new l("IDENTIFIER_NOT_FOUND",`Identifier not found: ${h}`)};return await mr(s,e)-i.length<1?{success:false,error:new l("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await Ir(s,e,i),{success:true,identifiers:(await re(s,e)).map(h=>({id:h.id,type:h.type,value:h.value}))})}n(ke,"bulkDeleteIdentifiers");async function kr(e,r){let t=N(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=N(t.dialect),i=await fe(s,r);i&&i.userId===e&&await he(s,r);}n(Er,"revokeSession");function x(e){return g(e).cookieName}n(x,"getCookieName");function Ee(e){return g(e).accessCookieName}n(Ee,"getAccessCookieName");function te(e,r,t){let s=t.cookie??{},i=$(g(t).refreshExpiresIn);e.cookies.set(x(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(te,"setCookieFromConfig");function Te(e,r){let t=r.cookie??{};e.cookies.set(x(r),null,{path:t.path??"/"});}n(Te,"clearCookieFromConfig");function se(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,i=$(g(t).accessExpiresIn);e.cookies.set(Ee(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}n(se,"setAccessCookieFromConfig");function xe(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.cookies.set(Ee(r),null,{path:t.path??"/"});}n(xe,"clearAccessCookieFromConfig");function Ne(e,r){let t=e.get("Authorization");return t?.startsWith("Bearer ")?t.slice(7):e.cookies.get(Ee(r))}n(Ne,"getCurrentAccessToken");function S(e){let r=e.logger??O,t=e.loggerService??"sentri";return e.mode==="client"?Mr(e.keyUri,r,t):Br(e,r,t)}n(S,"protect");function Mr(e,r,t){return async(s,i)=>{let a=s.get("Authorization"),d=a?.startsWith("Bearer ")?a.slice(7):void 0,h=s.requestId??s.state?.requestId;if(!d)throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...h!==void 0&&{requestId:h}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let u=await tr(e),c=fr(d,u);s.state.user={id:c.id,roles:c.roles},r.info(p(t,"auth.protect.success",{mode:"client",userId:c.id,...h!==void 0&&{requestId:h}})),await i();}catch(u){let c=u instanceof l?u.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"client",errorCode:c,...h!==void 0&&{requestId:h}})),u}}}n(Mr,"protectClient");function Br(e,r,t){return async(s,i)=>{let a=Ne(s,e),d=s.requestId??s.state?.requestId;if(!a)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Missing or malformed Authorization header");try{let h=ce(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(h.sessionId))throw r.warn(p(t,"auth.protect.token_revoked",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token has been revoked");s.state.user={id:h.id,roles:h.roles},r.info(p(t,"auth.protect.success",{mode:"server",userId:h.id,...d!==void 0&&{requestId:d}})),await i();}catch(h){if(h instanceof l&&h.code==="TOKEN_EXPIRED"){let u=s.cookies.get(x(e));if(!u)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Token expired. Please login again.");try{let c=await H(u,e);if(!c.success)throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:c.error.code,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.");te(s,c.refreshToken,e),se(s,c.accessToken,e),s.set("X-New-Access-Token",c.accessToken),s.state.user=c.user,r.info(p(t,"auth.protect.auto_refresh",{mode:"server",userId:c.user.id,...d!==void 0&&{requestId:d}})),await i();}catch{throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Session expired. Please login again.")}}else {let u=h instanceof l?h.code:"TOKEN_INVALID";throw r.warn(p(t,"auth.protect.failure",{mode:"server",errorCode:u,...d!==void 0&&{requestId:d}})),h}}}}n(Br,"protectServer");function Tr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return async(i,a)=>{let d=i.requestId??i.state?.requestId;if(!i.state.user)throw r.warn(p(t,"auth.authorize.unauthenticated",{requiredRoles:e,...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Not authenticated");let h=i.state.user.roles;if(!e.some(u=>h.includes(u)))throw r.warn(p(t,"auth.authorize.denied",{userId:i.state.user.id,userRoles:[...h],requiredRoles:e,...d!==void 0&&{requestId:d}})),new l("FORBIDDEN",s);r.info(p(t,"auth.authorize.passed",{userId:i.state.user.id,userRoles:[...h],requiredRoles:e,...d!==void 0&&{requestId:d}})),await a();}}n(Tr,"createAuthorizeHandler");function ne(e,r){return n(function(...s){return Tr(s,e,r)},"authorize")}n(ne,"createAuthorize");function jr(...e){return Tr(e,O,"sentri")}n(jr,"authorize");var zr=new l("FORBIDDEN","You do not have permission to perform this action");function Nr(e,r,t){return async(s,i)=>{let a=s.state?.user,d=s.requestId??s.state?.requestId;if(!a)throw r.warn(p(t,"auth.permit.unauthenticated",{...d!==void 0&&{requestId:d}})),new l("UNAUTHORIZED","Not authenticated");if(e.roles&&e.roles.length>0){let h=a.roles;if(e.roles.some(u=>h.includes(u)))return r.info(p(t,"auth.permit.role_bypass",{userId:a.id,bypassedByRole:true,...d!==void 0&&{requestId:d}})),await i()}try{let h=e.check(s);if(h instanceof Promise?await h:h)r.info(p(t,"auth.permit.passed",{userId:a.id,...d!==void 0&&{requestId:d}})),await i();else throw r.warn(p(t,"auth.permit.denied",{userId:a.id,...d!==void 0&&{requestId:d}})),zr}catch(h){throw h}}}n(Nr,"createPermitHandler");function oe(e,r){return n(function(s){return Nr(typeof s=="function"?{check:s}:s,e,r)},"permit")}n(oe,"createPermit");function Xr(e){return Nr(typeof e=="function"?{check:e}:e,O,"sentri")}n(Xr,"permit");function ie(e={}){return async(r,t)=>{try{await t();}catch(s){s instanceof l?(r.status=s.statusCode,r.body={error:true,statusCode:s.statusCode,code:s.code,message:s.message,data:null}):(r.status=s.status||s.statusCode||500,r.body={error:true,statusCode:r.status,code:s.code||"INTERNAL_SERVER_ERROR",message:s.message||"Internal Server Error",data:null},r.app.emit("error",s,r));}}}n(ie,"createErrorHandler");var Pe=class{static{n(this,"InMemoryRateLimiter");}store=new Map;constructor(){setInterval(()=>{let r=Date.now();for(let[t,s]of this.store.entries())s.expiresAt<r&&this.store.delete(t);},6e4).unref();}async consume(r,t,s){let i=Date.now(),a=this.store.get(r);if((!a||a.expiresAt<i)&&(a={count:0,expiresAt:i+s},this.store.set(r,a)),a.count>t){let d=Math.ceil((a.expiresAt-i)/1e3);throw new Error(`Rate limit exceeded. Try again in ${d} seconds.`)}a.count++;}},qe=class{static{n(this,"RedisRateLimiter");}redis;logger;constructor(r,t){this.redis=r,this.logger=t;}async consume(r,t,s){let i=`sentri:rl:${r}`,a=Math.ceil(s/1e3);try{let d=await this.redis.multi().incr(i).expire(i,a,"NX").exec();if(!d||d.length===0)return;if(d[0][1]>t)throw new Error("Rate limit exceeded. Try again later.")}catch(d){if(d instanceof Error&&d.message.includes("Rate limit exceeded"))throw d;this.logger.error({msg:"Redis RateLimiter failed, bypassing limit",error:d});}}},V=null;async function Dr(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 qe(s,r??O),V}catch(t){r?.warn({msg:"Failed to connect to Redis for Rate Limiting, falling back to InMemoryRateLimiter",error:t});}return V=new Pe,V}n(Dr,"getRateLimiter");var De=8,M=72,Se=255,Sr=100,B=50;function R(e){return new l("VALIDATION_ERROR",e)}n(R,"badRequest");function C(e,r,t,s){e.status=r,e.body={error:false,statusCode:r,message:t,data:s};}n(C,"ok");function F(e,r){e.status=r.statusCode,e.body={error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null};}n(F,"fail");function P(e){let r=e.request.body;if(r==null||typeof r!="object"||Array.isArray(r))throw new l("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you apply koa-bodyparser?");return r}n(P,"parseBody");function Wr(e,r){if(!r.apiKey)return;let t=e.get("x-api-key");if(typeof t!="string"||t!==r.apiKey)throw new l("UNAUTHORIZED","Invalid or missing API key")}n(Wr,"validateApiKey");function Ke(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}n(Ke,"fireHook");function Zr(e){return e.startsWith("RS")||e.startsWith("PS")}n(Zr,"isRSA");function $e(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw R(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw R(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Sr)throw R(`identifiers[${r}].type must not exceed ${Sr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw R(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Se)throw R(`identifiers[${r}].value must not exceed ${Se} characters`);return {type:t.type,value:t.value}}n($e,"validateIdentifierInput");function v(e){let r=e.requestId??e.state?.requestId;return r!==void 0?{requestId:r}:{}}n(v,"reqId");function Cr(e){let r=new Gr,t=e,s=g(t),i=e.logger??O,a=e.loggerService??"sentri",d=ne(i,a),h=oe(i,a),u=Dr(e.redisUrl,i),c=e.router?.register??(o=>we(o,t)),E=e.router?.login??(o=>me(o,t)),U=e.router?.refresh??(o=>H(o,t)),D=e.router?.logout??(o=>o!==void 0?ye(o,t):Promise.resolve()),L=e.router?.logoutAll??(o=>Ie(o,t)),A=e.router?.getUser??(o=>Re(o,t)),q=e.router?.assignRoles??((o,_)=>ge(o,_,t)),ae=e.router?.changePassword??((o,_,y)=>_e(o,_,y,t)),ue=e.router?.bulkCreateIdentifiers??((o,_)=>ve(o,_,t)),br=e.router?.bulkUpdateIdentifiers??((o,_)=>Ae(o,_,t)),Or=e.router?.bulkDeleteIdentifiers??((o,_)=>ke(o,_,t));Zr(s.algorithm)&&r.get("/keys",o=>{o.set("Cache-Control","public, max-age=3600"),o.body=er(e.secret);}),r.post("/register",async(o,_)=>{let y=Date.now();try{Wr(o,e);let f=P(o),{identifiers:m,password:I,roles:w}=f;if(!Array.isArray(m)||m.length===0)throw R("identifiers is required and must be a non-empty array");if(m.length>B)throw R(`identifiers must not exceed ${B} entries`);let k=m.map((X,Ce)=>$e(X,Ce));if(typeof I!="string"||I.length<De)throw R(`password is required and must be at least ${De} characters`);if(I.length>M)throw R(`password must not exceed ${M} characters`);if(w!==void 0&&!Array.isArray(w))throw R("roles must be an array of strings when provided");if(Array.isArray(w)&&!w.every(X=>typeof X=="string"))throw R("each role must be a string");let b=Array.isArray(w)?w:void 0,T=b!==void 0?{identifiers:k,password:I,roles:b}:{identifiers:k,password:I},K=o.ip||"127.0.0.1",j=o.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let X=await u,Ce=typeof e.rateLimit=="object"?e.rateLimit.maxRegisterAttempts??5:5;await X.consume(`register:${K}`,Ce,900*1e3);}let z=await c(T,{ip:K,userAgent:j});if(!z.success){i.warn(p(a,"auth.register.failure",{errorCode:z.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,z.error);return}i.info(p(a,"auth.register.success",{userId:z.user.id,duration_ms:Date.now()-y,...v(o)})),C(o,201,"User registered successfully",{user:z.user});}catch(f){throw f}}),r.post("/login",async(o,_)=>{let y=Date.now();try{let f=P(o),{identifier:m,password:I}=f;if(typeof m!="string"||m.trim().length===0)throw R("identifier is required and must be a non-empty string");if(m.length>Se)throw R(`identifier must not exceed ${Se} characters`);if(typeof I!="string"||I.length===0)throw R("password is required");if(I.length>M)throw R(`password must not exceed ${M} characters`);let w=m.trim(),k=o.ip||"127.0.0.1",b=o.get("user-agent")||"Unknown";if(e.rateLimit!==!1){let K=await u,j=typeof e.rateLimit=="object"?e.rateLimit.maxLoginAttempts??5:5;await K.consume(`login:${k}`,j,900*1e3);}let T=await E({identifier:w,password:I},{ip:k,userAgent:b});if(!T.success){Ke(()=>e.hooks?.onLoginFailed?.(w,T.error,{ip:k})),i.warn(p(a,"auth.login.failure",{errorCode:T.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,T.error);return}Ke(()=>e.hooks?.onLoginSuccess?.(T.user,{ip:k,userAgent:b})),te(o,T.refreshToken,e),se(o,T.accessToken,e),i.info(p(a,"auth.login.success",{userId:T.user.id,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Login successful",{accessToken:T.accessToken,user:T.user});}catch(f){throw f}}),r.post("/refresh",async(o,_)=>{let y=Date.now();try{let f=o.cookies.get(x(e));if(!f)throw new l("UNAUTHORIZED","Refresh token cookie is missing");let m=o.ip||"127.0.0.1",I=o.get("user-agent")||"Unknown",w=await U(f,{ip:m,userAgent:I});if(!w.success){Te(o,e),i.warn(p(a,"auth.refresh.failure",{errorCode:w.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,w.error);return}te(o,w.refreshToken,e),se(o,w.accessToken,e),i.info(p(a,"auth.refresh.success",{userId:w.user.id,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Token refreshed",{accessToken:w.accessToken});}catch(f){throw f}}),r.post("/logout",async(o,_)=>{let y=Date.now();try{let f=o.cookies.get(x(e));await D(f),Te(o,e),xe(o,e),i.info(p(a,"auth.logout",{duration_ms:Date.now()-y,...v(o)})),C(o,200,"Logged out",null);}catch(f){throw f}}),r.post("/logout-all",S(e),async(o,_)=>{let y=Date.now();try{let f=o.state.user.id;await L(f),Ke(()=>e.hooks?.onLogout?.(f)),Te(o,e),xe(o,e),i.info(p(a,"auth.logout_all",{userId:f,duration_ms:Date.now()-y,...v(o)})),C(o,200,"All sessions revoked",null);}catch(f){throw f}}),r.get("/me",S(e),async(o,_)=>{let y=Date.now();try{let f=await A(o.state.user.id);if(!f.success){i.warn(p(a,"auth.me.failure",{userId:o.state.user.id,errorCode:f.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,f.error);return}i.info(p(a,"auth.me.success",{userId:f.user.id,duration_ms:Date.now()-y,...v(o)})),C(o,200,"OK",f.user);}catch(f){throw f}}),r.get("/me/identifiers",S(e),async(o,_)=>{let y=Date.now();try{let f=await A(o.state.user.id);if(!f.success){i.warn(p(a,"auth.me.identifiers.failure",{userId:o.state.user.id,errorCode:f.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,f.error);return}i.info(p(a,"auth.me.identifiers.success",{userId:f.user.id,count:f.user.identifiers?.length??0,duration_ms:Date.now()-y,...v(o)})),C(o,200,"OK",{identifiers:f.user.identifiers??[]});}catch(f){throw f}});let de=h(o=>!!o.state.user);return r.post("/me/identifiers",S(e),de,async(o,_)=>{let y=Date.now();try{let f=P(o),{identifiers:m}=f;if(!Array.isArray(m)||m.length===0)throw R("identifiers is required and must be a non-empty array");if(m.length>B)throw R(`identifiers must not exceed ${B} entries`);let I=m.map((k,b)=>$e(k,b)),w=await ue(o.state.user.id,I);if(!w.success){i.warn(p(a,"auth.identifiers.create_failure",{userId:o.state.user.id,errorCode:w.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,w.error);return}i.info(p(a,"auth.identifiers.created",{userId:o.state.user.id,count:w.identifiers.length,duration_ms:Date.now()-y,...v(o)})),C(o,201,"Identifiers added successfully",{identifiers:w.identifiers});}catch(f){throw f}}),r.put("/me/identifiers",S(e),de,async(o,_)=>{let y=Date.now();try{let f=P(o),{identifiers:m}=f;if(!Array.isArray(m)||m.length===0)throw R("identifiers is required and must be a non-empty array");if(m.length>B)throw R(`identifiers must not exceed ${B} entries`);let I=m.map((k,b)=>{if(typeof k!="object"||k===null||Array.isArray(k))throw R(`identifiers[${b}] must be an object`);let T=k;if(typeof T.id!="string"||T.id.trim().length===0)throw R(`identifiers[${b}].id is required and must be a non-empty string`);let{type:K,value:j}=$e(k,b);return {id:T.id,type:K,value:j}}),w=await br(o.state.user.id,I);if(!w.success){i.warn(p(a,"auth.identifiers.update_failure",{userId:o.state.user.id,errorCode:w.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,w.error);return}i.info(p(a,"auth.identifiers.updated",{userId:o.state.user.id,count:w.identifiers.length,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Identifiers updated successfully",{identifiers:w.identifiers});}catch(f){throw f}}),r.delete("/me/identifiers",S(e),de,async(o,_)=>{let y=Date.now();try{let f=P(o),{ids:m}=f;if(!Array.isArray(m)||m.length===0)throw R("ids is required and must be a non-empty array of strings");if(!m.every(w=>typeof w=="string"))throw R("each id must be a string");let I=await Or(o.state.user.id,m);if(!I.success){i.warn(p(a,"auth.identifiers.delete_failure",{userId:o.state.user.id,errorCode:I.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,I.error);return}i.info(p(a,"auth.identifiers.deleted",{userId:o.state.user.id,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Identifiers deleted successfully",{identifiers:I.identifiers});}catch(f){throw f}}),r.patch("/me/password",S(e),de,async(o,_)=>{let y=Date.now();try{let f=P(o),{currentPassword:m,newPassword:I}=f;if(typeof m!="string"||m.length===0)throw R("currentPassword is required");if(typeof I!="string"||I.length<De)throw R(`newPassword must be at least ${De} characters`);if(I.length>M)throw R(`newPassword must not exceed ${M} characters`);if(m===I)throw R("newPassword must be different from currentPassword");let w=await ae(o.state.user.id,m,I);if(!w.success){i.warn(p(a,"auth.password.change_failure",{userId:o.state.user.id,errorCode:w.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,w.error);return}i.info(p(a,"auth.password.changed",{userId:o.state.user.id,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Password updated successfully. All sessions have been revoked.",null);}catch(f){throw f}}),r.post("/users/:userId/roles",S(e),d("admin"),async(o,_)=>{let y=Date.now();try{let f=P(o),{roles:m}=f,I=o.params.userId,w=typeof I=="string"?I:void 0;if(!w)throw R("userId is required");if(!Array.isArray(m)||m.length===0)throw R("roles must be a non-empty array of strings");if(!m.every(b=>typeof b=="string"))throw R("each role must be a string");let k=await q(w,m);if(!k.success){i.warn(p(a,"auth.roles.assign_failure",{targetUserId:w,errorCode:k.error.code,duration_ms:Date.now()-y,...v(o)})),F(o,k.error);return}i.info(p(a,"auth.roles.assigned",{targetUserId:w,roles:m,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Roles assigned successfully",{user:k.user});}catch(f){throw f}}),r.use(ie()),r.get("/sessions",S(e),async(o,_)=>{let y=Date.now();try{let f=o.state.user.id,I=await(e.router?.getSessions??(w=>kr(w,t)))(f);i.info(p(a,"auth.sessions.get",{userId:f,duration_ms:Date.now()-y,...v(o)})),C(o,200,"OK",{sessions:I});}catch(f){throw await _(),f}}),r.delete("/sessions/:id",S(e),async(o,_)=>{let y=Date.now();try{let f=o.state.user.id,m=o.params.id;await(e.router?.revokeSession??((w,k)=>Er(w,k,t)))(f,m),i.info(p(a,"auth.sessions.revoke",{userId:f,sessionId:m,duration_ms:Date.now()-y,...v(o)})),C(o,200,"Session revoked",null);}catch(f){throw await _(),f}}),r}n(Cr,"createAuthRouter");function Vs(e){if(e.mode==="client"){let u={mode:"client",keyUri:e.keyUri,...e.validRoles!==void 0&&{validRoles:e.validRoles},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};be(u);let c=u.logger??O,E=u.loggerService??"sentri",U=ne(c,E),D=oe(c,E);return {protect:n(()=>S(u),"protect"),authorize:n((...L)=>U(...L),"authorize"),permit:n(L=>D(L),"permit"),errorHandler:n(L=>ie(L),"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}};be(t);let s=t.logger??O,i=t.loggerService??"sentri",a=g(t),d=ne(s,i),h=oe(s,i);return {protect:n(()=>S(t),"protect"),authorize:n((...u)=>d(...u),"authorize"),permit:n(u=>h(u),"permit"),errorHandler:n(u=>ie(u),"errorHandler"),migrate:n(async()=>{let u=N(t.dialect);await Xe(u);},"migrate"),router:n(()=>Cr(t),"router"),register:n(u=>we(u,t),"register"),login:n(u=>me(u,t),"login"),refresh:n(u=>H(u,t),"refresh"),logout:n(u=>ye(u,t),"logout"),logoutAll:n(u=>Ie(u,t),"logoutAll"),getUser:n(u=>Re(u,t),"getUser"),changePassword:n((u,c,E)=>_e(u,c,E,t),"changePassword"),assignRoles:n((u,c)=>ge(u,c,t),"assignRoles"),bulkCreateIdentifiers:n((u,c)=>ve(u,c,t),"bulkCreateIdentifiers"),bulkUpdateIdentifiers:n((u,c)=>Ae(u,c,t),"bulkUpdateIdentifiers"),bulkDeleteIdentifiers:n((u,c)=>ke(u,c,t),"bulkDeleteIdentifiers"),hashPassword:n(u=>G(u,a.saltRounds),"hashPassword"),verifyPassword:n((u,c)=>W(u,c),"verifyPassword"),signAccessToken:n(u=>J(u,t),"signAccessToken"),signRefreshToken:n(u=>Y(u,t),"signRefreshToken"),verifyAccessToken:n(u=>ce(u,t),"verifyAccessToken"),verifyRefreshToken:n(u=>Q(u,t),"verifyRefreshToken"),getCurrentAccessToken:n(u=>Ne(u,t),"getCurrentAccessToken")}}n(Vs,"createAuthKoa");export{He as SENTRI_ERROR_STATUS,l as SentriError,jr as authorize,Vs as createAuthKoa,Cr as createAuthRouter,ne as createAuthorize,ie as createErrorHandler,oe as createPermit,Ne as getCurrentAccessToken,Xr as permit,S as protect};
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var promises=require('fs/promises'),fs=require('fs'),path=require('path');var y=Object.defineProperty;var s=(e,o)=>y(e,"name",{value:o,configurable:true});var g={server:{express:`import { createAuthExpress } from 'sentri/express';
|
|
3
|
+
import { PostgresDialect } from 'kysely';
|
|
4
|
+
import pg from 'pg';
|
|
5
|
+
|
|
6
|
+
const { Pool } = pg;
|
|
7
|
+
|
|
8
|
+
type Role = 'admin' | 'user';
|
|
9
|
+
|
|
10
|
+
export const sentriAuth = createAuthExpress<Role>({
|
|
11
|
+
mode: 'server',
|
|
12
|
+
|
|
13
|
+
// -- Roles ------------------------------------------------------------------
|
|
14
|
+
validRoles: ['admin', 'user'],
|
|
15
|
+
|
|
16
|
+
// -- Identifiers ------------------------------------------------------------
|
|
17
|
+
validIdentifiers: ['email', 'username'],
|
|
18
|
+
|
|
19
|
+
// -- Database ---------------------------------------------------------------
|
|
20
|
+
dialect: new PostgresDialect({
|
|
21
|
+
pool: new Pool({ connectionString: process.env.DATABASE_URL! })
|
|
22
|
+
}),
|
|
23
|
+
|
|
24
|
+
// -- Token ------------------------------------------------------------------
|
|
25
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
|
|
26
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
|
|
27
|
+
|
|
28
|
+
// -- Security ---------------------------------------------------------------
|
|
29
|
+
saltRounds: parseInt(process.env.SALT_ROUNDS ?? '12', 10),
|
|
30
|
+
// apiKey: process.env.API_KEY, // uncomment to restrict POST /register
|
|
31
|
+
|
|
32
|
+
// -- Redis (optional) -------------------------------------------------------
|
|
33
|
+
// redisUrl: process.env.REDIS_URL,
|
|
34
|
+
|
|
35
|
+
// -- Cookie (optional) ------------------------------------------------------
|
|
36
|
+
// cookie: { secure: true, sameSite: 'strict' },
|
|
37
|
+
// accessCookie: { secure: true, sameSite: 'strict' },
|
|
38
|
+
|
|
39
|
+
// -- Hooks (optional) -------------------------------------------------------
|
|
40
|
+
// hooks: {
|
|
41
|
+
// onLogin: (user) => console.log(\`login: \${user.identifier}\`),
|
|
42
|
+
// onFailedLogin: (identifier) => console.warn(\`failed login: \${identifier}\`),
|
|
43
|
+
// onLogout: (userId) => console.log(\`logout: \${userId}\`),
|
|
44
|
+
// },
|
|
45
|
+
|
|
46
|
+
// -- Token revocation (optional) --------------------------------------------
|
|
47
|
+
// isTokenRevoked: async (sessionId) => false,
|
|
48
|
+
|
|
49
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
50
|
+
// logger: console,
|
|
51
|
+
// loggerService: 'sentri',
|
|
52
|
+
});
|
|
53
|
+
`,fastify:`import { createAuthFastify } from 'sentri/fastify';
|
|
54
|
+
import { PostgresDialect } from 'kysely';
|
|
55
|
+
import pg from 'pg';
|
|
56
|
+
|
|
57
|
+
const { Pool } = pg;
|
|
58
|
+
|
|
59
|
+
type Role = 'admin' | 'user';
|
|
60
|
+
|
|
61
|
+
export const sentriAuth = createAuthFastify<Role>({
|
|
62
|
+
mode: 'server',
|
|
63
|
+
|
|
64
|
+
// -- Roles ------------------------------------------------------------------
|
|
65
|
+
validRoles: ['admin', 'user'],
|
|
66
|
+
|
|
67
|
+
// -- Identifiers ------------------------------------------------------------
|
|
68
|
+
validIdentifiers: ['email', 'username'],
|
|
69
|
+
|
|
70
|
+
// -- Database ---------------------------------------------------------------
|
|
71
|
+
dialect: new PostgresDialect({
|
|
72
|
+
pool: new Pool({ connectionString: process.env.DATABASE_URL! })
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
// -- Token ------------------------------------------------------------------
|
|
76
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
|
|
77
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
|
|
78
|
+
|
|
79
|
+
// -- Security ---------------------------------------------------------------
|
|
80
|
+
saltRounds: parseInt(process.env.SALT_ROUNDS ?? '12', 10),
|
|
81
|
+
// apiKey: process.env.API_KEY,
|
|
82
|
+
|
|
83
|
+
// -- Redis (optional) -------------------------------------------------------
|
|
84
|
+
// redisUrl: process.env.REDIS_URL,
|
|
85
|
+
|
|
86
|
+
// -- Cookie (optional) ------------------------------------------------------
|
|
87
|
+
// cookie: { secure: true, sameSite: 'strict' },
|
|
88
|
+
// accessCookie: { secure: true, sameSite: 'strict' },
|
|
89
|
+
|
|
90
|
+
// -- Hooks (optional) -------------------------------------------------------
|
|
91
|
+
// hooks: {
|
|
92
|
+
// onLogin: (user) => console.log(\`login: \${user.identifier}\`),
|
|
93
|
+
// onFailedLogin: (identifier) => console.warn(\`failed login: \${identifier}\`),
|
|
94
|
+
// onLogout: (userId) => console.log(\`logout: \${userId}\`),
|
|
95
|
+
// },
|
|
96
|
+
|
|
97
|
+
// -- Token revocation (optional) --------------------------------------------
|
|
98
|
+
// isTokenRevoked: async (sessionId) => false,
|
|
99
|
+
|
|
100
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
101
|
+
// logger: console,
|
|
102
|
+
// loggerService: 'sentri',
|
|
103
|
+
});
|
|
104
|
+
`,hono:`import { createAuthHono } from 'sentri/hono';
|
|
105
|
+
import { PostgresDialect } from 'kysely';
|
|
106
|
+
import pg from 'pg';
|
|
107
|
+
|
|
108
|
+
const { Pool } = pg;
|
|
109
|
+
|
|
110
|
+
type Role = 'admin' | 'user';
|
|
111
|
+
|
|
112
|
+
export const sentriAuth = createAuthHono<Role>({
|
|
113
|
+
mode: 'server',
|
|
114
|
+
|
|
115
|
+
// -- Roles ------------------------------------------------------------------
|
|
116
|
+
validRoles: ['admin', 'user'],
|
|
117
|
+
|
|
118
|
+
// -- Identifiers ------------------------------------------------------------
|
|
119
|
+
validIdentifiers: ['email', 'username'],
|
|
120
|
+
|
|
121
|
+
// -- Database ---------------------------------------------------------------
|
|
122
|
+
dialect: new PostgresDialect({
|
|
123
|
+
pool: new Pool({ connectionString: process.env.DATABASE_URL! })
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
// -- Token ------------------------------------------------------------------
|
|
127
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
|
|
128
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
|
|
129
|
+
|
|
130
|
+
// -- Security ---------------------------------------------------------------
|
|
131
|
+
saltRounds: parseInt(process.env.SALT_ROUNDS ?? '12', 10),
|
|
132
|
+
// apiKey: process.env.API_KEY,
|
|
133
|
+
|
|
134
|
+
// -- Redis (optional) -------------------------------------------------------
|
|
135
|
+
// redisUrl: process.env.REDIS_URL,
|
|
136
|
+
|
|
137
|
+
// -- Cookie (optional) ------------------------------------------------------
|
|
138
|
+
// cookie: { secure: true, sameSite: 'strict' },
|
|
139
|
+
// accessCookie: { secure: true, sameSite: 'strict' },
|
|
140
|
+
|
|
141
|
+
// -- Hooks (optional) -------------------------------------------------------
|
|
142
|
+
// hooks: {
|
|
143
|
+
// onLogin: (user) => console.log(\`login: \${user.identifier}\`),
|
|
144
|
+
// onFailedLogin: (identifier) => console.warn(\`failed login: \${identifier}\`),
|
|
145
|
+
// onLogout: (userId) => console.log(\`logout: \${userId}\`),
|
|
146
|
+
// },
|
|
147
|
+
|
|
148
|
+
// -- Token revocation (optional) --------------------------------------------
|
|
149
|
+
// isTokenRevoked: async (sessionId) => false,
|
|
150
|
+
|
|
151
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
152
|
+
// logger: console,
|
|
153
|
+
// loggerService: 'sentri',
|
|
154
|
+
});
|
|
155
|
+
`,elysia:`import { createAuthElysia } from 'sentri/elysia';
|
|
156
|
+
import { PostgresDialect } from 'kysely';
|
|
157
|
+
import pg from 'pg';
|
|
158
|
+
|
|
159
|
+
const { Pool } = pg;
|
|
160
|
+
|
|
161
|
+
type Role = 'admin' | 'user';
|
|
162
|
+
|
|
163
|
+
export const sentriAuth = createAuthElysia<Role>({
|
|
164
|
+
mode: 'server',
|
|
165
|
+
|
|
166
|
+
// -- Roles ------------------------------------------------------------------
|
|
167
|
+
validRoles: ['admin', 'user'],
|
|
168
|
+
|
|
169
|
+
// -- Identifiers ------------------------------------------------------------
|
|
170
|
+
validIdentifiers: ['email', 'username'],
|
|
171
|
+
|
|
172
|
+
// -- Database ---------------------------------------------------------------
|
|
173
|
+
dialect: new PostgresDialect({
|
|
174
|
+
pool: new Pool({ connectionString: process.env.DATABASE_URL! })
|
|
175
|
+
}),
|
|
176
|
+
|
|
177
|
+
// -- Token ------------------------------------------------------------------
|
|
178
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
|
|
179
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
|
|
180
|
+
|
|
181
|
+
// -- Security ---------------------------------------------------------------
|
|
182
|
+
saltRounds: parseInt(process.env.SALT_ROUNDS ?? '12', 10),
|
|
183
|
+
});
|
|
184
|
+
`,koa:`import { createAuthKoa } from 'sentri/koa';
|
|
185
|
+
import { PostgresDialect } from 'kysely';
|
|
186
|
+
import pg from 'pg';
|
|
187
|
+
|
|
188
|
+
const { Pool } = pg;
|
|
189
|
+
|
|
190
|
+
type Role = 'admin' | 'user';
|
|
191
|
+
|
|
192
|
+
export const sentriAuth = createAuthKoa<Role>({
|
|
193
|
+
mode: 'server',
|
|
194
|
+
|
|
195
|
+
// -- Roles ------------------------------------------------------------------
|
|
196
|
+
validRoles: ['admin', 'user'],
|
|
197
|
+
|
|
198
|
+
// -- Identifiers ------------------------------------------------------------
|
|
199
|
+
validIdentifiers: ['email', 'username'],
|
|
200
|
+
|
|
201
|
+
// -- Database ---------------------------------------------------------------
|
|
202
|
+
dialect: new PostgresDialect({
|
|
203
|
+
pool: new Pool({ connectionString: process.env.DATABASE_URL! })
|
|
204
|
+
}),
|
|
205
|
+
|
|
206
|
+
// -- Token ------------------------------------------------------------------
|
|
207
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
|
|
208
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
|
|
209
|
+
|
|
210
|
+
// -- Security ---------------------------------------------------------------
|
|
211
|
+
saltRounds: parseInt(process.env.SALT_ROUNDS ?? '12', 10),
|
|
212
|
+
});
|
|
213
|
+
`},client:{express:`import { createAuthExpress } from 'sentri/express';
|
|
214
|
+
|
|
215
|
+
type Role = 'admin' | 'user';
|
|
216
|
+
|
|
217
|
+
export const sentriAuth = createAuthExpress<Role>({
|
|
218
|
+
mode: 'client',
|
|
219
|
+
|
|
220
|
+
// -- Auth server ------------------------------------------------------------
|
|
221
|
+
// URL of the auth server's GET /auth/keys endpoint (JWKS).
|
|
222
|
+
// The server must use RS256 to expose this endpoint.
|
|
223
|
+
keyUri: process.env.AUTH_KEY_URI!,
|
|
224
|
+
|
|
225
|
+
// -- Roles (optional) -------------------------------------------------------
|
|
226
|
+
// Only used for TypeScript type safety on authorize() \u2014 not validated at runtime.
|
|
227
|
+
validRoles: ['admin', 'user'],
|
|
228
|
+
|
|
229
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
230
|
+
// logger: console,
|
|
231
|
+
// loggerService: 'auth-service',
|
|
232
|
+
});
|
|
233
|
+
`,fastify:`import { createAuthFastify } from 'sentri/fastify';
|
|
234
|
+
|
|
235
|
+
type Role = 'admin' | 'user';
|
|
236
|
+
|
|
237
|
+
export const sentriAuth = createAuthFastify<Role>({
|
|
238
|
+
mode: 'client',
|
|
239
|
+
|
|
240
|
+
// -- Auth server ------------------------------------------------------------
|
|
241
|
+
// URL of the auth server's GET /auth/keys endpoint (JWKS).
|
|
242
|
+
keyUri: process.env.AUTH_KEY_URI!,
|
|
243
|
+
|
|
244
|
+
// -- Roles (optional) -------------------------------------------------------
|
|
245
|
+
validRoles: ['admin', 'user'],
|
|
246
|
+
|
|
247
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
248
|
+
// logger: console,
|
|
249
|
+
// loggerService: 'auth-service',
|
|
250
|
+
});
|
|
251
|
+
`,hono:`import { createAuthHono } from 'sentri/hono';
|
|
252
|
+
|
|
253
|
+
type Role = 'admin' | 'user';
|
|
254
|
+
|
|
255
|
+
export const sentriAuth = createAuthHono<Role>({
|
|
256
|
+
mode: 'client',
|
|
257
|
+
|
|
258
|
+
// -- Auth server ------------------------------------------------------------
|
|
259
|
+
// URL of the auth server's GET /auth/keys endpoint (JWKS).
|
|
260
|
+
keyUri: process.env.AUTH_KEY_URI!,
|
|
261
|
+
|
|
262
|
+
// -- Roles (optional) -------------------------------------------------------
|
|
263
|
+
validRoles: ['admin', 'user'],
|
|
264
|
+
|
|
265
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
266
|
+
// logger: console,
|
|
267
|
+
// loggerService: 'auth-service',
|
|
268
|
+
});
|
|
269
|
+
`,elysia:`import { createAuthElysia } from 'sentri/elysia';
|
|
270
|
+
|
|
271
|
+
type Role = 'admin' | 'user';
|
|
272
|
+
|
|
273
|
+
export const sentriAuth = createAuthElysia<Role>({
|
|
274
|
+
mode: 'client',
|
|
275
|
+
|
|
276
|
+
// -- Auth server ------------------------------------------------------------
|
|
277
|
+
// URL of the auth server's GET /auth/keys endpoint (JWKS).
|
|
278
|
+
// The server must use RS256 to expose this endpoint.
|
|
279
|
+
keyUri: process.env.AUTH_KEY_URI!,
|
|
280
|
+
|
|
281
|
+
// -- Roles (optional) -------------------------------------------------------
|
|
282
|
+
// Only used for TypeScript type safety on authorize() \u2014 not validated at runtime.
|
|
283
|
+
validRoles: ['admin', 'user'],
|
|
284
|
+
|
|
285
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
286
|
+
// logger: console,
|
|
287
|
+
// loggerService: 'auth-service',
|
|
288
|
+
});
|
|
289
|
+
`,koa:`import { createAuthKoa } from 'sentri/koa';
|
|
290
|
+
|
|
291
|
+
type Role = 'admin' | 'user';
|
|
292
|
+
|
|
293
|
+
export const sentriAuth = createAuthKoa<Role>({
|
|
294
|
+
mode: 'client',
|
|
295
|
+
|
|
296
|
+
// -- Auth server ------------------------------------------------------------
|
|
297
|
+
// URL of the auth server's GET /auth/keys endpoint (JWKS).
|
|
298
|
+
// The server must use RS256 to expose this endpoint.
|
|
299
|
+
keyUri: process.env.AUTH_KEY_URI!,
|
|
300
|
+
|
|
301
|
+
// -- Roles (optional) -------------------------------------------------------
|
|
302
|
+
// Only used for TypeScript type safety on authorize() \u2014 not validated at runtime.
|
|
303
|
+
validRoles: ['admin', 'user'],
|
|
304
|
+
|
|
305
|
+
// -- Logger (optional) ------------------------------------------------------
|
|
306
|
+
// logger: console,
|
|
307
|
+
// loggerService: 'auth-service',
|
|
308
|
+
});
|
|
309
|
+
`}},A=`# -- Server -------------------------------------------------------------------
|
|
310
|
+
PORT=3000
|
|
311
|
+
|
|
312
|
+
# -- Database -----------------------------------------------------------------
|
|
313
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
|
|
314
|
+
|
|
315
|
+
# -- Token --------------------------------------------------------------------
|
|
316
|
+
JWT_ACCESS_EXPIRES_IN=15m
|
|
317
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
318
|
+
|
|
319
|
+
# -- Security -----------------------------------------------------------------
|
|
320
|
+
SALT_ROUNDS=12
|
|
321
|
+
# API_KEY=your-register-api-key
|
|
322
|
+
|
|
323
|
+
# -- Redis (optional) ---------------------------------------------------------
|
|
324
|
+
# REDIS_URL=redis://localhost:6379
|
|
325
|
+
`,R=`# -- Server -------------------------------------------------------------------
|
|
326
|
+
PORT=3000
|
|
327
|
+
|
|
328
|
+
# -- Auth Server --------------------------------------------------------------
|
|
329
|
+
# URL of the auth server's GET /auth/keys endpoint (JWKS).
|
|
330
|
+
AUTH_KEY_URI=http://localhost:3000/auth/keys
|
|
331
|
+
`,E={server:{express:`
|
|
332
|
+
Done. Next steps:
|
|
333
|
+
|
|
334
|
+
1. Copy .env.example to .env and fill in your values
|
|
335
|
+
2. Add to your app entry point:
|
|
336
|
+
|
|
337
|
+
import express from 'express';
|
|
338
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
339
|
+
|
|
340
|
+
const app = express();
|
|
341
|
+
app.use(express.json());
|
|
342
|
+
await sentriAuth.migrate();
|
|
343
|
+
app.use('/auth', sentriAuth.router());
|
|
344
|
+
app.use(sentriAuth.errorHandler());
|
|
345
|
+
|
|
346
|
+
3. Protect routes:
|
|
347
|
+
|
|
348
|
+
app.get('/me', sentriAuth.protect(), (req, res) => res.json(req.user));
|
|
349
|
+
`,fastify:`
|
|
350
|
+
Done. Next steps:
|
|
351
|
+
|
|
352
|
+
1. Copy .env.example to .env and fill in your values
|
|
353
|
+
2. Install peer deps: npm install fastify @fastify/cookie
|
|
354
|
+
3. Add to your app entry point:
|
|
355
|
+
|
|
356
|
+
import Fastify from 'fastify';
|
|
357
|
+
import cookie from '@fastify/cookie';
|
|
358
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
359
|
+
|
|
360
|
+
const app = Fastify();
|
|
361
|
+
await app.register(cookie);
|
|
362
|
+
await sentriAuth.migrate();
|
|
363
|
+
await app.register(sentriAuth.plugin(), { prefix: '/auth' });
|
|
364
|
+
app.setErrorHandler(sentriAuth.errorHandler());
|
|
365
|
+
|
|
366
|
+
4. Protect routes:
|
|
367
|
+
|
|
368
|
+
app.get('/me', { preHandler: sentriAuth.protect() }, async (req) => req.user);
|
|
369
|
+
`,hono:`
|
|
370
|
+
Done. Next steps:
|
|
371
|
+
|
|
372
|
+
1. Copy .env.example to .env and fill in your values
|
|
373
|
+
2. Install peer deps: npm install hono
|
|
374
|
+
3. Add to your app entry point:
|
|
375
|
+
|
|
376
|
+
import { Hono } from 'hono';
|
|
377
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
378
|
+
import type { SentriHonoEnv } from 'sentri/hono';
|
|
379
|
+
|
|
380
|
+
const app = new Hono<SentriHonoEnv>();
|
|
381
|
+
await sentriAuth.migrate();
|
|
382
|
+
app.route('/auth', sentriAuth.router());
|
|
383
|
+
app.onError(sentriAuth.errorHandler());
|
|
384
|
+
|
|
385
|
+
4. Protect routes:
|
|
386
|
+
|
|
387
|
+
app.get('/me', sentriAuth.protect(), (c) => c.json(c.get('user')));
|
|
388
|
+
`,elysia:`
|
|
389
|
+
Done. Next steps:
|
|
390
|
+
|
|
391
|
+
1. Copy .env.example to .env and fill in your values
|
|
392
|
+
2. Install peer deps: npm install elysia
|
|
393
|
+
3. Add to your app entry point:
|
|
394
|
+
|
|
395
|
+
import { Elysia } from 'elysia';
|
|
396
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
397
|
+
|
|
398
|
+
const app = new Elysia()
|
|
399
|
+
.onError(sentriAuth.errorHandler())
|
|
400
|
+
.group('/auth', app => app.use(sentriAuth.router()))
|
|
401
|
+
.use(sentriAuth.protect())
|
|
402
|
+
.get('/me', ({ user }) => user);
|
|
403
|
+
|
|
404
|
+
4. Protect routes:
|
|
405
|
+
|
|
406
|
+
app.use(sentriAuth.protect())
|
|
407
|
+
.get('/me', ({ user }) => user);
|
|
408
|
+
`,koa:`
|
|
409
|
+
Done. Next steps:
|
|
410
|
+
|
|
411
|
+
1. Copy .env.example to .env and fill in your values
|
|
412
|
+
2. Install peer deps: npm install koa @koa/router koa-bodyparser
|
|
413
|
+
3. Add to your app entry point:
|
|
414
|
+
|
|
415
|
+
import Koa from 'koa';
|
|
416
|
+
import Router from '@koa/router';
|
|
417
|
+
import bodyParser from 'koa-bodyparser';
|
|
418
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
419
|
+
|
|
420
|
+
const app = new Koa();
|
|
421
|
+
app.use(bodyParser());
|
|
422
|
+
app.use(sentriAuth.errorHandler());
|
|
423
|
+
await sentriAuth.migrate();
|
|
424
|
+
|
|
425
|
+
const rootRouter = new Router();
|
|
426
|
+
rootRouter.use('/auth', sentriAuth.router().routes());
|
|
427
|
+
app.use(rootRouter.routes());
|
|
428
|
+
|
|
429
|
+
4. Protect routes:
|
|
430
|
+
|
|
431
|
+
rootRouter.get('/me', sentriAuth.protect(), (ctx) => {
|
|
432
|
+
ctx.body = ctx.state.user;
|
|
433
|
+
});
|
|
434
|
+
`},client:{express:`
|
|
435
|
+
Done. Next steps:
|
|
436
|
+
|
|
437
|
+
1. Copy .env.example to .env and set AUTH_KEY_URI to your auth server's /auth/keys endpoint
|
|
438
|
+
2. Add to your app entry point:
|
|
439
|
+
|
|
440
|
+
import express from 'express';
|
|
441
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
442
|
+
|
|
443
|
+
const app = express();
|
|
444
|
+
app.get('/protected', sentriAuth.protect(), (req, res) => res.json(req.user));
|
|
445
|
+
app.use(sentriAuth.errorHandler());
|
|
446
|
+
`,fastify:`
|
|
447
|
+
Done. Next steps:
|
|
448
|
+
|
|
449
|
+
1. Copy .env.example to .env and set AUTH_KEY_URI to your auth server's /auth/keys endpoint
|
|
450
|
+
2. Install peer deps: npm install fastify
|
|
451
|
+
3. Add to your app entry point:
|
|
452
|
+
|
|
453
|
+
import Fastify from 'fastify';
|
|
454
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
455
|
+
|
|
456
|
+
const app = Fastify();
|
|
457
|
+
app.get('/protected', { preHandler: sentriAuth.protect() }, async (req) => req.user);
|
|
458
|
+
app.setErrorHandler(sentriAuth.errorHandler());
|
|
459
|
+
`,hono:`
|
|
460
|
+
Done. Next steps:
|
|
461
|
+
|
|
462
|
+
1. Copy .env.example to .env and set AUTH_KEY_URI to your auth server's /auth/keys endpoint
|
|
463
|
+
2. Install peer deps: npm install hono
|
|
464
|
+
3. Add to your app entry point:
|
|
465
|
+
|
|
466
|
+
import { Hono } from 'hono';
|
|
467
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
468
|
+
import type { SentriHonoEnv } from 'sentri/hono';
|
|
469
|
+
|
|
470
|
+
const app = new Hono<SentriHonoEnv>();
|
|
471
|
+
app.get('/protected', sentriAuth.protect(), (c) => c.json(c.get('user')));
|
|
472
|
+
app.onError(sentriAuth.errorHandler());
|
|
473
|
+
`,elysia:`
|
|
474
|
+
Done. Next steps:
|
|
475
|
+
|
|
476
|
+
1. Copy .env.example to .env and set AUTH_KEY_URI to your auth server's /auth/keys endpoint
|
|
477
|
+
2. Install peer deps: npm install elysia
|
|
478
|
+
3. Add to your app entry point:
|
|
479
|
+
|
|
480
|
+
import { Elysia } from 'elysia';
|
|
481
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
482
|
+
|
|
483
|
+
const app = new Elysia()
|
|
484
|
+
.onError(sentriAuth.errorHandler())
|
|
485
|
+
.use(sentriAuth.protect())
|
|
486
|
+
.get('/protected', ({ user }) => user);
|
|
487
|
+
`,koa:`
|
|
488
|
+
Done. Next steps:
|
|
489
|
+
|
|
490
|
+
1. Copy .env.example to .env and set AUTH_KEY_URI to your auth server's /auth/keys endpoint
|
|
491
|
+
2. Install peer deps: npm install koa @koa/router
|
|
492
|
+
3. Add to your app entry point:
|
|
493
|
+
|
|
494
|
+
import Koa from 'koa';
|
|
495
|
+
import Router from '@koa/router';
|
|
496
|
+
import { sentriAuth } from './lib/sentri.js';
|
|
497
|
+
|
|
498
|
+
const app = new Koa();
|
|
499
|
+
app.use(sentriAuth.errorHandler());
|
|
500
|
+
|
|
501
|
+
const rootRouter = new Router();
|
|
502
|
+
rootRouter.get('/protected', sentriAuth.protect(), (ctx) => {
|
|
503
|
+
ctx.body = ctx.state.user;
|
|
504
|
+
});
|
|
505
|
+
app.use(rootRouter.routes());
|
|
506
|
+
`}};function c(){console.log(`
|
|
507
|
+
sentri \u2014 auth/authorization library for Express, Fastify, Hono, Elysia, and Koa
|
|
508
|
+
|
|
509
|
+
Usage:
|
|
510
|
+
npx sentri <command>
|
|
511
|
+
|
|
512
|
+
Commands:
|
|
513
|
+
init [mode] [adapter] Generate src/lib/sentri.ts
|
|
514
|
+
|
|
515
|
+
mode: server (default) | client
|
|
516
|
+
adapter: express (default) | fastify | hono | elysia | koa
|
|
517
|
+
|
|
518
|
+
Examples:
|
|
519
|
+
npx sentri init \u2192 server mode, express adapter
|
|
520
|
+
npx sentri init server \u2192 server mode, express adapter
|
|
521
|
+
npx sentri init server hono \u2192 server mode, hono adapter
|
|
522
|
+
npx sentri init server elysia \u2192 server mode, elysia adapter
|
|
523
|
+
npx sentri init server koa \u2192 server mode, koa adapter
|
|
524
|
+
npx sentri init client \u2192 client mode, express adapter
|
|
525
|
+
npx sentri init client fastify \u2192 client mode, fastify adapter
|
|
526
|
+
npx sentri init client hono \u2192 client mode, hono adapter
|
|
527
|
+
`);}s(c,"help");async function l(e,o,t){if(fs.existsSync(e)){console.log(` skip ${t} (already exists)`);return}await promises.writeFile(e,o,"utf8"),console.log(` create ${t}`);}s(l,"writeIfNotExists");async function S(e,o){let t=["server","client"],i=["express","fastify","hono","elysia","koa"];t.includes(e)||(console.error(`Unknown mode: "${e}". Valid modes: ${t.join(", ")}`),process.exit(1)),i.includes(o)||(console.error(`Unknown adapter: "${o}". Valid adapters: ${i.join(", ")}`),process.exit(1));let a=process.cwd(),p=path.join(a,"src","lib");await promises.mkdir(p,{recursive:true});let u=g[e][o],d=e==="server"?A:R,m=E[e][o];console.log(`
|
|
528
|
+
Generating sentri ${e} mode files (${o})...
|
|
529
|
+
`),await l(path.join(p,"sentri.ts"),u,"src/lib/sentri.ts"),await l(path.join(a,".env.example"),d,".env.example"),console.log(m);}s(S,"init");var x=process.argv.slice(2),[r,I,k]=x;(!r||r==="--help"||r==="-h")&&(c(),process.exit(0));r==="init"?S(I??"server",k??"express").catch(t=>{console.error(t),process.exit(1);}):(console.error(`Unknown command: ${r}`),c(),process.exit(1));
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|