fastify / fastify-secure-session

Create a secure stateless cookie session for Fastify
MIT License
201 stars 45 forks source link

"reply.signCookie is not a function" when setting option signed: true #219

Closed JohanManders closed 4 months ago

JohanManders commented 5 months ago

Prerequisites

Fastify version

4.26.2

Plugin version

7.1.0

Node.js version

14.18.1

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

10

Description

Setting the option signed: true when initializing @fastify/secure-session or setting the same option signed: true when using the setCookie function, you get an error: "reply.signCookie is not a function".

Example

const fastify = require("fastify")({ logger: true });
const fs = require("node:fs");
const path = require("node:path");

fastify.register(require("@fastify/secure-session"), {
  key: fs.readFileSync(path.join(__dirname, "secret.key")),
  cookie: {
    secure: true,
    httpOnly: true,
    path: "/",
    signed: true,
  },
});

fastify.get("/", function (request, reply) {
  /**
   * Both examples below won't work because of signed: true in cookie options.
   * The error: "reply.signCookie is not a function"
   */

  // Example 1: setting session variable
  request.session.set("my-session-variable", "some-value");

  // Example 2: setting cookie
  reply.setCookie("my-cookie", "some-value", {
    httpOnly: true,
    path: "/",
    secure: true,
    signed: true,
  });

  return "done";
});

fastify.listen({ port: 3001 }, function (err, address) {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
});

What happens

This following happens inside the @fastify/secure-session index.js file (simplyfied):

// defaultSecret is later used to set secret for @fastify/cookie
let defaultSecret

for (const sessionOptions of options) {
    let key

    if (sessionOptions.secret) {
        /**
         * In our example this code gets skipped, because we did not set a secret (and salt), but used a key
         * when initializing @fastify/secure-session
         */

        // defaultSecret is set using sessionOptions.secret
        if (!defaultSecret) {
            defaultSecret = sessionOptions.secret
        }

        // key is set using secret (and salt), code is not important for this example
    }

    if (sessionOptions.key) {
        /**
         * In our example this code gets executed, because we did set a key
         * when initializing @fastify/secure-session
         */
    }
}

if (fastify.hasPlugin('@fastify/cookie')) {
    /**
     * This code gets skipped, because we did not initialize @fastify/cookie before @fastify/secure-session
     */ 
} else {
    /**
     * This code gets executed in our example.
     * 
     * Because the secret will stay empty, later on the signCookie function will be removed, so we can't use signCookie.
     */ 
    fastify
        .register(require('@fastify/cookie'), {
            secret: defaultSecret
        })
        .register(fp(addHooks))
}

Possible solution

I think one of the possible solutions could be to change from:

if (sessionOptions.secret) {
  // setting defaultSecret and key
}
if (sessionOptions.key) {
  // setting key
}

to something like:

if (sessionOptions.key) {
  // setting key
  // derive defaultSecret from key
}
if (!key) {
  if (sessionOptions.secret) {
    // setting defaultSecret
  }
}

Steps to Reproduce

I have set up an example project in CodeSandBox: fastify-secure-session-error-example

Expected Behavior

No response

mcollina commented 5 months ago

Thanks for reporting! Would you like to send a Pull Request to address this issue? Remember to add unit tests.

JohanManders commented 5 months ago

Hi @mcollina, I created a pull request (#220) for this issue. I think I found the right balance between using the key(s) and secret, hope you agree.