Compare commits

..

10 Commits

25 changed files with 593 additions and 147 deletions

1
app/.gitignore vendored
View File

@ -5,3 +5,4 @@ venv/
env/
.beaker/data/*
!.beaker/data/.noremove
nohup.out

View File

@ -1,24 +1,18 @@
from bottle import Bottle, get, static_file
from core import template
page = {
'public' :'pages/public',
'email' :'pages/email'
}
app = Bottle()
# Default staticdir
@app.get("/css/<filepath:re:.*\.(css|sass|css.map)>")
def static(filepath):
return static_file(filepath, root="./static/css")
@app.get("/js/<filepath:re:.*\.(js)>")
def static(filepath):
return static_file(filepath, root="./static/js")
# Template staticdir: plain
@app.get("/templates/plain/css/<filepath:re:.*\.(css|sass|css.map)>")
def static(filepath):
return static_file(filepath, root="./templates/plain/static/css")
static = [
{
"route" :"/css/<filepath:re:.*\.(css|sass|css.map)>",
"root" :"./static/css"
},
{
"route" :"/js/<filepath:re:.*\.(js)>",
"root" :"./static/js"
}
]
template.add(static, "templates")

View File

@ -3,7 +3,7 @@ header = f"Welcome to {title}"
copyright = "Copyright (C) 2024 Dita Aji Pratama"
production = False
su_mode = True
forbidden_registration = ['su', 'admin']
auth_key = 'your_key'
@ -49,7 +49,7 @@ def verification_url(token):
return url
def change_forgot_url(token):
url = f"{baseurl}/portal/change/forgot?token={token}"
url = f"{baseurl}/reset?token={token}"
return url
menu = {

7
app/core/staticdir.py Normal file
View File

@ -0,0 +1,7 @@
from bottle import Bottle, static_file
from config import directory
app = Bottle()
for item in directory.static:
app.route(item['route'], "GET", lambda filepath, root=item['root']: static_file(filepath, root=root) )

8
app/core/template.py Normal file
View File

@ -0,0 +1,8 @@
import os
def add(dirconfig, template_directory):
template_list = [d for d in os.listdir(template_directory) if os.path.isdir(os.path.join(template_directory, d))]
for template_name in template_list:
template_module = __import__(f"{template_directory}.{template_name}.main", fromlist=["static"])
for static in getattr(template_module, "static", []):
dirconfig.append(static)

View File

@ -5,19 +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/.
import sys
from bottle import Bottle, run
from beaker.middleware import SessionMiddleware
import handler
from core import staticdir
from config import server
from config import directory
app = Bottle()
app.merge(handler.app)
app.merge(directory.app)
app.merge(staticdir.app)
app = SessionMiddleware(app, server.session_opts)

View File

@ -5,7 +5,7 @@
# 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, request, response
from bottle import Bottle, route, request, response, redirect
import json
from config import directory
@ -18,6 +18,8 @@ import modules.public.register as public_register
import modules.public.notme as public_notme
import modules.public.verify as public_verify
import modules.public.login as public_login
import modules.public.forgot as public_forgot
import modules.public.reset as public_reset
import modules.api.auth as api_auth
@ -69,6 +71,44 @@ def index():
}
return public_login.login().html(params)
@app.route('/forgot')
def index():
params = {
"mako" : {
"website" : template_public.main(directory.page["public"], "forgot")
}
}
return public_forgot.forgot().html(params)
@app.route('/reset', method='GET')
def index():
params = {
"mako" : {
"website" : template_public.main(directory.page["public"], "reset")
}
}
return public_reset.reset().html(params)
@app.route('/logout')
def index():
beaker_session = request.environ.get('beaker.session')
if "token" in beaker_session:
params = {
"jwt" : beaker_session["token"],
"type" : "out"
}
response_session = api_auth.auth().session(params)
response_logout = api_auth.auth().logout(params)
if response_session['status'] == 'success' and response_logout['status'] == 'success' :
redirect('/?message=logout success')
else:
print('logout failed')
print(f"response session: {response_session['status']}")
print(f"response logout: {response_logout['status']}")
redirect('/?message=logout failed')
else:
redirect('/')
@app.route('/api/auth/registration/register/<roles>', method='POST')
def index(roles):
try:
@ -144,3 +184,34 @@ def index(type):
except Exception as e:
print(str(e))
return json.dumps({}, indent = 2).encode()
@app.route('/api/auth/logout', method='POST')
def index():
try:
params = request.json
response.content_type = 'application/json'
return json.dumps(api_auth.auth().logout(params), indent = 2).encode()
except Exception as e:
print(str(e))
return json.dumps({}, indent = 2).encode()
@app.route('/api/auth/password/forgot/<type>', method='POST')
def index(type):
try:
params = request.json
params["type"] = type
if type == "send":
params["mako"] = {
"email" : template_email.main(directory.page["email"], "reset")
}
elif type == "change":
params["mako"] = {
"email" : template_email.main(directory.page["email"], "message")
}
else:
pass
response.content_type = 'application/json'
return json.dumps(api_auth.auth().forgot(params), indent = 2).encode()
except Exception as e:
print(str(e))
return json.dumps({}, indent = 2).encode()

View File

@ -4,3 +4,7 @@ pip install bottle # Micro Framework
pip install gunicorn # WSGI Server Backend
pip install beaker # Session & caching library
pip install mako # Template library
pip install mysql-connector # Database
pip install bcrypt
pip install pyjwt[crypto]

View File

@ -53,9 +53,9 @@ class auth:
response["data" ] = {
"recaptcha":captcha_r
}
elif globalvar.su_mode == False and roles == 'su':
elif roles in globalvar.forbidden_registration:
response["status" ] = "failed"
response["desc" ] = "Forbidden to become super user"
response["desc" ] = f"Forbidden to become {roles}"
response["data" ] = {
"recaptcha":captcha_r
}
@ -471,14 +471,16 @@ class auth:
type = params["type" ] # POST: send / change
self.cursor.execute("BEGIN;")
try:
loggorilla.fyinf(APIADDR, f"type: {type}")
if type == "send":
loggorilla.prcss(APIADDR, "Define parameters inside decision")
email = params["email"].lower()
loggorilla.prcss(APIADDR, "Get dependency data")
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.auth_profile WHERE auth_profile.email = %s AND auth_profile_verification.type = 'email' AND auth_profile_verification.verified = 1 ; ", (email,) )
result_verified = self.cursor.fetchone()
token = result_verified["token"].decode()
if result_verified["count"] >= 1:
loggorilla.prcss(APIADDR, "Get token")
token = result_verified["token"].decode()
loggorilla.prcss(APIADDR, "Generate URL")
# TODO: set expired time
expired = datetime.datetime.now() + datetime.timedelta(minutes=30) # Can be hours or minutes
@ -493,14 +495,13 @@ class auth:
loggorilla.prcss(APIADDR, "Sending email")
self.smtpconfig['subject' ] = f"{globalvar.title} forgot password"
self.smtpconfig['to' ] = email
self.smtpconfig['text' ] = f"Please visit this link to change password: {change_forgot_url}. Avoid the link if you are not request this."
self.smtpconfig['html' ] = Template(params["mako"]["email"]['template']).render(
self.smtpconfig['text' ] = f"Please visit this link to reset password: {change_forgot_url}. Avoid the link if you are not request this."
self.smtpconfig['html' ] = Template(params["mako"]["email"]['index']).render(
title = globalvar.title,
heading = self.smtpconfig['subject'],
image = "https://colorlib.com/etc/email-template/10/images/email.png",
unsubscribe = "#",
header = globalvar.title,
copyright = globalvar.copyright,
container = Template(params["mako"]["email"]['container']).render(
change = change_forgot_url
reset = change_forgot_url
)
)
sendwave.smtp(self.smtpconfig)
@ -544,11 +545,10 @@ class auth:
self.smtpconfig['subject' ] = f"{globalvar.title} password change success"
self.smtpconfig['to' ] = email
self.smtpconfig['text' ] = f"You had change your password."
self.smtpconfig['html' ] = Template(params["mako"]["email"]['template']).render(
self.smtpconfig['html' ] = Template(params["mako"]["email"]['index']).render(
title = globalvar.title,
heading = self.smtpconfig['subject'],
image = "https://colorlib.com/etc/email-template/10/images/email.png",
unsubscribe = "#",
header = globalvar.title,
copyright = globalvar.copyright,
container = Template(params["mako"]["email"]['container']).render(
message = f"You had change your password."
)

View File

@ -0,0 +1,30 @@
from mako.template import Template
from config import globalvar
from bottle import request
class forgot:
def __init__(self):
pass
def html(self, params):
active_page = "Forgot"
user_roles = [0]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
if jwt is not None:
return redirect('/')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = user_roles,
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(
copyright = globalvar.copyright,
),
container = Template(params["mako"]["website"]['container']).render()
)

View File

@ -1,30 +1,49 @@
import mysql.connector as mariadb
from mako.template import Template
from config import globalvar, database
from bottle import request
import procedure.session as procedure_session
class home:
def __init__(self):
self.db_main = mariadb.connect(**database.db_main)
self.cursor = self.db_main.cursor(dictionary=True)
self.user_roles = [0] # Cari user roles disini
self.user = {
"data":{
"profile":{
"username":None,
"email":None,
"phone":None,
"roles":[0]
}
}
}
def html(self, params):
active_page = "Home"
allowed_roles = [0,1,2,3]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
self.user = procedure_session.session().user(jwt, allowed_roles) if jwt is not None else self.user
user = self.user['data']
if 'valid' in user and user['valid']['status'] == 0:
return redirect('/logout')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = self.user_roles,
user_roles = user['profile']['roles'],
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(
copyright = globalvar.copyright,
),
container = Template(params["mako"]["website"]['container']).render(
greeting = f"Welcome to your new web application! This placeholder page is here to let you know that your web framework is successfully set up and ready to go. Now, it's time to start building your project. Dive into the documentation to explore the features and capabilities at your disposal."
greeting = f"Welcome to your new web application! This placeholder page is here to let you know that your web framework is successfully set up and ready to go. Now, it's time to start building your project. Dive into the documentation to explore the features and capabilities at your disposal.",
user = user
)
)

View File

@ -1,22 +1,26 @@
import mysql.connector as mariadb
from mako.template import Template
from config import globalvar, database
from config import globalvar
from bottle import request
class login:
def __init__(self):
self.db_main = mariadb.connect(**database.db_main)
self.cursor = self.db_main.cursor(dictionary=True)
self.user_roles = [0] # Cari user roles disini
pass
def html(self, params):
active_page = "Login"
user_roles = [0]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
if jwt is not None:
return redirect('/')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = self.user_roles,
user_roles = user_roles,
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(

View File

@ -1,22 +1,39 @@
import mysql.connector as mariadb
from mako.template import Template
from config import globalvar, database
from config import globalvar
from bottle import request
import procedure.session as procedure_session
class notme:
def __init__(self):
self.db_main = mariadb.connect(**database.db_main)
self.cursor = self.db_main.cursor(dictionary=True)
self.user_roles = [0] # Cari user roles disini
self.user = {
"data":{
"profile":{
"username":None,
"email":None,
"phone":None,
"roles":[0]
}
}
}
def html(self, params):
active_page = None
allowed_roles = [0,1,2,3]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
self.user = procedure_session.session().user(jwt, allowed_roles) if jwt is not None else self.user
user = self.user['data']
if 'valid' in user and user['valid']['status'] == 0:
return redirect('/logout')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = self.user_roles,
user_roles = user['profile']['roles'],
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(

View File

@ -1,25 +1,27 @@
import mysql.connector as mariadb
from mako.template import Template
from config import globalvar, database
from config import globalvar
from bottle import request
class register:
def __init__(self):
self.db_main = mariadb.connect(**database.db_main)
self.cursor = self.db_main.cursor(dictionary=True)
self.user_roles = [0] # Cari user roles disini
pass
def html(self, params):
roles = params["roles"]
active_page = "Register"
user_roles = [0]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
if jwt is not None:
return redirect('/')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = self.user_roles,
user_roles = user_roles,
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(

View File

@ -0,0 +1,30 @@
from mako.template import Template
from config import globalvar
from bottle import request
class reset:
def __init__(self):
pass
def html(self, params):
active_page = "Reset"
user_roles = [0]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
if jwt is not None:
return redirect('/')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = user_roles,
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(
copyright = globalvar.copyright,
),
container = Template(params["mako"]["website"]['container']).render()
)

View File

@ -1,22 +1,39 @@
import mysql.connector as mariadb
from mako.template import Template
from config import globalvar, database
from config import globalvar
from bottle import request
import procedure.session as procedure_session
class verify:
def __init__(self):
self.db_main = mariadb.connect(**database.db_main)
self.cursor = self.db_main.cursor(dictionary=True)
self.user_roles = [0] # Cari user roles disini
self.user = {
"data":{
"profile":{
"username":None,
"email":None,
"phone":None,
"roles":[0]
}
}
}
def html(self, params):
active_page = None
allowed_roles = [0,1,2,3]
beaker_session = request.environ.get('beaker.session')
jwt = beaker_session["token"] if "token" in beaker_session else None
self.user = procedure_session.session().user(jwt, allowed_roles) if jwt is not None else self.user
user = self.user['data']
if 'valid' in user and user['valid']['status'] == 0:
return redirect('/logout')
else:
return Template(params["mako"]["website"]['index']).render(
title = globalvar.title,
header = globalvar.header,
navbar = Template(params["mako"]["website"]['navbar']).render(
menu = globalvar.menu['public']['navbar'],
user_roles = self.user_roles,
user_roles = user['profile']['roles'],
active_page = active_page
),
footer = Template(params["mako"]["website"]['footer']).render(

View File

@ -0,0 +1,6 @@
<h2>Forgot Password</h2>
<p>Please visit this link below to reset password. <b>Avoid the link if you are not request this.</b> </p>
<a href="${reset}" class="button">
Reset password
</a>

View File

@ -0,0 +1,15 @@
<h1>Forgot Password</h1>
<script type="text/javascript" src="/js/carrack.js"></script>
<!-- FORM -->
<input required type="email" id="form-email" placeholder="Email"> <br>
<button onclick="onSubmit()">Submit</button>
<!-- RESPONSE -->
<div id="alert-response" role="alert">
<b id="alert-status">Loading...</b> <span id="alert-desc">Please wait...</span>
</div>
<script type="text/javascript" src="/js/auth/forgot.js"></script>

View File

@ -1 +1,7 @@
<p>${greeting}</p>
<p>
<b>Username:</b> ${user['profile']['username']} <br>
<b>Email:</b> ${user['profile']['email']} <br>
<b>Phone:</b> ${user['profile']['phone']} <br>
<b>Roles:</b> ${str(user['profile']['roles'])} <br>
</p>

View File

@ -4,7 +4,9 @@
<input required type="text" id="form-username" placeholder="Username" > <br>
<input required type="password" id="form-password" placeholder="Password" > <br>
<button type="button" onclick="onSubmit()">Login</button>
<button type="button" onclick="onSubmit()">Login</button> <br>
<a href="/forgot">Forgot password</a>
<div id="alert-response" role="alert">
<b id="alert-status">Loading...</b> <span id="alert-desc">Please wait...</span>

View File

@ -0,0 +1,16 @@
<h1>Reset Password</h1>
<script type="text/javascript" src="/js/carrack.js"></script>
<!-- FORM -->
<div id="form">
<input required type="password" id="form-password" placeholder="New Password"> <br>
<button onclick="onSubmit()">Submit</button>
</div>
<!-- RESPONSE -->
<div id="alert-response" role="alert">
<b id="alert-status">Loading...</b> <span id="alert-desc">Please wait...</span>
</div>
<script type="text/javascript" src="/js/auth/reset.js"></script>

100
app/procedure/session.py Normal file
View File

@ -0,0 +1,100 @@
import mysql.connector as mariadb
import datetime
import config.database as database
import config.globalvar as globalvar
import scripts.loggorilla as loggorilla
import scripts.tokenguard as tokenguard
class session():
def __init__(self):
self.db_main = mariadb.connect(**database.db_main)
self.cursor = self.db_main.cursor(dictionary=True)
def user(self, jwt, allowed_roles):
APIADDR = "procedure.validation"
response = {}
try:
loggorilla.prcss(APIADDR, "Define parameters")
payload = tokenguard.decode(jwt, globalvar.ssh['key']['public'])
loggorilla.prcss(APIADDR, "Get dependency data")
self.cursor.execute(f"SELECT * FROM auth_session WHERE id = %s ; ", (payload["session"]["id"],) )
r_session = self.cursor.fetchone()
self.cursor.execute(f"SELECT COUNT(*) AS `count`, auth_profile.* FROM auth_profile_verification LEFT JOIN auth_profile ON auth_profile.id = auth_profile_verification.auth_profile WHERE auth_profile.token = %s AND auth_profile_verification.type = 'email' AND auth_profile_verification.verified = 1 ; ", (r_session['token'],) )
r_profile = self.cursor.fetchone()
self.cursor.execute(f"SELECT auth_roles FROM auth_profile_roles WHERE auth_profile = %s ; ", (r_profile['id'],) )
r_roles = self.cursor.fetchall()
r_profile['roles'] = [0]
for row in r_roles:
r_profile['roles'].remove(0)
r_profile['roles'].append(row['auth_roles'])
loggorilla.prcss(APIADDR, "Validation")
if datetime.datetime.now() > r_session['end']:
loggorilla.prcss(APIADDR, "Deleting")
self.cursor.execute("DELETE FROM auth_session WHERE id = %s ; ", (r_session['id'],) )
loggorilla.prcss(APIADDR, "Giving response")
loggorilla.accss(APIADDR, "Expired. Your session removed." )
response["status" ] = "failed"
response["desc" ] = "Expired. Your session removed."
response["data" ] = {
"valid" :{
"status" : 0,
"desc" : "expired"
},
"session" : r_session,
"profile" : r_profile
}
elif r_profile["count"] == 0:
loggorilla.prcss(APIADDR, "Giving response")
loggorilla.accss(APIADDR, "No active account for this" )
response["status" ] = "failed"
response["desc" ] = "No active account for this"
response["data" ] = {
"message" : "Please contact us if you still had a problem",
"valid" :{
"status" : 0,
"desc" : "forbidden"
},
"session" : r_session,
"profile" : r_profile
}
elif any(role in allowed_roles for role in r_profile['roles']):
loggorilla.prcss(APIADDR, "Giving response")
response["status" ] = "success"
response["desc" ] = "User roles authorized"
response["data" ] = {
"valid" :{
"status" : 1,
"desc" : "authorized"
},
"session" : r_session,
"profile" : r_profile
}
else:
loggorilla.prcss(APIADDR, "Giving response")
loggorilla.accss(APIADDR, "User roles unauthorized" )
response["status" ] = "failed"
response["desc" ] = "User roles unauthorized"
response["data" ] = {
"valid" :{
"status" : 0,
"desc" : "unauthorized"
},
"session" : r_session,
"profile" : r_profile
}
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

View File

@ -0,0 +1,42 @@
function flushResponse() {
document.getElementById("alert-response" ).style.display = 'none';
document.getElementById("alert-response" ).classList.remove('alert-success' );
document.getElementById("alert-response" ).classList.remove('alert-danger' );
document.getElementById("alert-response" ).classList.remove('alert-primary' );
}
function loadingResponse() {
flushResponse();
document.getElementById("alert-status" ).innerHTML = "Loading...";
document.getElementById("alert-desc" ).innerHTML = "Please wait...";
document.getElementById("alert-response").classList.add('alert-primary');
document.getElementById("alert-response").style.display = 'block';
}
function responseAlert(response) {
flushResponse();
const obj = JSON.parse(response);
if (obj.status == "success" ) document.getElementById("alert-response").classList.add('alert-success' );
if (obj.status == "failed" ) document.getElementById("alert-response").classList.add('alert-danger' );
document.getElementById("alert-status" ).innerHTML = obj.status;
document.getElementById("alert-desc" ).innerHTML = obj.desc;
document.getElementById("alert-response").style.display = 'block';
}
function onSubmit() {
loadingResponse();
var email = document.getElementById("form-email" ).value;
var url = "/api/auth/password/forgot/send";
var payload = {
"email" : email
};
sendHttpRequest(url, "POST", payload, function (error, response) {
if (error) console.error("Error:", error);
else {
console.log("JSON Response:", response);
responseAlert(response);
}
}, "application/json");
}
flushResponse();

View File

@ -0,0 +1,49 @@
function flushResponse() {
document.getElementById("alert-response" ).style.display = 'none';
document.getElementById("alert-response" ).classList.remove('alert-success' );
document.getElementById("alert-response" ).classList.remove('alert-danger' );
document.getElementById("alert-response" ).classList.remove('alert-primary' );
}
function loadingResponse() {
flushResponse();
document.getElementById("alert-status" ).innerHTML = "Loading...";
document.getElementById("alert-desc" ).innerHTML = "Please wait...";
document.getElementById("alert-response").classList.add('alert-primary');
document.getElementById("alert-response").style.display = 'block';
}
function responseAlert(response) {
flushResponse();
const obj = JSON.parse(response);
if (obj.status == "success" ) {
document.getElementById("form").style.display = 'none';
document.getElementById("alert-response").classList.add('alert-success' );
}
if (obj.status == "failed" ) document.getElementById("alert-response").classList.add('alert-danger' );
document.getElementById("alert-status" ).innerHTML = obj.status;
document.getElementById("alert-desc" ).innerHTML = obj.desc;
document.getElementById("alert-response").style.display = 'block';
}
function onSubmit() {
loadingResponse();
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const token = urlParams.get('token')
var password = document.getElementById("form-password").value;
var url = "/api/auth/password/forgot/change";
var payload = {
"token" : token,
"password" : password
};
sendHttpRequest(url, "POST", payload, function (error, response) {
if (error) console.error("Error:", error);
else {
console.log("JSON Response:", response);
responseAlert(response);
}
}, "application/json");
}
flushResponse();

View File

@ -7,6 +7,13 @@
from core import html
static = [
{
"route" : "/templates/plain/css/<filepath:re:.*\.(css|sass|css.map)>",
"root" : "./templates/plain/static/css"
}
]
def main(dir, page):
html_template = html.main.get_html("templates/plain/html")
html_page = html.main.get_html(dir)