initial commit

This commit is contained in:
tylen 2024-10-29 21:00:35 +00:00
commit 3d8ba6d3be
13 changed files with 479 additions and 0 deletions

162
.gitignore vendored Normal file
View File

@ -0,0 +1,162 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# dungeon
A Dungeon for your API keys, passwords, passphrases, secrets and what not!
Dungeon is a a simple open-source application to manage your secrets either
through a Browser or a Cli. Simplicity, security and reliability are key
factors followed while developing this app.

View File

@ -0,0 +1,12 @@
FROM python:3.11-slim-buster
ENV PYTHONPATH=/usr/local/lib
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
CMD ["/bin/sh", "entrypoint.sh"]

5
backend/README.md Normal file
View File

@ -0,0 +1,5 @@
# Dungeon Backend Source
This directory is intented for the source code of the Dungeon's backend service.

6
backend/entrypoint.sh Executable file
View File

@ -0,0 +1,6 @@
#! /bin/sh
export DB_PASSWORD="UJ4tDsE39bT!"
export DB_SERVER="192.168.100.95"
python3 -m flask --app /app/src/server.py run --host=0.0.0.0

3
backend/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
flask==3.0.2
pymssql==2.3.0
argon2-cffi

13
backend/run.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
# Generate a random name for the container using uuidgen
container_name=$(uuidgen)
# Build the Docker image with your project code and unit tests
docker build -t dungeon-backend -f Dockerfile.backend .
# Run the Docker container with the unit tests and attach the output
docker run --tty --rm --name "$container_name" -p 5000:5000 dungeon-backend
# Exit the script with the exit code of the container
exit $?

View File

@ -0,0 +1,110 @@
#!/usr/bin/env python
# encoding: utf-8
'''
auth.py is the functional api generator for users
'''
from flask import jsonify, make_response, request
import uuid
from argon2 import PasswordHasher, exceptions
USER_KEYS = ['email', 'password']
class User():
def __init__(self, json):
self.email = None
self.password = None
self.uid = None
if all(key in json for key in USER_KEYS):
self.email = json['email']
self.password = json["password"]
def hash_password(self):
if self.password:
ph = PasswordHasher()
self.password = ph.hash(self.password)
def set_uid(self, uid):
if uid:
self.uid = int(uid)
class AuthApi:
def __init__(self, db_cli):
self.db = db_cli
def valid_user(self, user):
return isinstance(user.email, str) and user.email.strip() != '' and \
isinstance(user.password, str) and user.password.strip() != ''
def email_exists(self, email):
checkUserEmail = f"""
SELECT *
FROM users
WHERE userEmail='{email}';
"""
return True if self.db.query(checkUserEmail) else False
def verify_user(self, user):
extractUserHash = f"""
SELECT *
FROM users
WHERE userEmail='{user.email}';
"""
db_user_object = self.db.query(extractUserHash)
if not db_user_object:
return False
db_user_hash = db_user_object[0]['userPassword']
user.set_uid(db_user_object[0]['UID'])
if db_user_hash:
ph = PasswordHasher()
try:
return ph.verify(db_user_hash, user.password)
except exceptions.VerifyMismatchError as e:
return False
return False
def generate_session_token(self, user):
token = str(uuid.uuid4())
createNewToken = f"""
INSERT INTO sessions (sessionToken, userID)
VALUES ('{token}', '{user.uid}')
"""
print(self.db.query(createNewToken, quiet=True))
return token
def create_user(self, user):
if not self.valid_user(user):
return jsonify({"message": "Corrupted data supplied."}), 400
if self.email_exists(user.email):
return jsonify({"message": "Email already in use"}), 400
user.hash_password()
initialiseUser = f"""
INSERT INTO users (userEmail, userPassword)
VALUES ('{user.email}', '{user.password}');
"""
print(self.db.query(initialiseUser, quiet=True))
return jsonify({'message': 'User added successfully!'}), 201
def create_session(self, user):
if not self.valid_user(user):
return jsonify({"message": "Corrupted data supplied."}), 400
if self.verify_user(user):
res = make_response(jsonify({'message': f'New session token generated for {user.email}'}), 201)
res.set_cookie('SESSION_ID', self.generate_session_token(user))
return res
else:
return jsonify({'message': 'Passwords don\'t match or user not found.'}), 400
def delete_session(self):
if "SESSION_ID" not in request.cookies:
return jsonify({'message': "No session token provided."})
deleteSession = f"""
DELETE FROM sessions WHERE
sessionToken = '{request.cookies.get("SESSION_ID")}'
"""
self.db.query(deleteSession, quiet=True)
res = make_response(jsonify({'message': 'Logout!'}))
res.set_cookie('SESSION_ID', expires=0)
return res

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
# encoding: utf-8
'''
middleware.py is the module for user auth middleware
'''
from flask import Flask, request, make_response, g
class AuthMiddleware:
def __init__(self, db_cli):
self.db = db_cli
def extract_user_from_token(self, token):
tokenIsPresent = f"""
SELECT *
FROM sessions
WHERE sessionToken ='{token}'
"""
data = self.db.query(tokenIsPresent)
return data[0]['userID']
def validate_request(self):
if request.path == '/register' or request.path == '/login':
return None
session_token = request.cookies.get('SESSION_ID')
if not session_token:
return make_response('Unauthorized: Missing session_token cookie', 401)
userID = self.extract_user_from_token(session_token)
if not userID:
return make_response('Unauthorized: corrupted session_token cookie', 401)
# Flask's global object for custom data
g.user_info = {'id': userID}
return None

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
# encoding: utf-8
'''
dashboard.py is the functional api generator for dashboard route
'''
def construct_dashboard():
return {'page': 'dashboard'}
if __name__ == "__main__":
print("This is the dashboard.py file.")

View File

@ -0,0 +1,11 @@
version: '3.8'
services:
sql-server:
image: mcr.microsoft.com/mssql/server:latest
environment:
SA_PASSWORD: "UJ4tDsE39bT!"
ACCEPT_EULA: "Y"
ports:
- "1433:1433"

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python
# encoding: utf-8
'''
dungeon_db_cli.py is the module for managing teh Dungeon's database services.
'''
import json
import pymssql
import os
class DungeonDBClient:
def __init__(self):
self.db_server = os.environ.get('DB_SERVER')
print(self.db_server)
self.password = os.environ.get('DB_PASSWORD')
print(self.password)
self.database = 'dungeondev'
self.connection = pymssql.connect(
server=self.db_server,
user='sa',
password=self.password,
as_dict=True,
autocommit=True
)
self.cursor = self.connection.cursor()
self.initialize_database()
def create_database(self, db_name):
query = f"IF NOT EXISTS(SELECT * FROM sys.databases WHERE name='{db_name}') CREATE DATABASE {db_name};"
self.cursor.execute(query)
def switch_database(self, db_name):
self.cursor.execute(f"USE {db_name};")
def initialize_database(self):
self.create_database(self.database)
self.switch_database(self.database)
def query(self, query_str, quiet=False):
self.cursor.execute(query_str)
if quiet:
return []
return self.cursor.fetchall()

55
backend/src/server.py Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# encoding: utf-8
'''
server.py is the main source file for the Dungeon's backend service.
'''
import json
from flask import Flask, redirect, url_for, request, jsonify
from auth.auth_api import User, AuthApi
from auth.middleware import AuthMiddleware
from dashboard_api import construct_dashboard
from database.dungeon_db_cli import DungeonDBClient
app = Flask(__name__)
database = DungeonDBClient()
auth_api = AuthApi(database)
middleware = AuthMiddleware(database)
@app.before_request
def validator():
res = middleware.validate_request()
if res:
return res
@app.route('/register', methods=['POST'])
def register():
if request.is_json:
new_user = User(request.json)
return auth_api.create_user(new_user)
else:
return jsonify({'error': 'Request must contain JSON data'}), 400
@app.route('/login', methods=['POST'])
def login():
if request.is_json:
new_user = User(request.json)
return auth_api.create_session(new_user)
else:
return jsonify({'error': 'Request must contain JSON data'}), 400
@app.route('/logout')
def logout():
return auth_api.delete_session()
@app.route('/dashboard')
def dashboard():
return jsonify(construct_dashboard())
@app.route('/projects')
def projects():
return jsonify(construct_projects())
if __name__ == "__main__":
app.run(debug=True)