express-tailscale-auth
Version:
Express middleware for Tailscale authentication
15 lines (10 loc) • 2.86 kB
JavaScript
;var P=require('picomatch'),R=require('proxy-addr'),tailscaleLocalApi=require('tailscale-local-api'),zod=require('zod');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var P__default=/*#__PURE__*/_interopDefault(P);var R__default=/*#__PURE__*/_interopDefault(R);var d=zod.z.enum(["GET","POST","PUT","DELETE","*"]),m=zod.z.object({routes:zod.z.array(zod.z.object({route:zod.z.string(),methods:zod.z.array(d)})).optional()});var b=s=>{let n=s.app.get("trust proxy");return !!(s.headers["x-forwarded-for"]||s.headers["x-forwarded-host"]||s.headers["x-forwarded-proto"])&&(n===false||n===void 0)?(console.warn(`
\u26A0\uFE0F Tailscale Auth Middleware Warning \u26A0\uFE0F
Detected X-Forwarded-* headers but Express 'trust proxy' is not configured.
This may result in incorrect IP address detection and authentication failures.
To fix this, add one of the following to your Express app:
// For most reverse proxy setups (recommended)
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal'])
See: https://expressjs.com/en/guide/behind-proxies.html
`),true):false};var x=false,O=(s={})=>{let n=new tailscaleLocalApi.TailscaleLocalApi(s),a=s.debug||false,g=e=>{a&&console.debug("all addrs of req",R.all(e)),!x&&b(e)&&(x=true);let t=e.app.get("trust proxy");if(t===false||t===void 0)return a&&console.debug("Not trusting reverse proxy, returning socket address",e.socket.remoteAddress),e.socket.remoteAddress;if(typeof t=="boolean"&&t===true)return R.all(e).at(-1);let c=R.compile(t),i=R__default.default(e,c);return a&&console.debug("trusting remote address",i),i};return async(e,t,c)=>{let i=g(e);if(!i){a&&console.debug("No client IP found",i),t.status(404).send(`Cannot ${e.method} ${e.path}`);return}let h=n.isInTailscaleIpRange(i);if(!h){a&&console.debug("Request IP not from Tailscale range",i),t.status(404).send(`Cannot ${e.method} ${e.path}`);return}let u;try{let o=await n.whoIs(h),f=s.capabilitiesNamespace?o.capMap[s.capabilitiesNamespace]?.[0]:{routes:[{route:"**",methods:["*"]}]},l=m.safeParse(f);if(u=l.data,e.tailscaleUser={...o.userProfile,capabilities:u??{}},!l.success){a&&console.debug("Couldn't find or parse capabilities",l.error),t.status(401).send(`Cannot ${e.method} ${e.path}`);return}}catch(o){console.error(o),t.status(401).send(`Cannot ${e.method} ${e.path}`);return}let C=e.originalUrl,p=d.safeParse(e.method);if(!p.success){a&&console.debug("Invalid method",p.error),t.status(401).send(`Cannot ${e.method} ${e.path}`);return}let T=p.data;if(!u?.routes?.some(o=>P__default.default(o.route)(C)?o.methods.includes("*")||o.methods.includes(T):false)){a&&console.debug("Requester has no capability for route",e.path,e.method),t.status(401).send(`Cannot ${e.method} ${e.path}`);return}c();}};exports.createTailscaleAuthMw=O;//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map