init: copied over stuff from shuttlecraft/identity

This commit is contained in:
Aravinth Manivannan 2021-01-02 12:33:35 +05:30
commit 846ddb52f8
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
9 changed files with 588 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

22
Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "argon2-creds"
version = "0.1.0"
authors = ["realaravinth <realaravinth@batsense.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "argon2_creds"
path = "src/lib.rs"
[dependencies]
rust-argon2 = "0.8.3"
derive_more = "0.99.11"
unicode-normalization = "0.1.6"
ammonia = "3.1.0"
validator = "0.12.0"
validator_derive = "0.12.0"
lazy_static = "1.4.0"
regex = { version = "1.3.9", features = [ "perf-inline", "perf-dfa", "perf-literal", "perf-cache", "perf"]}
rand = "0.8.0"

46
src/errors.rs Normal file
View file

@ -0,0 +1,46 @@
use std::convert::From;
use derive_more::{Display, Error};
use validator::ValidationErrors;
/// Errors that can occur when processing credentials
#[derive(Debug, PartialEq, Display, Clone, Error)]
#[cfg(not(tarpaulin_include))]
pub enum CredsError {
/// when the value passed contains profainity
#[display(fmt = "the value you passed contains profainity")]
ProfainityError,
/// when the value passed contains characters not present
/// in [UsernameCaseMapped](https://tools.ietf.org/html/rfc8265#page-7)
/// profile
#[display(fmt = "username_case_mapped violation")]
UsernameCaseMappedError,
/// when the value passed contains blacklisted words
/// see [blacklist](https://github.com/shuttlecraft/The-Big-Username-Blacklist)
#[display(fmt = "contains blacklisted words")]
BlacklistError,
/// email validation error
#[display(fmt = "The value passed in not an email")]
NotAnEmail,
/// Errors from argon2
#[display(fmt = "{}", _0)]
Argon2Error(argon2::Error),
}
impl From<argon2::Error> for CredsError {
fn from(e: argon2::Error) -> CredsError {
CredsError::Argon2Error(e)
}
}
impl From<ValidationErrors> for CredsError {
fn from(_: ValidationErrors) -> CredsError {
CredsError::NotAnEmail
}
}
pub type CredsResult<V> = std::result::Result<V, crate::errors::CredsError>;

32
src/filters/blacklist.rs Normal file
View file

@ -0,0 +1,32 @@
use crate::errors::{CredsError, CredsResult};
use lazy_static::lazy_static;
use regex::Regex;
pub fn forbidden(target: &str) -> CredsResult<()> {
static BLACKLIST: &'static str = r"[^!@#$%^&*]*(.htaccess|.htpasswd|.well-known|400|401|403|404|405|406|407|408|409|410|411|412|413|414|415|416|417|421|422|423|424|426|428|429|431|500|501|502|503|504|505|506|507|508|509|510|511|_domainkey|about|about-us|abuse|access|account|accounts|ad|add|admin|administration|administrator|ads|ads.txt|advertise|advertising|aes128-ctr|aes128-gcm|aes192-ctr|aes256-ctr|aes256-gcm|affiliate|affiliates|ajax|alert|alerts|alpha|amp|analytics|api|app|app-ads.txt|apps|asc|assets|atom|auth|authentication|authorize|autoconfig|autodiscover|avatar|backup|banner|banners|bbs|beta|billing|billings|blog|blogs|board|bookmark|bookmarks|broadcasthost|business|buy|cache|calendar|campaign|captcha|careers|cart|cas|categories|category|cdn|cgi|cgi-bin|chacha20-poly1305|change|channel|channels|chart|chat|checkout|clear|client|close|cloud|cms|com|comment|comments|community|compare|compose|config|connect|contact|contest|cookies|copy|copyright|count|cp|cpanel|create|crossdomain.xml|css|curve25519-sha256|customer|customers|customize|dashboard|db|deals|debug|delete|desc|destroy|dev|developer|developers|diffie-hellman-group-exchange-sha256|diffie-hellman-group14-sha1|disconnect|discuss|dns|dns0|dns1|dns2|dns3|dns4|docs|documentation|domain|download|downloads|downvote|draft|drop|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|edit|editor|email|enterprise|error|errors|event|events|example|exception|exit|explore|export|extensions|false|family|faq|faqs|favicon.ico|features|feed|feedback|feeds|file|files|filter|follow|follower|followers|following|fonts|forgot|forgot-password|forgotpassword|form|forms|forum|forums|friend|friends|ftp|get|git|go|graphql|group|groups|guest|guidelines|guides|head|header|help|hide|hmac-sha|hmac-sha1|hmac-sha1-etm|hmac-sha2-256|hmac-sha2-256-etm|hmac-sha2-512|hmac-sha2-512-etm|home|host|hosting|hostmaster|htpasswd|http|httpd|https|humans.txt|icons|images|imap|img|import|index|info|insert|investors|invitations|invite|invites|invoice|is|isatap|issues|it|jobs|join|js|json|keybase.txt|learn|legal|license|licensing|like|limit|live|load|local|localdomain|localhost|lock|login|logout|lost-password|m|mail|mail0|mail1|mail2|mail3|mail4|mail5|mail6|mail7|mail8|mail9|mailer-daemon|mailerdaemon|map|marketing|marketplace|master|me|media|member|members|message|messages|metrics|mis|mobile|moderator|modify|more|mx|mx1|my|net|network|new|news|newsletter|newsletters|next|nil|no-reply|nobody|noc|none|noreply|notification|notifications|ns|ns0|ns1|ns2|ns3|ns4|ns5|ns6|ns7|ns8|ns9|null|oauth|oauth2|offer|offers|online|openid|order|orders|overview|owa|owner|page|pages|partners|passwd|password|pay|payment|payments|photo|photos|pixel|plans|plugins|policies|policy|pop|pop3|popular|portal|portfolio|post|postfix|postmaster|poweruser|preferences|premium|press|previous|pricing|print|privacy|privacy-policy|private|prod|product|production|profile|profiles|project|projects|public|purchase|put|quota|redirect|reduce|refund|refunds|register|registration|remove|replies|reply|report|request|request-password|reset|reset-password|response|return|returns|review|reviews|robots.txt|root|rootuser|rsa-sha2-2|rsa-sha2-512|rss|rules|sales|save|script|sdk|search|secure|security|select|services|session|sessions|settings|setup|share|shift|shop|signin|signup|site|sitemap|sites|smtp|sort|source|sql|ssh|ssh-rsa|ssl|ssladmin|ssladministrator|sslwebmaster|stage|staging|stat|static|statistics|stats|status|store|style|styles|stylesheet|stylesheets|subdomain|subscribe|sudo|super|superuser|support|survey|sync|sysadmin|system|tablet|tag|tags|team|telnet|terms|terms-of-use|test|testimonials|theme|themes|today|tools|topic|topics|tour|training|translate|translations|trending|trial|true|umac-128|umac-128-etm|umac-64|umac-64-etm|undefined|unfollow|unlike|unsubscribe|update|upgrade|usenet|user|username|users|uucp|var|verify|video|view|void|vote|vpn|webmail|webmaster|website|widget|widgets|wiki|wpad|write|www|www-data|www1|www2|www3|www4|you|yourname|yourusername|zlib)[^!@#$%^&*]*";
lazy_static! {
static ref RE_BLACKLIST: Regex =
Regex::new(BLACKLIST).expect("coudln't setup blacklist filter");
}
if RE_BLACKLIST.is_match(&target) {
Err(CredsError::BlacklistError)
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_forbidden() {
let illegal = "zlib";
let legal = "rust";
let illegal2 = ".htaccess_yolo";
assert_eq!(forbidden(legal), Ok(()));
assert_eq!(forbidden(illegal), Err(CredsError::BlacklistError));
assert_eq!(forbidden(illegal2), Err(CredsError::BlacklistError));
}
}

24
src/filters/mod.rs Normal file
View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2020 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
mod blacklist;
mod profainity;
mod user_case_mapped;
pub use blacklist::forbidden;
pub use profainity::beep;
pub use user_case_mapped::filter;

51
src/filters/profainity.rs Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

175
src/lib.rs Normal file
View file

@ -0,0 +1,175 @@
pub mod errors;
mod filters;
use ammonia::clean;
use argon2::{self, Config, ThreadMode, Variant, Version};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use unicode_normalization::UnicodeNormalization;
use validator::Validate;
use validator_derive::Validate;
use errors::*;
pub use filters::{beep, filter, forbidden};
#[derive(Debug, Clone, PartialEq)]
pub struct UnvalidatedRegisterCreds {
pub username: String,
pub email_id: Option<String>,
pub password: String,
}
#[derive(Debug, Default, Clone, PartialEq, Validate)]
pub struct RegisterCreds {
pub username: String,
#[validate(email)]
pub email_id: Option<String>,
pub password: String,
}
impl UnvalidatedRegisterCreds {
pub fn process(&self) -> CredsResult<RegisterCreds> {
let creds = RegisterCreds::default()
.set_email(&self.email_id)?
.set_username(&self.username)
.validate_fields()?
.set_password(&self.password)?
.build();
Ok(creds)
}
}
impl RegisterCreds {
pub fn set_username<'a>(&'a mut self, username: &str) -> &'a mut Self {
self.username = clean(username)
.to_lowercase()
.nfc()
.collect::<String>()
.trim()
.to_owned();
self
}
pub fn set_email<'a>(&'a mut self, email_id: &Option<String>) -> CredsResult<&'a mut Self> {
if let Some(email) = email_id {
self.email_id = Some(email.trim().to_owned());
self.validate()?;
}
Ok(self)
}
pub fn validate_fields<'a>(&'a mut self) -> CredsResult<&'a mut Self> {
filter(&self.username)?;
forbidden(&self.username)?;
beep(&self.username)?;
Ok(self)
}
pub fn set_password<'a>(&'a mut self, password: &str) -> CredsResult<&'a mut Self> {
// let config = Config {
// variant: Variant::Argon2i,
// version: Version::Version13,
// mem_cost: SETTINGS.password_difficulty.mem_cost,
// time_cost: SETTINGS.password_difficulty.time_cost,
// lanes: SETTINGS.password_difficulty.lanes,
// thread_mode: ThreadMode::Parallel,
// secret: &[],
// ad: &[],
// hash_length: 32,
// };
let config = Config::default();
let mut rng = thread_rng();
let salt: String = std::iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(32)
.collect();
self.password = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
Ok(self)
}
pub fn build(&mut self) -> Self {
self.to_owned()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utils_register_builer() {
let registered_creds = RegisterCreds::default()
.set_password("password")
.unwrap()
.set_username("realaravinth")
.set_email(&Some("batman@we.net".into()))
.unwrap()
.validate_fields()
.unwrap()
.build();
assert_eq!(registered_creds.username, "realaravinth");
assert_eq!(registered_creds.email_id, Some("batman@we.net".into()));
}
#[test]
fn utils_register_email_err() {
let mut email_err = RegisterCreds::default()
.set_password("password")
.unwrap()
.set_username("realaravinth")
.build();
assert_eq!(
email_err.set_email(&Some("sdfasdf".into())),
Err(CredsError::NotAnEmail)
);
}
#[test]
fn utils_create_new_organisation() {
let password = "somepassword";
let org = RegisterCreds::default()
.set_email(&Some("batman@we.net".into()))
.unwrap()
.set_username("Realaravinth")
.validate_fields()
.unwrap()
.set_password(password)
.unwrap()
.build();
assert_eq!(org.username, "realaravinth");
assert!(
argon2::verify_encoded(&org.password, password.as_bytes()).unwrap(),
"verify hahsing"
);
}
#[test]
fn utils_create_new_profane_organisation() {
let mut profane_org = RegisterCreds::default();
profane_org.set_username("fuck");
assert_eq!(
profane_org.validate_fields(),
Err(CredsError::ProfainityError)
);
}
#[test]
fn utils_create_new_forbidden_organisation() {
let mut forbidden_org = RegisterCreds::default()
.set_username("htaccessasnc")
.build();
assert_eq!(
forbidden_org.validate_fields(),
Err(CredsError::BlacklistError)
);
}
}

207
src/payload.rs Normal file
View file

@ -0,0 +1,207 @@
/*
* Copyright (C) 2020 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use ammonia::clean;
use argon2::{self, Config, ThreadMode, Variant, Version};
use pow_sha256::PoW;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use unicode_normalization::UnicodeNormalization;
use validator::Validate;
use validator_derive::Validate;
use super::{beep, filter, forbidden};
use crate::errors::*;
use crate::SETTINGS;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct UnvalidatedRegisterCreds {
pub username: String,
pub email_id: Option<String>,
pub password: String,
pub pow: PoW<Vec<u8>>,
}
#[derive(Debug, Default, Clone, PartialEq, Validate, Deserialize, Serialize)]
pub struct RegisterCreds {
pub username: String,
#[validate(email)]
pub email_id: Option<String>,
pub password: String,
}
//impl AsRef<RegisterCreds> for RegisterCreds {
// fn as_ref(&self) -> &RegisterCreds {
// &self
// }
//}
//
//impl AsMut<RegisterCreds> for RegisterCreds {
// fn as_mut<'a>(&'a mut self) -> &'a mut Self {
// &mut self
// }
//}
impl UnvalidatedRegisterCreds {
pub fn process(&self) -> ServiceResult<RegisterCreds> {
let creds = RegisterCreds::new()
.set_email(&self.email_id)?
.set_username(&self.username)
.validate_fields()?
.set_password(&self.password)?
.build();
Ok(creds)
}
}
impl RegisterCreds {
// pub fn create_register_creds<'a>(&'a mut self) -> ServiceResult<Self> {
// let creds = RegisterCreds::new()
// .set_email(&u_creds.email_id)?
// .set_username(&u_creds.username)
// .validate_fields()?
// .set_password(&u_creds.password)?
// .build();
// Ok(creds)
// }
fn new() -> Self {
let registered_creds: RegisterCreds = Default::default();
registered_creds
}
fn set_username<'a>(&'a mut self, username: &str) -> &'a mut Self {
self.username = clean(username)
.to_lowercase()
.nfc()
.collect::<String>()
.trim()
.to_owned();
self
}
fn set_email<'a>(&'a mut self, email_id: &Option<String>) -> ServiceResult<&'a mut Self> {
if let Some(email) = email_id {
self.email_id = Some(email.trim().to_owned());
self.validate()?;
}
Ok(self)
}
fn validate_fields<'a>(&'a mut self) -> ServiceResult<&'a mut Self> {
filter(&self.username)?;
forbidden(&self.username)?;
beep(&self.username)?;
Ok(self)
}
fn set_password<'a>(&'a mut self, password: &str) -> ServiceResult<&'a mut Self> {
let config = Config {
variant: Variant::Argon2i,
version: Version::Version13,
mem_cost: SETTINGS.password_difficulty.mem_cost,
time_cost: SETTINGS.password_difficulty.time_cost,
lanes: SETTINGS.password_difficulty.lanes,
thread_mode: ThreadMode::Parallel,
secret: &[],
ad: &[],
hash_length: 32,
};
let salt: String = thread_rng().sample_iter(&Alphanumeric).take(32).collect();
self.password = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
Ok(self)
}
fn build(&mut self) -> Self {
self.to_owned()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utils_register_builer() {
let registered_creds = RegisterCreds::new()
.set_password("password")
.unwrap()
.set_username("realaravinth")
.set_email(&Some("batman@we.net".into()))
.unwrap()
.validate_fields()
.unwrap()
.build();
assert_eq!(registered_creds.username, "realaravinth");
assert_eq!(registered_creds.email_id, Some("batman@we.net".into()));
}
#[test]
fn utils_register_email_err() {
let mut email_err = RegisterCreds::new()
.set_password("password")
.unwrap()
.set_username("realaravinth")
.build();
assert_eq!(
email_err.set_email(&Some("sdfasdf".into())),
Err(ServiceError::NotAnEmail)
);
}
#[test]
fn utils_create_new_organisation() {
let password = "somepassword";
let org = RegisterCreds::new()
.set_email(&Some("batman@we.net".into()))
.unwrap()
.set_username("Realaravinth")
.validate_fields()
.unwrap()
.set_password(password)
.unwrap()
.build();
assert_eq!(org.username, "realaravinth");
assert!(
argon2::verify_encoded(&org.password, password.as_bytes()).unwrap(),
"verify hahsing"
);
}
#[test]
fn utils_create_new_profane_organisation() {
let mut profane_org = RegisterCreds::new();
profane_org.set_username("fuck");
assert_eq!(
profane_org.validate_fields(),
Err(ServiceError::UsernameError)
);
}
#[test]
fn utils_create_new_forbidden_organisation() {
let mut forbidden_org = RegisterCreds::new().set_username("htaccessasnc").build();
assert_eq!(
forbidden_org.validate_fields(),
Err(ServiceError::UsernameError)
);
}
}