But... it seems like if you're willing to expose your data model to the client, then it seems like some combination of code signing the SQL (with parameters left empty) along with the acceptable list of named parameters, e.g.:
Signed blob:
{
sql: "SELECT name, email, ... FROM users WHERE users.id = %(userid)s"
allowedParameters: []
}
Such that the client is allowed to send this query, but they're not allowed to provide any parameters, the backend has to provide the userid. That seems workable to me.Likewise:
{
sql: "SELECT order.price, order.... FROM orders WHERE orders.user_id = %(userid)s AND orders.id = %(orderid)s"
allowedParameters: ["orderid"]
}
This would allow the frontend to provide an order ID, but not the user ID - so the user still can't look at other people's orders and if not logged in, it's an invalid query without a userid. And the client isn't allowed to change the "sql" or parameters sent or the signature is invalid.Edit: Someone else replied that if you have sufficiently advanced build tooling, you can statically extract the SQL/GraphQL from the client side code and replace them with IDs, and then move the SQL to the backend. That's very cool, but the average startup isn't using bazel/buck/etc. Those tools aren't yet small app friendly enough (which is unfortunate, IMO.)