Merge pull request #156 from TerryGeng/token
Several improvements to azlux's token auth scheme #154
This commit is contained in:
commit
390c0034f6
16
command.py
16
command.py
@ -1183,9 +1183,19 @@ def cmd_web_access(bot, user, text, command, parameter):
|
|||||||
import secrets
|
import secrets
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
token = secrets.token_urlsafe(5)
|
|
||||||
var.db.set("user", user, json.dumps({'token': token, 'datetime': str(datetime.datetime.now()), 'IP':''}))
|
user_info = var.db.get("user", user, fallback=None)
|
||||||
bot.send_msg(constants.strings('webpage_token',token=token), text)
|
if user_info is not None:
|
||||||
|
user_dict = json.loads(user_info)
|
||||||
|
token = user_dict['token']
|
||||||
|
else:
|
||||||
|
token = secrets.token_urlsafe(5)
|
||||||
|
var.db.set("web_token", token, user)
|
||||||
|
|
||||||
|
var.db.set("user", user, json.dumps({'token': token, 'datetime': str(datetime.datetime.now()), 'IP': ''}))
|
||||||
|
|
||||||
|
access_address = var.config.get("webinterface", "access_address")
|
||||||
|
bot.send_msg(constants.strings('webpage_token', address=access_address, token=token), text)
|
||||||
|
|
||||||
# Just for debug use
|
# Just for debug use
|
||||||
def cmd_real_time_rms(bot, user, text, command, parameter):
|
def cmd_real_time_rms(bot, user, text, command, parameter):
|
||||||
|
@ -96,14 +96,13 @@ listening_addr = 127.0.0.1
|
|||||||
listening_port = 8181
|
listening_port = 8181
|
||||||
web_logfile =
|
web_logfile =
|
||||||
|
|
||||||
# Set this option to True to enable password protection for the web interface
|
auth_method = password
|
||||||
require_auth = False
|
user = botamusique
|
||||||
user =
|
password = mumble
|
||||||
password =
|
|
||||||
|
access_address = http://127.0.0.1:8181
|
||||||
|
|
||||||
# Set this option to match mumble user with token on flask and add a password to encrypt/sign cookies
|
|
||||||
flask_secret = ChangeThisPassword
|
flask_secret = ChangeThisPassword
|
||||||
match_mumble_user = False
|
|
||||||
|
|
||||||
[debug]
|
[debug]
|
||||||
# Set ffmpeg to True if you want to display DEBUG level log of ffmpeg.
|
# Set ffmpeg to True if you want to display DEBUG level log of ffmpeg.
|
||||||
@ -259,7 +258,7 @@ cleared_tags = Removed all tags from <b>{song}</b>.
|
|||||||
cleared_tags_from_all = Removed all tags from songs on the playlist.
|
cleared_tags_from_all = Removed all tags from songs on the playlist.
|
||||||
shortlist_instruction = Use <i>!sl {indexes}</i> to play the item you want.
|
shortlist_instruction = Use <i>!sl {indexes}</i> to play the item you want.
|
||||||
auto_paused = Use <i>!play</i> to resume music!
|
auto_paused = Use <i>!play</i> to resume music!
|
||||||
webpage_token= Your token to access the Bot webpage is {token}, short <a href="YOUR_URL_HERE?token={token}">URL</a>
|
webpage_token= Your own address to access the web interface is <a href="{address}/?token={token}">{address}/?token={token}</a>
|
||||||
|
|
||||||
help = <h3>Commands</h3>
|
help = <h3>Commands</h3>
|
||||||
<b>Control</b>
|
<b>Control</b>
|
||||||
|
@ -108,33 +108,35 @@ port = 64738
|
|||||||
# - "pause",
|
# - "pause",
|
||||||
# - "pause_resume" (pause and resume once somebody re-enters the channel)
|
# - "pause_resume" (pause and resume once somebody re-enters the channel)
|
||||||
# - "stop" (also clears playlist)
|
# - "stop" (also clears playlist)
|
||||||
# - "nothing" or leave empty (do nothing)
|
# - leave empty (do nothing)
|
||||||
#when_nobody_in_channel = nothing
|
#when_nobody_in_channel =
|
||||||
|
|
||||||
# [webinterface] stores settings related to the web interface.
|
# [webinterface] stores settings related to the web interface.
|
||||||
[webinterface]
|
[webinterface]
|
||||||
# 'enable': Set 'enabled' to True if you'd like to use the web interface to manage
|
# 'enable': Set 'enabled' to True if you'd like to use the web interface to manage
|
||||||
# your playlist, upload files, etc.
|
# your playlist, upload files, etc.
|
||||||
# The web interface is disable by default for security and performance reason.
|
# The web interface is disable by default for security and performance reason.
|
||||||
|
# 'access_address': Used when user are questing the address to access the web interface.
|
||||||
#enabled = False
|
#enabled = False
|
||||||
#listening_addr = 127.0.0.1
|
#listening_addr = 127.0.0.1
|
||||||
#listening_port = 8181
|
#listening_port = 8181
|
||||||
#is_web_proxified = True
|
#is_web_proxified = True
|
||||||
|
#access_address = http://127.0.0.1:8181
|
||||||
|
|
||||||
# 'web_logfile': write access logs of the web server into this file.
|
# 'web_logfile': write access logs of the web server into this file.
|
||||||
#web_logfile =
|
#web_logfile =
|
||||||
|
|
||||||
# 'required_auth': Set this to True to enable password protection for the web interface.
|
# 'auth_method': Method used to authenticate users accessing the web interface.
|
||||||
#require_auth = False
|
# Options are 'password', 'token', 'none'
|
||||||
#user =
|
#auth_method = password
|
||||||
#password =
|
|
||||||
|
|
||||||
# Set this option to match mumble user with user on the webinterface
|
# 'user', 'password': If auth_method set to 'password', you need to set the username and
|
||||||
# It's working with an unique token an user can ask to the bot with token and to add music to the bot.
|
# password.
|
||||||
# It's also allow users to know who have add a music from the webinterface
|
#user = botamusique
|
||||||
# match_mumble_user = True
|
#password = mumble
|
||||||
|
|
||||||
# To use token (need session) flask need a password to encrypt/sign cookies used. !! YOU NEED TO CHANGE IT IF PREVIOUS OPTION IS TRUE!!
|
# 'flask_secret': To use token, flask need a password to encrypt/sign cookies.
|
||||||
|
# !! YOU NEED TO CHANGE IT IF auth_method IS 'token'!!
|
||||||
# flask_secret = ChangeThisPassword
|
# flask_secret = ChangeThisPassword
|
||||||
|
|
||||||
# [debug] stores some debug settings.
|
# [debug] stores some debug settings.
|
||||||
|
88
interface.py
88
interface.py
@ -94,53 +94,47 @@ def authenticate():
|
|||||||
|
|
||||||
|
|
||||||
def requires_auth(f):
|
def requires_auth(f):
|
||||||
@wraps(f)
|
|
||||||
def decorated(*args, **kwargs):
|
|
||||||
global log
|
|
||||||
auth = request.authorization
|
|
||||||
if var.config.getboolean("webinterface", "require_auth") and (
|
|
||||||
not auth or not check_auth(auth.username, auth.password)):
|
|
||||||
if auth:
|
|
||||||
log.info("web: Failed login attempt, user: %s" % auth.username)
|
|
||||||
return authenticate()
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
|
|
||||||
def set_cookie_token(f):
|
|
||||||
@wraps(f)
|
|
||||||
def decorated(*args, **kwargs):
|
|
||||||
global log
|
|
||||||
if var.config.getboolean("webinterface", "match_mumble_user"):
|
|
||||||
users = var.db.items('user')
|
|
||||||
if users:
|
|
||||||
for user in users:
|
|
||||||
tp = json.loads(user[1])
|
|
||||||
log.info(tp)
|
|
||||||
if tp['token'] == request.args.get('token'):
|
|
||||||
t_user = user[0]
|
|
||||||
log.info(f"web: token validated for the user: {t_user}")
|
|
||||||
session['user']=t_user
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
log.info("web: Bad token used")
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
|
|
||||||
def requires_token(f):
|
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
global log, user
|
global log, user
|
||||||
if var.config.getboolean("webinterface", "match_mumble_user"):
|
|
||||||
if 'user' in session:
|
auth_method = var.config.get("webinterface", "auth_method")
|
||||||
log.debug(f"Request done by {session['user']}")
|
|
||||||
user = session['user']
|
if auth_method == 'password':
|
||||||
else:
|
auth = request.authorization
|
||||||
abort(403)
|
if var.config.getboolean("webinterface", "require_auth") and (
|
||||||
|
not auth or not check_auth(auth.username, auth.password)):
|
||||||
|
if auth:
|
||||||
|
log.warning(f"web: failed login attempt, user: {auth.username}, from ip {request.remote_addr}.")
|
||||||
|
return authenticate()
|
||||||
|
if auth_method == 'token':
|
||||||
|
if 'token' in session and 'token' not in request.args:
|
||||||
|
token = session['token']
|
||||||
|
token_user = var.db.get("web_token", token, fallback=None)
|
||||||
|
if token_user is not None:
|
||||||
|
user = token_user
|
||||||
|
log.debug(f"web: token validated for the user: {token_user}, from ip {request.remote_addr}.")
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
elif 'token' in request.args:
|
||||||
|
token = request.args.get('token')
|
||||||
|
token_user = var.db.get("web_token", token, fallback=None)
|
||||||
|
if token_user is not None:
|
||||||
|
user = token_user
|
||||||
|
|
||||||
|
user_info = var.db.get("user", user, fallback=None)
|
||||||
|
user_dict = json.loads(user_info)
|
||||||
|
user_dict['IP'] = request.remote_addr
|
||||||
|
var.db.set("user", user, json.dumps(user_dict))
|
||||||
|
|
||||||
|
log.info(f"web: new user access, token validated for the user: {token_user}, from ip {request.remote_addr}.")
|
||||||
|
session['token'] = token
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
log.info(f"web: bad token from ip {request.remote_addr}.")
|
||||||
|
abort(403)
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
@ -190,7 +184,6 @@ def get_all_dirs():
|
|||||||
|
|
||||||
@web.route("/", methods=['GET'])
|
@web.route("/", methods=['GET'])
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@set_cookie_token
|
|
||||||
def index():
|
def index():
|
||||||
while var.cache.dir_lock.locked():
|
while var.cache.dir_lock.locked():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
@ -205,7 +198,6 @@ def index():
|
|||||||
|
|
||||||
@web.route("/playlist", methods=['GET'])
|
@web.route("/playlist", methods=['GET'])
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@requires_token
|
|
||||||
def playlist():
|
def playlist():
|
||||||
if len(var.playlist) == 0:
|
if len(var.playlist) == 0:
|
||||||
return ('', 204)
|
return ('', 204)
|
||||||
@ -301,7 +293,6 @@ def status():
|
|||||||
|
|
||||||
@web.route("/post", methods=['POST'])
|
@web.route("/post", methods=['POST'])
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@requires_token
|
|
||||||
def post():
|
def post():
|
||||||
global log
|
global log
|
||||||
|
|
||||||
@ -520,7 +511,6 @@ def build_library_query_condition(form):
|
|||||||
|
|
||||||
@web.route("/library", methods=['POST'])
|
@web.route("/library", methods=['POST'])
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@requires_token
|
|
||||||
def library():
|
def library():
|
||||||
global log
|
global log
|
||||||
ITEM_PER_PAGE = 10
|
ITEM_PER_PAGE = 10
|
||||||
@ -622,8 +612,7 @@ def library():
|
|||||||
|
|
||||||
|
|
||||||
@web.route('/upload', methods=["POST"])
|
@web.route('/upload', methods=["POST"])
|
||||||
#@requires_auth missing here ?
|
@requires_auth
|
||||||
@requires_token
|
|
||||||
def upload():
|
def upload():
|
||||||
global log
|
global log
|
||||||
|
|
||||||
@ -672,6 +661,7 @@ def upload():
|
|||||||
|
|
||||||
|
|
||||||
@web.route('/download', methods=["GET"])
|
@web.route('/download', methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def download():
|
def download():
|
||||||
global log
|
global log
|
||||||
|
|
||||||
|
@ -928,6 +928,7 @@ function uploadNextFile(){
|
|||||||
form.append('targetdir', uploadTargetDir.value);
|
form.append('targetdir', uploadTargetDir.value);
|
||||||
|
|
||||||
req.open('POST', 'upload');
|
req.open('POST', 'upload');
|
||||||
|
req.withCredentials = true;
|
||||||
req.send(form);
|
req.send(form);
|
||||||
|
|
||||||
file_progress_item.progress.classList.add("progress-bar-striped");
|
file_progress_item.progress.classList.add("progress-bar-striped");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user