Skip to content

Commit

Permalink
Merge pull request #18 from JohnGrubba/dev
Browse files Browse the repository at this point in the history
Config Validator Added
  • Loading branch information
JohnGrubba authored Jul 26, 2024
2 parents 973d5fb + 063fa1a commit 9cf4088
Show file tree
Hide file tree
Showing 19 changed files with 424 additions and 113 deletions.
11 changes: 8 additions & 3 deletions src/api/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ def password_check_hash(cls, password: SecretStr) -> str:
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:
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")
Expand Down Expand Up @@ -98,9 +101,11 @@ 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 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")
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
2 changes: 1 addition & 1 deletion src/api/oauth_providers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from fastapi import APIRouter
from tools.conf import SignupConfig
from tools import SignupConfig

router = APIRouter(
prefix="/oauth",
Expand Down
10 changes: 8 additions & 2 deletions src/api/oauth_providers/github.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Request, BackgroundTasks, Response, HTTPException
from fastapi.responses import RedirectResponse
from tools.conf import SignupConfig, SessionConfig
from tools import SignupConfig, SessionConfig
import json
import requests
import re
Expand All @@ -13,7 +13,13 @@
from crud.sessions import create_login_session
from api.model import LoginResponse

github_cnf = json.load(open("/src/app/config/github_client_secret.env.json"))
try:
github_cnf = json.load(open("/src/app/config/github_client_secret.env.json"))
except FileNotFoundError:
raise FileNotFoundError(
"GitHub OAuth Config File not found (github_client_secret.env.json).\
Please disable this OAuth Provider, or create the file as described in the Docs."
)
REDIRECT_URI = SignupConfig.oauth_base_url + "/oauth/github/callback"
CLIENT_ID = github_cnf["client_id"]
CLIENT_SECRET = github_cnf["client_secret"]
Expand Down
24 changes: 15 additions & 9 deletions src/api/oauth_providers/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@
)

# Initialize Googles OAuth Flow
flow = Flow.from_client_secrets_file(
client_secrets_file="/src/app/config/google_client_secret.env.json",
scopes=[
"https://www.googleapis.com/auth/userinfo.email",
"openid",
"https://www.googleapis.com/auth/userinfo.profile",
],
redirect_uri=SignupConfig.oauth_base_url + "/oauth/google/callback",
)
try:
flow = Flow.from_client_secrets_file(
client_secrets_file="/src/app/config/google_client_secret.env.json",
scopes=[
"https://www.googleapis.com/auth/userinfo.email",
"openid",
"https://www.googleapis.com/auth/userinfo.profile",
],
redirect_uri=SignupConfig.oauth_base_url + "/oauth/google/callback",
)
except FileNotFoundError:
raise FileNotFoundError(
"Google OAuth Config File not found (google_client_secret.env.json).\
Please disable this OAuth Provider, or create the file as described in the Docs."
)


@router.get("/login")
Expand Down
2 changes: 1 addition & 1 deletion src/api/profile.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Response
from tools.conf import AccountFeaturesConfig, SignupConfig
from tools import AccountFeaturesConfig, SignupConfig
from api.model import ResetPasswordRequest, ConfirmEmailRequest, DeleteAccountRequest
import json
import bcrypt
Expand Down
2 changes: 1 addition & 1 deletion src/crud/sessions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uuid
from tools import sessions_collection, users_collection
import datetime
from tools.conf import SessionConfig
from tools import SessionConfig
from fastapi import Request
from user_agents import parse
from bson import ObjectId, errors
Expand Down
12 changes: 6 additions & 6 deletions src/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from .db import users_collection, sessions_collection, bson_to_json, r
from .conf import (
SignupConfig,
EmailConfig,
SessionConfig,
InternalConfig,
AccountFeaturesConfig,
default_signup_fields,
insecure_cols,
SecurityConfig,
default_signup_fields,
AccountFeaturesConfig,
InternalConfig,
SessionConfig,
EmailConfig,
SignupConfig,
)
from .mail import send_email, broadcast_emails
from .confirmation_codes import all_ids, regenerate_ids
Expand Down
88 changes: 0 additions & 88 deletions src/tools/conf.py

This file was deleted.

90 changes: 90 additions & 0 deletions src/tools/conf/AccountFeaturesConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from .conf import config, not_updateable_cols_internal


class AccountFeaturesConfig:
enable_reset_pswd: bool = config["account_features"]["enable_reset_pswd"]
reset_pswd_conf_mail: bool = config["account_features"]["reset_pswd_conf_mail"]
enable_2fa: bool = config["account_features"]["2fa"]["enable"]
issuer_name_2fa: str = config["account_features"]["2fa"]["issuer_name"]
issuer_image_url_2fa: str = config["account_features"]["2fa"]["issuer_image_url"]
qr_code_endpoint_2fa: bool = config["account_features"]["2fa"]["qr_endpoint"]

if not isinstance(config["account_features"]["allow_add_fields_on_signup"], list):
raise ValueError(
"account_features.allow_add_fields_on_signup must be a list (got type {})".format(
type(config["account_features"]["allow_add_fields_on_signup"])
)
)

allow_add_fields_on_signup: set[str] = set(
config["account_features"]["allow_add_fields_on_signup"]
) - set(not_updateable_cols_internal)

if not isinstance(config["account_features"]["allow_add_fields_patch_user"], list):
raise ValueError(
"account_features.allow_add_fields_patch_user must be a list (got type {})".format(
type(config["account_features"]["allow_add_fields_patch_user"])
)
)

allow_add_fields_patch_user: set[str] = set(
config["account_features"]["allow_add_fields_patch_user"]
) - set(not_updateable_cols_internal)
allow_deletion: bool = config["account_features"]["allow_deletion"]
deletion_pending_minutes: int = config["account_features"][
"deletion_pending_minutes"
]

def validate_types(self) -> bool:
"""This is to Type Check the Configuration"""
if not isinstance(self.enable_reset_pswd, bool):
raise ValueError(
"account_features.enable_reset_pswd must be a boolean (got type {})".format(
type(self.enable_reset_pswd)
)
)
if not isinstance(self.reset_pswd_conf_mail, bool):
raise ValueError(
"account_features.reset_pswd_conf_mail must be a boolean (got type {})".format(
type(self.reset_pswd_conf_mail)
)
)
if not isinstance(self.enable_2fa, bool):
raise ValueError(
"account_features.2fa.enable must be a boolean (got type {})".format(
type(self.enable_2fa)
)
)
if not isinstance(self.issuer_name_2fa, str):
raise ValueError(
"account_features.2fa.issuer_name must be a string (got type {})".format(
type(self.issuer_name_2fa)
)
)
if not isinstance(self.issuer_image_url_2fa, str):
raise ValueError(
"account_features.2fa.issuer_image_url must be a string (got type {})".format(
type(self.issuer_image_url_2fa)
)
)
if not isinstance(self.qr_code_endpoint_2fa, bool):
raise ValueError(
"account_features.2fa.qr_endpoint must be a boolean (got type {})".format(
type(self.qr_code_endpoint_2fa)
)
)
if not isinstance(self.allow_deletion, bool):
raise ValueError(
"account_features.allow_deletion must be a boolean (got type {})".format(
type(self.allow_deletion)
)
)
if not isinstance(self.deletion_pending_minutes, int):
raise ValueError(
"account_features.deletion_pending_minutes must be an integer (got type {})".format(
type(self.deletion_pending_minutes)
)
)


AccountFeaturesConfig().validate_types()
45 changes: 45 additions & 0 deletions src/tools/conf/EmailConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from .conf import config


class EmailConfig:
login_usr: str = config["email"]["login_usr"]
login_pwd: str = config["email"]["login_pwd"]
sender_email: str = config["email"]["sender_email"]
smtp_host: str = config["email"]["smtp_host"]
smtp_port: int = config["email"]["smtp_port"]

def validate_types(self) -> None:
"""This is to Type Check the Configuration"""
if not isinstance(self.login_usr, str):
raise ValueError(
"email.login_usr must be a string (got type {})".format(
type(self.login_usr)
)
)
if not isinstance(self.login_pwd, str):
raise ValueError(
"email.login_pwd must be a string (got type {})".format(
type(self.login_pwd)
)
)
if not isinstance(self.sender_email, str):
raise ValueError(
"email.sender_email must be a string (got type {})".format(
type(self.sender_email)
)
)
if not isinstance(self.smtp_host, str):
raise ValueError(
"email.smtp_host must be a string (got type {})".format(
type(self.smtp_host)
)
)
if not isinstance(self.smtp_port, int):
raise ValueError(
"email.smtp_port must be an integer (got type {})".format(
type(self.smtp_port)
)
)


EmailConfig().validate_types()
41 changes: 41 additions & 0 deletions src/tools/conf/InternalConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from .conf import config, insecure_cols, not_updateable_cols_internal
from collections import ChainMap


class InternalConfig:
internal_api_key: str = config["internal"]["internal_api_key"]
# Type Check here because calculations need to be done immediately
if not isinstance(config["internal"]["internal_columns"], list):
raise ValueError(
"internal.internal_columns must be a list (got type {})".format(
type(config["internal"]["internal_columns"])
)
)
internal_columns: dict = dict(
ChainMap(*[{col: 0} for col in set(config["internal"]["internal_columns"])])
)
internal_columns.update(insecure_cols)
# Insecure Cols + Internal Cols can't be updated by the user
if not isinstance(config["internal"]["not_updateable_columns"], list):
raise ValueError(
"internal.not_updateable_columns must be a list (got type {})".format(
type(config["internal"]["not_updateable_columns"])
)
)
not_updateable_columns: set = set(
config["internal"]["not_updateable_columns"]
+ list(internal_columns.keys())
+ not_updateable_cols_internal
)

def validate_types(self) -> bool:
"""This is to Type Check the Configuration"""
if not isinstance(self.internal_api_key, str):
raise ValueError(
"internal.internal_api_key must be a string (got type {})".format(
type(self.internal_api_key)
)
)


InternalConfig().validate_types()
Loading

0 comments on commit 9cf4088

Please sign in to comment.