@dr.pogodin/csurf
Version:
CSRF token middleware for ExpressJS
1 lines • 15.1 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":["serialize","createError","sign","Tokens","verify","getCookieOptions","options","undefined","opts","key","path","value","Object","entries","defaultValue","req","body","_csrf","query","headers","getIgnoredMethods","methods","obj","method","toUpperCase","getSecretBag","sessionKey","cookie","cookieKey","signed","getSecret","bag","Error","setCookie","res","name","val","data","rawPrev","getHeader","prev","toString","header","Array","isArray","concat","setHeader","setSecret","secret","csrfSecret","verifyConfiguration","csurf","tokens","ignoreMethods","TypeError","ignoreMethod","next","token","csrfToken","sec","secretSync","create","code"],"sources":["../../src/index.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\nimport { type SerializeOptions, serialize } from 'cookie';\nimport createError from 'http-errors';\nimport { sign } from 'cookie-signature';\nimport Tokens, { type Options as TokensOptions, verify } from './tokens';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface Request {\n // The CSRF token generation routine, added by this library.\n csrfToken: () => string;\n\n // Cookie-signature secret, configured and set by \"cookie-parser\" middleware,\n // if that is configured to support signed cookies:\n // https://github.com/expressjs/cookie-parser\n secret?: string;\n }\n }\n}\n\n// TODO: This should come from a cookie library.\ntype CookieOptions = {\n domain?: string;\n httpOnly?: boolean;\n key: string;\n maxAge?: number;\n path: string;\n sameSite?: 'lax' | 'none' | 'strict' | true;\n secure?: boolean;\n signed?: boolean;\n};\n\nexport type Options = TokensOptions & {\n cookie?: true | CookieOptions;\n ignoreMethods?: string[];\n sessionKey?: string;\n value?: (req: Request) => string;\n};\n\n/**\n * Get options for cookie.\n *\n * @param {boolean|object} [options]\n */\nfunction getCookieOptions(\n options: boolean | Partial<CookieOptions> | undefined,\n): CookieOptions | undefined {\n if (options !== true && typeof options !== 'object') {\n return undefined;\n }\n\n const opts: CookieOptions = {\n key: '_csrf',\n path: '/',\n };\n\n if (typeof options === 'object') {\n for (const [key, value] of Object.entries(options)) {\n // TODO: It actually breaks one of existing tests, if we don't check\n // for it. Perhaps, we should correct typings, or do some other refactoring\n // to avoid this.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (value !== undefined) {\n (opts[key as keyof CookieOptions] as unknown) = value;\n }\n }\n }\n\n return opts;\n}\n\n/**\n * Default value function, checking the `req.body`\n * and `req.query` for the CSRF token.\n *\n * @param req\n * @return\n */\nfunction defaultValue(req: Request<unknown, unknown, undefined | {\n _csrf?: string;\n}, undefined | {\n _csrf?: string;\n}>): string {\n /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */\n // eslint-disable-next-line no-underscore-dangle\n return (req.body?._csrf || req.query?._csrf\n || req.headers['csrf-token']\n || req.headers['xsrf-token']\n || req.headers['x-csrf-token']\n || req.headers['x-xsrf-token']) as string;\n /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */\n}\n\n// TODO: Actually, we should type `methods` stricter, limiting it to the valid\n// method name literals.\n/**\n * Get a lookup of ignored methods.\n *\n * @param {array} methods\n * @returns {object}\n * @api private\n */\nfunction getIgnoredMethods(methods: string[]): Record<string, true> {\n const obj: Record<string, true> = {};\n\n for (const method of methods) {\n obj[method.toUpperCase()] = true;\n }\n\n return obj;\n}\n\ntype SecretBag = Record<string, string>;\n\n/**\n * Get the token secret bag from the request.\n *\n * @param {IncomingMessage} req\n * @param {String} sessionKey\n * @param {Object} [cookie]\n * @api private\n */\nfunction getSecretBag(\n req: Request,\n sessionKey: string,\n cookie: CookieOptions | undefined,\n): SecretBag | undefined {\n if (cookie) {\n // get secret from cookie\n const cookieKey = cookie.signed\n ? 'signedCookies'\n : 'cookies';\n\n return req[cookieKey] as SecretBag;\n }\n\n // TODO: A less forceful type casting would be nice to have here.\n // get secret from session\n return (req as unknown as Record<string, unknown>)[sessionKey] as SecretBag;\n}\n\n/**\n * Get the token secret from the request.\n *\n * @param {IncomingMessage} req\n * @param {String} sessionKey\n * @param {Object} [cookie]\n * @api private\n */\nfunction getSecret(\n req: Request,\n sessionKey: string,\n cookie: CookieOptions | undefined,\n) {\n // get the bag & key\n const bag = getSecretBag(req, sessionKey, cookie);\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n const key = cookie?.key || 'csrfSecret';\n\n if (!bag) {\n throw new Error('misconfigured csrf');\n }\n\n // return secret from bag\n return bag[key];\n}\n\n/**\n * Set a cookie on the HTTP response.\n *\n * @param {OutgoingMessage} res\n * @param {string} name\n * @param {string} val\n * @param {Object} [options]\n * @api private\n */\nfunction setCookie(\n res: Response,\n name: string,\n val: string,\n options: SerializeOptions,\n) {\n const data = serialize(name, val, options);\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n const rawPrev = res.getHeader('set-cookie') || [];\n const prev = typeof rawPrev === 'number' ? rawPrev.toString() : rawPrev;\n const header = Array.isArray(prev) ? prev.concat(data)\n : [prev, data];\n\n res.setHeader('set-cookie', header);\n}\n\n/**\n * Set the token secret on the request.\n *\n * @param {IncomingMessage} req\n * @param {OutgoingMessage} res\n * @param {string} sessionKey\n * @param {string} val\n * @param {Object} [cookie]\n * @api private\n */\nfunction setSecret(\n req: Request,\n res: Response,\n sessionKey: string,\n val: string,\n cookie?: CookieOptions,\n) {\n if (cookie) {\n // set secret on cookie\n let value = val;\n\n if (cookie.signed) {\n // NOTE: This one is not expected to be hit, as if the cookie signature\n // secret is not properly configured via \"cookie-parser\" middleware, that\n // is checked and throws earlier in the code.\n if (!req.secret) throw Error('Internal error');\n\n value = `s:${sign(val, req.secret)}`;\n }\n\n setCookie(res, cookie.key, value, cookie);\n } else {\n // set secret on session\n // TODO: Can we type it in a better way, to avoid such forced type-cast?\n // eslint-disable-next-line no-param-reassign\n (req as unknown as Record<string, { csrfSecret: string }>)[sessionKey]!\n .csrfSecret = val;\n }\n}\n\n/**\n * Verify the configuration against the request.\n * @private\n */\nfunction verifyConfiguration(\n req: Request,\n sessionKey: string,\n cookie: CookieOptions | undefined,\n): boolean {\n if (!getSecretBag(req, sessionKey, cookie)) {\n return false;\n }\n\n // NOTE: `req.secret` is the cookie signature secret, configured and set by\n // \"cookie-parser\" middleware: https://github.com/expressjs/cookie-parser\n if (cookie && cookie.signed && !req.secret) {\n return false;\n }\n\n return true;\n}\n\n/**\n * CSRF protection middleware.\n *\n * This middleware adds a `req.csrfToken()` function to make a token\n * which should be added to requests which mutate\n * state, within a hidden form field, query-string etc. This\n * token is validated against the visitor's session.\n *\n * @param {Object} options\n * @return {Function} middleware\n * @public\n */\nfunction csurf(options: Options = {}) {\n // get cookie options\n const cookie = getCookieOptions(options.cookie);\n\n // get session options\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n const sessionKey = options.sessionKey || 'session';\n\n // get value getter\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n const value = options.value || defaultValue;\n\n // token repo\n const tokens = new Tokens(options);\n\n // ignored methods\n const ignoreMethods = options.ignoreMethods ?? ['GET', 'HEAD', 'OPTIONS'];\n\n if (!Array.isArray(ignoreMethods)) {\n throw new TypeError('option ignoreMethods must be an array');\n }\n\n // generate lookup\n const ignoreMethod = getIgnoredMethods(ignoreMethods);\n\n return (req: Request, res: Response, next: NextFunction): void => {\n // validate the configuration against request\n if (!verifyConfiguration(req, sessionKey, cookie)) {\n next(new Error('misconfigured csrf'));\n return;\n }\n\n // get the secret from the request\n let secret = getSecret(req, sessionKey, cookie);\n let token: string;\n\n // lazy-load token getter\n // eslint-disable-next-line no-param-reassign\n req.csrfToken = () => {\n let sec = cookie ? secret : getSecret(req, sessionKey, cookie);\n\n // use cached token if secret has not changed\n if (token && sec === secret) {\n return token;\n }\n\n // generate & set new secret\n if (sec === undefined) {\n sec = tokens.secretSync();\n setSecret(req, res, sessionKey, sec, cookie);\n }\n\n // update changed secret\n secret = sec;\n\n // create new token\n token = tokens.create(secret);\n\n return token;\n };\n\n // generate & set secret\n if (!secret) {\n secret = tokens.secretSync();\n setSecret(req, res, sessionKey, secret, cookie);\n }\n\n // verify the incoming token\n if (!ignoreMethod[req.method] && !verify(secret, value(req))) {\n next(createError(403, 'invalid csrf token', {\n code: 'EBADCSRFTOKEN',\n }));\n return;\n }\n\n next();\n };\n}\n\nexport default csurf;\n"],"mappings":"AAEA,SAAgCA,SAAS,QAAQ,QAAQ;AACzD,OAAOC,WAAW,MAAM,aAAa;AACrC,SAASC,IAAI,QAAQ,kBAAkB;AAAC,OACjCC,MAAM,IAAmCC,MAAM,wBAkBtD;AAmBA;AACA;AACA;AACA;AACA;AACA,SAASC,gBAAgBA,CACvBC,OAAqD,EAC1B;EAC3B,IAAIA,OAAO,KAAK,IAAI,IAAI,OAAOA,OAAO,KAAK,QAAQ,EAAE;IACnD,OAAOC,SAAS;EAClB;EAEA,MAAMC,IAAmB,GAAG;IAC1BC,GAAG,EAAE,OAAO;IACZC,IAAI,EAAE;EACR,CAAC;EAED,IAAI,OAAOJ,OAAO,KAAK,QAAQ,EAAE;IAC/B,KAAK,MAAM,CAACG,GAAG,EAAEE,KAAK,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACP,OAAO,CAAC,EAAE;MAClD;MACA;MACA;MACA;MACA,IAAIK,KAAK,KAAKJ,SAAS,EAAE;QACtBC,IAAI,CAACC,GAAG,CAAwB,GAAeE,KAAK;MACvD;IACF;EACF;EAEA,OAAOH,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASM,YAAYA,CAACC,GAIpB,EAAU;EACV;EACA;EACA,OAAQA,GAAG,CAACC,IAAI,EAAEC,KAAK,IAAIF,GAAG,CAACG,KAAK,EAAED,KAAK,IACtCF,GAAG,CAACI,OAAO,CAAC,YAAY,CAAC,IACzBJ,GAAG,CAACI,OAAO,CAAC,YAAY,CAAC,IACzBJ,GAAG,CAACI,OAAO,CAAC,cAAc,CAAC,IAC3BJ,GAAG,CAACI,OAAO,CAAC,cAAc,CAAC;EAChC;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,iBAAiBA,CAACC,OAAiB,EAAwB;EAClE,MAAMC,GAAyB,GAAG,CAAC,CAAC;EAEpC,KAAK,MAAMC,MAAM,IAAIF,OAAO,EAAE;IAC5BC,GAAG,CAACC,MAAM,CAACC,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI;EAClC;EAEA,OAAOF,GAAG;AACZ;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASG,YAAYA,CACnBV,GAAY,EACZW,UAAkB,EAClBC,MAAiC,EACV;EACvB,IAAIA,MAAM,EAAE;IACV;IACA,MAAMC,SAAS,GAAGD,MAAM,CAACE,MAAM,GAC3B,eAAe,GACf,SAAS;IAEb,OAAOd,GAAG,CAACa,SAAS,CAAC;EACvB;;EAEA;EACA;EACA,OAAQb,GAAG,CAAwCW,UAAU,CAAC;AAChE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASI,SAASA,CAChBf,GAAY,EACZW,UAAkB,EAClBC,MAAiC,EACjC;EACA;EACA,MAAMI,GAAG,GAAGN,YAAY,CAACV,GAAG,EAAEW,UAAU,EAAEC,MAAM,CAAC;EACjD;EACA,MAAMlB,GAAG,GAAGkB,MAAM,EAAElB,GAAG,IAAI,YAAY;EAEvC,IAAI,CAACsB,GAAG,EAAE;IACR,MAAM,IAAIC,KAAK,CAAC,oBAAoB,CAAC;EACvC;;EAEA;EACA,OAAOD,GAAG,CAACtB,GAAG,CAAC;AACjB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASwB,SAASA,CAChBC,GAAa,EACbC,IAAY,EACZC,GAAW,EACX9B,OAAyB,EACzB;EACA,MAAM+B,IAAI,GAAGrC,SAAS,CAACmC,IAAI,EAAEC,GAAG,EAAE9B,OAAO,CAAC;EAC1C;EACA,MAAMgC,OAAO,GAAGJ,GAAG,CAACK,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE;EACjD,MAAMC,IAAI,GAAG,OAAOF,OAAO,KAAK,QAAQ,GAAGA,OAAO,CAACG,QAAQ,CAAC,CAAC,GAAGH,OAAO;EACvE,MAAMI,MAAM,GAAGC,KAAK,CAACC,OAAO,CAACJ,IAAI,CAAC,GAAGA,IAAI,CAACK,MAAM,CAACR,IAAI,CAAC,GAClD,CAACG,IAAI,EAAEH,IAAI,CAAC;EAEhBH,GAAG,CAACY,SAAS,CAAC,YAAY,EAAEJ,MAAM,CAAC;AACrC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASK,SAASA,CAChBhC,GAAY,EACZmB,GAAa,EACbR,UAAkB,EAClBU,GAAW,EACXT,MAAsB,EACtB;EACA,IAAIA,MAAM,EAAE;IACV;IACA,IAAIhB,KAAK,GAAGyB,GAAG;IAEf,IAAIT,MAAM,CAACE,MAAM,EAAE;MACjB;MACA;MACA;MACA,IAAI,CAACd,GAAG,CAACiC,MAAM,EAAE,MAAMhB,KAAK,CAAC,gBAAgB,CAAC;MAE9CrB,KAAK,GAAG,KAAKT,IAAI,CAACkC,GAAG,EAAErB,GAAG,CAACiC,MAAM,CAAC,EAAE;IACtC;IAEAf,SAAS,CAACC,GAAG,EAAEP,MAAM,CAAClB,GAAG,EAAEE,KAAK,EAAEgB,MAAM,CAAC;EAC3C,CAAC,MAAM;IACL;IACA;IACA;IACCZ,GAAG,CAAuDW,UAAU,CAAC,CACnEuB,UAAU,GAAGb,GAAG;EACrB;AACF;;AAEA;AACA;AACA;AACA;AACA,SAASc,mBAAmBA,CAC1BnC,GAAY,EACZW,UAAkB,EAClBC,MAAiC,EACxB;EACT,IAAI,CAACF,YAAY,CAACV,GAAG,EAAEW,UAAU,EAAEC,MAAM,CAAC,EAAE;IAC1C,OAAO,KAAK;EACd;;EAEA;EACA;EACA,IAAIA,MAAM,IAAIA,MAAM,CAACE,MAAM,IAAI,CAACd,GAAG,CAACiC,MAAM,EAAE;IAC1C,OAAO,KAAK;EACd;EAEA,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASG,KAAKA,CAAC7C,OAAgB,GAAG,CAAC,CAAC,EAAE;EACpC;EACA,MAAMqB,MAAM,GAAGtB,gBAAgB,CAACC,OAAO,CAACqB,MAAM,CAAC;;EAE/C;EACA;EACA,MAAMD,UAAU,GAAGpB,OAAO,CAACoB,UAAU,IAAI,SAAS;;EAElD;EACA;EACA,MAAMf,KAAK,GAAGL,OAAO,CAACK,KAAK,IAAIG,YAAY;;EAE3C;EACA,MAAMsC,MAAM,GAAG,IAAIjD,MAAM,CAACG,OAAO,CAAC;;EAElC;EACA,MAAM+C,aAAa,GAAG/C,OAAO,CAAC+C,aAAa,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC;EAEzE,IAAI,CAACV,KAAK,CAACC,OAAO,CAACS,aAAa,CAAC,EAAE;IACjC,MAAM,IAAIC,SAAS,CAAC,uCAAuC,CAAC;EAC9D;;EAEA;EACA,MAAMC,YAAY,GAAGnC,iBAAiB,CAACiC,aAAa,CAAC;EAErD,OAAO,CAACtC,GAAY,EAAEmB,GAAa,EAAEsB,IAAkB,KAAW;IAChE;IACA,IAAI,CAACN,mBAAmB,CAACnC,GAAG,EAAEW,UAAU,EAAEC,MAAM,CAAC,EAAE;MACjD6B,IAAI,CAAC,IAAIxB,KAAK,CAAC,oBAAoB,CAAC,CAAC;MACrC;IACF;;IAEA;IACA,IAAIgB,MAAM,GAAGlB,SAAS,CAACf,GAAG,EAAEW,UAAU,EAAEC,MAAM,CAAC;IAC/C,IAAI8B,KAAa;;IAEjB;IACA;IACA1C,GAAG,CAAC2C,SAAS,GAAG,MAAM;MACpB,IAAIC,GAAG,GAAGhC,MAAM,GAAGqB,MAAM,GAAGlB,SAAS,CAACf,GAAG,EAAEW,UAAU,EAAEC,MAAM,CAAC;;MAE9D;MACA,IAAI8B,KAAK,IAAIE,GAAG,KAAKX,MAAM,EAAE;QAC3B,OAAOS,KAAK;MACd;;MAEA;MACA,IAAIE,GAAG,KAAKpD,SAAS,EAAE;QACrBoD,GAAG,GAAGP,MAAM,CAACQ,UAAU,CAAC,CAAC;QACzBb,SAAS,CAAChC,GAAG,EAAEmB,GAAG,EAAER,UAAU,EAAEiC,GAAG,EAAEhC,MAAM,CAAC;MAC9C;;MAEA;MACAqB,MAAM,GAAGW,GAAG;;MAEZ;MACAF,KAAK,GAAGL,MAAM,CAACS,MAAM,CAACb,MAAM,CAAC;MAE7B,OAAOS,KAAK;IACd,CAAC;;IAED;IACA,IAAI,CAACT,MAAM,EAAE;MACXA,MAAM,GAAGI,MAAM,CAACQ,UAAU,CAAC,CAAC;MAC5Bb,SAAS,CAAChC,GAAG,EAAEmB,GAAG,EAAER,UAAU,EAAEsB,MAAM,EAAErB,MAAM,CAAC;IACjD;;IAEA;IACA,IAAI,CAAC4B,YAAY,CAACxC,GAAG,CAACQ,MAAM,CAAC,IAAI,CAACnB,MAAM,CAAC4C,MAAM,EAAErC,KAAK,CAACI,GAAG,CAAC,CAAC,EAAE;MAC5DyC,IAAI,CAACvD,WAAW,CAAC,GAAG,EAAE,oBAAoB,EAAE;QAC1C6D,IAAI,EAAE;MACR,CAAC,CAAC,CAAC;MACH;IACF;IAEAN,IAAI,CAAC,CAAC;EACR,CAAC;AACH;AAEA,eAAeL,KAAK","ignoreList":[]}