mirror of
https://github.com/DarkflameUniverse/NexusDashboard.git
synced 2024-11-09 11:48:20 +00:00
Merge pull request #78 from DarkflameUniverse/captcha
feat: add recaptcha support
This commit is contained in:
commit
5ae2769ad2
@ -204,19 +204,23 @@ def register_settings(app):
|
|||||||
# Load environment specific settings
|
# Load environment specific settings
|
||||||
app.config['TESTING'] = False
|
app.config['TESTING'] = False
|
||||||
app.config['DEBUG'] = False
|
app.config['DEBUG'] = False
|
||||||
|
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
||||||
|
"pool_pre_ping": True,
|
||||||
|
"pool_size": 10,
|
||||||
|
"max_overflow": 2,
|
||||||
|
"pool_recycle": 300,
|
||||||
|
"pool_pre_ping": True,
|
||||||
|
"pool_use_lifo": True
|
||||||
|
}
|
||||||
|
|
||||||
# always pull these two from the env
|
|
||||||
app.config['SECRET_KEY'] = os.getenv(
|
app.config['SECRET_KEY'] = os.getenv(
|
||||||
'APP_SECRET_KEY',
|
'APP_SECRET_KEY',
|
||||||
app.config['APP_SECRET_KEY']
|
app.config['APP_SECRET_KEY']
|
||||||
|
|
||||||
)
|
)
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv(
|
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv(
|
||||||
'APP_DATABASE_URI',
|
'APP_DATABASE_URI',
|
||||||
app.config['APP_DATABASE_URI']
|
app.config['APP_DATABASE_URI']
|
||||||
)
|
)
|
||||||
|
|
||||||
# try to get overides, otherwise just use what we have already
|
|
||||||
app.config['USER_ENABLE_REGISTER'] = os.getenv(
|
app.config['USER_ENABLE_REGISTER'] = os.getenv(
|
||||||
'USER_ENABLE_REGISTER',
|
'USER_ENABLE_REGISTER',
|
||||||
app.config['USER_ENABLE_REGISTER']
|
app.config['USER_ENABLE_REGISTER']
|
||||||
@ -241,14 +245,6 @@ def register_settings(app):
|
|||||||
'USER_REQUIRE_INVITATION',
|
'USER_REQUIRE_INVITATION',
|
||||||
app.config['USER_REQUIRE_INVITATION']
|
app.config['USER_REQUIRE_INVITATION']
|
||||||
)
|
)
|
||||||
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
|
||||||
"pool_pre_ping": True,
|
|
||||||
"pool_size": 10,
|
|
||||||
"max_overflow": 2,
|
|
||||||
"pool_recycle": 300,
|
|
||||||
"pool_pre_ping": True,
|
|
||||||
"pool_use_lifo": True
|
|
||||||
}
|
|
||||||
app.config['MAIL_SERVER'] = os.getenv(
|
app.config['MAIL_SERVER'] = os.getenv(
|
||||||
'MAIL_SERVER',
|
'MAIL_SERVER',
|
||||||
app.config['MAIL_SERVER']
|
app.config['MAIL_SERVER']
|
||||||
@ -312,6 +308,42 @@ def register_settings(app):
|
|||||||
app.config['CACHE_LOCATION']
|
app.config['CACHE_LOCATION']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Recaptcha settings
|
||||||
|
if "RECAPTCHA_ENABLE" not in app.config:
|
||||||
|
app.config['RECAPTCHA_ENABLE'] = False
|
||||||
|
app.config['RECAPTCHA_ENABLE'] = os.getenv(
|
||||||
|
'RECAPTCHA_ENABLE',
|
||||||
|
app.config['RECAPTCHA_ENABLE']
|
||||||
|
)
|
||||||
|
if "RECAPTCHA_PUBLIC_KEY" not in app.config:
|
||||||
|
app.config['RECAPTCHA_PUBLIC_KEY'] = ''
|
||||||
|
app.config['RECAPTCHA_PUBLIC_KEY'] = os.getenv(
|
||||||
|
'RECAPTCHA_PUBLIC_KEY',
|
||||||
|
app.config['RECAPTCHA_PUBLIC_KEY']
|
||||||
|
)
|
||||||
|
if "RECAPTCHA_PRIVATE_KEY" not in app.config:
|
||||||
|
app.config['RECAPTCHA_PRIVATE_KEY'] = ''
|
||||||
|
app.config['RECAPTCHA_PRIVATE_KEY'] = os.getenv(
|
||||||
|
'RECAPTCHA_PRIVATE_KEY',
|
||||||
|
app.config['RECAPTCHA_PRIVATE_KEY']
|
||||||
|
)
|
||||||
|
# Optional
|
||||||
|
if "RECAPTCHA_API_SERVER" in app.config:
|
||||||
|
app.config['RECAPTCHA_API_SERVER'] = os.getenv(
|
||||||
|
'RECAPTCHA_API_SERVER',
|
||||||
|
app.config['RECAPTCHA_API_SERVER']
|
||||||
|
)
|
||||||
|
if "RECAPTCHA_PARAMETERS" in app.config:
|
||||||
|
app.config['RECAPTCHA_PARAMETERS'] = os.getenv(
|
||||||
|
'RECAPTCHA_PARAMETERS',
|
||||||
|
app.config['RECAPTCHA_PARAMETERS']
|
||||||
|
)
|
||||||
|
if "RECAPTCHA_DATA_ATTRS" in app.config:
|
||||||
|
app.config['RECAPTCHA_DATA_ATTRS'] = os.getenv(
|
||||||
|
'RECAPTCHA_DATA_ATTRS',
|
||||||
|
app.config['RECAPTCHA_DATA_ATTRS']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def gm_level(gm_level):
|
def gm_level(gm_level):
|
||||||
|
61
app/forms.py
61
app/forms.py
@ -1,17 +1,15 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm, Recaptcha, RecaptchaField
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from flask_user.forms import (
|
from flask_user.forms import (
|
||||||
unique_email_validator,
|
unique_email_validator,
|
||||||
password_validator,
|
LoginForm,
|
||||||
unique_username_validator
|
RegisterForm
|
||||||
)
|
)
|
||||||
from flask_user import UserManager
|
from flask_user import UserManager
|
||||||
from wtforms.widgets import TextArea, NumberInput
|
from wtforms.widgets import TextArea, NumberInput
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
StringField,
|
StringField,
|
||||||
HiddenField,
|
|
||||||
PasswordField,
|
|
||||||
BooleanField,
|
BooleanField,
|
||||||
SubmitField,
|
SubmitField,
|
||||||
validators,
|
validators,
|
||||||
@ -36,35 +34,19 @@ def validate_play_key(form, field):
|
|||||||
field.data = PlayKey.key_is_valid(key_string=field.data)
|
field.data = PlayKey.key_is_valid(key_string=field.data)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
class CustomRecaptcha(Recaptcha):
|
||||||
|
def __call__(self, form, field):
|
||||||
|
if not current_app.config.get("RECAPTCHA_ENABLE", False):
|
||||||
|
return True
|
||||||
|
return super(CustomRecaptcha, self).__call__(form, field)
|
||||||
|
|
||||||
|
|
||||||
class CustomUserManager(UserManager):
|
class CustomUserManager(UserManager):
|
||||||
def customize(self, app):
|
def customize(self, app):
|
||||||
self.RegisterFormClass = CustomRegisterForm
|
self.RegisterFormClass = CustomRegisterForm
|
||||||
|
self.LoginFormClass = CustomLoginForm
|
||||||
|
|
||||||
|
class CustomRegisterForm(RegisterForm):
|
||||||
class CustomRegisterForm(FlaskForm):
|
|
||||||
"""Registration form"""
|
|
||||||
next = HiddenField()
|
|
||||||
reg_next = HiddenField()
|
|
||||||
|
|
||||||
# Login Info
|
|
||||||
email = StringField(
|
|
||||||
'E-Mail',
|
|
||||||
validators=[
|
|
||||||
Optional(),
|
|
||||||
validators.Email('Invalid email address'),
|
|
||||||
unique_email_validator,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
username = StringField(
|
|
||||||
'Username',
|
|
||||||
validators=[
|
|
||||||
DataRequired(),
|
|
||||||
unique_username_validator,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
play_key_id = StringField(
|
play_key_id = StringField(
|
||||||
'Play Key',
|
'Play Key',
|
||||||
validators=[
|
validators=[
|
||||||
@ -72,21 +54,14 @@ class CustomRegisterForm(FlaskForm):
|
|||||||
validate_play_key,
|
validate_play_key,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
recaptcha = RecaptchaField(
|
||||||
|
validators=[CustomRecaptcha()]
|
||||||
|
)
|
||||||
|
|
||||||
password = PasswordField('Password', validators=[
|
class CustomLoginForm(LoginForm):
|
||||||
DataRequired(),
|
recaptcha = RecaptchaField(
|
||||||
password_validator,
|
validators=[CustomRecaptcha()]
|
||||||
validators.length(max=40, message="The maximum length of the password is 40 characters due to game client limitations")
|
)
|
||||||
])
|
|
||||||
retype_password = PasswordField('Retype Password', validators=[
|
|
||||||
validators.EqualTo('password', message='Passwords did not match'),
|
|
||||||
validators.length(max=40, message="The maximum length of the password is 40 characters due to game client limitations")
|
|
||||||
])
|
|
||||||
|
|
||||||
invite_token = HiddenField('Token')
|
|
||||||
|
|
||||||
submit = SubmitField('Register')
|
|
||||||
|
|
||||||
|
|
||||||
class CreatePlayKeyForm(FlaskForm):
|
class CreatePlayKeyForm(FlaskForm):
|
||||||
|
|
||||||
|
@ -61,3 +61,13 @@ USER_AFTER_LOGOUT_ENDPOINT = "main.index"
|
|||||||
|
|
||||||
# Option will be removed once this feature is full implemeted
|
# Option will be removed once this feature is full implemeted
|
||||||
ENABLE_CHAR_XML_UPLOAD = False
|
ENABLE_CHAR_XML_UPLOAD = False
|
||||||
|
|
||||||
|
# Recaptcha settings
|
||||||
|
# See: https://flask-wtf.readthedocs.io/en/1.2.x/form/#recaptcha
|
||||||
|
RECAPTCHA_ENABLE = False
|
||||||
|
RECAPTCHA_PUBLIC_KEY = ''
|
||||||
|
RECAPTCHA_PRIVATE_KEY = ''
|
||||||
|
# Optional
|
||||||
|
# RECAPTCHA_API_SERVER = ''
|
||||||
|
# RECAPTCHA_PARAMETERS = ''
|
||||||
|
RECAPTCHA_DATA_ATTRS = {'theme': 'white', 'size': 'invisible'}
|
||||||
|
@ -1,188 +0,0 @@
|
|||||||
{% extends "bootstrap/base.html" %}
|
|
||||||
{% block title %}Key Creation{% endblock %}
|
|
||||||
|
|
||||||
{% block navbar %}
|
|
||||||
<nav class="navbar navbar-default">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<a class="navbar-brand" href="{{ url_for('dashboard') }}">Dashboard</a>
|
|
||||||
</div>
|
|
||||||
<ul class="nav navbar-nav">
|
|
||||||
</ul>
|
|
||||||
<ul class="nav navbar-nav" style="float: right">
|
|
||||||
<li class="active"><a href="#">Welcome {{ current_user.username }}!</a></li>
|
|
||||||
<li><a href="{{ url_for('logout') }}">Logout</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
{% endblock navbar %}}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{# LOGO #}
|
|
||||||
<div class="container" style="margin-top: 50px">
|
|
||||||
|
|
||||||
{# Display logo #}
|
|
||||||
<div style="margin-bottom: 50px">
|
|
||||||
<img
|
|
||||||
src="{{ url_for('static', filename=resources.LOGO) }}"
|
|
||||||
class="center-block img-responsive mx-auto d-block"
|
|
||||||
alt="Logo"
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Key creation #}
|
|
||||||
<div class="container">
|
|
||||||
<div class="text-center">
|
|
||||||
<h3>Key Creation</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-3">
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-6">
|
|
||||||
|
|
||||||
{# If the error value is set, display the error in red text #}
|
|
||||||
{% if error %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# If the message value is set, display the message in green text #}
|
|
||||||
{% if message %}
|
|
||||||
<div class="alert alert-success">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Form which takes in Admin Username, Admin Password, and the amount of keys to create. #}
|
|
||||||
<form action="{{ url_for('dashboard') }}" method="post">
|
|
||||||
{# Key count input #}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="key_count">Generate keys</label>
|
|
||||||
<input type="number" class="form-control" name="key_count" placeholder="Enter number of keys...">
|
|
||||||
<small class="form-text text-muted">Number of keys to create.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Submit button #}
|
|
||||||
<div class="form-group">
|
|
||||||
<button type="submit" class="btn btn-primary">Generate Keys</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{# If the keys value is set, create a list for each key in keys #}
|
|
||||||
{% if keys %}
|
|
||||||
<div class="alert alert-success">
|
|
||||||
<ul>
|
|
||||||
{% for key in keys %}
|
|
||||||
<li>{{ key }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Activity graphs #}
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<h3>Activity</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-3">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-6">
|
|
||||||
|
|
||||||
<canvas id="sessions_graph" width="400" height="400"></canvas>
|
|
||||||
<canvas id="play_time_graph" width="400" height="400"></canvas>
|
|
||||||
<canvas id="zone_play_time_graph" width="400" height="400"></canvas>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-3">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Make a get request to the server to load the activity data
|
|
||||||
$.get("{{ url_for('load_activities') }}", function(data) {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make a get request to the server to get the activity data for "sessions"
|
|
||||||
$.get("{{ url_for('activity_data', name='sessions') }}", function(data) {
|
|
||||||
// Load data as a json object
|
|
||||||
data = JSON.parse(data);
|
|
||||||
var ctx = document.getElementById('sessions_graph').getContext('2d');
|
|
||||||
var myChart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: data.labels,
|
|
||||||
datasets: data.datasets
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make a get request to the server to get the activity data for "play_time"
|
|
||||||
$.get("{{ url_for('activity_data', name='play_time') }}", function(data) {
|
|
||||||
// Load data as a json object
|
|
||||||
data = JSON.parse(data);
|
|
||||||
var ctx = document.getElementById('play_time_graph').getContext('2d');
|
|
||||||
var myChart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: data.labels,
|
|
||||||
datasets: data.datasets
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make a get request to the server to get the activity data for "zone_play_time"
|
|
||||||
$.get("{{ url_for('activity_data', name='zone_play_time') }}", function(data) {
|
|
||||||
// Load data as a json object
|
|
||||||
data = JSON.parse(data);
|
|
||||||
var ctx = document.getElementById('zone_play_time_graph').getContext('2d');
|
|
||||||
var myChart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: data.labels,
|
|
||||||
datasets: data.datasets
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock scripts %}}
|
|
@ -60,7 +60,12 @@
|
|||||||
|
|
||||||
{# Remember me #}
|
{# Remember me #}
|
||||||
{% if user_manager.USER_ENABLE_REMEMBER_ME %}
|
{% if user_manager.USER_ENABLE_REMEMBER_ME %}
|
||||||
{{ render_checkbox_field(login_form.remember_me, tabindex=130) }}
|
{{ render_checkbox_field(login_form.remember_me, tabindex=130) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# recaptcha #}
|
||||||
|
{% if config.RECAPTCHA_ENABLE %}
|
||||||
|
{{ render_field(form.recaptcha, tabindex=250) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Submit button #}
|
{# Submit button #}
|
||||||
|
@ -29,6 +29,11 @@
|
|||||||
{{ render_checkbox_field(login_form.remember_me, tabindex=130) }}
|
{{ render_checkbox_field(login_form.remember_me, tabindex=130) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# recaptcha #}
|
||||||
|
{% if config.RECAPTCHA_ENABLE %}
|
||||||
|
{{ form.recaptcha }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Submit button #}
|
{# Submit button #}
|
||||||
{{ render_submit_field(login_form.submit, tabindex=180) }}
|
{{ render_submit_field(login_form.submit, tabindex=180) }}
|
||||||
</form>
|
</form>
|
||||||
@ -63,6 +68,11 @@
|
|||||||
{{ render_field(register_form.retype_password, tabindex=240) }}
|
{{ render_field(register_form.retype_password, tabindex=240) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# recaptcha #}
|
||||||
|
{% if config.RECAPTCHA_ENABLE %}
|
||||||
|
{{ register_form.recaptcha }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ render_submit_field(register_form.submit, tabindex=280) }}
|
{{ render_submit_field(register_form.submit, tabindex=280) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -47,6 +47,11 @@
|
|||||||
{{ render_field(form.retype_password, tabindex=240) }}
|
{{ render_field(form.retype_password, tabindex=240) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# recaptcha #}
|
||||||
|
{% if config.RECAPTCHA_ENABLE %}
|
||||||
|
{{ render_field(form.recaptcha, tabindex=250) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ render_submit_field(form.submit, tabindex=280) }}
|
{{ render_submit_field(form.submit, tabindex=280) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -82,7 +82,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-right">
|
<div class="col text-right">
|
||||||
Source
|
Source
|
||||||
|
Loading…
Reference in New Issue
Block a user