Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
sqlite: improve error handling
  • Loading branch information
araujogui committed Sep 18, 2025
commit bf0fc42fcdaf8af99cfaace60f7789fa5bb81e94
4 changes: 2 additions & 2 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1921,15 +1921,15 @@ void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();

if (args.Length() == 0 || args[0]->IsNull() || args[0]->IsUndefined()) {
if ( args[0]->IsNull() ) {
// Clear the authorizer
sqlite3_set_authorizer(db->connection_, nullptr, nullptr);
return;
}

if (!args[0]->IsFunction()) {
THROW_ERR_INVALID_ARG_TYPE(isolate,
"The \"callback\" argument must be a function.");
"The \"callback\" argument must be a function or null.");
return;
}

Expand Down
123 changes: 99 additions & 24 deletions test/parallel/test-sqlite-authz.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,165 @@
'use strict';

const { skipIfSQLiteMissing } = require('../common');
skipIfSQLiteMissing();

const assert = require('node:assert');
const { DatabaseSync, constants } = require('node:sqlite');
const { suite, it } = require('node:test');

suite('DatabaseSync.prototype.setAuthorizer()', () => {
it('calls the authorizer with the correct parameters', (t) => {
const authorizer = t.mock.fn(() => constants.SQLITE_OK);

const createTestDatabase = () => {
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE users (id INTEGER, name TEXT)');
return db;
};

it('receives correct parameters for SELECT operations', (t) => {
const authorizer = t.mock.fn(() => constants.SQLITE_OK);
const db = createTestDatabase();

db.setAuthorizer(authorizer);
db.prepare('SELECT id FROM users').get();

assert.strictEqual(authorizer.mock.callCount(), 2);
const callArguments = authorizer.mock.calls.map((call) => call.arguments);

assert.deepStrictEqual(
callArguments,
[
[constants.SQLITE_SELECT, null, null, null, null],
[constants.SQLITE_READ, 'users', 'id', 'main', null],
]
);
});

const insert = db.prepare('SELECT 1');
insert.run();
it('receives correct parameters for INSERT operations', (t) => {
const authorizer = t.mock.fn(() => constants.SQLITE_OK);
const db = createTestDatabase();

db.setAuthorizer(authorizer);
db.prepare('INSERT INTO users (id, name) VALUES (?, ?)').run(1, 'node');

assert.strictEqual(authorizer.mock.callCount(), 1);

const call = authorizer.mock.calls[0];
assert.deepStrictEqual(call.arguments, [constants.SQLITE_SELECT, null, null, null, null]);
assert.strictEqual(call.result, constants.SQLITE_OK);
assert.strictEqual(call.error, undefined);
const callArguments = authorizer.mock.calls.map((call) => call.arguments);
assert.deepStrictEqual(
callArguments,
[[constants.SQLITE_INSERT, 'users', null, 'main', null]],
);
});

it('allows operations when authorizer returns SQLITE_OK', () => {
const db = new DatabaseSync(':memory:');

db.setAuthorizer(() => constants.SQLITE_OK);

db.exec('CREATE TABLE users (id INTEGER, name TEXT)');

const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();

assert.strictEqual(tables[0].name, 'users');
});

it('blocks operations when authorizer returns SQLITE_DENY', () => {
const db = new DatabaseSync(':memory:');

db.setAuthorizer(() => constants.SQLITE_DENY);

assert.throws(() => {
db.exec('SELECT 1');
}, {
code: 'ERR_SQLITE_ERROR',
message: /not authorized/,
message: /not authorized/
});
});

it('clears authorizer with null', (t) => {
const authorizer = t.mock.fn(() => constants.SQLITE_OK);

it('blocks operations when authorizer throws error', () => {
const db = new DatabaseSync(':memory:');
db.setAuthorizer(() => {
throw new Error('Unknown error');
});

db.setAuthorizer(authorizer);
assert.throws(() => {
db.exec('SELECT 1');
}, {
code: 'ERR_SQLITE_ERROR',
message: /not authorized/
});
});

it('clears authorizer when set to null', (t) => {
const authorizer = t.mock.fn(() => constants.SQLITE_OK);
const db = new DatabaseSync(':memory:');
const statement = db.prepare('SELECT 1');
statement.run();

// Set authorizer and verify it's called
db.setAuthorizer(authorizer);
statement.run();
assert.strictEqual(authorizer.mock.callCount(), 1);

// Clear authorizer
// Clear authorizer and verify it's no longer called
db.setAuthorizer(null);

statement.run();

assert.strictEqual(authorizer.mock.callCount(), 1);
});

it('throws with invalid callback type', () => {
it('throws when callback is a string', () => {
const db = new DatabaseSync(':memory:');

assert.throws(() => {
db.setAuthorizer('not a function');
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "callback" argument must be a function/,
message: /The "callback" argument must be a function/
});
});

it('throws when callback is a number', () => {
const db = new DatabaseSync(':memory:');

assert.throws(() => {
db.setAuthorizer(1);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "callback" argument must be a function/,
message: /The "callback" argument must be a function/
});
});

it('throws when callback is an object', () => {
const db = new DatabaseSync(':memory:');

assert.throws(() => {
db.setAuthorizer({});
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "callback" argument must be a function/
});
});

it('throws when callback is an array', () => {
const db = new DatabaseSync(':memory:');

assert.throws(() => {
db.setAuthorizer([]);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "callback" argument must be a function/
});
});

it('throws when callback is undefined', () => {
const db = new DatabaseSync(':memory:');

assert.throws(() => {
db.setAuthorizer();
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "callback" argument must be a function/
});
});

it('accepts null as valid input for clearing authorizer', () => {
const db = new DatabaseSync(':memory:');

// does not throw
db.setAuthorizer(null);
});
});