diff --git a/handler.py b/handler.py index 64ae435..6c140cb 100644 --- a/handler.py +++ b/handler.py @@ -5,12 +5,18 @@ # 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 General Public License for more details. # You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. -from bottle import Bottle, route +from bottle import Bottle, route, request, response, redirect from config import directory +import json + import templates.plain.main as template_public +import templates.postcard.main as template_email + import modules.public.home as public_home +import modules.api.auth as api_auth + app = Bottle() @app.route('/') @@ -21,3 +27,38 @@ def index(): } } return public_home.main().html(params) + +@app.route('/api/auth/registration/register/', method=['OPTIONS', 'POST']) +def index(roles): + try: + if request.method == 'OPTIONS': + return None + else: + response.content_type = 'application/json' + params = request.json + params["roles" ] = roles + params["mako" ] = { + "email" : template_email.main(directory.page["email"], "verification") + } + return json.dumps(api_auth.auth().register(params), indent = 2).encode() + except Exception as e: + print(str(e),flush=True) + return json.dumps({}, indent = 2).encode() + +@app.route('/api/auth/registration/resend', method='GET') +def index(): + try: + if request.method == 'OPTIONS': + return None + else: + response.content_type = 'application/json' + params = { + "email" : request.query.email, + "mako" : { + "email" : template_email.main(directory.page["email"], "verification") + } + } + return json.dumps(api_auth.auth().resend(params), indent = 2).encode() + except Exception as e: + print(str(e),flush=True) + return json.dumps({}, indent = 2).encode() diff --git a/modules/api/auth.py b/modules/api/auth.py new file mode 100644 index 0000000..0e7d86b --- /dev/null +++ b/modules/api/auth.py @@ -0,0 +1,136 @@ +import mysql.connector as mariadb +from mako.template import Template +from bottle import request + +from config import database, globalvar + +import bcrypt +import datetime + +from scripts import loggorilla, saltedkey, googly, tokenguard, sendwave + +import procedure.validation as procedure_validation +import procedure.webmail as procedure_webmail + +class auth: + + def __init__(self): + self.db_main = mariadb.connect(**database.db_main) + self.cursor = self.db_main.cursor(dictionary=True) + self.smtpconfig = globalvar.smtpconfig + + def register(self, params): + APIADDR = "/api/auth/registration/register/:roles" + loggorilla.prcss(APIADDR, "Define parameters") + response = {} + captcha = params["captcha" ] + username = params["username" ].lower() + email = params["email" ].lower() + password = params["password" ] + roles = params["roles" ] + self.cursor.execute("BEGIN;") + try: + loggorilla.prcss(APIADDR, "Get dependency data") + self.cursor.execute(f"SELECT id, name FROM `auth_roles` WHERE auth_roles.name = %s ; ", (roles,) ) + result_roles = self.cursor.fetchone() + loggorilla.prcss(APIADDR, "Process parameters") + hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() + token = saltedkey.token(username, hashed) + if globalvar.production == True: + captcha_r = googly.recaptcha(captcha, globalvar.reCAPTCHA['server']) + score = captcha_r["score"] + else: + captcha_r = 'dev mode' + score = 0.9 + loggorilla.fyinf(APIADDR, f'captcha_r : {captcha_r}') + loggorilla.fyinf(APIADDR, f'score : {score}') + + loggorilla.prcss(APIADDR, "Validation") + result_validation = procedure_validation.validation().register(APIADDR, captcha, score, roles, username, password, email) + if result_validation['status'] == "valid": + loggorilla.prcss(APIADDR, "Inserting") + self.cursor.execute("INSERT INTO `auth` VALUES (%s, %s);", (token, hashed) ) + self.cursor.execute("INSERT INTO `auth_profile` VALUES (DEFAULT, %s, %s, %s, NULL);", (token, username, email) ) + auth_profile_lastrowid = self.cursor.lastrowid + self.cursor.execute("INSERT INTO `auth_profile_verification` VALUES (DEFAULT, %s, 'email', 0);", (auth_profile_lastrowid,) ) + self.cursor.execute("INSERT INTO `auth_profile_roles` VALUES (DEFAULT, %s, %s);", (auth_profile_lastrowid, result_roles['id']) ) + loggorilla.prcss(APIADDR, "Generate URL") + expired = globalvar.verification_link_expiration + expired_isoformat = expired.isoformat() + payload = { + "token" : token, + "expired": expired_isoformat + } + token_encrypt = tokenguard.encode(payload, globalvar.ssh['key']['private'], globalvar.ssh['passphrase']) + verification_url = globalvar.verification_url(token_encrypt) + notme_url = globalvar.notme_url(token_encrypt) + loggorilla.prcss(APIADDR, "Sending email") + webmail_data = {"verify": verification_url, "notme": notme_url} + result_webmail = procedure_webmail.webmail().verification(APIADDR, params, webmail_data) + self.smtpconfig['to' ] = email + self.smtpconfig['subject' ] = result_webmail['subject'] + self.smtpconfig['text' ] = result_webmail['text' ] + self.smtpconfig['html' ] = result_webmail['html' ] + sendwave.smtp(self.smtpconfig) + loggorilla.prcss(APIADDR, "Giving response") + response["status" ] = "success" + response["desc" ] = "Register success. Check email for verification." + response["data" ] = { + "recaptcha":captcha_r + } + else: + response = result_validation + except Exception as e: + self.cursor.execute("ROLLBACK;") + loggorilla.error(APIADDR, str(e) ) + response["status" ] = "failed" + response["desc" ] = "Internal Server Error. Please contact us if you still have an error." + finally: + self.cursor.execute("COMMIT;") + self.cursor.close() + self.db_main.close() + return response + + def resend(self, params): + APIADDR = "/api/auth/registration/resend" + loggorilla.prcss(APIADDR, "Define parameters") + response = {} + email = params["email"].lower() + try: + loggorilla.prcss(APIADDR, "Get data for checking") + self.cursor.execute(f"SELECT COUNT(*) AS `count`, auth_profile.token, auth_profile.email FROM auth_profile_verification INNER JOIN auth_profile ON auth_profile.id = auth_profile_verification.profile WHERE auth_profile.email = %s AND auth_profile_verification.type = 'email' AND auth_profile_verification.verified = 0 ; ", (email,) ) + result_unverified = self.cursor.fetchone() + token = result_unverified["token"].decode() + if result_unverified["count"] >= 1: + loggorilla.prcss(APIADDR, "Generate URL") + expired = globalvar.verification_link_expiration + expired_isoformat = expired.isoformat() + payload = { + "token" : token, + "expired": expired_isoformat + } + token_encrypt = tokenguard.encode(payload, globalvar.ssh['key']['private'], globalvar.ssh['passphrase']) + verification_url = globalvar.verification_url(token_encrypt) + notme_url = globalvar.notme_url(token_encrypt) + loggorilla.prcss(APIADDR, "Sending email") + webmail_data = {"verify": verification_url, "notme": notme_url} + result_webmail = procedure_webmail.webmail().verification(APIADDR, params, webmail_data) + self.smtpconfig['to' ] = email + self.smtpconfig['subject' ] = result_webmail['subject'] + self.smtpconfig['text' ] = result_webmail['text' ] + self.smtpconfig['html' ] = result_webmail['html' ] + sendwave.smtp(self.smtpconfig) + loggorilla.prcss(APIADDR, "Giving response") + response["status" ] = "success" + response["desc" ] = "Resend success. Check email for verification." + else: + response["status" ] = "failed" + response["desc" ] = "The parameters seems suspicious and you are not authorized for that" + except Exception as e: + loggorilla.error(APIADDR, str(e) ) + response["status" ] = "failed" + response["desc" ] = "Internal Server Error. Please contact us if you still have an error. for detail" + finally: + self.cursor.close() + self.db_main.close() + return response diff --git a/pages/email/message.html b/pages/email/message.html new file mode 100644 index 0000000..c0e5212 --- /dev/null +++ b/pages/email/message.html @@ -0,0 +1 @@ +

${message}

diff --git a/pages/email/verification.html b/pages/email/verification.html new file mode 100644 index 0000000..96142c2 --- /dev/null +++ b/pages/email/verification.html @@ -0,0 +1,11 @@ +

${header}

+ +

Thanks for signing up! This is the start of an exciting journey and you’re just one step away from completing your account setup to start using your profile.

+

Click the button below to verify your account and get started.

+ + Verify email address + +
+
+

You are not registering this? I'm not registering this

+
diff --git a/scripts/googly.py b/scripts/googly.py new file mode 100644 index 0000000..6772178 --- /dev/null +++ b/scripts/googly.py @@ -0,0 +1,11 @@ +import json +import requests + +def recaptcha(captcha, secret): + url = "https://www.google.com/recaptcha/api/siteverify" + myobj = { + "secret" : secret, + "response" : captcha + } + response = json.loads(requests.post(url, data = myobj).text) + return response diff --git a/scripts/loggorilla.py b/scripts/loggorilla.py new file mode 100644 index 0000000..ea76d63 --- /dev/null +++ b/scripts/loggorilla.py @@ -0,0 +1,13 @@ +import datetime + +def prcss(loc, msg): + print(f"[loggorilla][{datetime.datetime.now()}][\033[32mprcss\033[39m][\033[95m{loc}\033[39m] {msg}", flush=True) + +def accss(loc, msg): + print(f"[loggorilla][{datetime.datetime.now()}][\033[36maccss\033[39m][\033[95m{loc}\033[39m] {msg}", flush=True) + +def fyinf(loc, msg): + print(f"[loggorilla][{datetime.datetime.now()}][\033[93mfyinf\033[39m][\033[95m{loc}\033[39m] {msg}", flush=True) + +def error(loc, msg): + print(f"[loggorilla][{datetime.datetime.now()}][\033[31merror\033[39m][\033[95m{loc}\033[39m] {msg}", flush=True) diff --git a/scripts/saltedkey.py b/scripts/saltedkey.py new file mode 100644 index 0000000..d5a8e35 --- /dev/null +++ b/scripts/saltedkey.py @@ -0,0 +1,5 @@ +import bcrypt +import hashlib + +def token(username, hashed): + return hashlib.sha1( (username+username[:3]+hashed[-6:]).encode() ).hexdigest() diff --git a/scripts/sendwave.py b/scripts/sendwave.py new file mode 100644 index 0000000..452424e --- /dev/null +++ b/scripts/sendwave.py @@ -0,0 +1,23 @@ +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +import smtplib + +def smtp(config): + + msg = MIMEMultipart('alternative') + msg['Subject' ] = config['subject' ] + msg['From' ] = config['from' ] + msg['To' ] = config['to' ] + + part1 = MIMEText(config['text'], 'plain') + part2 = MIMEText(config['html'], 'html' ) + + msg.attach(part1) + msg.attach(part2) + + smtp_server = smtplib.SMTP(config['server']['host'], config['server']['port']) + smtp_server.ehlo() + smtp_server.starttls() + smtp_server.login( config['login']['email'], config['login']['password'] ) + smtp_server.sendmail('&&&&&&', config['to'], msg.as_string() ) + smtp_server.quit() diff --git a/scripts/tokenguard.py b/scripts/tokenguard.py new file mode 100644 index 0000000..b2d6ef6 --- /dev/null +++ b/scripts/tokenguard.py @@ -0,0 +1,23 @@ +from cryptography.hazmat.primitives import serialization +import jwt + +def encode(payload, id_rsa, passphrase): + private_key = open(id_rsa, 'r').read() + key = serialization.load_ssh_private_key(private_key.encode(), password=passphrase) + token = jwt.encode( + payload = payload, + key = key, + algorithm = 'RS256' + ) + return token + +def decode(token, id_rsa): + public_key = open(id_rsa, 'r').read() + key = serialization.load_ssh_public_key(public_key.encode()) + header = jwt.get_unverified_header(token) + payload = jwt.decode( + jwt = token, + key = key, + algorithms = [header['alg'], ] + ) + return payload