aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml3
-rw-r--r--Cargo.lock59
-rw-r--r--Cargo.toml5
-rw-r--r--src/authentication_page.html10
-rw-r--r--src/datastructures.rs195
-rw-r--r--src/main.rs131
6 files changed, 312 insertions, 91 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ae8f4a0..ef5e2be 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,9 +39,6 @@ jobs:
elif [ "$RUNNER_OS" == "macOS" ]; then
BIN='cgit-simple-authentication_darwin_amd64'
mv target/release/cgit-simple-authentication target/release/$BIN
- else
- BIN='cgit-simple-authentication_windows_amd64.exe'
- mv target/release/cgit-simple-authentication.exe target/release/$BIN
fi
echo "::set-output name=bin::target/release/$BIN"
- uses: actions/[email protected]
diff --git a/Cargo.lock b/Cargo.lock
index 1cf83ae..ceed4a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -60,6 +60,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8"
[[package]]
+name = "argon2"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fa6ffe98a5aacd627ea719b7295646e6c457ff78bc87dff0a8d1e1a00c80557"
+dependencies = [
+ "blake2",
+ "password-hash",
+]
+
+[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -227,6 +237,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
+name = "base64ct"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0d27fb6b6f1e43147af148af49d49329413ba781aa0d5e10979831c210173b5"
+
+[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -245,6 +261,17 @@ dependencies = [
]
[[package]]
+name = "blake2"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -350,9 +377,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cgit-simple-authentication"
-version = "0.1.5"
+version = "0.2.0"
dependencies = [
"anyhow",
+ "argon2",
"base64",
"clap",
"env_logger",
@@ -361,11 +389,11 @@ dependencies = [
"log4rs",
"openssl",
"rand 0.7.3",
+ "rand_core 0.6.2",
"redis",
"serde",
"serde_derive",
"serde_json",
- "sha2",
"sqlx",
"tokio 1.5.0",
"tokio-stream",
@@ -490,6 +518,16 @@ dependencies = [
]
[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle",
+]
+
+[[package]]
name = "ctor"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1288,6 +1326,17 @@ dependencies = [
]
[[package]]
+name = "password-hash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1a5d4e9c205d2c1ae73b84aab6240e98218c0e72e63b50422cfb2d1ca952282"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.2",
+ "subtle",
+]
+
+[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1846,6 +1895,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
+[[package]]
name = "syn"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index aaa7f41..f16bad0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cgit-simple-authentication"
-version = "0.1.5"
+version = "0.2.0"
authors = ["KunoiSayami <[email protected]>"]
edition = "2018"
@@ -19,10 +19,11 @@ clap = "2.33"
handlebars = "3.4"
url = "2.1"
redis = { version = "0.17", features = ["tokio-comp"] }
-sha2 = "0.9"
base64 = "0.13"
log4rs = "1"
tokio-stream = "0.1"
+argon2 = "0.2"
+rand_core = "0.6"
[target.aarch64-unknown-linux-musl.dependencies]
openssl = { version = "0.10", features = ["vendored"] } \ No newline at end of file
diff --git a/src/authentication_page.html b/src/authentication_page.html
index 73ec85a..c62dce0 100644
--- a/src/authentication_page.html
+++ b/src/authentication_page.html
@@ -1,9 +1,9 @@
<h2>Authentication Required</h2>
-<form method='post' action='{{action}}'>
- <input type='hidden' name='redirect' value='{{redirect}}' />
+<form method="post" action="{{action}}">
+ <input type="hidden" name="redirect" value={{redirect}}" />
<table>
- <tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>
- <tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>
- <tr><td colspan='2'><input value='Login' type='submit' /></td></tr>
+ <tr><td><label for="username">Username:</label></td><td><input id="username" name="username" autofocus /></td></tr>
+ <tr><td><label for="password">Password:</label></td><td><input id="password" name="password" type="password" /></td></tr>
+ <tr><td colspan="2"><input value="Login" type="submit" /></td></tr>
</table>
</form> \ No newline at end of file
diff --git a/src/datastructures.rs b/src/datastructures.rs
index 2a19087..bb91cfb 100644
--- a/src/datastructures.rs
+++ b/src/datastructures.rs
@@ -19,16 +19,56 @@
*/
use anyhow::Result;
-use sha2::Digest;
use std::borrow::Cow;
use std::fs::read_to_string;
use std::path::Path;
use url::form_urlencoded;
+use std::fmt::Formatter;
+use rand::Rng;
+use serde::{Serialize, Deserialize};
+use argon2::{
+ password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
+ Argon2
+};
+use rand_core::OsRng;
const DEFAULT_CONFIG_LOCATION: &str = "/etc/cgitrc";
const DEFAULT_COOKIE_TTL: u64 = 1200;
const DEFAULT_DATABASE_LOCATION: &str = "/etc/cgit/auth.db";
pub const CACHE_DIR: &str = "/var/cache/cgit";
+pub type RandIntType = u32;
+pub const MINIMUM_SECRET_LENGTH: usize = 8;
+
+pub fn get_current_timestamp() -> u64 {
+ let start = std::time::SystemTime::now();
+ let since_the_epoch = start
+ .duration_since(std::time::UNIX_EPOCH)
+ .expect("Time went backwards");
+ since_the_epoch.as_secs()
+}
+
+pub fn rand_int() -> RandIntType {
+ let mut rng = rand::thread_rng();
+ rng.gen()
+}
+
+pub fn rand_str(len: usize) -> String {
+ const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
+ abcdefghijklmnopqrstuvwxyz\
+ 0123456789";
+ let mut rng = rand::thread_rng();
+
+ let password: String = (0..len)
+ .map(|_| {
+ let idx = rng.gen_range(0, CHARSET.len());
+ CHARSET[idx] as char
+ })
+ .collect();
+
+ password
+}
+
+
#[derive(Debug, Clone)]
pub struct Config {
@@ -36,6 +76,7 @@ pub struct Config {
database: String,
//access_node: hashmap,
pub bypass_root: bool,
+ //secret: String,
}
impl Default for Config {
@@ -44,6 +85,7 @@ impl Default for Config {
cookie_ttl: DEFAULT_COOKIE_TTL,
database: DEFAULT_DATABASE_LOCATION.to_string(),
bypass_root: false,
+ //secret: Default::default(),
}
}
}
@@ -58,6 +100,7 @@ impl Config {
let mut cookie_ttl: u64 = DEFAULT_COOKIE_TTL;
let mut database: &str = "/etc/cgit/auth.db";
let mut bypass_root: bool = false;
+ let mut secret: &str = "";
for line in file.lines() {
let line = line.trim();
if !line.contains('=') || !line.starts_with("cgit-simple-auth-") {
@@ -74,6 +117,7 @@ impl Config {
"cookie-ttl" => cookie_ttl = value.parse().unwrap_or(DEFAULT_COOKIE_TTL),
"database" => database = value,
"bypass-root" => bypass_root = value.to_lowercase().eq("true"),
+ "secret" => secret = value,
_ => {}
}
}
@@ -81,12 +125,23 @@ impl Config {
cookie_ttl,
database: database.to_string(),
bypass_root,
+ //secret: secret.to_string(),
}
}
pub fn get_database_location(&self) -> &str {
self.database.as_str()
}
+
+/* pub fn get_secret_warning(&self) -> &str {
+ if self.secret.is_empty() {
+ r#"<span color="red">Warning: You should specify secret in your cgitrc file.</span>"#
+ } else if self.secret.len() < MINIMUM_SECRET_LENGTH {
+ r#"<span color="yellow">Warning: You should set key length more than MINIMUM_SECRET_LENGTH.</span>"#
+ } else {
+ ""
+ }
+ }*/
}
#[derive(Debug, Clone, Default)]
@@ -103,10 +158,13 @@ impl FormData {
}
}
- pub fn get_string_sha256_value(s: &str) -> Result<String> {
- let mut hasher = sha2::Sha256::new();
- hasher.update(s.as_bytes());
- Ok(format!("{:x}", hasher.finalize()))
+ pub fn get_string_argon2_hash(s: &str) -> Result<String> {
+ let passwd = s.as_bytes();
+ let salt = SaltString::generate(&mut OsRng);
+
+ let argon2_alg = Argon2::default();
+
+ Ok(argon2_alg.hash_password_simple(passwd, salt.as_ref()).unwrap().to_string())
}
pub fn set_password(&mut self, password: String) {
@@ -114,6 +172,11 @@ impl FormData {
self.hash = Default::default();
}
+ pub fn verify_password(&self, password_hash: &PasswordHash) -> bool {
+ let argon2_alg = Argon2::default();
+ argon2_alg.verify_password(self.password.as_bytes(), password_hash).is_ok()
+ }
+
pub fn set_user(&mut self, user: String) {
self.user = user
}
@@ -122,20 +185,20 @@ impl FormData {
&self.user
}
- pub fn get_password_sha256(&self) -> Result<String> {
- Self::get_string_sha256_value(&self.password)
+ pub fn get_password_argon2(&self) -> Result<String> {
+ Self::get_string_argon2_hash(&self.password)
}
#[allow(dead_code)]
- pub fn get_password_sha256_cache(&mut self) -> Result<String> {
+ pub fn get_password_argon2_cache(&mut self) -> Result<String> {
if self.hash.is_empty() {
- self.hash = self.get_password_sha256()?;
+ self.hash = self.get_password_argon2()?;
}
Ok(self.hash.clone())
}
#[allow(dead_code)]
- pub fn get_sha256_without_calc(&self) -> &String {
+ pub fn get_argon2_without_calc(&self) -> &String {
&self.hash
}
}
@@ -151,7 +214,6 @@ impl From<&[u8]> for FormData {
}
Cow::Borrowed("password") => {
data.set_password(f.1.to_string());
- data.get_password_sha256_cache().unwrap();
}
_ => {}
}
@@ -171,3 +233,114 @@ impl From<String> for FormData {
Self::from(&s)
}
}
+
+#[derive(Serialize, Deserialize)]
+struct IvFile {
+ iv: String,
+ timestamp: u64,
+}
+
+pub struct Cookie {
+ timestamp: u64,
+ randint: RandIntType,
+ body: String,
+}
+
+impl Cookie {
+ fn new(randint: RandIntType, body: &String) -> Self {
+ Self {
+ timestamp: get_current_timestamp(),
+ randint,
+ body: body.clone()
+ }
+ }
+
+ pub fn load_from_request(cookies: &str) -> Result<Option<Self>> {
+ let mut cookie_self = None;
+ for cookie in cookies.split(';').map(|x| x.trim()) {
+ let (key, value) = cookie.split_once('=').unwrap();
+ if key.eq("cgit_auth") {
+ let value = base64::decode(value).unwrap_or_default();
+ let value = std::str::from_utf8(&value).unwrap_or("");
+
+ if !value.contains(';') {
+ break;
+ }
+
+ let (key, value) = value.split_once(';').unwrap();
+
+ let (timestamp, randint) = key.split_once("_").unwrap_or(("0", ""));
+
+ cookie_self = Some(Self{
+ timestamp: timestamp.parse()?,
+ randint: randint.parse()?,
+ body: value.to_string(),
+ });
+ break
+ }
+ }
+ Ok(cookie_self)
+ }
+
+ pub fn eq_body(&self, s: &str) -> bool {
+ self.body.eq(s)
+ }
+
+ pub fn get_key(&self) -> String {
+ format!("{}_{}", self.timestamp, self.randint)
+ }
+}
+
+
+impl std::fmt::Display for Cookie {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let s = format!("{}_{}; {}", self.timestamp, self.randint, self.body);
+ write!(f, "{}", base64::encode(s))
+ }
+}
+
+/*
+pub struct CookieOrigin {
+ timestamp: u64,
+ randint: RandIntType,
+ user: String,
+ rand_str: String,
+}
+
+impl CookieOrigin {
+ pub fn new(randint: RandIntType, user: &String, rand_str: &String) -> Self {
+ Self {
+ timestamp: get_current_timestamp(),
+ randint,
+ user: user.clone(),
+ rand_str: rand_str.clone(),
+ }
+ }
+
+ pub async fn to_cookie(&self, cfg: &Config) -> Result<Cookie> {
+ let mut file = File::open(Path::new(CACHE_DIR).join(DEFAULT_IV_FILE_NAME)).await?;
+ let mut context = String::new();
+ file.read_to_string(&mut context).await?;
+ let iv_file: IvFile = serde_json::from_str(&context)?;
+ let key = cfg.secret.to_hex();
+ let iv = iv_file.iv.to_hex();
+
+ let mut cipher = Blowfish::new(key);
+
+ cipher.write_all().awa
+
+ let mut buffer = [0u8, 128];
+
+ let plain_text = format!("{}, {}", self.user, self.rand_str);
+
+ let pos = plain_text.len();
+
+ buffer[..pos].copy_from_slice(plain_text.as_bytes());
+
+ let b = Block::from_mut_slice(&mut buffer);
+
+
+ Ok(())
+ }
+}
+*/ \ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 89be8e3..0f5fd48 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+#![feature(array_methods)]
/*
** Copyright (C) 2021 KunoiSayami
**
@@ -21,14 +22,13 @@
mod database;
mod datastructures;
-use crate::datastructures::{Config, FormData};
+use crate::datastructures::{Config, FormData, get_current_timestamp, Cookie, rand_int, rand_str};
use anyhow::Result;
use clap::{App, Arg, ArgMatches, SubCommand};
use handlebars::Handlebars;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Root};
use log4rs::encode::pattern::PatternEncoder;
-use rand::Rng;
use redis::AsyncCommands;
use serde::Serialize;
use sqlx::Connection;
@@ -36,43 +36,12 @@ use std::env;
use std::io::{stdin, Read};
use std::result::Result::Ok;
use tokio_stream::StreamExt as _;
+use argon2::{
+ password_hash::{PasswordHash},
+};
const COOKIE_LENGTH: usize = 45;
-fn get_current_timestamp() -> u64 {
- let start = std::time::SystemTime::now();
- let since_the_epoch = start
- .duration_since(std::time::UNIX_EPOCH)
- .expect("Time went backwards");
- since_the_epoch.as_secs()
-}
-
-fn rand_str(len: usize) -> String {
- const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
- abcdefghijklmnopqrstuvwxyz\
- 0123456789";
- let mut rng = rand::thread_rng();
-
- let password: String = (0..len)
- .map(|_| {
- let idx = rng.gen_range(0, CHARSET.len());
- CHARSET[idx] as char
- })
- .collect();
-
- password
-}
-
-fn rand_int() -> i32 {
- let mut rng = rand::thread_rng();
- rng.gen()
-}
-
-#[derive(Serialize)]
-struct Meta<'a> {
- action: &'a str,
- redirect: &'a str,
-}
// Processing the `authenticate-cookie` called by cgit.
async fn cmd_authenticate_cookie(matches: &ArgMatches<'_>, cfg: Config) -> Result<bool> {
@@ -95,30 +64,11 @@ async fn cmd_authenticate_cookie(matches: &ArgMatches<'_>, cfg: Config) -> Resul
let redis_conn = redis::Client::open("redis://127.0.0.1/")?;
let mut conn = redis_conn.get_async_connection().await?;
- for cookie in cookies.split(';').map(|x| x.trim()) {
- let (key, value) = cookie.split_once('=').unwrap();
- if key.eq("cgit_auth") {
- let value = base64::decode(value).unwrap_or_default();
- let value = std::str::from_utf8(&value).unwrap_or("");
-
- if !value.contains(';') {
- break;
- }
-
- let (key, value) = value.split_once(';').unwrap(); //.unwrap_or(("0_0", "0"));
-
- let (timestamp, _) = key.split_once("_").unwrap_or(("0", ""));
-
- if get_current_timestamp() - timestamp.parse::<u64>().unwrap_or(0) > cfg.cookie_ttl {
- break;
+ if let Ok(Some(cookie)) = Cookie::load_from_request(cookies) {
+ if let Ok(r) = conn.get::<_, String>(format!("cgit_auth_{}", cookie.get_key())).await{
+ if cookie.eq_body(r.as_str()) {
+ return Ok(true);
}
-
- if let Ok(r) = conn.get::<_, String>(format!("cgit_auth_{}", key)).await {
- if r == value {
- return Ok(true);
- }
- }
- break;
}
}
@@ -150,6 +100,8 @@ async fn cmd_init(cfg: Config) -> Result<()> {
}
async fn verify_login(cfg: &Config, data: &FormData) -> Result<bool> {
+ // TODO: use timestamp to mark file diff
+ // or copy in init process
let database_file_name = std::path::Path::new(datastructures::CACHE_DIR).join(
std::path::Path::new(cfg.get_database_location())
.file_name()
@@ -157,14 +109,12 @@ async fn verify_login(cfg: &Config, data: &FormData) -> Result<bool> {
);
std::fs::copy(cfg.get_database_location(), database_file_name.clone())?;
let mut conn = sqlx::SqliteConnection::connect(database_file_name.to_str().unwrap()).await?;
- let password_sha = data.get_password_sha256()?;
- log::debug!("password: {}", password_sha);
- let ret = sqlx::query(r#"SELECT 1 FROM "accounts" WHERE "user" = ? AND "password" = ? "#)
+ let (passwd_hash,) = sqlx::query_as::<_, (String, )>(r#"SELECT "password" FROM "accounts" WHERE "user" = ?"#)
.bind(data.get_user())
- .bind(password_sha)
- .fetch_all(&mut conn)
+ .fetch_one(&mut conn)
.await?;
- Ok(!ret.is_empty())
+ let parsed_hash = PasswordHash::new(passwd_hash.as_str()).unwrap();
+ Ok(data.verify_password(&parsed_hash))
}
// Processing the `authenticate-post` called by cgit.
@@ -218,13 +168,22 @@ async fn cmd_authenticate_post(matches: &ArgMatches<'_>, cfg: Config) -> Result<
Ok(())
}
+#[derive(Serialize)]
+pub struct Meta<'a> {
+ action: &'a str,
+ redirect: &'a str,
+ //custom_warning: &'a str,
+}
+
+
// Processing the `body` called by cgit.
-async fn cmd_body(matches: &ArgMatches<'_>, _cfg: Config) {
+async fn cmd_body(matches: &ArgMatches<'_>, cfg: Config) {
let source = include_str!("authentication_page.html");
let handlebars = Handlebars::new();
let meta = Meta {
action: matches.value_of("login-url").unwrap_or(""),
redirect: matches.value_of("current-url").unwrap_or(""),
+ //custom_warning: cfg.get_secret_warning()
};
handlebars
.render_template_to_write(source, &meta, std::io::stdout())
@@ -237,6 +196,11 @@ async fn cmd_add_user(matches: &ArgMatches<'_>, cfg: Config) -> Result<()> {
if user.is_empty() || passwd.is_empty() {
return Err(anyhow::Error::msg("Invalid user or password"));
}
+
+ if user.len() > 20 {
+ return Err(anyhow::Error::msg("Username length should less than 20"))
+ }
+
let mut conn = sqlx::SqliteConnection::connect(cfg.get_database_location()).await?;
let items = sqlx::query(r#"SELECT 1 FROM "accounts" WHERE "user" = ? "#)
@@ -250,7 +214,7 @@ async fn cmd_add_user(matches: &ArgMatches<'_>, cfg: Config) -> Result<()> {
sqlx::query(r#"INSERT INTO "accounts" ("user", "password") VALUES (?, ?) "#)
.bind(user)
- .bind(FormData::get_string_sha256_value(&passwd)?)
+ .bind(FormData::get_string_argon2_hash(&passwd)?)
.execute(&mut conn)
.await?;
println!("Insert {} to database", user);
@@ -348,7 +312,7 @@ fn main() -> Result<()> {
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)}- {h({l})} - {m}{n}",
)))
- .build("/tmp/output.log")?;
+ .build(option_env!("RUST_LOG_FILE").unwrap_or("/tmp/auth.log"))?;
let config = log4rs::Config::builder()
.appender(Appender::builder().build("logfile", Box::new(logfile)))
@@ -438,3 +402,34 @@ fn main() -> Result<()> {
Ok(())
}
+
+mod test {
+ const PASSWORD: &str = "hunter2";
+ const ARGON2_HASH: &str = "$argon2id$v=19$m=4096,t=3,p=1$szYDnoQSVPmXq+RD2LneBw$fRETH//iCQuIX+SgjYPdZ9iIbM8gEy9fBjTJ/KFFJNM";
+ use argon2::{
+ password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
+ Argon2
+ };
+ use rand_core::OsRng;
+
+
+ #[test]
+ fn test_argon2() {
+ let passwd = PASSWORD.as_bytes();
+ let salt = SaltString::generate(&mut OsRng);
+
+ let argon2 = Argon2::default();
+
+ argon2.hash_password_simple(passwd, salt.as_ref()).unwrap();
+
+ }
+
+ #[test]
+ fn test_argon2_verify() {
+ let passwd = PASSWORD.as_bytes();
+ let parsed_hash = PasswordHash::new(ARGON2_HASH).unwrap();
+ let argon2 = Argon2::default();
+ assert!(argon2.verify_password(passwd, &parsed_hash).is_ok())
+ }
+}
+