Added regex pattern validation

This commit is contained in:
Dr. Patrick Urbanke
2025-05-07 05:01:32 +02:00
parent 313516217c
commit 4f2e5a7ba5
6 changed files with 193 additions and 0 deletions

View File

@@ -20,6 +20,8 @@
[sqlgen::Literal](literal.md) - How to define fields that only allow for a fixed set of values.
[sqlgen::Pattern](pattern.md) - How to add regex pattern validation to avoid SQL injection.
## Supported databases
[postgres](postgres.md) - How to interact with PostgreSQL or a related database (Redshift, Aurora, Greenplum...).

110
docs/pattern.md Normal file
View File

@@ -0,0 +1,110 @@
# `sqlgen::Pattern`
The `sqlgen::Pattern` class provides a type-safe way to validate string fields against regular expressions in C++. It's particularly useful for preventing SQL injection by ensuring data conforms to expected patterns before it reaches the database.
## Usage
### Basic Definition
Define a pattern using a regular expression and a descriptive name:
```cpp
// Define a pattern for usernames (alphanumeric with underscores)
using Username = sqlgen::Pattern<"[a-zA-Z0-9_]+", "username">;
struct User {
sqlgen::PrimaryKey<uint32_t> id;
Username username;
sqlgen::Email email;
};
```
### Built-in Patterns
SQLGen provides several commonly used patterns out of the box:
```cpp
// Alphanumeric strings
sqlgen::AlphaNumeric name;
// Base64 encoded strings
sqlgen::Base64Encoded encoded_data;
// Email addresses
sqlgen::Email email;
// UUID formats
sqlgen::UUIDv1 uuid1;
sqlgen::UUIDv2 uuid2;
sqlgen::UUIDv3 uuid3;
sqlgen::UUIDv4 uuid4;
```
### Assignment and Validation
Assign values to pattern-validated fields:
```cpp
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::AlphaNumeric first_name;
sqlgen::AlphaNumeric last_name;
int age;
};
const auto person = Person{
.id = 1,
.first_name = "Homer", // Valid: contains only alphanumeric characters
.last_name = "Simpson", // Valid: contains only alphanumeric characters
.age = 45
};
```
### Accessing Values
Access the underlying string value:
```cpp
const auto person = Person{
.first_name = "Homer",
.last_name = "Simpson"
};
// Get the value
const std::string& value1 = person.first_name();
const std::string& value2 = person.first_name.get();
const std::string& value3 = person.first_name.value();
```
## SQL Injection Prevention
Pattern validation is a crucial security feature that helps prevent SQL injection attacks by:
1. Enforcing strict input validation before data reaches the database
2. Ensuring data conforms to expected formats
3. Rejecting potentially malicious input that doesn't match the pattern
For example, using `AlphaNumeric` for usernames prevents injection of SQL commands:
```cpp
struct User {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::AlphaNumeric username; // Rejects: "admin'; DROP TABLE users;--"
sqlgen::Email email; // Rejects: "invalid@email'; DELETE FROM users;--"
};
```
## Notes
- Pattern validation occurs at runtime when values are assigned
- The template parameters are:
- `_regex`: The regular expression pattern to match against
- `_name`: A descriptive name for the pattern (used in error messages)
- The class supports:
- Direct string assignment with validation
- Multiple access methods for the underlying value
- Reflection for SQL operations
- Move and copy semantics
- Built-in patterns provide common validation rules
- Custom patterns can be defined for specific use cases
- Pattern validation is an important part of a defense-in-depth security strategy

View File

@@ -6,6 +6,7 @@
#include "sqlgen/Iterator.hpp"
#include "sqlgen/IteratorBase.hpp"
#include "sqlgen/Literal.hpp"
#include "sqlgen/Pattern.hpp"
#include "sqlgen/PrimaryKey.hpp"
#include "sqlgen/Range.hpp"
#include "sqlgen/Ref.hpp"
@@ -14,6 +15,7 @@
#include "sqlgen/col.hpp"
#include "sqlgen/limit.hpp"
#include "sqlgen/order_by.hpp"
#include "sqlgen/patterns.hpp"
#include "sqlgen/read.hpp"
#include "sqlgen/where.hpp"
#include "sqlgen/write.hpp"

View File

@@ -0,0 +1,15 @@
#ifndef SQLGEN_PATTERN_HPP_
#define SQLGEN_PATTERN_HPP_
#include <rfl.hpp>
namespace sqlgen {
template <rfl::internal::StringLiteral _regex,
rfl::internal::StringLiteral _name>
using Pattern = rfl::Pattern<_regex, _name>;
}; // namespace sqlgen
#endif

View File

@@ -0,0 +1,25 @@
#ifndef SQLGEN_PATTERNS_HPP_
#define SQLGEN_PATTERNS_HPP_
#include <rfl.hpp>
namespace sqlgen {
using AlphaNumeric = rfl::AlphaNumeric;
using Base64Encoded = rfl::Base64Encoded;
using Email = rfl::Email;
using UUIDv1 = rfl::UUIDv1;
using UUIDv2 = rfl::UUIDv2;
using UUIDv3 = rfl::UUIDv3;
using UUIDv4 = rfl::UUIDv4;
} // namespace sqlgen
#endif

View File

@@ -0,0 +1,39 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_alpha_numeric {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::AlphaNumeric first_name;
sqlgen::AlphaNumeric last_name;
int age;
};
TEST(sqlite, test_alpha_numeric) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
const auto conn = sqlgen::sqlite::connect();
sqlgen::write(conn, people1);
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_alpha_numeric