Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTPS Support and Config Value Validation #19

Merged
merged 2 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/advanced/ssl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
If you want to deploy EZAuth you may want to run it via `HTTPS` instead of `HTTP`. This can be easily achieved with EZAuth, by putting certificates in the `config/ssl` directory. The certificates have to be named **`cert.pem` and `key.pem`**. The `cert.pem` file should contain the certificate and the intermediate certificate, while the `key.pem` file should contain the private key.

EZAuth will automatically detect the certificates and run on `HTTPS` instead of `HTTP`. If you want to run EZAuth on `HTTP` again, just remove the certificates from the `config/ssl` directory.

## Self Signing with [MKCert](https://github.com/FiloSottile/mkcert)

If you want to test EZAuth with self-signed certificates, you can use [MKCert](https://github.com/FiloSottile/mkcert).

!!! warning "SSL Certificates"
Make sure that the certificates are valid and not self-signed. Browsers will not accept self-signed certificates and will show a warning to the user. Use [Let's Encrypt](https://letsencrypt.org/) or a similar service to get valid certificates.

To generate a self-signed certificate with MKCert, [install MKCert](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation) and follow the instructions below.

=== "Debian/Ubuntu"
Run the following commands to generate a Certificate with MKCert

```bash
cd config
mkdir -p ssl
cd ssl
mkcert yourdomain.com localhost 127.0.0.1
```

=== "Windows"
Create a new folder in the `config` directory called `ssl`. Open a command prompt and navigate to the `config/ssl` directory. Run the following command to generate a Certificate with MKCert

```sh
mkcert yourdomain.com localhost 127.0.0.1
```

After running the command, you will see two files in the `config/ssl` directory: `yourdomain.com.pem` and `yourdomain.com-key.pem`. Rename the files to `cert.pem` and `key.pem` respectively. Then restart the EZAuth service to apply the changes.
4 changes: 2 additions & 2 deletions docs/configuration/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Make sure that all parameters are set correctly before starting the service.
| `signup.conf_code_expiry` | **Datatype:** Integer <br> **Default:** `5` <br> The time in minutes until the confirmation code expires. |
| `signup.conf_code_complexity` | **Datatype:** Integer <br> **Default:** `1` <br> The complexity of the confirmation code. <br> **Possible Values** <br> <ul><li>**1**: `4 Digit Numeric`</li><li>**2**: `6 Digit Numeric`</li><li>**3**: `4 Characters`</li><li>**4**: `6 Characters`</li></ul> |
| `signup.enable_welcome_email` | **Datatype:** Boolean <br> **Default:** `false` <br> Enable or disable the welcome E-Mail for new users. |
| `signup.oauth.providers_enabled` | **Datatype:** List <br> **Default:** `[]` <br> Enabled OAuth Providers. <br> **Possible Providers**<ul><li>[**google**](../advanced/oauth.md#google-oauth)</li></ul> |
| `signup.oauth.providers_enabled` | **Datatype:** List <br> **Default:** `[]` <br> Enabled OAuth Providers. <br> **Possible Providers**<ul><li>[**Google**](../advanced/oauth.md#google-oauth)</li><li>[**GitHub**](../advanced/oauth.md#github-oauth)</li></ul> |
| `signup.oauth.base_url` | **Datatype:** String <br> **Default:** `"http://localhost:3250/"` <br> 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. |
| `signup.password_complexity` | **Datatype:** Integer <br> **Default:** `4` <br> Password Complexity Requirement. Every higher value, includes all the previous ones too.<br> <ul><li>**1**: Minimum 8 Characters</li><li>**2**: Min. One Digit</li><li>**3**: Min. One Capital Letter</li><li>**4**: Min. One Special Character</li></ul> |
| `signup.username_complexity` | **Datatype:** Integer <br> **Default:** `2` <br> Username Complexity Requirement. Every higher value, includes all the previous ones too.<br> <ul><li>**1**: Minimum 4 Characters</li><li>**2**: Max. 20 Characters</li></ul> |
Expand All @@ -32,7 +32,7 @@ Make sure that all parameters are set correctly before starting the service.
|------------|-------------|
| `email.login_usr` | **Datatype:** String <br> **Default:** `""` <br> E-Mail Login Identifier (mostly the E-Mail itself). <br> **Example:** [email protected] |
| `email.login_pwd` | **Datatype:** String <br> **Default:** `""` <br> E-Mail Login Password. |
| `email.sender_email` | **Datatype:** String <br> **Default:** `""` <br> E-Mail address from which the E-Mails are sent (mostly the same as `email.login_usr`) |
| `email.sender_email` | **Datatype:** String <br> **Default:** `""` <br> E-Mail address from which the E-Mails are sent. Can be changed to something like `EZAuth <[email protected]` to achieve a nicer looking E-Mail. |
| `email.smtp_host` | **Datatype:** String <br> **Default:** `""` <br> SMTP Host for the E-Mail server. <br> **Example:** `smtp.gmail.com` |
| `email.smtp_port` | **Datatype:** Integer <br> **Default:** `465` <br> SMTP Port for the E-Mail server. |

Expand Down
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ nav:
- Advanced E-Mail Templating: advanced/email_templates.md
- Further Customization: advanced/further_custom.md
- OAuth: advanced/oauth.md
- SSL / HTTPS: advanced/ssl.md
theme:
name: material
logo: "ezauth_logo.png"
Expand Down Expand Up @@ -54,3 +55,6 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tilde
6 changes: 5 additions & 1 deletion src/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ RUN pip install --no-cache-dir --upgrade -r /src/app/requirements.txt

COPY . /src/app

CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "80", "--log-level", "critical"]
# Add a startup script
COPY start.sh /src/app/start.sh
RUN chmod +x /src/app/start.sh

CMD ["/src/app/start.sh"]
7 changes: 7 additions & 0 deletions src/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

if [ -f "/src/app/config/ssl/key.pem" ] && [ -f "/src/app/config/ssl/cert.pem" ]; then
uvicorn api.main:app --host 0.0.0.0 --port 80 --log-level critical --ssl-keyfile /src/app/config/ssl/key.pem --ssl-certfile /src/app/config/ssl/cert.pem
else
uvicorn api.main:app --host 0.0.0.0 --port 80 --log-level critical
fi
12 changes: 12 additions & 0 deletions src/tools/conf/AccountFeaturesConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,17 @@ def validate_types(self) -> bool:
)
)

def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.issuer_name_2fa:
raise ValueError("account_features.2fa.issuer_name must not be empty")
if not self.deletion_pending_minutes > 0:
raise ValueError(
"account_features.deletion_pending_minutes must be a positive integer (got {})".format(
self.deletion_pending_minutes
)
)


AccountFeaturesConfig().validate_types()
AccountFeaturesConfig().validate_values()
23 changes: 23 additions & 0 deletions src/tools/conf/EmailConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,28 @@ def validate_types(self) -> None:
)
)

def validate_values(self) -> None:
"""This is to Value Check the Configuration"""
if self.sender_email and "@" not in self.sender_email:
raise ValueError(
"email.sender_email must contain a valid email address.\
This also works: `Test <[email protected]>` (got {})".format(
self.sender_email
)
)
if self.smtp_host.startswith("http"):
raise ValueError(
"email.smtp_host must not contain `http` or `https` or `smtp` (got {})".format(
self.smtp_host
)
)
if not 0 < self.smtp_port < 65536:
raise ValueError(
"email.smtp_port must be a valid port number (got {})".format(
self.smtp_port
)
)


EmailConfig().validate_types()
EmailConfig().validate_values()
10 changes: 10 additions & 0 deletions src/tools/conf/InternalConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,15 @@ def validate_types(self) -> bool:
)
)

def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.internal_api_key:
raise ValueError("internal.internal_api_key must not be empty")
if len(self.internal_api_key) < 8:
raise ValueError(
"internal.internal_api_key must be at least 8 characters long"
)


InternalConfig().validate_types()
InternalConfig().validate_values()
22 changes: 22 additions & 0 deletions src/tools/conf/SecurityConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,27 @@ def validate_types(self) -> bool:
)
)

def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.max_login_attempts >= 0:
raise ValueError(
"security.max_login_attempts must be a positive integer (got {})".format(
self.max_login_attempts
)
)
if not self.login_timeout > 0:
raise ValueError(
"security.login_timeout must be a positive integer (got {})".format(
self.login_timeout
)
)
if not self.expire_unfinished_timeout > 0:
raise ValueError(
"security.expire_unfinished_timeout must be a positive integer (got {})".format(
self.expire_unfinished_timeout
)
)


SecurityConfig().validate_types()
SecurityConfig().validate_values()
26 changes: 26 additions & 0 deletions src/tools/conf/SessionConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,31 @@ def validate_types(self) -> bool:
)
)

def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.session_expiry_seconds > 0:
raise ValueError(
"session.session_expiry_seconds must be a positive integer (got {})".format(
self.session_expiry_seconds
)
)
if not self.max_session_count > 0:
raise ValueError(
"session.max_session_count must be a positive integer (got {})".format(
self.max_session_count
)
)
if self.auto_cookie and self.auto_cookie_name == "":
raise ValueError(
"session.auto_cookie_name must not be an empty string, when enabled"
)
if self.cookie_samesite not in ["strict", "lax", "none"]:
raise ValueError(
"session.cookie_samesite must be one of 'strict', 'lax', or 'none' (got {})".format(
self.cookie_samesite
)
)


SessionConfig().validate_types()
SessionConfig().validate_values()
28 changes: 28 additions & 0 deletions src/tools/conf/SignupConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,33 @@ def validate_types(self) -> bool:
)
)

def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.conf_code_expiry > 0:
raise ValueError(
f"signup.conf_code_expiry must be greater than 0, got {self.conf_code_expiry}"
)
if self.conf_code_complexity not in range(1, 5):
raise ValueError(
f"signup.conf_code_complexity must be between 1 and 4 (Check Docs), got {self.conf_code_complexity}"
)
if (
len(self.oauth_providers) > 0
and not self.oauth_base_url
and "http" not in self.oauth_base_url
):
raise ValueError(
f"signup.oauth.base_url cannot be empty or malformed when OAuth is enabled, got {self.oauth_base_url}"
)
if self.password_complexity not in range(1, 5):
raise ValueError(
f"signup.password_complexity must be between 1 and 4 (Check Docs), got {self.password_complexity}"
)
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}"
)


SignupConfig().validate_types()
SignupConfig().validate_values()
2 changes: 1 addition & 1 deletion src/tools/conf/testing_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"login_usr": "",
"login_pwd": "",
"sender_email": "",
"smtp_host": "",
"smtp_host": "smtp.gmail.com",
"smtp_port": 465
},
"session": {
Expand Down
Loading