sqlite: create authorization api

PR-URL: https://github.com/nodejs/node/pull/59928
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
This commit is contained in:
Guilherme Araújo 2025-09-18 11:31:43 -03:00 committed by Anna Henningsen
parent 4a9a022dd4
commit b217051d36
No known key found for this signature in database
4 changed files with 694 additions and 0 deletions

View File

@ -318,6 +318,81 @@ added:
This method is used to create SQLite user-defined functions. This method is a
wrapper around [`sqlite3_create_function_v2()`][].
### `database.setAuthorizer(callback)`
<!-- YAML
added: REPLACEME
-->
* `callback` {Function|null} The authorizer function to set, or `null` to
clear the current authorizer.
Sets an authorizer callback that SQLite will invoke whenever it attempts to
access data or modify the database schema through prepared statements.
This can be used to implement security policies, audit access, or restrict certain operations.
This method is a wrapper around [`sqlite3_set_authorizer()`][].
When invoked, the callback receives five arguments:
* `actionCode` {number} The type of operation being performed (e.g.,
`SQLITE_INSERT`, `SQLITE_UPDATE`, `SQLITE_SELECT`).
* `arg1` {string|null} The first argument (context-dependent, often a table name).
* `arg2` {string|null} The second argument (context-dependent, often a column name).
* `dbName` {string|null} The name of the database.
* `triggerOrView` {string|null} The name of the trigger or view causing the access.
The callback must return one of the following constants:
* `SQLITE_OK` - Allow the operation.
* `SQLITE_DENY` - Deny the operation (causes an error).
* `SQLITE_IGNORE` - Ignore the operation (silently skip).
```cjs
const { DatabaseSync, constants } = require('node:sqlite');
const db = new DatabaseSync(':memory:');
// Set up an authorizer that denies all table creation
db.setAuthorizer((actionCode) => {
if (actionCode === constants.SQLITE_CREATE_TABLE) {
return constants.SQLITE_DENY;
}
return constants.SQLITE_OK;
});
// This will work
db.prepare('SELECT 1').get();
// This will throw an error due to authorization denial
try {
db.exec('CREATE TABLE blocked (id INTEGER)');
} catch (err) {
console.log('Operation blocked:', err.message);
}
```
```mjs
import { DatabaseSync, constants } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
// Set up an authorizer that denies all table creation
db.setAuthorizer((actionCode) => {
if (actionCode === constants.SQLITE_CREATE_TABLE) {
return constants.SQLITE_DENY;
}
return constants.SQLITE_OK;
});
// This will work
db.prepare('SELECT 1').get();
// This will throw an error due to authorization denial
try {
db.exec('CREATE TABLE blocked (id INTEGER)');
} catch (err) {
console.log('Operation blocked:', err.message);
}
```
### `database.isOpen`
<!-- YAML
@ -1048,6 +1123,182 @@ resolution handler passed to [`database.applyChangeset()`][]. See also
</tr>
</table>
#### Authorization constants
The following constants are used with the [`database.setAuthorizer()`][] method.
##### Authorization result codes
One of the following constants must be returned from the authorizer callback
function passed to [`database.setAuthorizer()`][].
<table>
<tr>
<th>Constant</th>
<th>Description</th>
</tr>
<tr>
<td><code>SQLITE_OK</code></td>
<td>Allow the operation to proceed normally.</td>
</tr>
<tr>
<td><code>SQLITE_DENY</code></td>
<td>Deny the operation and cause an error to be returned.</td>
</tr>
<tr>
<td><code>SQLITE_IGNORE</code></td>
<td>Ignore the operation and continue as if it had never been requested.</td>
</tr>
</table>
##### Authorization action codes
The following constants are passed as the first argument to the authorizer
callback function to indicate what type of operation is being authorized.
<table>
<tr>
<th>Constant</th>
<th>Description</th>
</tr>
<tr>
<td><code>SQLITE_CREATE_INDEX</code></td>
<td>Create an index</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_TABLE</code></td>
<td>Create a table</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_TEMP_INDEX</code></td>
<td>Create a temporary index</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_TEMP_TABLE</code></td>
<td>Create a temporary table</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_TEMP_TRIGGER</code></td>
<td>Create a temporary trigger</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_TEMP_VIEW</code></td>
<td>Create a temporary view</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_TRIGGER</code></td>
<td>Create a trigger</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_VIEW</code></td>
<td>Create a view</td>
</tr>
<tr>
<td><code>SQLITE_DELETE</code></td>
<td>Delete from a table</td>
</tr>
<tr>
<td><code>SQLITE_DROP_INDEX</code></td>
<td>Drop an index</td>
</tr>
<tr>
<td><code>SQLITE_DROP_TABLE</code></td>
<td>Drop a table</td>
</tr>
<tr>
<td><code>SQLITE_DROP_TEMP_INDEX</code></td>
<td>Drop a temporary index</td>
</tr>
<tr>
<td><code>SQLITE_DROP_TEMP_TABLE</code></td>
<td>Drop a temporary table</td>
</tr>
<tr>
<td><code>SQLITE_DROP_TEMP_TRIGGER</code></td>
<td>Drop a temporary trigger</td>
</tr>
<tr>
<td><code>SQLITE_DROP_TEMP_VIEW</code></td>
<td>Drop a temporary view</td>
</tr>
<tr>
<td><code>SQLITE_DROP_TRIGGER</code></td>
<td>Drop a trigger</td>
</tr>
<tr>
<td><code>SQLITE_DROP_VIEW</code></td>
<td>Drop a view</td>
</tr>
<tr>
<td><code>SQLITE_INSERT</code></td>
<td>Insert into a table</td>
</tr>
<tr>
<td><code>SQLITE_PRAGMA</code></td>
<td>Execute a PRAGMA statement</td>
</tr>
<tr>
<td><code>SQLITE_READ</code></td>
<td>Read from a table</td>
</tr>
<tr>
<td><code>SQLITE_SELECT</code></td>
<td>Execute a SELECT statement</td>
</tr>
<tr>
<td><code>SQLITE_TRANSACTION</code></td>
<td>Begin, commit, or rollback a transaction</td>
</tr>
<tr>
<td><code>SQLITE_UPDATE</code></td>
<td>Update a table</td>
</tr>
<tr>
<td><code>SQLITE_ATTACH</code></td>
<td>Attach a database</td>
</tr>
<tr>
<td><code>SQLITE_DETACH</code></td>
<td>Detach a database</td>
</tr>
<tr>
<td><code>SQLITE_ALTER_TABLE</code></td>
<td>Alter a table</td>
</tr>
<tr>
<td><code>SQLITE_REINDEX</code></td>
<td>Reindex</td>
</tr>
<tr>
<td><code>SQLITE_ANALYZE</code></td>
<td>Analyze the database</td>
</tr>
<tr>
<td><code>SQLITE_CREATE_VTABLE</code></td>
<td>Create a virtual table</td>
</tr>
<tr>
<td><code>SQLITE_DROP_VTABLE</code></td>
<td>Drop a virtual table</td>
</tr>
<tr>
<td><code>SQLITE_FUNCTION</code></td>
<td>Use a function</td>
</tr>
<tr>
<td><code>SQLITE_SAVEPOINT</code></td>
<td>Create, release, or rollback a savepoint</td>
</tr>
<tr>
<td><code>SQLITE_COPY</code></td>
<td>Copy data (legacy)</td>
</tr>
<tr>
<td><code>SQLITE_RECURSIVE</code></td>
<td>Recursive query</td>
</tr>
</table>
[Changesets and Patchsets]: https://www.sqlite.org/sessionintro.html#changesets_and_patchsets
[Constants Passed To The Conflict Handler]: https://www.sqlite.org/session/c_changeset_conflict.html
[Constants Returned From The Conflict Handler]: https://www.sqlite.org/session/c_changeset_abort.html
@ -1059,6 +1310,7 @@ resolution handler passed to [`database.applyChangeset()`][]. See also
[`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html
[`SQLITE_MAX_FUNCTION_ARG`]: https://www.sqlite.org/limits.html#max_function_arg
[`database.applyChangeset()`]: #databaseapplychangesetchangeset-options
[`database.setAuthorizer()`]: #databasesetauthorizercallback
[`sqlite3_backup_finish()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
[`sqlite3_backup_init()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
[`sqlite3_backup_step()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
@ -1078,6 +1330,7 @@ resolution handler passed to [`database.applyChangeset()`][]. See also
[`sqlite3_last_insert_rowid()`]: https://www.sqlite.org/c3ref/last_insert_rowid.html
[`sqlite3_load_extension()`]: https://www.sqlite.org/c3ref/load_extension.html
[`sqlite3_prepare_v2()`]: https://www.sqlite.org/c3ref/prepare.html
[`sqlite3_set_authorizer()`]: https://sqlite.org/c3ref/set_authorizer.html
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
[`sqlite3changeset_apply()`]: https://www.sqlite.org/session/sqlite3changeset_apply.html
[`sqlite3session_attach()`]: https://www.sqlite.org/session/sqlite3session_attach.html

View File

@ -1126,6 +1126,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
Utf8Value sql(env->isolate(), args[0].As<String>());
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
BaseObjectPtr<StatementSync> stmt =
StatementSync::Create(env, BaseObjectPtr<DatabaseSync>(db), s);
@ -1871,6 +1872,113 @@ void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
}
}
void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
DatabaseSync* db;
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
if (args[0]->IsNull()) {
// Clear the authorizer
sqlite3_set_authorizer(db->connection_, nullptr, nullptr);
db->object()->SetInternalField(kAuthorizerCallback, Null(isolate));
return;
}
if (!args[0]->IsFunction()) {
THROW_ERR_INVALID_ARG_TYPE(
isolate, "The \"callback\" argument must be a function or null.");
return;
}
Local<Function> fn = args[0].As<Function>();
db->object()->SetInternalField(kAuthorizerCallback, fn);
int r = sqlite3_set_authorizer(
db->connection_, DatabaseSync::AuthorizerCallback, db);
if (r != SQLITE_OK) {
CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, void());
}
}
int DatabaseSync::AuthorizerCallback(void* user_data,
int action_code,
const char* param1,
const char* param2,
const char* param3,
const char* param4) {
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
Environment* env = db->env();
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Local<Context> context = env->context();
Local<Value> cb =
db->object()->GetInternalField(kAuthorizerCallback).template As<Value>();
CHECK(cb->IsFunction());
Local<Function> callback = cb.As<Function>();
LocalVector<Value> js_argv(isolate);
// Convert SQLite authorizer parameters to JavaScript values
js_argv.emplace_back(Integer::New(isolate, action_code));
js_argv.emplace_back(
NullableSQLiteStringToValue(isolate, param1).ToLocalChecked());
js_argv.emplace_back(
NullableSQLiteStringToValue(isolate, param2).ToLocalChecked());
js_argv.emplace_back(
NullableSQLiteStringToValue(isolate, param3).ToLocalChecked());
js_argv.emplace_back(
NullableSQLiteStringToValue(isolate, param4).ToLocalChecked());
MaybeLocal<Value> retval = callback->Call(
context, Undefined(isolate), js_argv.size(), js_argv.data());
Local<Value> result;
if (!retval.ToLocal(&result)) {
db->SetIgnoreNextSQLiteError(true);
return SQLITE_DENY;
}
Local<String> error_message;
if (!result->IsInt32()) {
if (!String::NewFromUtf8(
isolate,
"Authorizer callback must return an integer authorization code")
.ToLocal(&error_message)) {
return SQLITE_DENY;
}
Local<Value> err = Exception::TypeError(error_message);
isolate->ThrowException(err);
db->SetIgnoreNextSQLiteError(true);
return SQLITE_DENY;
}
int32_t int_result = result.As<Int32>()->Value();
if (int_result != SQLITE_OK && int_result != SQLITE_DENY &&
int_result != SQLITE_IGNORE) {
if (!String::NewFromUtf8(
isolate,
"Authorizer callback returned a invalid authorization code")
.ToLocal(&error_message)) {
return SQLITE_DENY;
}
Local<Value> err = Exception::RangeError(error_message);
isolate->ThrowException(err);
db->SetIgnoreNextSQLiteError(true);
return SQLITE_DENY;
}
return int_result;
}
StatementSync::StatementSync(Environment* env,
Local<Object> object,
BaseObjectPtr<DatabaseSync> db,
@ -3102,6 +3210,47 @@ void DefineConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONFLICT);
NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONSTRAINT);
NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_FOREIGN_KEY);
// Authorization result codes
NODE_DEFINE_CONSTANT(target, SQLITE_OK);
NODE_DEFINE_CONSTANT(target, SQLITE_DENY);
NODE_DEFINE_CONSTANT(target, SQLITE_IGNORE);
// Authorization action codes
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_INDEX);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_INDEX);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_TABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_TRIGGER);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_VIEW);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TRIGGER);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_VIEW);
NODE_DEFINE_CONSTANT(target, SQLITE_DELETE);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_INDEX);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_INDEX);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_TABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_TRIGGER);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_VIEW);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TRIGGER);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_VIEW);
NODE_DEFINE_CONSTANT(target, SQLITE_INSERT);
NODE_DEFINE_CONSTANT(target, SQLITE_PRAGMA);
NODE_DEFINE_CONSTANT(target, SQLITE_READ);
NODE_DEFINE_CONSTANT(target, SQLITE_SELECT);
NODE_DEFINE_CONSTANT(target, SQLITE_TRANSACTION);
NODE_DEFINE_CONSTANT(target, SQLITE_UPDATE);
NODE_DEFINE_CONSTANT(target, SQLITE_ATTACH);
NODE_DEFINE_CONSTANT(target, SQLITE_DETACH);
NODE_DEFINE_CONSTANT(target, SQLITE_ALTER_TABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_REINDEX);
NODE_DEFINE_CONSTANT(target, SQLITE_ANALYZE);
NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_VTABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_DROP_VTABLE);
NODE_DEFINE_CONSTANT(target, SQLITE_FUNCTION);
NODE_DEFINE_CONSTANT(target, SQLITE_SAVEPOINT);
NODE_DEFINE_CONSTANT(target, SQLITE_COPY);
NODE_DEFINE_CONSTANT(target, SQLITE_RECURSIVE);
}
static void Initialize(Local<Object> target,
@ -3140,6 +3289,8 @@ static void Initialize(Local<Object> target,
DatabaseSync::EnableLoadExtension);
SetProtoMethod(
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
SetProtoMethod(
isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer);
SetSideEffectFreeGetter(isolate,
db_tmpl,
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),

View File

@ -111,6 +111,11 @@ class StatementExecutionHelper {
class DatabaseSync : public BaseObject {
public:
enum InternalFields {
kAuthorizerCallback = BaseObject::kInternalFieldCount,
kInternalFieldCount
};
DatabaseSync(Environment* env,
v8::Local<v8::Object> object,
DatabaseOpenConfiguration&& open_config,
@ -136,6 +141,13 @@ class DatabaseSync : public BaseObject {
static void EnableLoadExtension(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAuthorizer(const v8::FunctionCallbackInfo<v8::Value>& args);
static int AuthorizerCallback(void* user_data,
int action_code,
const char* param1,
const char* param2,
const char* param3,
const char* param4);
void FinalizeStatements();
void RemoveBackup(BackupJob* backup);
void AddBackup(BackupJob* backup);

View File

@ -0,0 +1,278 @@
'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()', () => {
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],
]
);
});
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 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/
});
});
it('ignores SELECT operations when authorizer returns SQLITE_IGNORE', () => {
const db = createTestDatabase();
db.prepare('INSERT INTO users (id, name) VALUES (?, ?)').run(1, 'Alice');
db.setAuthorizer((actionCode) => {
if (actionCode === constants.SQLITE_SELECT) {
return constants.SQLITE_IGNORE;
}
return constants.SQLITE_OK;
});
// SELECT should be ignored and return no results
const result = db.prepare('SELECT * FROM users').all();
assert.deepStrictEqual(result, []);
});
it('ignores READ operations when authorizer returns SQLITE_IGNORE', () => {
const db = createTestDatabase();
db.prepare('INSERT INTO users (id, name) VALUES (?, ?)').run(1, 'Alice');
db.setAuthorizer((actionCode, arg1, arg2) => {
if (actionCode === constants.SQLITE_READ && arg1 === 'users' && arg2 === 'name') {
return constants.SQLITE_IGNORE;
}
return constants.SQLITE_OK;
});
// Reading the 'name' column should be ignored, returning NULL
const result = db.prepare('SELECT id, name FROM users WHERE id = 1').get();
assert.strictEqual(result.id, 1);
assert.strictEqual(result.name, null);
});
it('ignores INSERT operations when authorizer returns SQLITE_IGNORE', () => {
const db = createTestDatabase();
db.setAuthorizer((actionCode) => {
if (actionCode === constants.SQLITE_INSERT) {
return constants.SQLITE_IGNORE;
}
return constants.SQLITE_OK;
});
db.prepare('INSERT INTO users (id, name) VALUES (?, ?)').run(1, 'Alice');
// Verify no data was inserted
const count = db.prepare('SELECT COUNT(*) as count FROM users').get();
assert.strictEqual(count.count, 0);
});
it('ignores UPDATE operations when authorizer returns SQLITE_IGNORE', () => {
const db = createTestDatabase();
db.exec("INSERT INTO users (id, name) VALUES (1, 'Alice')");
db.setAuthorizer((actionCode) => {
if (actionCode === constants.SQLITE_UPDATE) {
return constants.SQLITE_IGNORE;
}
return constants.SQLITE_OK;
});
db.prepare('UPDATE users SET name = ? WHERE id = ?').run('Bob', 1);
// Verify data was not updated
const result = db.prepare('SELECT name FROM users WHERE id = 1').get();
assert.strictEqual(result.name, 'Alice');
});
it('ignores DELETE operations when authorizer returns SQLITE_IGNORE', () => {
const db = createTestDatabase();
db.exec("INSERT INTO users (id, name) VALUES (1, 'Alice')");
db.setAuthorizer(() => constants.SQLITE_IGNORE);
db.prepare('DELETE FROM users WHERE id = ?').run(1);
db.setAuthorizer(null);
// Verify data was not deleted
const count = db.prepare('SELECT COUNT(*) as count FROM users').get();
assert.strictEqual(count.count, 1);
});
it('rethrows error when authorizer throws error', () => {
const db = new DatabaseSync(':memory:');
db.setAuthorizer(() => {
throw new Error('Unknown error');
});
assert.throws(() => {
db.exec('SELECT 1');
}, {
message: 'Unknown error'
});
});
it('throws error when authorizer returns nothing', () => {
const db = new DatabaseSync(':memory:');
db.setAuthorizer(() => {
});
assert.throws(() => {
db.exec('SELECT 1');
}, {
message: 'Authorizer callback must return an integer authorization code'
});
});
it('throws error when authorizer returns NaN', () => {
const db = new DatabaseSync(':memory:');
db.setAuthorizer(() => {
return '1';
});
assert.throws(() => {
db.exec('SELECT 1');
}, {
message: 'Authorizer callback must return an integer authorization code'
});
});
it('throws error when authorizer returns a invalid code', () => {
const db = new DatabaseSync(':memory:');
db.setAuthorizer(() => {
return 3;
});
assert.throws(() => {
db.exec('SELECT 1');
}, {
message: 'Authorizer callback returned a invalid authorization code'
});
});
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');
// Set authorizer and verify it's called
db.setAuthorizer(authorizer);
statement.run();
assert.strictEqual(authorizer.mock.callCount(), 1);
// Clear authorizer and verify it's no longer called
db.setAuthorizer(null);
statement.run();
assert.strictEqual(authorizer.mock.callCount(), 1);
});
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/
});
});
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/
});
});
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/
});
});
});