sentri 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +325 -181
  2. package/dist/cli.d.ts +0 -2
  3. package/dist/cli.js +10 -103
  4. package/dist/index.d.ts +1046 -11
  5. package/dist/index.js +1 -5
  6. package/package.json +13 -6
  7. package/templates/drizzle/auth.ts +47 -4
  8. package/templates/prisma/auth.ts +47 -4
  9. package/dist/cli.d.ts.map +0 -1
  10. package/dist/cli.js.map +0 -1
  11. package/dist/client.d.ts +0 -160
  12. package/dist/client.d.ts.map +0 -1
  13. package/dist/client.js +0 -45
  14. package/dist/client.js.map +0 -1
  15. package/dist/errors/AuthError.d.ts +0 -99
  16. package/dist/errors/AuthError.d.ts.map +0 -1
  17. package/dist/errors/AuthError.js +0 -97
  18. package/dist/errors/AuthError.js.map +0 -1
  19. package/dist/index.d.ts.map +0 -1
  20. package/dist/index.js.map +0 -1
  21. package/dist/libs/config.d.ts +0 -62
  22. package/dist/libs/config.d.ts.map +0 -1
  23. package/dist/libs/config.js +0 -97
  24. package/dist/libs/config.js.map +0 -1
  25. package/dist/libs/hash.d.ts +0 -17
  26. package/dist/libs/hash.d.ts.map +0 -1
  27. package/dist/libs/hash.js +0 -22
  28. package/dist/libs/hash.js.map +0 -1
  29. package/dist/libs/token.d.ts +0 -46
  30. package/dist/libs/token.d.ts.map +0 -1
  31. package/dist/libs/token.js +0 -118
  32. package/dist/libs/token.js.map +0 -1
  33. package/dist/middleware/authorize.d.ts +0 -18
  34. package/dist/middleware/authorize.d.ts.map +0 -1
  35. package/dist/middleware/authorize.js +0 -30
  36. package/dist/middleware/authorize.js.map +0 -1
  37. package/dist/middleware/errorHandler.d.ts +0 -71
  38. package/dist/middleware/errorHandler.d.ts.map +0 -1
  39. package/dist/middleware/errorHandler.js +0 -74
  40. package/dist/middleware/errorHandler.js.map +0 -1
  41. package/dist/middleware/permit.d.ts +0 -62
  42. package/dist/middleware/permit.d.ts.map +0 -1
  43. package/dist/middleware/permit.js +0 -61
  44. package/dist/middleware/permit.js.map +0 -1
  45. package/dist/middleware/protect.d.ts +0 -31
  46. package/dist/middleware/protect.d.ts.map +0 -1
  47. package/dist/middleware/protect.js +0 -54
  48. package/dist/middleware/protect.js.map +0 -1
  49. package/dist/middleware/router.d.ts +0 -34
  50. package/dist/middleware/router.d.ts.map +0 -1
  51. package/dist/middleware/router.js +0 -264
  52. package/dist/middleware/router.js.map +0 -1
  53. package/dist/services/auth.d.ts +0 -85
  54. package/dist/services/auth.d.ts.map +0 -1
  55. package/dist/services/auth.js +0 -173
  56. package/dist/services/auth.js.map +0 -1
  57. package/dist/types/auth.d.ts +0 -450
  58. package/dist/types/auth.d.ts.map +0 -1
  59. package/dist/types/auth.js +0 -21
  60. package/dist/types/auth.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- export { SentriError, AUTH_ERROR_STATUS } from './errors/AuthError.js';
2
- export { createAuth } from './client.js';
3
- export { createErrorHandler } from './middleware/errorHandler.js';
4
- export { register } from './services/auth.js';
5
- //# sourceMappingURL=index.js.map
1
+ import W from'bcrypt';import K from'jsonwebtoken';import {Router}from'express';var Y=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500}),i=class extends Error{code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??Y[r]??500;}};async function D(e,r=12){return W.hash(e,r)}async function H(e,r){return W.compare(e,r)}var J=new WeakMap,Q=32,ee=10,re=31;function se(e){if(!e.secret||e.secret.trim().length===0)throw new i("CONFIGURATION_ERROR","secret must not be empty");if(e.secret.length<Q)throw new i("CONFIGURATION_ERROR",`secret must be at least ${Q} characters to be cryptographically safe`);let r=e.saltRounds??12;if(!Number.isInteger(r)||r<ee||r>re)throw new i("CONFIGURATION_ERROR",`saltRounds must be an integer between ${ee} and ${re}`);if(!e.validRoles||e.validRoles.length===0)throw new i("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.adapter)throw new i("CONFIGURATION_ERROR","adapter is required")}function h(e){let r=J.get(e);if(r)return r;let t={secret:e.secret,accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",algorithm:e.algorithm??"HS256",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),adapter:e.adapter,cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return J.set(e,t),t}var Re=/^(\d+)([smhdw])$/,ge={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},te=new Map;function T(e){if(typeof e=="number")return e*1e3;let r=te.get(e);if(r!==void 0)return r;let t=Re.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=ge[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let a=parseInt(t[1],10)*s;return te.set(e,a),a}var oe=new Map,ne=new Map;function P(e){let r=oe.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},oe.set(e,r)),r}function ie(e,r,t,s){let a=`${t}:${s}`,n=ne.get(a);return n||(n={expiresIn:t,algorithm:s},ne.set(a,n)),K.sign(e,r,n)}function ae(e,r,t){try{let s=K.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new i("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof i?s:s instanceof K.TokenExpiredError?new i("TOKEN_EXPIRED","Token has expired"):new i("TOKEN_INVALID","Token is invalid or malformed")}}function x(e,r){let t=h(r),{access:s}=P(t.secret);return ie(e,s,t.accessExpiresIn,t.algorithm)}function v(e,r){let t=h(r),{refresh:s}=P(t.secret);return ie({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}function b(e,r){let t=h(r),{access:s}=P(t.secret);return ae(e,s,t.algorithm)}function N(e,r){let t=h(r),{refresh:s}=P(t.secret);return ae(e,s,t.algorithm)}function I(e){return h(e).cookieName}function E(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let a=e.indexOf(";",s),n=a===-1?e.length:a;if(e.startsWith(t,s))return e.slice(s+t.length,n);s=n+1;}}function O(e,r,t){let s=t.cookie??{},a=h(t),n=T(a.refreshExpiresIn);e.cookie(I(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:n});}function L(e,r){let t=r.cookie??{};e.clearCookie(I(r),{path:t.path??"/"});}function M(e){return h(e).accessCookieName}function S(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,a=h(t),n=T(a.accessExpiresIn);e.cookie(M(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:n});}function X(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(M(r),{path:t.path??"/"});}function _(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):E(e.headers.cookie,M(r))}async function Z(e,r){let t=h(r),s=e.roles??[],a=s.filter(u=>!t.validRolesSet.has(u));if(a.length>0)return {success:false,error:new i("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let n=e.identifier.trim();if(await t.adapter.user.findByIdentifier(n))return {success:false,error:new i("USER_ALREADY_EXISTS","User already exists")};let f=await D(e.password,t.saltRounds);return {success:true,user:{id:(await t.adapter.user.create({identifier:n,passwordHash:f,roles:s})).id,identifier:n,roles:s}}}async function ue(e,r){let t=h(r),s=await t.adapter.user.findByIdentifier(e.identifier.trim());if(!s)return {success:false,error:new i("INVALID_CREDENTIALS","Invalid credentials")};if(!await H(e.password,s.passwordHash))return {success:false,error:new i("INVALID_CREDENTIALS","Invalid credentials")};let n=new Date(Date.now()+T(t.refreshExpiresIn)),c=await t.adapter.session.create({userId:s.id,expiresAt:n}),f={id:s.id,identifier:s.identifier,roles:s.roles},m=x({id:s.id,identifier:s.identifier,roles:s.roles,sessionId:c.id},r),o=v(c.id,r);return {success:true,accessToken:m,refreshToken:o,user:f}}async function j(e,r){let t=h(r),s;try{({sessionId:s}=N(e,r));}catch(u){return u instanceof i?{success:false,error:u}:{success:false,error:new i("TOKEN_INVALID","Invalid refresh token")}}let a=await t.adapter.session.findById(s);if(!a)return {success:false,error:new i("UNAUTHORIZED","Session not found or revoked")};if(a.expiresAt.getTime()<Date.now())return await t.adapter.session.delete(s),{success:false,error:new i("TOKEN_EXPIRED","Session has expired")};await t.adapter.session.delete(s);let n=new Date(Date.now()+T(t.refreshExpiresIn)),c=await t.adapter.session.create({userId:a.userId,expiresAt:n}),f={id:a.user.id,identifier:a.user.identifier,roles:a.user.roles},m=x({id:f.id,identifier:f.identifier,roles:f.roles,sessionId:c.id},r),o=v(c.id,r);return {success:true,accessToken:m,refreshToken:o,user:f}}async function ce(e,r){let t;try{({sessionId:t}=N(e,r));}catch{return}await h(r).adapter.session.delete(t);}async function de(e,r){await h(r).adapter.session.deleteAllForUser(e);}async function le(e,r,t){let s=h(t),a=r.filter(m=>!s.validRolesSet.has(m));if(a.length>0)return {success:false,error:new i("INVALID_ROLE",`Invalid roles: ${a.join(", ")}`)};let n=await s.adapter.user.findById(e);if(!n)return {success:false,error:new i("USER_NOT_FOUND","User not found")};let c=new Set(n.roles);for(let m of r)c.add(m);let f=Array.from(c);return await s.adapter.user.updateRoles(e,f),{success:true,user:{id:n.id,identifier:n.identifier,roles:f}}}function k(e){return async(r,t,s)=>{let a=_(r,e);if(!a)return s(new i("UNAUTHORIZED","Missing or malformed Authorization header"));try{let n=b(a,e);if(e.isTokenRevoked&&await e.isTokenRevoked(n.sessionId))return s(new i("UNAUTHORIZED","Token has been revoked"));r.user={id:n.id,identifier:n.identifier,roles:n.roles},s();}catch(n){if(n instanceof i&&n.code==="TOKEN_EXPIRED"){let c=E(r.headers.cookie,I(e));if(!c)return s(new i("UNAUTHORIZED","Token expired. Please login again."));try{let f=await j(c,e);if(!f.success)return s(new i("UNAUTHORIZED","Session expired. Please login again."));O(t,f.refreshToken,e),S(t,f.accessToken,e),t.setHeader("X-New-Access-Token",f.accessToken),r.user=f.user,s();}catch{s(new i("UNAUTHORIZED","Session expired. Please login again."));}}else s(n);}}}function F(...e){let r=`Requires one of roles: ${e.join(", ")}`;return (t,s,a)=>{if(!t.user)return a(new i("UNAUTHORIZED","Not authenticated"));let n=t.user.roles;if(!e.some(c=>n.includes(c)))return a(new i("FORBIDDEN",r));a();}}var Ae=new i("FORBIDDEN","You do not have permission to perform this action");function fe(e){let r=typeof e=="function"?{check:e}:e;return async(t,s,a)=>{if(!t.user)return a(new i("UNAUTHORIZED","Not authenticated"));if(r.roles&&r.roles.length>0){let n=t.user.roles;if(r.roles.some(c=>n.includes(c)))return a()}try{let n=r.check(t);(n instanceof Promise?await n:n)?a():a(Ae);}catch(n){a(n);}}}var pe=8,q=72,$=255;function y(e){return new i("VALIDATION_ERROR",e)}function C(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}function U(e,r){e.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}function z(e){if(e==null||typeof e!="object"||Array.isArray(e))throw new i("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you apply express.json()?");return e}function Ie(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new i("UNAUTHORIZED","Invalid or missing API key")}function B(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}function he(e){let r=Router(),t=e,s=e.router?.register??(o=>Z(o,t)),a=e.router?.login??(o=>ue(o,t)),n=e.router?.refresh??(o=>j(o,t)),c=e.router?.logout??(o=>o!==void 0?ce(o,t):Promise.resolve()),f=e.router?.logoutAll??(o=>de(o,t)),m=e.router?.assignRoles??((o,u)=>le(o,u,t));return r.post("/register",async(o,u,p)=>{try{Ie(o,e);let d=z(o.body),{identifier:l,password:g,roles:A}=d;if(typeof l!="string"||l.trim().length===0)throw y("identifier is required and must be a non-empty string");if(l.length>$)throw y(`identifier must not exceed ${$} characters`);if(typeof g!="string"||g.length<pe)throw y(`password is required and must be at least ${pe} characters`);if(g.length>q)throw y(`password must not exceed ${q} characters`);if(A!==void 0&&!Array.isArray(A))throw y("roles must be an array of strings when provided");if(Array.isArray(A)&&!A.every(me=>typeof me=="string"))throw y("each role must be a string");let R=Array.isArray(A)?A:void 0,w=R!==void 0?{identifier:l.trim(),password:g,roles:R}:{identifier:l.trim(),password:g},V=await s(w);if(!V.success){U(u,V.error);return}C(u,201,"User registered successfully",{user:V.user});}catch(d){p(d);}}),r.post("/login",async(o,u,p)=>{try{let d=z(o.body),{identifier:l,password:g}=d;if(typeof l!="string"||l.trim().length===0)throw y("identifier is required and must be a non-empty string");if(l.length>$)throw y(`identifier must not exceed ${$} characters`);if(typeof g!="string"||g.length===0)throw y("password is required");if(g.length>q)throw y(`password must not exceed ${q} characters`);let A=l.trim(),R=await a({identifier:A,password:g});if(!R.success){B(()=>e.hooks?.onFailedLogin?.(A,R.error)),U(u,R.error);return}B(()=>e.hooks?.onLogin?.(R.user)),O(u,R.refreshToken,e),S(u,R.accessToken,e),C(u,200,"Login successful",{accessToken:R.accessToken,user:R.user});}catch(d){p(d);}}),r.post("/refresh",async(o,u,p)=>{try{let d=E(o.headers.cookie,I(e));if(!d)throw new i("UNAUTHORIZED","Refresh token cookie is missing");let l=await n(d);if(!l.success){L(u,e),U(u,l.error);return}O(u,l.refreshToken,e),S(u,l.accessToken,e),C(u,200,"Token refreshed",{accessToken:l.accessToken});}catch(d){p(d);}}),r.post("/logout",async(o,u,p)=>{try{let d=E(o.headers.cookie,I(e));await c(d),L(u,e),X(u,e),C(u,200,"Logged out",null);}catch(d){p(d);}}),r.post("/logout-all",k(e),async(o,u,p)=>{try{let d=o.user.id;await f(d),B(()=>e.hooks?.onLogout?.(d)),L(u,e),X(u,e),C(u,200,"All sessions revoked",null);}catch(d){p(d);}}),r.get("/me",k(e),(o,u)=>{C(u,200,"OK",o.user);}),r.post("/users/:userId/roles",k(e),F("admin"),async(o,u,p)=>{try{let d=z(o.body),{roles:l}=d,g=o.params.userId,A=typeof g=="string"?g:void 0;if(!A)throw y("userId is required");if(!Array.isArray(l)||l.length===0)throw y("roles must be a non-empty array of strings");if(!l.every(w=>typeof w=="string"))throw y("each role must be a string");let R=await m(A,l);if(!R.success){U(u,R.error);return}C(u,200,"Roles assigned successfully",{user:R.user});}catch(d){p(d);}}),r.use((o,u,p,d)=>{o instanceof i?U(p,o):p.status(500).json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}),r}function G(e){return (r,t,s,a)=>{if(r instanceof i){s.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.status(500).json({error:true,statusCode:500,message:"Internal server error",data:null});}}function Ce(e){se(e);let r=h(e);return {protect:()=>k(e),authorize:(...t)=>F(...t),permit:t=>fe(t),hashPassword:t=>D(t,r.saltRounds),verifyPassword:(t,s)=>H(t,s),signAccessToken:t=>x(t,e),signRefreshToken:t=>v(t,e),verifyAccessToken:t=>b(t,e),verifyRefreshToken:t=>N(t,e),getCurrentAccessToken:t=>_(t,e),router:()=>he(e),errorHandler:t=>G(t)}}function we(e){let r=e?.ttl??3e5,t=Math.max(r,5e3),s=(e?.header??"X-Idempotency-Key").toLowerCase(),a=new Set((e?.methods??["POST","PUT","PATCH"]).map(m=>m.toUpperCase())),n=e?.maxSize??1e4,c=new Map,f=setInterval(()=>{let m=Date.now();for(let[o,u]of c)u.expiresAt<=m&&c.delete(o);},t);return typeof f=="object"&&f!==null&&"unref"in f&&f.unref(),(m,o,u)=>{let p=m.headers[s];if(!p||typeof p!="string"||!a.has(m.method))return u();m.requestId=p,o.setHeader("X-Request-Id",p);let d=Date.now(),l=c.get(p);if(l&&l.expiresAt>d)return o.setHeader("X-Idempotent-Replayed","true"),o.status(l.statusCode).json(l.body);let g=o.json.bind(o);o.json=function(R){if(o.statusCode>=200&&o.statusCode<300){if(c.size>=n){let w=c.keys().next().value;w!==void 0&&c.delete(w);}c.set(p,{statusCode:o.statusCode,body:R,expiresAt:Date.now()+r});}return g(R)},u();}}export{Y as AUTH_ERROR_STATUS,i as SentriError,Ce as createAuth,G as createErrorHandler,we as createIdempotencyMiddleware,_ as getCurrentAccessToken,Z as register};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentri",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "Personal auth/authorization library for Express + Postgres",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,8 @@
20
20
  "templates"
21
21
  ],
22
22
  "scripts": {
23
- "build": "tsc",
23
+ "build": "tsup",
24
+ "build:types": "tsc --emitDeclarationOnly",
24
25
  "test": "vitest run",
25
26
  "test:watch": "vitest",
26
27
  "test:coverage": "vitest run --coverage"
@@ -44,18 +45,24 @@
44
45
  "@vitest/coverage-v8": "^4.1.9",
45
46
  "express": "^5.2.1",
46
47
  "supertest": "^7.2.2",
48
+ "tsup": "^8.5.1",
47
49
  "tsx": "^4.22.4",
48
50
  "typescript": "^6.0.3",
49
51
  "vitest": "^4.1.9"
50
52
  },
51
53
  "dependencies": {
52
- "@prisma/adapter-pg": "^7.8.0",
53
- "@prisma/client": "^7.8.0",
54
54
  "bcrypt": "^6.0.0",
55
- "jsonwebtoken": "^9.0.3",
56
- "sentri": "^1.0.6"
55
+ "jsonwebtoken": "^9.0.3"
57
56
  },
58
57
  "peerDependencies": {
59
58
  "express": ">=4.0.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@prisma/client": {
62
+ "optional": true
63
+ },
64
+ "drizzle-orm": {
65
+ "optional": true
66
+ }
60
67
  }
61
68
  }
@@ -13,11 +13,23 @@ export const auth = createAuth({
13
13
  secret: process.env.JWT_SECRET!,
14
14
  validRoles: ['user', 'admin'] as const,
15
15
  adapter: createAdapter(db),
16
- // accessExpiresIn: '15m',
16
+ accessExpiresIn: '5m', // short-lived; silent refresh happens automatically in protect()
17
17
  // refreshExpiresIn: '7d',
18
18
  // algorithm: 'HS256',
19
19
  // saltRounds: 12,
20
20
  // apiKey: process.env.REGISTER_API_KEY, // when set, POST /register requires X-Api-Key header
21
+
22
+ // Access token stored in a non-httpOnly cookie — browser JS can read it via
23
+ // document.cookie or getCurrentAccessToken(req). protect() reads from this
24
+ // cookie automatically when no Authorization header is present.
25
+ accessCookie: {
26
+ secure: process.env.NODE_ENV === 'production',
27
+ // name: 'access_token',
28
+ // sameSite: 'strict',
29
+ // path: '/',
30
+ },
31
+
32
+ // Refresh token stored in an httpOnly cookie — not readable by JS.
21
33
  cookie: {
22
34
  secure: process.env.NODE_ENV === 'production',
23
35
  // name: 'refresh_token',
@@ -25,9 +37,31 @@ export const auth = createAuth({
25
37
  // sameSite: 'strict',
26
38
  // path: '/',
27
39
  },
40
+
41
+ // hooks: {
42
+ // // Fired after every successful login — good for audit logs, notifications.
43
+ // onLogin: async (user) => {
44
+ // await auditLog.record('login', user.id);
45
+ // },
46
+ // // Fired after every failed login — good for rate limiting, alerting.
47
+ // onFailedLogin: (identifier, error) => {
48
+ // rateLimiter.hit(`login:${identifier}`);
49
+ // },
50
+ // // Fired after logout (single session or all sessions).
51
+ // onLogout: async (userId) => {
52
+ // await cache.invalidate(userId);
53
+ // },
54
+ // },
55
+
56
+ // isTokenRevoked: async (sessionId) => {
57
+ // // Optional: immediate revocation via Redis (called on every protected request).
58
+ // // Keep this fast — a slow check negates the performance benefit of stateless JWTs.
59
+ // return await redis.sismember('revoked_sessions', sessionId);
60
+ // },
61
+
28
62
  // router: {
29
63
  // register: async (input) => {
30
- // // custom register logic — must return SignupResult
64
+ // // custom register logic — must return RegisterResult
31
65
  // },
32
66
  // login: async (input) => {
33
67
  // // custom login logic — must return AuthResult
@@ -50,19 +84,28 @@ export const auth = createAuth({
50
84
  // --- Express app setup ---
51
85
  //
52
86
  // import express from 'express';
53
- // import { SentriError } from 'sentri';
87
+ // import { SentriError, createIdempotencyMiddleware, getCurrentAccessToken } from 'sentri';
54
88
  //
55
89
  // const app = express();
56
90
  // app.use(express.json());
57
91
  //
92
+ // // Optional: make create/update operations idempotent via X-Idempotency-Key header
93
+ // app.use(createIdempotencyMiddleware());
94
+ //
58
95
  // // Mount the auth router (POST /auth/register, /auth/login, etc.)
59
96
  // app.use('/auth', auth.router());
60
97
  //
61
- // // Your own routes — throw SentriError (or any subclass) and errorHandler catches them
98
+ // // Your own routes — protect() reads token from Authorization header OR access_token cookie
62
99
  // app.get('/protected', auth.protect(), (req, res) => {
63
100
  // res.json(req.user);
64
101
  // });
65
102
  //
103
+ // // Read the raw access token if needed (header → cookie fallback)
104
+ // app.get('/debug', (req, res) => {
105
+ // const token = auth.getCurrentAccessToken(req); // or: getCurrentAccessToken(req, config)
106
+ // res.json({ hasToken: !!token });
107
+ // });
108
+ //
66
109
  // // Domain-specific error by extending SentriError
67
110
  // class NotFoundError extends SentriError {
68
111
  // constructor(resource: string) {
@@ -16,11 +16,23 @@ export const auth = createAuth({
16
16
  secret: process.env.JWT_SECRET!,
17
17
  validRoles: ['user', 'admin'] as const,
18
18
  adapter: createAdapter(prisma),
19
- // accessExpiresIn: '15m',
19
+ accessExpiresIn: '5m', // short-lived; silent refresh happens automatically in protect()
20
20
  // refreshExpiresIn: '7d',
21
21
  // algorithm: 'HS256',
22
22
  // saltRounds: 12,
23
23
  // apiKey: process.env.REGISTER_API_KEY, // when set, POST /register requires X-Api-Key header
24
+
25
+ // Access token stored in a non-httpOnly cookie — browser JS can read it via
26
+ // document.cookie or getCurrentAccessToken(req). protect() reads from this
27
+ // cookie automatically when no Authorization header is present.
28
+ accessCookie: {
29
+ secure: process.env.NODE_ENV === 'production',
30
+ // name: 'access_token',
31
+ // sameSite: 'strict',
32
+ // path: '/',
33
+ },
34
+
35
+ // Refresh token stored in an httpOnly cookie — not readable by JS.
24
36
  cookie: {
25
37
  secure: process.env.NODE_ENV === 'production',
26
38
  // name: 'refresh_token',
@@ -28,9 +40,31 @@ export const auth = createAuth({
28
40
  // sameSite: 'strict',
29
41
  // path: '/',
30
42
  },
43
+
44
+ // hooks: {
45
+ // // Fired after every successful login — good for audit logs, notifications.
46
+ // onLogin: async (user) => {
47
+ // await auditLog.record('login', user.id);
48
+ // },
49
+ // // Fired after every failed login — good for rate limiting, alerting.
50
+ // onFailedLogin: (identifier, error) => {
51
+ // rateLimiter.hit(`login:${identifier}`);
52
+ // },
53
+ // // Fired after logout (single session or all sessions).
54
+ // onLogout: async (userId) => {
55
+ // await cache.invalidate(userId);
56
+ // },
57
+ // },
58
+
59
+ // isTokenRevoked: async (sessionId) => {
60
+ // // Optional: immediate revocation via Redis (called on every protected request).
61
+ // // Keep this fast — a slow check negates the performance benefit of stateless JWTs.
62
+ // return await redis.sismember('revoked_sessions', sessionId);
63
+ // },
64
+
31
65
  // router: {
32
66
  // register: async (input) => {
33
- // // custom register logic — must return SignupResult
67
+ // // custom register logic — must return RegisterResult
34
68
  // },
35
69
  // login: async (input) => {
36
70
  // // custom login logic — must return AuthResult
@@ -53,19 +87,28 @@ export const auth = createAuth({
53
87
  // --- Express app setup ---
54
88
  //
55
89
  // import express from 'express';
56
- // import { SentriError } from 'sentri';
90
+ // import { SentriError, createIdempotencyMiddleware, getCurrentAccessToken } from 'sentri';
57
91
  //
58
92
  // const app = express();
59
93
  // app.use(express.json());
60
94
  //
95
+ // // Optional: make create/update operations idempotent via X-Idempotency-Key header
96
+ // app.use(createIdempotencyMiddleware());
97
+ //
61
98
  // // Mount the auth router (POST /auth/register, /auth/login, etc.)
62
99
  // app.use('/auth', auth.router());
63
100
  //
64
- // // Your own routes — throw SentriError (or any subclass) and errorHandler catches them
101
+ // // Your own routes — protect() reads token from Authorization header OR access_token cookie
65
102
  // app.get('/protected', auth.protect(), (req, res) => {
66
103
  // res.json(req.user);
67
104
  // });
68
105
  //
106
+ // // Read the raw access token if needed (header → cookie fallback)
107
+ // app.get('/debug', (req, res) => {
108
+ // const token = auth.getCurrentAccessToken(req); // or: getCurrentAccessToken(req, config)
109
+ // res.json({ hasToken: !!token });
110
+ // });
111
+ //
69
112
  // // Domain-specific error by extending SentriError
70
113
  // class NotFoundError extends SentriError {
71
114
  // constructor(resource: string) {
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts DELETED
@@ -1,160 +0,0 @@
1
- import type { PermitCheck, PermitOptions } from './middleware/permit.js';
2
- import type { ErrorHandlerOptions } from './middleware/errorHandler.js';
3
- import type { AuthConfig, AuthUser } from './types/auth.js';
4
- import type { ErrorRequestHandler, RequestHandler, Router } from 'express';
5
- /**
6
- * The bound auth client returned by {@link createAuth}.
7
- *
8
- * All methods are pre-configured with the options passed to `createAuth` —
9
- * you never need to pass config around yourself.
10
- *
11
- * `TRole` is inferred from `validRoles` and narrows role strings to your
12
- * application's exact union type everywhere (authorize, req.user, etc.).
13
- */
14
- export interface AuthClient<TRole extends string = string> {
15
- /**
16
- * Express middleware factory that enforces authentication.
17
- *
18
- * Reads the `Authorization: Bearer <token>` header, verifies the access token,
19
- * confirms the session is still active in the database, and injects the decoded
20
- * payload as `request.user`. Calls `next(SentriError)` on any failure.
21
- *
22
- * @example
23
- * router.get('/me', auth.protect(), (request, response) => {
24
- * response.json(request.user);
25
- * });
26
- */
27
- protect(): RequestHandler;
28
- /**
29
- * Express middleware factory that enforces role-based access.
30
- *
31
- * Must be used **after** `protect()`. Passes if the authenticated user has
32
- * at least one of the specified roles; otherwise calls `next(SentriError)` with
33
- * code `FORBIDDEN`.
34
- *
35
- * @example
36
- * router.delete('/posts/:id', auth.protect(), auth.authorize('admin'), handler);
37
- */
38
- authorize(...roles: TRole[]): RequestHandler;
39
- /**
40
- * Express middleware factory for resource-level permission checks.
41
- *
42
- * Must be used **after** `protect()`. Evaluates a check function against the
43
- * current request and calls `next(SentriError)` with `FORBIDDEN` if it returns `false`.
44
- *
45
- * Accepts either a bare check function or an options object with an optional
46
- * `roles` list whose members bypass the check entirely.
47
- *
48
- * @example
49
- * // User can only update their own profile
50
- * router.put('/users/:id',
51
- * auth.protect(),
52
- * auth.permit((request) => request.user!.id === request.params['id']),
53
- * handler,
54
- * );
55
- *
56
- * @example
57
- * // Admins bypass the check; others must own the resource
58
- * router.delete('/posts/:id',
59
- * auth.protect(),
60
- * auth.permit({
61
- * roles: ['admin'],
62
- * check: async (request) => {
63
- * const post = await db.post.findUnique({ where: { id: request.params['id'] } });
64
- * return post?.authorId === request.user!.id;
65
- * },
66
- * }),
67
- * handler,
68
- * );
69
- */
70
- permit(check: PermitCheck): RequestHandler;
71
- permit(options: PermitOptions<TRole>): RequestHandler;
72
- /** Hash a plain-text password using the configured `saltRounds`. */
73
- hashPassword(plain: string): Promise<string>;
74
- /** Compare a plain-text password against a stored bcrypt hash. */
75
- verifyPassword(plain: string, hash: string): Promise<boolean>;
76
- /** Sign an access token for the given user payload. */
77
- signAccessToken(payload: AuthUser<TRole>): string;
78
- /** Sign a refresh token bound to a session ID. */
79
- signRefreshToken(sessionId: string): string;
80
- /** Verify and decode an access token. Throws `SentriError` if invalid or expired. */
81
- verifyAccessToken(token: string): AuthUser<TRole>;
82
- /** Verify and decode a refresh token. Throws `SentriError` if invalid or expired. */
83
- verifyRefreshToken(token: string): {
84
- sessionId: string;
85
- };
86
- /**
87
- * Returns a pre-built Express Router with all standard auth endpoints mounted.
88
- *
89
- * Endpoints:
90
- * - `POST /register` — register a new user. Requires `X-Api-Key` header when `config.apiKey` is set.
91
- * - `POST /login` — authenticate, sets refresh token cookie, returns `{ accessToken, user }`
92
- * - `POST /refresh` — rotate refresh token, returns new `{ accessToken }`
93
- * - `POST /logout` — delete the current session; the bound access token is immediately rejected by `protect()`
94
- * - `POST /logout-all` — delete all sessions for the user (requires valid access token)
95
- * - `GET /me` — return the authenticated user
96
- * - `POST /users/:userId/roles` — assign roles (requires admin)
97
- *
98
- * Requires `express.json()` before the router.
99
- *
100
- * @example
101
- * app.use(express.json());
102
- * app.use('/auth', auth.router());
103
- */
104
- router(): Router;
105
- /**
106
- * Returns an Express error-handling middleware that formats every `SentriError`
107
- * (and any subclass) into the standard sentri response envelope:
108
- *
109
- * ```json
110
- * { "error": true, "statusCode": 401, "code": "UNAUTHORIZED", "message": "...", "data": null }
111
- * ```
112
- *
113
- * Mount it **after all your routes** so it acts as the global catch-all for
114
- * both sentri errors and your own `SentriError` subclasses.
115
- *
116
- * @example
117
- * import { SentriError } from 'sentri';
118
- *
119
- * // Define app-specific errors by extending SentriError
120
- * class NotFoundError extends SentriError {
121
- * constructor(resource: string) {
122
- * super('NOT_FOUND', `${resource} not found`, 404);
123
- * }
124
- * }
125
- *
126
- * app.use('/auth', auth.router());
127
- * app.use('/api', apiRouter);
128
- *
129
- * // Catches errors from sentri AND your own subclasses
130
- * app.use(auth.errorHandler());
131
- *
132
- * @example
133
- * // With optional unhandled-error logger
134
- * app.use(auth.errorHandler({
135
- * onUnhandled: (err) => logger.error('Unexpected error', { err }),
136
- * }));
137
- */
138
- errorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
139
- }
140
- /**
141
- * Create a fully configured auth client for your application.
142
- *
143
- * Pass your config once here and use the returned client everywhere — it
144
- * binds all library functions to your settings so you never need to pass
145
- * config manually.
146
- *
147
- * The generic parameter `TRole` is inferred automatically from `validRoles`
148
- * when you use `as const`:
149
- *
150
- * @example
151
- * export const auth = createAuth({
152
- * secret: process.env.JWT_SECRET!,
153
- * validRoles: ['user', 'admin', 'moderator'] as const,
154
- * adapter: myAdapter,
155
- * });
156
- *
157
- * // auth.authorize('admin') is type-safe — 'superuser' would be a compile error.
158
- */
159
- export declare function createAuth<TRole extends string = string>(config: AuthConfig<TRole>): AuthClient<TRole>;
160
- //# sourceMappingURL=client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACvD;;;;;;;;;;;OAWG;IACH,OAAO,IAAI,cAAc,CAAC;IAE1B;;;;;;;;;OASG;IACH,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC;IAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,CAAC;IAC3C,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC;IAEtD,oEAAoE;IACpE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7C,kEAAkE;IAClE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,uDAAuD;IACvD,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;IAElD,kDAAkD;IAClD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5C,qFAAqF;IACrF,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,qFAAqF;IACrF,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzD;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,IAAI,MAAM,CAAC;IAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,YAAY,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EACtD,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GACxB,UAAU,CAAC,KAAK,CAAC,CAiBnB"}
package/dist/client.js DELETED
@@ -1,45 +0,0 @@
1
- import { hashPassword, verifyPassword } from './libs/hash.js';
2
- import { signAccessToken, signRefreshToken, verifyAccessToken, verifyRefreshToken } from './libs/token.js';
3
- import { resolveConfig, validateConfig } from './libs/config.js';
4
- import { protect } from './middleware/protect.js';
5
- import { authorize } from './middleware/authorize.js';
6
- import { permit } from './middleware/permit.js';
7
- import { createAuthRouter } from './middleware/router.js';
8
- import { createErrorHandler } from './middleware/errorHandler.js';
9
- /**
10
- * Create a fully configured auth client for your application.
11
- *
12
- * Pass your config once here and use the returned client everywhere — it
13
- * binds all library functions to your settings so you never need to pass
14
- * config manually.
15
- *
16
- * The generic parameter `TRole` is inferred automatically from `validRoles`
17
- * when you use `as const`:
18
- *
19
- * @example
20
- * export const auth = createAuth({
21
- * secret: process.env.JWT_SECRET!,
22
- * validRoles: ['user', 'admin', 'moderator'] as const,
23
- * adapter: myAdapter,
24
- * });
25
- *
26
- * // auth.authorize('admin') is type-safe — 'superuser' would be a compile error.
27
- */
28
- export function createAuth(config) {
29
- validateConfig(config);
30
- const resolved = resolveConfig(config);
31
- return {
32
- protect: () => protect(config),
33
- authorize: (...roles) => authorize(...roles),
34
- permit: (optionsOrCheck) => permit(optionsOrCheck),
35
- hashPassword: (plain) => hashPassword(plain, resolved.saltRounds),
36
- verifyPassword: (plain, hash) => verifyPassword(plain, hash),
37
- signAccessToken: (payload) => signAccessToken(payload, config),
38
- signRefreshToken: (sessionId) => signRefreshToken(sessionId, config),
39
- verifyAccessToken: (token) => verifyAccessToken(token, config),
40
- verifyRefreshToken: (token) => verifyRefreshToken(token, config),
41
- router: () => createAuthRouter(config),
42
- errorHandler: (options) => createErrorHandler(options),
43
- };
44
- }
45
- //# sourceMappingURL=client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAsJlE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,UAAU,CACxB,MAAyB;IAEzB,cAAc,CAAC,MAAoB,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAoB,CAAC,CAAC;IAErD,OAAO;QACL,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAoB,CAAC;QAC5C,SAAS,EAAE,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;QAC5C,MAAM,EAAE,CAAC,cAAkD,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC;QACtF,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC;QACjE,cAAc,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC;QAC5D,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAmB,EAAE,MAAoB,CAAC;QACxF,gBAAgB,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAoB,CAAC;QAClF,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAoB,CAAoB;QAC/F,kBAAkB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAoB,CAAC;QAC9E,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACtC,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -1,99 +0,0 @@
1
- /**
2
- * Discriminant codes for built-in {@link SentriError} instances.
3
- *
4
- * - `INVALID_CREDENTIALS` — identifier or password did not match (intentionally vague to prevent user enumeration)
5
- * - `USER_NOT_FOUND` — an operation required a user that does not exist
6
- * - `USER_ALREADY_EXISTS` — registration was attempted with an identifier already in the database
7
- * - `TOKEN_EXPIRED` — the JWT was valid but its `exp` claim is in the past
8
- * - `TOKEN_INVALID` — the JWT could not be verified (bad signature, malformed, wrong type)
9
- * - `FORBIDDEN` — the user is authenticated but lacks the required role
10
- * - `UNAUTHORIZED` — no valid access token was present on the request, or the session was revoked
11
- * - `INVALID_ROLE` — a role name was used that is not in `validRoles`
12
- * - `VALIDATION_ERROR` — a required field was missing or had an invalid value
13
- * - `CONFIGURATION_ERROR` — `createAuth` was called with an invalid configuration
14
- *
15
- * When you extend {@link SentriError} for your own error types you can use any
16
- * string as `code` — it does not need to be one of these built-in values.
17
- */
18
- export type SentriErrorCode = 'INVALID_CREDENTIALS' | 'USER_NOT_FOUND' | 'USER_ALREADY_EXISTS' | 'TOKEN_EXPIRED' | 'TOKEN_INVALID' | 'FORBIDDEN' | 'UNAUTHORIZED' | 'INVALID_ROLE' | 'VALIDATION_ERROR' | 'CONFIGURATION_ERROR';
19
- /**
20
- * Default HTTP status codes for built-in error codes.
21
- * Custom codes that are not in this map default to 500.
22
- *
23
- * @internal
24
- */
25
- export declare const AUTH_ERROR_STATUS: Record<string, number>;
26
- /**
27
- * Base error class for all authentication and authorization failures in sentri.
28
- *
29
- * Every error thrown by sentri is an instance of `SentriError`. The `code`
30
- * property is a machine-readable string that lets you distinguish error
31
- * types without string-matching on the message. Built-in codes are listed
32
- * in {@link SentriErrorCode}; custom subclasses may use any string.
33
- *
34
- * The `statusCode` property holds the HTTP status that the built-in router
35
- * and `auth.errorHandler()` will use in the response. For built-in codes
36
- * it is derived automatically. Pass it explicitly when subclassing with a
37
- * custom code.
38
- *
39
- * ---
40
- *
41
- * **Extending SentriError**
42
- *
43
- * You can create application-specific error classes by extending `SentriError`.
44
- * Any subclass will be caught automatically by `auth.errorHandler()` because
45
- * `instanceof SentriError` is `true` for all subclasses.
46
- *
47
- * ```typescript
48
- * import { SentriError } from 'sentri';
49
- *
50
- * // Domain error with a custom code and explicit HTTP status
51
- * export class PaymentError extends SentriError {
52
- * constructor(message: string) {
53
- * super('PAYMENT_FAILED', message, 402);
54
- * }
55
- * }
56
- *
57
- * // Throw it anywhere in your routes — auth.errorHandler() catches it
58
- * router.post('/checkout', auth.protect(), async (req, res) => {
59
- * const ok = await chargeCard(req.body.cardToken);
60
- * if (!ok) throw new PaymentError('Card declined');
61
- * res.json({ success: true });
62
- * });
63
- * ```
64
- *
65
- * ---
66
- *
67
- * **Error handling in custom routes**
68
- *
69
- * ```typescript
70
- * app.use('/auth', auth.router());
71
- * app.use('/api', apiRouter);
72
- *
73
- * // Mount after all routes — catches SentriError from sentri AND your subclasses
74
- * app.use(auth.errorHandler());
75
- * ```
76
- */
77
- export declare class SentriError extends Error {
78
- /**
79
- * Machine-readable error code.
80
- * Built-in codes are defined by {@link SentriErrorCode}.
81
- * Custom subclasses may use any string.
82
- */
83
- readonly code: string;
84
- /**
85
- * HTTP status code associated with this error.
86
- * Derived automatically for built-in codes; pass it explicitly when
87
- * subclassing with a custom `code`.
88
- */
89
- readonly statusCode: number;
90
- /**
91
- * @param code - Machine-readable error code. Use a built-in {@link SentriErrorCode}
92
- * or any string for custom subclasses.
93
- * @param message - Human-readable description of the error.
94
- * @param statusCode - HTTP status to use in the response. For built-in codes
95
- * this is derived automatically; for custom codes it defaults to `500`.
96
- */
97
- constructor(code: SentriErrorCode | (string & {}), message: string, statusCode?: number);
98
- }
99
- //# sourceMappingURL=AuthError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AuthError.d.ts","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,GACf,eAAe,GACf,WAAW,GACX,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAWpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC;;;;OAIG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B;;;;OAIG;IACH,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC;;;;;;OAMG;gBAED,IAAI,EAAE,eAAe,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,EACrC,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM;CAOtB"}