Skip to content

Commit e66a64c

Browse files
committed
sqlite: improve authorizer error handling
1 parent 2714896 commit e66a64c

File tree

3 files changed

+85
-23
lines changed

3 files changed

+85
-23
lines changed

src/node_sqlite.cc

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,27 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, DatabaseSync* db) {
233233
}
234234
}
235235

236+
bool DatabaseSync::HasPendingAuthorizerError() const {
237+
return has_pending_authorizer_error_;
238+
}
239+
240+
void DatabaseSync::StoreAuthorizerError(Local<Value> error) {
241+
if (!has_pending_authorizer_error_) {
242+
pending_authorizer_error_.Reset(env()->isolate(), error);
243+
has_pending_authorizer_error_ = true;
244+
}
245+
}
246+
247+
void DatabaseSync::RethrowPendingAuthorizerError() {
248+
if (has_pending_authorizer_error_) {
249+
Isolate* isolate = env()->isolate();
250+
Local<Value> err = pending_authorizer_error_.Get(isolate);
251+
pending_authorizer_error_.Reset();
252+
has_pending_authorizer_error_ = false;
253+
isolate->ThrowException(err);
254+
}
255+
}
256+
236257
inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) {
237258
Local<Object> e;
238259
if (CreateSQLiteError(isolate, message).ToLocal(&e)) {
@@ -1126,6 +1147,15 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
11261147
Utf8Value sql(env->isolate(), args[0].As<String>());
11271148
sqlite3_stmt* s = nullptr;
11281149
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0);
1150+
1151+
if (db->HasPendingAuthorizerError()) {
1152+
db->RethrowPendingAuthorizerError();
1153+
if (s != nullptr) {
1154+
sqlite3_finalize(s);
1155+
}
1156+
return;
1157+
}
1158+
11291159
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
11301160
BaseObjectPtr<StatementSync> stmt =
11311161
StatementSync::Create(env, BaseObjectPtr<DatabaseSync>(db), s);
@@ -1147,6 +1177,10 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
11471177

11481178
Utf8Value sql(env->isolate(), args[0].As<String>());
11491179
int r = sqlite3_exec(db->connection_, *sql, nullptr, nullptr, nullptr);
1180+
if (db->HasPendingAuthorizerError()) {
1181+
db->RethrowPendingAuthorizerError();
1182+
return;
1183+
}
11501184
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
11511185
}
11521186

@@ -1906,9 +1940,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
19061940
Local<Value> cb =
19071941
db->object()->GetInternalField(kAuthorizerCallback).template As<Value>();
19081942

1909-
if (!cb->IsFunction()) {
1910-
return SQLITE_DENY;
1911-
}
1943+
CHECK(cb->IsFunction());
19121944

19131945
Local<Function> callback = cb.As<Function>();
19141946
LocalVector<Value> js_argv(isolate);
@@ -1929,29 +1961,43 @@ int DatabaseSync::AuthorizerCallback(void* user_data,
19291961
context, Undefined(isolate), js_argv.size(), js_argv.data());
19301962

19311963
if (try_catch.HasCaught()) {
1932-
// If there's an exception in the callback, deny the operation
1933-
// TODO: Rethrow exception
1964+
db->StoreAuthorizerError(try_catch.Exception());
19341965
return SQLITE_DENY;
19351966
}
19361967

19371968
Local<Value> result;
1938-
if (!retval.ToLocal(&result)) {
1969+
if (!retval.ToLocal(&result) || result->IsUndefined() || result->IsNull()) {
1970+
// Missing return value
1971+
Local<Value> err = Exception::TypeError(
1972+
String::NewFromUtf8(
1973+
isolate,
1974+
"Authorizer callback must return an integer authorization code")
1975+
.ToLocalChecked());
1976+
db->StoreAuthorizerError(err);
19391977
return SQLITE_DENY;
19401978
}
19411979

1942-
if (!result->IsNumber()) {
1980+
if (!result->IsInt32()) {
1981+
Local<Value> err = Exception::TypeError(
1982+
String::NewFromUtf8(
1983+
isolate, "Authorizer callback return value must be an integer")
1984+
.ToLocalChecked());
1985+
db->StoreAuthorizerError(err);
19431986
return SQLITE_DENY;
19441987
}
19451988

1946-
double num_result = result.As<Number>()->Value();
1947-
int int_result = static_cast<int>(num_result);
1948-
1949-
if (int_result == SQLITE_OK || int_result == SQLITE_DENY ||
1950-
int_result == SQLITE_IGNORE) {
1951-
return int_result;
1989+
int32_t int_result = result.As<Int32>()->Value();
1990+
if (int_result != SQLITE_OK && int_result != SQLITE_DENY &&
1991+
int_result != SQLITE_IGNORE) {
1992+
Local<Value> err = Exception::RangeError(
1993+
String::NewFromUtf8(
1994+
isolate, "Authorizer callback returned invalid authorization code")
1995+
.ToLocalChecked());
1996+
db->StoreAuthorizerError(err);
1997+
return SQLITE_DENY;
19521998
}
19531999

1954-
return SQLITE_DENY;
2000+
return int_result;
19552001
}
19562002

19572003
StatementSync::StatementSync(Environment* env,

src/node_sqlite.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ class DatabaseSync : public BaseObject {
171171
void SetIgnoreNextSQLiteError(bool ignore);
172172
bool ShouldIgnoreSQLiteError();
173173

174+
bool HasPendingAuthorizerError() const;
175+
void StoreAuthorizerError(v8::Local<v8::Value> error);
176+
void RethrowPendingAuthorizerError();
177+
174178
SET_MEMORY_INFO_NAME(DatabaseSync)
175179
SET_SELF_SIZE(DatabaseSync)
176180

@@ -189,6 +193,9 @@ class DatabaseSync : public BaseObject {
189193
std::set<sqlite3_session*> sessions_;
190194
std::unordered_set<StatementSync*> statements_;
191195

196+
bool has_pending_authorizer_error_ = false;
197+
v8::Global<v8::Value> pending_authorizer_error_;
198+
192199
friend class Session;
193200
friend class SQLTagStore;
194201
friend class StatementExecutionHelper;

test/parallel/test-sqlite-authz.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ suite('DatabaseSync.prototype.setAuthorizer()', () => {
7171
});
7272
});
7373

74-
it('blocks operations when authorizer throws error', () => {
74+
it('rethrows error when authorizer throws error', () => {
7575
const db = new DatabaseSync(':memory:');
7676
db.setAuthorizer(() => {
7777
throw new Error('Unknown error');
@@ -80,12 +80,23 @@ suite('DatabaseSync.prototype.setAuthorizer()', () => {
8080
assert.throws(() => {
8181
db.exec('SELECT 1');
8282
}, {
83-
code: 'ERR_SQLITE_ERROR',
84-
message: /not authorized/
83+
message: 'Unknown error'
84+
});
85+
});
86+
87+
it('throws error when authorizer returns nothing', () => {
88+
const db = new DatabaseSync(':memory:');
89+
db.setAuthorizer(() => {
90+
});
91+
92+
assert.throws(() => {
93+
db.exec('SELECT 1');
94+
}, {
95+
message: 'Authorizer callback must return an integer authorization code'
8596
});
8697
});
8798

88-
it('blocks operations when authorizer returns NaN', () => {
99+
it('throws error when authorizer returns NaN', () => {
89100
const db = new DatabaseSync(':memory:');
90101
db.setAuthorizer(() => {
91102
return '1';
@@ -94,12 +105,11 @@ suite('DatabaseSync.prototype.setAuthorizer()', () => {
94105
assert.throws(() => {
95106
db.exec('SELECT 1');
96107
}, {
97-
code: 'ERR_SQLITE_ERROR',
98-
message: /not authorized/
108+
message: 'Authorizer callback return value must be an integer'
99109
});
100110
});
101111

102-
it('blocks operations when authorizer returns a invalid code', () => {
112+
it('throws error when authorizer returns a invalid code', () => {
103113
const db = new DatabaseSync(':memory:');
104114
db.setAuthorizer(() => {
105115
return 3;
@@ -108,8 +118,7 @@ suite('DatabaseSync.prototype.setAuthorizer()', () => {
108118
assert.throws(() => {
109119
db.exec('SELECT 1');
110120
}, {
111-
code: 'ERR_SQLITE_ERROR',
112-
message: /not authorized/
121+
message: 'Authorizer callback returned invalid authorization code'
113122
});
114123
});
115124

0 commit comments

Comments
 (0)