migrating from basic to JWT auth #34

Merged
Sarsoo merged 3 commits from jwt into master 2022-08-09 18:03:52 +01:00
10 changed files with 65 additions and 53 deletions
Showing only changes of commit 4c9a17f219 - Show all commits

View File

@ -5,7 +5,7 @@ from datetime import datetime
from google.cloud import tasks_v2 from google.cloud import tasks_v2
from music.api.decorators import login_or_basic_auth, admin_required from music.api.decorators import login_or_jwt, admin_required
blueprint = Blueprint('admin-api', __name__) blueprint = Blueprint('admin-api', __name__)
@ -16,9 +16,9 @@ logger = logging.getLogger(__name__)
@blueprint.route('/tasks', methods=['GET']) @blueprint.route('/tasks', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@admin_required @admin_required
def get_tasks(user=None): def get_tasks(auth=None, user=None):
tasks = list(tasker.list_tasks(task_path)) tasks = list(tasker.list_tasks(task_path))

View File

@ -7,7 +7,7 @@ import json
import logging import logging
from datetime import datetime from datetime import datetime
from music.api.decorators import login_or_jwt, login_required, login_or_basic_auth, \ from music.api.decorators import login_or_jwt, login_required, login_or_jwt, \
admin_required, cloud_task, validate_json, validate_args, spotify_link_required admin_required, cloud_task, validate_json, validate_args, spotify_link_required
from music.cloud import queue_run_user_playlist, offload_or_run_user_playlist from music.cloud import queue_run_user_playlist, offload_or_run_user_playlist
from music.cloud.tasks import update_all_user_playlists, update_playlists from music.cloud.tasks import update_all_user_playlists, update_playlists
@ -28,8 +28,8 @@ logger = logging.getLogger(__name__)
@blueprint.route('/playlists', methods=['GET']) @blueprint.route('/playlists', methods=['GET'])
@login_or_basic_auth @login_or_jwt
def all_playlists_route(user=None): def all_playlists_route(auth=None, user=None):
"""Retrieve all playlists for a given user """Retrieve all playlists for a given user
Args: Args:
@ -64,9 +64,9 @@ def playlist_get_delete_route(auth=None,user=None):
@blueprint.route('/playlist', methods=['POST', 'PUT']) @blueprint.route('/playlist', methods=['POST', 'PUT'])
@login_or_basic_auth @login_or_jwt
@validate_json(('name', str)) @validate_json(('name', str))
def playlist_post_put_route(user=None): def playlist_post_put_route(auth=None, user=None):
request_json = request.get_json() request_json = request.get_json()
@ -161,8 +161,8 @@ def playlist_post_put_route(user=None):
@blueprint.route('/user', methods=['GET', 'POST']) @blueprint.route('/user', methods=['GET', 'POST'])
@login_or_basic_auth @login_or_jwt
def user_route(user=None): def user_route(auth=None, user=None):
assert user is not None assert user is not None
if request.method == 'GET': if request.method == 'GET':
@ -202,9 +202,9 @@ def user_route(user=None):
@blueprint.route('/users', methods=['GET']) @blueprint.route('/users', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@admin_required @admin_required
def all_users_route(user=None): def all_users_route(auth=None, user=None):
return jsonify({ return jsonify({
'accounts': [i.to_dict() for i in User.collection.fetch()] 'accounts': [i.to_dict() for i in User.collection.fetch()]
}), 200 }), 200
@ -234,9 +234,9 @@ def change_password(user=None):
@blueprint.route('/playlist/run', methods=['GET']) @blueprint.route('/playlist/run', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@validate_args(('name', str)) @validate_args(('name', str))
def run_playlist(user=None): def run_playlist(auth=None, user=None):
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
queue_run_user_playlist(user.username, request.args['name']) # pass to either cloud tasks or functions queue_run_user_playlist(user.username, request.args['name']) # pass to either cloud tasks or functions
@ -264,8 +264,8 @@ def run_playlist_task(): # receives cloud tasks request for update
@blueprint.route('/playlist/run/user', methods=['GET']) @blueprint.route('/playlist/run/user', methods=['GET'])
@login_or_basic_auth @login_or_jwt
def run_user(user=None): def run_user(auth=None, user=None):
if user.type == 'admin': if user.type == 'admin':
user_name = request.args.get('username', user.username) user_name = request.args.get('username', user.username)
@ -288,19 +288,19 @@ def run_user_task():
@blueprint.route('/playlist/run/users', methods=['GET']) @blueprint.route('/playlist/run/users', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@admin_required @admin_required
def run_users(user=None): def run_users(auth=None, user=None):
update_all_user_playlists() update_all_user_playlists()
return jsonify({'message': 'executed all users', 'status': 'success'}), 200 return jsonify({'message': 'executed all users', 'status': 'success'}), 200
@blueprint.route('/playlist/image', methods=['GET']) @blueprint.route('/playlist/image', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
@validate_args(('name', str)) @validate_args(('name', str))
def image(user=None): def image(auth=None, user=None):
_playlist = user.get_playlist(request.args['name'], raise_error=False) _playlist = user.get_playlist(request.args['name'], raise_error=False)
if _playlist is None: if _playlist is None:

View File

@ -2,7 +2,7 @@ from flask import Blueprint, jsonify
from datetime import date from datetime import date
import logging import logging
from music.api.decorators import login_or_basic_auth, lastfm_username_required from music.api.decorators import login_or_jwt, lastfm_username_required
import music.db.database as database import music.db.database as database
@ -11,9 +11,9 @@ logger = logging.getLogger(__name__)
@blueprint.route('/today', methods=['GET']) @blueprint.route('/today', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@lastfm_username_required @lastfm_username_required
def daily_scrobbles(user=None): def daily_scrobbles(auth=None, user=None):
net = database.get_authed_lastfm_network(user) net = database.get_authed_lastfm_network(user)

View File

@ -2,7 +2,7 @@ from flask import Blueprint, request, jsonify
import logging import logging
from music.api.decorators import login_or_basic_auth, spotify_link_required, validate_json from music.api.decorators import login_or_jwt, spotify_link_required, validate_json
import music.db.database as database import music.db.database as database
from spotframework.net.network import SpotifyNetworkException from spotframework.net.network import SpotifyNetworkException
@ -16,9 +16,9 @@ logger = logging.getLogger(__name__)
@blueprint.route('/play', methods=['POST']) @blueprint.route('/play', methods=['POST'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
def play(user=None): def play(auth=None, user=None):
request_json = request.get_json() request_json = request.get_json()
if 'uri' in request_json: if 'uri' in request_json:
@ -78,9 +78,9 @@ def play(user=None):
@blueprint.route('/next', methods=['POST']) @blueprint.route('/next', methods=['POST'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
def next_track(user=None): def next_track(auth=None, user=None):
net = database.get_authed_spotify_network(user) net = database.get_authed_spotify_network(user)
player = Player(net) player = Player(net)
@ -89,10 +89,10 @@ def next_track(user=None):
@blueprint.route('/shuffle', methods=['POST']) @blueprint.route('/shuffle', methods=['POST'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
@validate_json(('state', bool)) @validate_json(('state', bool))
def shuffle(user=None): def shuffle(auth=None, user=None):
request_json = request.get_json() request_json = request.get_json()
net = database.get_authed_spotify_network(user) net = database.get_authed_spotify_network(user)
@ -103,10 +103,10 @@ def shuffle(user=None):
@blueprint.route('/volume', methods=['POST']) @blueprint.route('/volume', methods=['POST'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
@validate_json(('volume', int)) @validate_json(('volume', int))
def volume(user=None): def volume(auth=None, user=None):
request_json = request.get_json() request_json = request.get_json()
if 0 <= request_json['volume'] <= 100: if 0 <= request_json['volume'] <= 100:

View File

@ -3,7 +3,7 @@ import logging
import json import json
import os import os
from music.api.decorators import admin_required, login_or_basic_auth, lastfm_username_required, \ from music.api.decorators import admin_required, login_or_jwt, lastfm_username_required, \
spotify_link_required, cloud_task, validate_args spotify_link_required, cloud_task, validate_args
import music.db.database as database import music.db.database as database
from music.cloud.tasks import refresh_all_user_playlist_stats, refresh_user_playlist_stats, refresh_playlist_task from music.cloud.tasks import refresh_all_user_playlist_stats, refresh_user_playlist_stats, refresh_playlist_task
@ -20,10 +20,10 @@ logger = logging.getLogger(__name__)
@blueprint.route('/count', methods=['GET']) @blueprint.route('/count', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
@lastfm_username_required @lastfm_username_required
def count(user=None): def count(auth=None, user=None):
uri = request.args.get('uri', None) uri = request.args.get('uri', None)
playlist_name = request.args.get('playlist_name', None) playlist_name = request.args.get('playlist_name', None)
@ -69,11 +69,11 @@ def count(user=None):
@blueprint.route('/playlist/refresh', methods=['GET']) @blueprint.route('/playlist/refresh', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
@lastfm_username_required @lastfm_username_required
@validate_args(('name', str)) @validate_args(('name', str))
def playlist_refresh(user=None): def playlist_refresh(auth=None, user=None):
playlist_name = request.args['name'] playlist_name = request.args['name']
@ -133,16 +133,16 @@ def run_playlist_artist_task():
@blueprint.route('/playlist/refresh/users', methods=['GET']) @blueprint.route('/playlist/refresh/users', methods=['GET'])
@login_or_basic_auth @login_or_jwt
@admin_required @admin_required
def run_users(user=None): def run_users(auth=None, user=None):
refresh_all_user_playlist_stats() refresh_all_user_playlist_stats()
return jsonify({'message': 'executed all users', 'status': 'success'}), 200 return jsonify({'message': 'executed all users', 'status': 'success'}), 200
@blueprint.route('/playlist/refresh/user', methods=['GET']) @blueprint.route('/playlist/refresh/user', methods=['GET'])
@login_or_basic_auth @login_or_jwt
def run_user(user=None): def run_user(auth=None, user=None):
if user.type == 'admin': if user.type == 'admin':
user_name = request.args.get('username', user.username) user_name = request.args.get('username', user.username)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
import logging import logging
from music.api.decorators import login_or_basic_auth, spotify_link_required from music.api.decorators import login_or_jwt, spotify_link_required
import music.db.database as database import music.db.database as database
from spotframework.engine.playlistengine import PlaylistEngine from spotframework.engine.playlistengine import PlaylistEngine
@ -12,9 +12,9 @@ logger = logging.getLogger(__name__)
@blueprint.route('/sort', methods=['POST']) @blueprint.route('/sort', methods=['POST'])
@login_or_basic_auth @login_or_jwt
@spotify_link_required @spotify_link_required
def sort(user=None): def sort(auth=None, user=None):
request_json = request.get_json() request_json = request.get_json()
net = database.get_authed_spotify_network(user) net = database.get_authed_spotify_network(user)

View File

@ -4,7 +4,7 @@ import logging
import os import os
import json import json
from music.api.decorators import login_or_basic_auth, cloud_task from music.api.decorators import login_or_jwt, cloud_task
from music.cloud.function import update_tag as serverless_update_tag from music.cloud.function import update_tag as serverless_update_tag
from music.tasks.update_tag import update_tag from music.tasks.update_tag import update_tag
@ -15,8 +15,8 @@ logger = logging.getLogger(__name__)
@blueprint.route('/tag', methods=['GET']) @blueprint.route('/tag', methods=['GET'])
@login_or_basic_auth @login_or_jwt
def tags(user=None): def tags(auth=None, user=None):
logger.info(f'retrieving tags for {user.username}') logger.info(f'retrieving tags for {user.username}')
return jsonify({ return jsonify({
'tags': [i.to_dict() for i in Tag.collection.parent(user.key).fetch()] 'tags': [i.to_dict() for i in Tag.collection.parent(user.key).fetch()]
@ -24,8 +24,8 @@ def tags(user=None):
@blueprint.route('/tag/<tag_id>', methods=['GET', 'PUT', 'POST', "DELETE"]) @blueprint.route('/tag/<tag_id>', methods=['GET', 'PUT', 'POST', "DELETE"])
@login_or_basic_auth @login_or_jwt
def tag_route(tag_id, user=None): def tag_route(tag_id, auth=None, user=None):
if request.method == 'GET': if request.method == 'GET':
return get_tag(tag_id, user) return get_tag(tag_id, user)
elif request.method == 'PUT': elif request.method == 'PUT':
@ -126,8 +126,8 @@ def delete_tag(tag_id, user):
@blueprint.route('/tag/<tag_id>/update', methods=['GET']) @blueprint.route('/tag/<tag_id>/update', methods=['GET'])
@login_or_basic_auth @login_or_jwt
def tag_refresh(tag_id, user=None): def tag_refresh(tag_id, auth=None, user=None):
logger.info(f'updating {tag_id} tag for {user.username}') logger.info(f'updating {tag_id} tag for {user.username}')
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':

View File

@ -6,6 +6,8 @@ from music.auth.jwt_keys import generate_key
from urllib.parse import urlencode, urlunparse from urllib.parse import urlencode, urlunparse
import datetime import datetime
from datetime import timedelta
from numbers import Number
import logging import logging
from base64 import b64encode from base64 import b64encode
import requests import requests
@ -95,12 +97,19 @@ def jwt_token():
logger.warning(f'locked account token attempt {username}') logger.warning(f'locked account token attempt {username}')
return jsonify({"message": 'user locked', "status": "error"}), 403 return jsonify({"message": 'user locked', "status": "error"}), 403
user.last_login = datetime.datetime.utcnow() user.last_keygen = datetime.datetime.utcnow()
user.update() user.update()
logger.info(f'generating token for {username}') logger.info(f'generating token for {username}')
token = generate_key(user) config = Config.collection.get("config/music-tools")
if isinstance(expiry := request_json.get('expiry', None), Number):
expiry = min(expiry, config.jwt_max_length)
else:
expiry = config.jwt_default_length
token = generate_key(user, timeout=timedelta(seconds=expiry))
return jsonify({"token": token, "status": "success"}), 200 return jsonify({"token": token, "status": "success"}), 200
else: else:

View File

@ -20,3 +20,5 @@ class Config(Model):
""" """
secret_key = TextField() secret_key = TextField()
jwt_secret_key = TextField() jwt_secret_key = TextField()
jwt_max_length = NumberField()
jwt_default_length = NumberField()

View File

@ -20,6 +20,7 @@ class User(Model):
type = TextField(default="user") type = TextField(default="user")
last_login = DateTime() last_login = DateTime()
last_keygen = DateTime()
last_refreshed = DateTime() last_refreshed = DateTime()
locked = BooleanField(default=False, required=True) locked = BooleanField(default=False, required=True)
validated = BooleanField(default=True, required=True) validated = BooleanField(default=True, required=True)