@otterhttp/csrf-csrf
Advanced tools
Comparing version 3.0.0 to 3.1.0
@@ -20,8 +20,8 @@ import { IncomingMessage, ServerResponse } from 'node:http'; | ||
type ResolvedCSRFCookieOptions = SerializeOptions & Required<ExtraCookieOptions>; | ||
type TokenRetriever = (req: CSRFRequest) => string | null | undefined; | ||
type CsrfSecretRetriever = (req?: CSRFRequest) => string | Array<string>; | ||
type doubleCsrfProtection = (req: CSRFRequest, res: CSRFResponse, next: NextFunction) => void; | ||
type TokenRetriever = (req: CSRFRequest) => string | null | undefined | Promise<string | null | undefined>; | ||
type CsrfSecretRetriever = (req?: CSRFRequest) => string | Array<string> | Promise<string | Array<string>>; | ||
type doubleCsrfProtection = (req: CSRFRequest, res: CSRFResponse, next: NextFunction) => Promise<void>; | ||
type RequestMethod = "GET" | "HEAD" | "PATCH" | "PUT" | "POST" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE"; | ||
type CsrfIgnoredMethods = Array<RequestMethod>; | ||
type CsrfRequestValidator = (req: CSRFRequest) => boolean; | ||
type CsrfRequestValidator = (req: CSRFRequest) => Promise<boolean>; | ||
type CsrfTokenAndHashPairValidator = (req: CSRFRequest, { incomingHash, incomingToken, possibleSecrets, }: { | ||
@@ -31,5 +31,4 @@ incomingHash: unknown; | ||
possibleSecrets: Array<string>; | ||
}) => boolean; | ||
type CsrfCookieSetter = (res: CSRFResponse, name: string, value: string, options: CSRFCookieOptions) => void; | ||
type CsrfTokenCreator = (req: CSRFRequest, res: CSRFResponse, options?: GenerateCsrfTokenOptions) => string; | ||
}) => Promise<boolean>; | ||
type CsrfTokenCreator = (req: CSRFRequest, res: CSRFResponse, options?: GenerateCsrfTokenOptions) => Promise<string>; | ||
type CsrfErrorConfig = { | ||
@@ -49,3 +48,3 @@ statusCode: keyof typeof statusMessages; | ||
getSecret: CsrfSecretRetriever; | ||
getSessionIdentifier: (req: CSRFRequest) => string; | ||
getSessionIdentifier: (req: CSRFRequest) => string | Promise<string>; | ||
cookieOptions?: CSRFCookieOptions; | ||
@@ -68,2 +67,2 @@ delimiter?: string; | ||
export { type CSRFCookieOptions, type CSRFRequest, type CSRFResponse, type CsrfCookieSetter, type CsrfErrorConfig, type CsrfErrorConfigOptions, type CsrfIgnoredMethods, type CsrfRequestValidator, type CsrfSecretRetriever, type CsrfTokenAndHashPairValidator, type CsrfTokenCreator, type DoubleCsrfConfig, type DoubleCsrfUtilities, type GenerateCsrfTokenConfig, type GenerateCsrfTokenOptions, type RequestMethod, type ResolvedCSRFCookieOptions, type TokenRetriever, doubleCsrf, type doubleCsrfProtection }; | ||
export { type CSRFCookieOptions, type CSRFRequest, type CSRFResponse, type CsrfErrorConfig, type CsrfErrorConfigOptions, type CsrfIgnoredMethods, type CsrfRequestValidator, type CsrfSecretRetriever, type CsrfTokenAndHashPairValidator, type CsrfTokenCreator, type DoubleCsrfConfig, type DoubleCsrfUtilities, type GenerateCsrfTokenConfig, type GenerateCsrfTokenOptions, type RequestMethod, type ResolvedCSRFCookieOptions, type TokenRetriever, doubleCsrf, type doubleCsrfProtection }; |
@@ -38,4 +38,4 @@ // src/index.ts | ||
}); | ||
const generateTokenAndHash = (req, { overwrite, validateOnReuse }) => { | ||
const getSecretResult = getSecret(req); | ||
const generateTokenAndHash = async (req, { overwrite, validateOnReuse }) => { | ||
const getSecretResult = await getSecret(req); | ||
const possibleSecrets = Array.isArray(getSecretResult) ? getSecretResult : [getSecretResult]; | ||
@@ -45,3 +45,3 @@ const csrfCookie = getCsrfCookieFromRequest(req); | ||
const [csrfToken2, csrfTokenHash2] = csrfCookie.value.split(delimiter); | ||
if (validateTokenAndHashPair(req, { | ||
if (await validateTokenAndHashPair(req, { | ||
incomingToken: csrfToken2, | ||
@@ -59,7 +59,7 @@ incomingHash: csrfTokenHash2, | ||
const secret = possibleSecrets[0]; | ||
const csrfTokenHash = createHmac(hmacAlgorithm, secret).update(`${getSessionIdentifier(req)}${csrfToken}`).digest("hex"); | ||
const csrfTokenHash = createHmac(hmacAlgorithm, secret).update(`${await getSessionIdentifier(req)}${csrfToken}`).digest("hex"); | ||
return { csrfToken, csrfTokenHash }; | ||
}; | ||
const generateToken = (req, res, { cookieOptions: cookieOptions2 = defaultCookieOptions, overwrite = false, validateOnReuse = true } = {}) => { | ||
const { csrfToken, csrfTokenHash } = generateTokenAndHash(req, { | ||
const generateToken = async (req, res, { cookieOptions: cookieOptions2 = defaultCookieOptions, overwrite = false, validateOnReuse = true } = {}) => { | ||
const { csrfToken, csrfTokenHash } = await generateTokenAndHash(req, { | ||
overwrite, | ||
@@ -76,6 +76,6 @@ validateOnReuse | ||
}; | ||
const validateTokenAndHashPair = (req, { incomingHash, incomingToken, possibleSecrets }) => { | ||
const validateTokenAndHashPair = async (req, { incomingHash, incomingToken, possibleSecrets }) => { | ||
if (typeof incomingToken !== "string" || typeof incomingHash !== "string") return false; | ||
for (const secret of possibleSecrets) { | ||
const expectedHash = createHmac(hmacAlgorithm, secret).update(`${getSessionIdentifier(req)}${incomingToken}`).digest("hex"); | ||
const expectedHash = createHmac(hmacAlgorithm, secret).update(`${await getSessionIdentifier(req)}${incomingToken}`).digest("hex"); | ||
if (incomingHash === expectedHash) return true; | ||
@@ -85,10 +85,10 @@ } | ||
}; | ||
const validateRequest = (req) => { | ||
const validateRequest = async (req) => { | ||
const csrfCookie = getCsrfCookieFromRequest(req); | ||
if (typeof csrfCookie !== "object") return false; | ||
const [csrfTokenFromCookie, csrfTokenHash] = csrfCookie.value.split(delimiter); | ||
const csrfTokenFromRequest = getTokenFromRequest(req); | ||
const getSecretResult = getSecret(req); | ||
const csrfTokenFromRequest = await getTokenFromRequest(req); | ||
const getSecretResult = await getSecret(req); | ||
const possibleSecrets = Array.isArray(getSecretResult) ? getSecretResult : [getSecretResult]; | ||
return csrfTokenFromCookie === csrfTokenFromRequest && validateTokenAndHashPair(req, { | ||
return csrfTokenFromCookie === csrfTokenFromRequest && await validateTokenAndHashPair(req, { | ||
incomingToken: csrfTokenFromRequest, | ||
@@ -99,10 +99,11 @@ incomingHash: csrfTokenHash, | ||
}; | ||
const doubleCsrfProtection = (req, res, next) => { | ||
const doubleCsrfProtection = async (req, res, next) => { | ||
if (ignoredMethodsSet.has(req.method)) { | ||
next(); | ||
} else if (validateRequest(req)) { | ||
next(); | ||
} else { | ||
return; | ||
} | ||
if (!await validateRequest(req)) { | ||
throw invalidCsrfTokenError; | ||
} | ||
next(); | ||
}; | ||
@@ -109,0 +110,0 @@ return { |
@@ -64,3 +64,3 @@ { | ||
"packageManager": "pnpm@9.5.0", | ||
"version": "3.0.0" | ||
"version": "3.1.0" | ||
} |
# @otterjs/csrf-csrf | ||
**Double-submit cookie pattern CSRF protection middleware for modern Node.js.** | ||
**Double-submit cookie pattern CSRF protection middleware for Otterhttp.** | ||
@@ -43,6 +43,3 @@ > :pushpin: This project is a fork of [Psifi-Solutions/csrf-csrf](https://github.com/Psifi-Solutions/csrf-csrf). | ||
You will need to be using [tinyhttp/cookie-parser](https://github.com/tinyhttp/cookie-parser) whose middleware | ||
should be registered before `csrf-csrf`. | ||
In case you want to use signed CSRF cookies, you **will need to** provide `cookie-parser` with a unique secret | ||
for cookie signing. | ||
In case you want to use signed CSRF cookies, you **will need to** configure otterhttp for that. | ||
This utility will (1) set a cookie containing both the csrf token and a hash of the csrf token, and | ||
@@ -53,3 +50,3 @@ (2) provide the plain csrf token. | ||
``` | ||
npm install @tinyhttp/cookie-parser @otterjs/csrf-csrf | ||
npm install @otterhttp/csrf-csrf | ||
``` | ||
@@ -93,6 +90,4 @@ | ||
```js | ||
// Make sure your session middleware is registered before these | ||
express.use(session); | ||
express.get("/csrf-token", myRoute); | ||
express.use(doubleCsrfProtection); | ||
app.get("/csrf-token", myRoute); | ||
app.use(doubleCsrfProtection); | ||
// Any non GET routes registered after this will be considered "protected" | ||
@@ -185,3 +180,2 @@ ``` | ||
secure?: boolean; | ||
signed?: boolean; | ||
} | ||
@@ -201,3 +195,2 @@ ``` | ||
secure: true, | ||
signed: false, | ||
} | ||
@@ -204,0 +197,0 @@ ``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
311
40536
427