function validateQueryStringAndCookies()

in src/lambda-edge/parse-auth/index.ts [194:290]


function validateQueryStringAndCookies(props: {
  querystring: string;
  cookies: ReturnType<typeof extractAndParseCookies>;
}) {
  // Check if Cognito threw an Error. Cognito puts the error in the query string
  const {
    code,
    state,
    error: cognitoError,
    error_description,
  } = parseQueryString(props.querystring);
  if (cognitoError) {
    throw new Error(`[Cognito] ${cognitoError}: ${error_description}`);
  }

  // The querystring needs to have an authorization code and state
  if (
    !code ||
    !state ||
    typeof code !== "string" ||
    typeof state !== "string"
  ) {
    throw new Error(
      [
        'Invalid query string. Your query string does not include parameters "state" and "code".',
        "This can happen if your authentication attempt did not originate from this site.",
      ].join(" ")
    );
  }

  // The querystring state should be a JSON string
  let parsedState: { nonce?: string; requestedUri?: string };
  try {
    parsedState = JSON.parse(
      Buffer.from(urlSafe.parse(state), "base64").toString()
    );
  } catch {
    throw new Error(
      'Invalid query string. Your query string does not include a valid "state" parameter'
    );
  }

  // The querystring state needs to include the right pieces
  if (!parsedState.requestedUri || !parsedState.nonce) {
    throw new Error(
      'Invalid query string. Your query string does not include a valid "state" parameter'
    );
  }

  // The querystring state needs to correlate to the cookies
  const { nonce: originalNonce, pkce, nonceHmac } = props.cookies;
  if (
    !parsedState.nonce ||
    !originalNonce ||
    parsedState.nonce !== originalNonce
  ) {
    if (!originalNonce) {
      throw new RequiresConfirmationError(
        "Your browser didn't send the nonce cookie along, but it is required for security (prevent CSRF)."
      );
    }
    throw new RequiresConfirmationError(
      "Nonce mismatch. This can happen if you start multiple authentication attempts in parallel (e.g. in separate tabs)"
    );
  }
  if (!pkce) {
    throw new Error(
      "Your browser didn't send the pkce cookie along, but it is required for security (prevent CSRF)."
    );
  }

  // Nonce should not be too old
  const nonceTimestamp = parseInt(
    parsedState.nonce.slice(0, parsedState.nonce.indexOf("T"))
  );
  if (timestampInSeconds() - nonceTimestamp > CONFIG.nonceMaxAge) {
    throw new RequiresConfirmationError(
      `Nonce is too old (nonce is from ${new Date(
        nonceTimestamp * 1000
      ).toISOString()})`
    );
  }

  // Nonce should have the right signature: proving we were the ones generating it (and e.g. not malicious JS on a subdomain)
  const calculatedHmac = sign(
    parsedState.nonce,
    CONFIG.nonceSigningSecret,
    CONFIG.nonceLength
  );
  if (calculatedHmac !== nonceHmac) {
    throw new RequiresConfirmationError(
      `Nonce signature mismatch! Expected ${calculatedHmac} but got ${nonceHmac}`
    );
  }

  return { code, pkce, requestedUri: parsedState.requestedUri || "" };
}