Skip to content

Commit f09f828

Browse files
committed
implement the new hash_password function
1 parent a3c7a04 commit f09f828

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

examples/official-site/sqlpage/migrations/08_functions.sql

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,36 @@ SELECT ''authentication'' AS component,
103103
sqlpage.basic_auth_password() AS password;
104104
```
105105
'
106+
);
107+
INSERT INTO sqlpage_functions ("name", "icon", "description_md")
108+
VALUES (
109+
'hash_password',
110+
'spy',
111+
'Hashes a password using the [Argon2](https://en.wikipedia.org/wiki/Argon2) algorithm.
112+
The resulting hash can be stored in the database and then used with the [authentication component](documentation.sql?component=authentication#component).
113+
114+
### Example
115+
116+
```sql
117+
SELECT ''form'' AS component;
118+
SELECT ''username'' AS name;
119+
SELECT ''password'' AS name, ''password'' AS type;
120+
121+
INSERT INTO users (name, password_hash) VALUES (:username, sqlpage.hash_password(:password));
122+
```
123+
'
124+
);
125+
INSERT INTO sqlpage_function_parameters (
126+
"function",
127+
"index",
128+
"name",
129+
"description_md",
130+
"type"
131+
)
132+
VALUES (
133+
'hash_password',
134+
1,
135+
'password',
136+
'The password to hash.',
137+
'TEXT'
106138
);

src/webserver/database/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,24 @@ fn extract_req_param<'a>(
181181
StmtParam::BasicAuthUsername => extract_basic_auth_username(request)
182182
.map(Cow::Borrowed)
183183
.map(Some)?,
184-
StmtParam::HashPassword(_) => todo!(),
184+
StmtParam::HashPassword(inner) => {
185+
if let Some(password) = extract_req_param(inner, request)? {
186+
Some(Cow::Owned(hash_password(&password)?))
187+
} else {
188+
None
189+
}
190+
}
185191
})
186192
}
187193

194+
fn hash_password(password: &str) -> anyhow::Result<String> {
195+
let phf = argon2::Argon2::default();
196+
let salt = password_hash::SaltString::generate(&mut password_hash::rand_core::OsRng);
197+
let password_hash = &password_hash::PasswordHash::generate(phf, password, &salt)
198+
.map_err(|e| anyhow!("Unable to hash password: {}", e))?;
199+
Ok(password_hash.to_string())
200+
}
201+
188202
#[derive(Debug)]
189203
pub struct ErrorWithStatus {
190204
pub status: StatusCode,

src/webserver/database/sql.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ fn extract_variable_argument(
208208
args.as_mut_slice(),
209209
)),
210210
_ => Err(format!(
211-
"{func_name}({args}) is not a valid call. Expected a literal single quoted string.",
211+
"{func_name}({args}) is not a valid call. Expected either a placeholder or a sqlpage function call as argument.",
212212
args = arguments
213213
.iter()
214214
.map(ToString::to_string)
@@ -236,9 +236,12 @@ pub fn make_placeholder(db_kind: AnyKind, arg_number: usize) -> String {
236236

237237
impl VisitorMut for ParameterExtractor {
238238
type Break = ();
239-
fn post_visit_expr(&mut self, value: &mut Expr) -> ControlFlow<Self::Break> {
239+
fn pre_visit_expr(&mut self, value: &mut Expr) -> ControlFlow<Self::Break> {
240240
match value {
241-
Expr::Value(Value::Placeholder(param)) => {
241+
Expr::Value(Value::Placeholder(param))
242+
if param.chars().nth(1).is_some_and(char::is_alphabetic) =>
243+
// this check is to avoid recursively replacing placeholders in the form of '?', or '$1', '$2', which we emit ourselves
244+
{
242245
let new_expr = self.make_placeholder();
243246
let name = std::mem::take(param);
244247
self.parameters.push(map_param(name));

0 commit comments

Comments
 (0)