diff --git a/config/configtemplate.json b/config/configtemplate.json
index 0c908af..5edb1bc 100644
--- a/config/configtemplate.json
+++ b/config/configtemplate.json
@@ -11,7 +11,9 @@
"redirect_url": ""
},
"password_complexity": 4,
- "username_complexity": 2
+ "password_regex": "\\s",
+ "username_complexity": 2,
+ "username_regex": "[^a-zA-Z0-9]"
},
"email": {
"login_usr": "",
diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md
index ef842cf..68eccd1 100644
--- a/docs/configuration/configuration.md
+++ b/docs/configuration/configuration.md
@@ -20,8 +20,10 @@ Make sure that all parameters are set correctly before starting the service.
| `signup.oauth.providers_enabled` | **Datatype:** List
**Default:** `[]`
Enabled OAuth Providers.
**Possible Providers**
- [**Google**](../advanced/oauth.md#google-oauth)
- [**GitHub**](../advanced/oauth.md#github-oauth)
|
| `signup.oauth.base_url` | **Datatype:** String
**Default:** `"http://localhost:3250/"`
The Base URL for the callback URL from OAuth Providers. When you host the service somewhere, you may want to change this to the official Domain instead of an IP. This is also the value you set when setting up your OAuth Providers. Make sure those values match. (Mostly the URL of this instance unless you are overriding the callback function) |
| `signup.oauth.redirect_url` | **Datatype:** String
**Default:** `""`
Redirect URL after the callback has been called and account was created or logged in. Only makes sense when using EZAuth Client Side or on the same domain. (Cookie may be set only for the EZAuth Domain if different than your frontend) |
-| `signup.password_complexity` | **Datatype:** Integer
**Default:** `4`
Password Complexity Requirement. Every higher value, includes all the previous ones too.
- **1**: Minimum 8 Characters
- **2**: Min. One Digit
- **3**: Min. One Capital Letter
- **4**: Min. One Special Character
|
-| `signup.username_complexity` | **Datatype:** Integer
**Default:** `2`
Username Complexity Requirement. Every higher value, includes all the previous ones too.
- **1**: Minimum 4 Characters
- **2**: Max. 20 Characters
|
+| `signup.password_complexity` | **Datatype:** Integer
**Default:** `4`
Password Complexity Requirement. Every higher value, includes all the previous ones too.
- **0**: No Restrictions
- **1**: Minimum 8 Characters
- **2**: Min. One Digit
- **3**: Min. One Capital Letter
- **4**: Min. One Special Character
|
+| `signup.password_regex` | **Datatype:** String
**Default:** `\\s`
Additional Regex for Password validation. Default doesn't allow whitespaces in password. Should be used when `signup.password_complexity` is set to 0. |
+| `signup.username_complexity` | **Datatype:** Integer
**Default:** `2`
Username Complexity Requirement. Every higher value, includes all the previous ones too.
- **0**: No Restrictions
- **1**: Minimum 4 Characters
- **2**: Max. 20 Characters
|
+| `signup.username_regex` | **Datatype:** String
**Default:** `[^a-zA-Z0-9]`
Additional Regex for Username validation. Default value restricts special characters. Should be used when `signup.username_complexity` is set to 0. |
### E-Mail Configuration
diff --git a/src/api/helpers/validators.py b/src/api/helpers/validators.py
new file mode 100644
index 0000000..82e0575
--- /dev/null
+++ b/src/api/helpers/validators.py
@@ -0,0 +1,52 @@
+from tools import SignupConfig
+import re
+import re
+import bcrypt
+from pydantic import SecretStr
+
+
+def username_check(cls, username: str) -> str:
+ if len(username) == 0:
+ raise ValueError("Username cannot be empty")
+ if len(username) < 4:
+ if SignupConfig.username_complexity >= 1:
+ raise ValueError("Username must be at least 4 characters long")
+ if len(username) > 20:
+ if SignupConfig.username_complexity >= 2:
+ raise ValueError("Username must be at most 20 characters long")
+
+ # Additional Regex Checks
+ if re.search(SignupConfig.username_regex, username) != None:
+ raise ValueError("Username must only contain letters and numbers")
+
+ return username
+
+
+def password_check_hash(cls, password: SecretStr) -> str:
+ # Validate Password
+ pswd = password.get_secret_value()
+ if len(pswd) == 0:
+ raise ValueError("Password cannot be empty")
+ if len(pswd) < 8 and SignupConfig.password_complexity >= 1:
+ raise ValueError("Make sure your password has at least 8 letters")
+ if re.search("[0-9]", pswd) is None and SignupConfig.password_complexity >= 2:
+ raise ValueError("Make sure your password has a number in it")
+ if re.search("[A-Z]", pswd) is None and SignupConfig.password_complexity >= 3:
+ raise ValueError("Make sure your password has a capital letter in it")
+ if (
+ re.search("[^a-zA-Z0-9]", pswd) is None
+ and SignupConfig.password_complexity >= 4
+ ):
+ raise ValueError("Make sure your password has a special character in it")
+ if len(pswd) > 50:
+ raise ValueError("Make sure your password is at most 50 characters")
+
+ # Additional Regex Checks
+ if re.search(SignupConfig.password_regex, pswd) != None:
+ raise ValueError("Password does not meet complexity requirements")
+
+ # Hash Password
+ hashed_pswd = bcrypt.hashpw(pswd.encode("utf-8"), bcrypt.gensalt(10)).decode(
+ "utf-8"
+ )
+ return hashed_pswd
diff --git a/src/api/model.py b/src/api/model.py
index 1ba4049..932ffb7 100644
--- a/src/api/model.py
+++ b/src/api/model.py
@@ -1,8 +1,6 @@
from pydantic import BaseModel, field_validator, EmailStr, SecretStr, ConfigDict, Field
from typing import Optional, List
-from tools import SignupConfig
-import re
-import bcrypt
+from api.helpers.validators import username_check, password_check_hash
class InternalUserQuery(BaseModel):
@@ -59,29 +57,8 @@ class PasswordHashed(BaseModel):
@field_validator("password")
@classmethod
- def password_check_hash(cls, password: SecretStr) -> str:
- # Validate Password
- pswd = password.get_secret_value()
- if len(pswd) == 0:
- raise ValueError("Password cannot be empty")
- if len(pswd) < 8 and SignupConfig.password_complexity >= 1:
- raise ValueError("Make sure your password has at least 8 letters")
- elif re.search("[0-9]", pswd) is None and SignupConfig.password_complexity >= 2:
- raise ValueError("Make sure your password has a number in it")
- elif re.search("[A-Z]", pswd) is None and SignupConfig.password_complexity >= 3:
- raise ValueError("Make sure your password has a capital letter in it")
- elif (
- re.search("[^a-zA-Z0-9]", pswd) is None
- and SignupConfig.password_complexity >= 4
- ):
- raise ValueError("Make sure your password has a special character in it")
- elif len(pswd) > 50:
- raise ValueError("Make sure your password is at most 50 characters")
- # Hash Password
- hashed_pswd = bcrypt.hashpw(pswd.encode("utf-8"), bcrypt.gensalt(10)).decode(
- "utf-8"
- )
- return hashed_pswd
+ def pswdcheck(cls, password: SecretStr) -> str:
+ return password_check_hash(cls, password)
class Username(BaseModel):
@@ -89,18 +66,8 @@ class Username(BaseModel):
@field_validator("username")
@classmethod
- def username_check(cls, username: str) -> str:
- if len(username) == 0:
- raise ValueError("Username cannot be empty")
- if len(username) < 4:
- if SignupConfig.username_complexity >= 1:
- raise ValueError("Username must be at least 4 characters long")
- if len(username) > 20:
- if SignupConfig.username_complexity >= 2:
- raise ValueError("Username must be at most 20 characters long")
- elif re.search("[^a-zA-Z0-9]", username) is not None:
- raise ValueError("Username must only contain letters and numbers")
- return username
+ def usrchck(cls, username: str) -> str:
+ return username_check(cls, username)
class DeleteAccountRequest(BaseModel):
diff --git a/src/api/signup_test.py b/src/api/signup_test.py
index da9bbb8..f2db999 100644
--- a/src/api/signup_test.py
+++ b/src/api/signup_test.py
@@ -138,15 +138,15 @@ def test_create_account_invalid_username_too_short():
"/signup",
json={
"password": "Kennwort1!",
- "email": "user1@example.com",
+ "email": "user13@example.com",
"username": "SAS",
},
)
+ assert response.status_code == 422
assert (
response.json()["detail"][0]["msg"]
== "Value error, Username must be at least 4 characters long"
)
- assert response.status_code == 422
def test_create_account_invalid_username_special_cases():
@@ -154,15 +154,15 @@ def test_create_account_invalid_username_special_cases():
"/signup",
json={
"password": "Kennwort1!",
- "email": "user1@example.com",
+ "email": "user14@example.com",
"username": "",
},
)
+ assert response.status_code == 422
assert (
response.json()["detail"][0]["msg"]
== "Value error, Username must only contain letters and numbers"
)
- assert response.status_code == 422
def test_create_account_invalid_username_too_long():
@@ -170,10 +170,11 @@ def test_create_account_invalid_username_too_long():
"/signup",
json={
"password": "Kennwort1!",
- "email": "user1@example.com",
- "username": "adsjfölksajdflkasjdlkfjasdlkfjlksajdflöksajdfölkjsadfkjasölkfj",
+ "email": "user12@example.com",
+ "username": "adsjfölksajdflkasjdlkfjasdlaakfjlksajdflöksajdfölkjsadfkjasölkfj",
},
)
+ assert response.status_code == 422
assert (
response.json()["detail"][0]["msg"]
== "Value error, Username must be at most 20 characters long"
diff --git a/src/tools/conf/SignupConfig.py b/src/tools/conf/SignupConfig.py
index 48fa56d..59cd80e 100644
--- a/src/tools/conf/SignupConfig.py
+++ b/src/tools/conf/SignupConfig.py
@@ -1,4 +1,5 @@
from .conf import config
+import re
class SignupConfig:
@@ -13,7 +14,9 @@ class SignupConfig:
config["signup"]["oauth"]["redirect_url"]
).removesuffix("/")
password_complexity: int = config["signup"]["password_complexity"]
+ password_regex: str = config["signup"]["password_regex"]
username_complexity: int = config["signup"]["username_complexity"]
+ username_regex: str = config["signup"]["username_regex"]
def validate_types(self) -> bool:
"""This is to Type Check the Configuration"""
@@ -56,12 +59,21 @@ def validate_types(self) -> bool:
)
if not all(isinstance(i, str) for i in self.oauth_providers):
raise ValueError("signup.oauth.providers_enabled must be a list of strings")
+
if not isinstance(self.password_complexity, int):
raise ValueError(
"signup.password_complexity must be an integer (got type {})".format(
type(self.password_complexity)
)
)
+
+ if not isinstance(self.password_regex, str):
+ raise ValueError(
+ "signup.password_regex must be a string (got type {})".format(
+ type(self.password_regex)
+ )
+ )
+
if not isinstance(self.username_complexity, int):
raise ValueError(
"signup.username_complexity must be an integer (got type {})".format(
@@ -69,6 +81,13 @@ def validate_types(self) -> bool:
)
)
+ if not isinstance(self.username_regex, str):
+ raise ValueError(
+ "signup.username_regex must be a string (got type {})".format(
+ type(self.username_regex)
+ )
+ )
+
def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.conf_code_expiry > 0:
@@ -95,10 +114,22 @@ def validate_values(self) -> bool:
raise ValueError(
f"signup.password_complexity must be between 1 and 4 (Check Docs), got {self.password_complexity}"
)
+ try:
+ re.compile(self.password_regex)
+ except re.error:
+ raise ValueError(
+ f"signup.password_regex is invalid, got {self.password_regex}"
+ )
if self.username_complexity not in range(1, 3):
raise ValueError(
f"signup.username_complexity must be 1 or 2 (Check Docs), got {self.username_complexity}"
)
+ try:
+ re.compile(self.username_regex)
+ except re.error:
+ raise ValueError(
+ f"signup.username_regex is invalid, got {self.username_regex}"
+ )
SignupConfig().validate_types()
diff --git a/src/tools/conf/testing_config.json b/src/tools/conf/testing_config.json
index da23728..636721e 100644
--- a/src/tools/conf/testing_config.json
+++ b/src/tools/conf/testing_config.json
@@ -11,7 +11,9 @@
"redirect_url": ""
},
"password_complexity": 4,
- "username_complexity": 2
+ "password_regex": "\\s",
+ "username_complexity": 2,
+ "username_regex": "[^a-zA-Z0-9]"
},
"email": {
"login_usr": "",