node-postgres icon indicating copy to clipboard operation
node-postgres copied to clipboard

Error: SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string

Open yuneda opened this issue 3 years ago • 24 comments

https://github.com/brianc/node-postgres/blob/master/packages/pg/lib/sasl.js Line 23 to 25

yuneda avatar Jun 07 '22 11:06 yuneda

  if (typeof password !== 'string') {
    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')
  }

yuneda avatar Jun 07 '22 11:06 yuneda

Is there a question or issue? That check was added to the SASL code to provide the end user with a more informative error message rather than just failing due to the password being null / undefined.

sehrope avatar Jun 07 '22 13:06 sehrope

I'm using jest and supertest for testing my express APIs with Postgre. The error comes from my code below.

const app = require('../../app');
const request = require('supertest');
let agent = request.agent(app);

describe('Cars', function () {
  it('Get all car', (done) => {
    agent
      .get('/v1/cars')
      .expect('Content-Type', /json/)
      .then((response) => {
        expect(response.status).toEqual(200);
      });
    done();
  });
});

yuneda avatar Jun 07 '22 13:06 yuneda

You're probably running your tests without the database password available to the test environment.

If you're having an actual issue with this library then provide a self contained example that reproduces it.

sehrope avatar Jun 07 '22 14:06 sehrope

I changed the password using:

$ sudo passwd postgres
new password:
password again:
$ su postgres

remember change the password at the file

alexianaa avatar Jul 20 '22 02:07 alexianaa

I don't know if the root cause is the same, but I can reliably reproduce this just by trying to connect to a database that isn't password-protected, which I believe ought to be possible (even if not advisable in production, obviously...)

new pg.Pool({
  connectionString: "postgres://username@localhost/dbname"
});

Quintinius avatar Jul 31 '22 20:07 Quintinius

@Quintinius The "localhost" in that connection URI would connect over TCP, not a unix socket. Check your pg_hba to see if TCP connections are requiring SASL. It's probably only local unix socket connections that are password-less.

sehrope avatar Aug 01 '22 13:08 sehrope

Here is a minimal reproduction:

# .env
PGDATABASE=blahblahblah
PGUSER=blahblahblah
{
  "name": "example reproduction",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "UNLICENSED",
  "dependencies": {
    "dotenv": "^16.0.1",
    "pg": "^8.7.3"
  }
}
const { Pool } = require('pg');
const  dotenv = require('dotenv');

dotenv.config();

async function main() {
  const pool = new Pool();
  const client = await pool.connect();
  client.release();
}

main();

Environment requirements are a database, a database user, and no required database password. Observe that the script exits after some time in v16.14.0 but throws the SASL error in v18.7.0. Guarantee the password parameter is an empty string in both input options and .env files and observe no change in behavior.

I am assuming there is a breaking change with a NodeJS data type.

I've been using a database without a password for local testing and development. The database is only available on the local machine. node-postgres has been working fine in another project. I upgraded the node runtime to v18 so I could make use of experimental global Request/Response interfaces and can no longer use pg. Bummer! I hope this helps.

When I add a password blahblahblah to user blahblahblah, the script works as-expected regardless of node runtime.

bever1337 avatar Aug 20 '22 19:08 bever1337

Is there a question or issue?

It is not possible to connect to database using connectionString anymore, if the database account is without password. This is a breaking change, issue.

The problem - in my specific case - was outside this library. Sorry for noise.

Vlado-99 avatar Sep 15 '22 07:09 Vlado-99

If a blank password is sent to the postgreSQL server then and error is thrown

throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')

The error is very special; not caught by .catch on the client promise and not caught with a try catch

rhewy avatar Nov 20 '22 03:11 rhewy

Port can be blank --> defaults to 5432 Username can be blank --> defaults to the OS username Host can be blank --> defaults to localhost

Just a blank password caused the problem

rhewy avatar Nov 20 '22 03:11 rhewy

This seemed to help; if the password is blank/empty set it to a single space

    var connConfig = {
        host: req.body.host,
        port: req.body.port,
        database: req.body.database,
        user: req.body.username,
        password: req.body.password
    };

    connConfig.password = connConfig.password.length == 0 ? " " : connConfig.password

   

rhewy avatar Nov 20 '22 03:11 rhewy

Speculation

Could be that a blank password causes a syntax error in the JavaScript code; could be uncatchable.

rhewy avatar Nov 20 '22 04:11 rhewy

Hi ! just want to share my solution, I was getting this error by badly importing some models and using them before I sync the server The way I was importing the models : const { models: { User, Post } } = require('../sequelize');

The problem was that I was importing the models and using it directly in a helpers/utils/functions file, and then I solve it by passing the models through parameters :

async findPostAndUser(postId, userId, Post, User) { const post = await Post.findOne({ where: { id: { [Op.eq]: postId } } }); const user = await User.findOne({ where: { id: { [Op.eq]: userId } } }); return [post, user];}

Nikire avatar Dec 25 '22 05:12 Nikire

We are hitting this issue too, any password-protected database receiving a connection string with an empty password is raising this error.

I absolutely have no clue why we can't catch this error, this is actually my main issue. Does anyone know why it's not possible to catch this one? 😅

jgoux avatar Jan 19 '23 10:01 jgoux

I absolutely have no clue why we can't catch this error,

To use a Java analogy, it would be a difference between a compiler error versus a runtime error. The try catch does not get a chance to 'fire' as the JavaScript has syntax errors i.e. NOT compilable/runnable. Imagine a missing brace.

This next part is complete speculation, some regular expression strips quotes so that only the text of the password is sent to the database, however, if the password is blank then striping the quotes turns the password from a string to undefine which then causes a syntax error blowing up the code before the try catch can do its thing.

Or it could be something completely different.

For comic relief, I recommend watching this (so much applies): https://youtu.be/Uo3cL4nrGOk

The line below might help

// ==================================================
// if the password is blank set it to a single space
// ==================================================
connConfig.password = connConfig.password.length == 0 ? " " : connConfig.password

rhewy avatar Jan 19 '23 14:01 rhewy

I'm using typeorm and I had to export an environment variable with the database connection string:

export DATABASE_URL=postgres://postgres:postgres@localhost:5432/default

jean-pita avatar Dec 19 '23 15:12 jean-pita