+
+ {username ? `${username}` : 'My Account'}
+
+
+ {/* API Key Section */}
+
+
+
+
+ {/* New API Key Section */}
+
+
{
+ const value = e.target.value;
+ const isValid = /^[^\s]+$/.test(value); // No spaces
+ if (!isValid) {
+ setApiKeyError('API key must not contain spaces.');
+ } else {
+ setApiKeyError('');
+ }
+ setNewApiKey(value);
+ }}
+ placeholder="Enter new API key"
+ className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
+ />
+ {apiKeyError &&
{apiKeyError}
}
+
+ {/* Change Password Section */}
+
+
{
+ const value = e.target.value;
+ const isValid = /^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(value);
+ if (!isValid) {
+ setPasswordError('Password must not contain spaces or invalid characters.');
+ } else {
+ setPasswordError('');
+ }
+ setPassword(value);
+ }}
+ placeholder="New Password"
+ className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
+ />
+ {passwordError &&
{passwordError}
}
+
- {/* Right Panel */}
-
- {/* Widevine Section */}
-
-
Widevine CDMs
-
- {wvList.length === 0 ? (
-
No Widevine CDMs uploaded.
- ) : (
- wvList.map((filename, i) => (
-
- {filename}
-
- ))
- )}
-
-
-
+
+
- {/* Playready Section */}
-
-
Playready CDMs
-
- {prList.length === 0 ? (
-
No Playready CDMs uploaded.
- ) : (
- prList.map((filename, i) => (
-
- {filename}
-
- ))
- )}
-
-
+
+ {/* Widevine Section */}
+
+
Widevine CDMs
+
+ {wvList.length === 0 ? (
+
No Widevine CDMs uploaded.
+ ) : (
+ wvList.map((filename, i) => (
+
+ {filename}
+
+ ))
+ )}
+
+
+
+ {/* Playready Section */}
+
+
Playready CDMs
+
+ {prList.length === 0 ? (
+
No Playready CDMs uploaded.
+ ) : (
+ prList.map((filename, i) => (
+
+ {filename}
+
+ ))
+ )}
+
+
diff --git a/cdrm-frontend/src/components/Pages/Register.jsx b/cdrm-frontend/src/components/Pages/Register.jsx
index 7bd646c..f337297 100644
--- a/cdrm-frontend/src/components/Pages/Register.jsx
+++ b/cdrm-frontend/src/components/Pages/Register.jsx
@@ -5,7 +5,20 @@ function Register() {
const [password, setPassword] = useState('');
const [status, setStatus] = useState('');
+ // Validation functions
+ const validateUsername = (name) => /^[A-Za-z0-9_-]+$/.test(name);
+ const validatePassword = (pass) => /^\S+$/.test(pass); // No spaces
+
const handleRegister = async () => {
+ if (!validateUsername(username)) {
+ setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
+ return;
+ }
+ if (!validatePassword(password)) {
+ setStatus("Invalid password. Spaces are not allowed.");
+ return;
+ }
+
try {
const response = await fetch('/register', {
method: 'POST',
@@ -26,6 +39,15 @@ function Register() {
};
const handleLogin = async () => {
+ if (!validateUsername(username)) {
+ setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
+ return;
+ }
+ if (!validatePassword(password)) {
+ setStatus("Invalid password. Spaces are not allowed.");
+ return;
+ }
+
try {
const response = await fetch('/login', {
method: 'POST',
diff --git a/custom_functions/database/user_db.py b/custom_functions/database/user_db.py
index f8f3775..d668bf6 100644
--- a/custom_functions/database/user_db.py
+++ b/custom_functions/database/user_db.py
@@ -11,18 +11,20 @@ def create_user_database():
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_info (
Username TEXT PRIMARY KEY,
- Password TEXT
+ Password TEXT,
+ Styled_Username TEXT,
+ API_Key TEXT
)
''')
-def add_user(username, password):
+def add_user(username, password, api_key):
hashed_pw = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
cursor = conn.cursor()
try:
- cursor.execute('INSERT INTO user_info (Username, Password) VALUES (?, ?)', (username, hashed_pw))
+ cursor.execute('INSERT INTO user_info (Username, Password, Styled_Username, API_Key) VALUES (?, ?, ?, ?)', (username.lower(), hashed_pw, username, api_key))
conn.commit()
return True
except sqlite3.IntegrityError:
@@ -32,7 +34,7 @@ def add_user(username, password):
def verify_user(username, password):
with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
cursor = conn.cursor()
- cursor.execute('SELECT Password FROM user_info WHERE Username = ?', (username,))
+ cursor.execute('SELECT Password FROM user_info WHERE Username = ?', (username.lower(),))
result = cursor.fetchone()
if result:
@@ -43,3 +45,56 @@ def verify_user(username, password):
return bcrypt.checkpw(password.encode('utf-8'), stored_hash)
else:
return False
+
+def fetch_api_key(username):
+ with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
+ cursor = conn.cursor()
+ cursor.execute('SELECT API_Key FROM user_info WHERE Username = ?', (username.lower(),))
+ result = cursor.fetchone()
+
+ if result:
+ return result[0]
+ else:
+ return None
+
+def change_password(username, new_password):
+
+ # Hash the new password
+ new_hashed_pw = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt())
+
+ # Update the password in the database
+ with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
+ cursor = conn.cursor()
+ cursor.execute('UPDATE user_info SET Password = ? WHERE Username = ?', (new_hashed_pw, username.lower()))
+ conn.commit()
+ return True
+
+def change_api_key(username, new_api_key):
+ # Update the API key in the database
+ with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
+ cursor = conn.cursor()
+ cursor.execute('UPDATE user_info SET API_Key = ? WHERE Username = ?', (new_api_key, username.lower()))
+ conn.commit()
+ return True
+
+def fetch_styled_username(username):
+ with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
+ cursor = conn.cursor()
+ cursor.execute('SELECT Styled_Username FROM user_info WHERE Username = ?', (username.lower(),))
+ result = cursor.fetchone()
+
+ if result:
+ return result[0]
+ else:
+ return None
+
+def fetch_username_by_api_key(api_key):
+ with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
+ cursor = conn.cursor()
+ cursor.execute('SELECT Username FROM user_info WHERE API_Key = ?', (api_key,))
+ result = cursor.fetchone()
+
+ if result:
+ return result[0] # Return the username
+ else:
+ return None # If no user is found for the API key
\ No newline at end of file
diff --git a/main.py b/main.py
index 0edade8..ede50e1 100644
--- a/main.py
+++ b/main.py
@@ -12,6 +12,7 @@ from routes.upload import upload_bp
from routes.user_info import user_info_bp
from routes.register import register_bp
from routes.login import login_bp
+from routes.user_changes import user_change_bp
import os
import yaml
app = Flask(__name__)
@@ -30,6 +31,7 @@ app.register_blueprint(user_info_bp)
app.register_blueprint(upload_bp)
app.register_blueprint(remotecdm_wv_bp)
app.register_blueprint(remotecdm_pr_bp)
+app.register_blueprint(user_change_bp)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
\ No newline at end of file
diff --git a/routes/register.py b/routes/register.py
index 1b0dcd9..cbbe283 100644
--- a/routes/register.py
+++ b/routes/register.py
@@ -1,29 +1,42 @@
+import re
from flask import Blueprint, request, jsonify
from custom_functions.database.user_db import add_user
+import uuid
-register_bp = Blueprint(
- 'register_bp',
- __name__,
-)
+register_bp = Blueprint('register_bp', __name__)
+
+USERNAME_REGEX = re.compile(r'^[A-Za-z0-9_-]+$')
+PASSWORD_REGEX = re.compile(r'^\S+$')
@register_bp.route('/register', methods=['POST'])
def register():
- if request.method == 'POST':
- data = request.get_json()
- for required_field in ['username', 'password']:
- if required_field not in data:
- return jsonify({
- 'error': f'Missing required field: {required_field}'
- })
- if add_user(data['username'], data['password']):
- return jsonify({
- 'message': 'User successfully registered!'
- })
- else:
- return jsonify({
- 'error': 'User already exists!'
- })
- else:
+ if request.method != 'POST':
+ return jsonify({'error': 'Method not supported'}), 405
+
+ data = request.get_json()
+
+ # Check required fields
+ for required_field in ['username', 'password']:
+ if required_field not in data:
+ return jsonify({'error': f'Missing required field: {required_field}'}), 400
+
+ username = data['username']
+ password = data['password']
+ api_key = str(uuid.uuid4())
+
+ # Validate username and password
+ if not USERNAME_REGEX.fullmatch(username):
return jsonify({
- 'error': 'Method not supported'
- })
\ No newline at end of file
+ 'error': 'Invalid username. Only letters, numbers, hyphens, and underscores are allowed.'
+ }), 400
+
+ if not PASSWORD_REGEX.fullmatch(password):
+ return jsonify({
+ 'error': 'Invalid password. Spaces are not allowed.'
+ }), 400
+
+ # Attempt to add user
+ if add_user(username, password, api_key):
+ return jsonify({'message': 'User successfully registered!'}), 201
+ else:
+ return jsonify({'error': 'User already exists!'}), 409
diff --git a/routes/remote_device_pr.py b/routes/remote_device_pr.py
index 45076fc..29188a3 100644
--- a/routes/remote_device_pr.py
+++ b/routes/remote_device_pr.py
@@ -5,6 +5,8 @@ from pyplayready.device import Device as PlayReadyDevice
from pyplayready.cdm import Cdm as PlayReadyCDM
from pyplayready import PSSH as PlayReadyPSSH
from pyplayready.exceptions import (InvalidSession, TooManySessions, InvalidLicense, InvalidPssh)
+from custom_functions.database.user_db import fetch_username_by_api_key
+from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
@@ -53,148 +55,169 @@ def remote_cdm_playready_open(device):
}
}
})
+ if request.headers['X-Secret-Key'] and str(device).lower() != config['default_pr_cdm'].lower():
+ api_key = request.headers['X-Secret-Key']
+ user = fetch_username_by_api_key(api_key=api_key)
+ if user:
+ if user_allowed_to_use_device(device=device, username=user):
+ pr_device = PlayReadyDevice.load(f'{os.getcwd()}/configs/CDMs/{user}/PR/{device}.prd')
+ cdm = current_app.config['CDM'] = PlayReadyCDM.from_device(pr_device)
+ session_id = cdm.open()
+ return jsonify({
+ 'message': 'Success',
+ 'data': {
+ 'session_id': session_id.hex(),
+ 'device': {
+ 'security_level': cdm.security_level
+ }
+ }
+ })
+ else:
+ return jsonify({
+ 'message': f"Device '{device}' is not found or you are not authorized to use it.",
+ }), 403
+ else:
+ return jsonify({
+ 'message': f"Device '{device}' is not found or you are not authorized to use it.",
+ }), 403
+ else:
+ return jsonify({
+ 'message': f"Device '{device}' is not found or you are not authorized to use it.",
+ }), 403
@remotecdm_pr_bp.route('/remotecdm/playready/
/close/', methods=['GET'])
def remote_cdm_playready_close(device, session_id):
- if str(device).lower() == config['default_pr_cdm'].lower():
+ try:
session_id = bytes.fromhex(session_id)
cdm = current_app.config["CDM"]
if not cdm:
return jsonify({
- 'status': 400,
'message': f'No CDM for "{device}" has been opened yet. No session to close'
- })
+ }), 400
try:
cdm.close(session_id)
except InvalidSession:
return jsonify({
- 'status': 400,
'message': f'Invalid session ID "{session_id.hex()}", it may have expired'
- })
+ }), 400
return jsonify({
- 'status': 200,
'message': f'Successfully closed Session "{session_id.hex()}".',
- })
- else:
+ }), 200
+ except Exception as e:
return jsonify({
- 'status': 400,
- 'message': f'Unauthorized'
- })
+ 'message': f'Failed to close Session "{session_id.hex()}".'
+ }), 400
@remotecdm_pr_bp.route('/remotecdm/playready//get_license_challenge', methods=['POST'])
def remote_cdm_playready_get_license_challenge(device):
- if str(device).lower() == config['default_pr_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id", "init_data"):
- if not body.get(required_field):
- return jsonify({
- 'status': 400,
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
- cdm = current_app.config["CDM"]
- session_id = bytes.fromhex(body["session_id"])
- init_data = body["init_data"]
- if not init_data.startswith("/parse_license', methods=['POST'])
def remote_cdm_playready_parse_license(device):
- if str(device).lower() == config['default_pr_cdm'].lower():
- body = request.get_json()
- for required_field in ("license_message", "session_id"):
- if not body.get(required_field):
- return jsonify({
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
- cdm = current_app.config["CDM"]
- if not cdm:
+ body = request.get_json()
+ for required_field in ("license_message", "session_id"):
+ if not body.get(required_field):
return jsonify({
- 'message': f"No Cdm session for {device} has been opened yet. No session to use."
- })
- session_id = bytes.fromhex(body["session_id"])
- license_message = body["license_message"]
- try:
- cdm.parse_license(session_id, license_message)
- except InvalidSession:
- return jsonify({
- 'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
- })
- except InvalidLicense as e:
- return jsonify({
- 'message': f"Invalid License, {e}"
- })
- except Exception as e:
- return jsonify({
- 'message': f"Error, {e}"
+ 'message': f'Missing required field "{required_field}" in JSON body'
})
+ cdm = current_app.config["CDM"]
+ if not cdm:
return jsonify({
- 'message': 'Successfully parsed and loaded the Keys from the License message'
+ 'message': f"No Cdm session for {device} has been opened yet. No session to use."
})
+ session_id = bytes.fromhex(body["session_id"])
+ license_message = body["license_message"]
+ try:
+ cdm.parse_license(session_id, license_message)
+ except InvalidSession:
+ return jsonify({
+ 'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
+ })
+ except InvalidLicense as e:
+ return jsonify({
+ 'message': f"Invalid License, {e}"
+ })
+ except Exception as e:
+ return jsonify({
+ 'message': f"Error, {e}"
+ })
+ return jsonify({
+ 'message': 'Successfully parsed and loaded the Keys from the License message'
+ })
@remotecdm_pr_bp.route('/remotecdm/playready//get_keys', methods=['POST'])
def remote_cdm_playready_get_keys(device):
- if str(device).lower() == config['default_pr_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id",):
- if not body.get(required_field):
- return jsonify({
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
- session_id = bytes.fromhex(body["session_id"])
- cdm = current_app.config["CDM"]
- if not cdm:
+ body = request.get_json()
+ for required_field in ("session_id",):
+ if not body.get(required_field):
return jsonify({
- 'message': f"Missing required field '{required_field}' in JSON body."
+ 'message': f'Missing required field "{required_field}" in JSON body'
})
- try:
- keys = cdm.get_keys(session_id)
- except InvalidSession:
- return jsonify({
- 'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
- })
- except Exception as e:
- return jsonify({
- 'message': f"Error, {e}"
- })
- keys_json = [
- {
- "key_id": key.key_id.hex,
- "key": key.key.hex(),
- "type": key.key_type.value,
- "cipher_type": key.cipher_type.value,
- "key_length": key.key_length,
- }
- for key in keys
- ]
+ session_id = bytes.fromhex(body["session_id"])
+ cdm = current_app.config["CDM"]
+ if not cdm:
return jsonify({
- 'message': 'success',
- 'data': {
- 'keys': keys_json
- }
- })
\ No newline at end of file
+ 'message': f"Missing required field '{required_field}' in JSON body."
+ })
+ try:
+ keys = cdm.get_keys(session_id)
+ except InvalidSession:
+ return jsonify({
+ 'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
+ })
+ except Exception as e:
+ return jsonify({
+ 'message': f"Error, {e}"
+ })
+ keys_json = [
+ {
+ "key_id": key.key_id.hex,
+ "key": key.key.hex(),
+ "type": key.key_type.value,
+ "cipher_type": key.cipher_type.value,
+ "key_length": key.key_length,
+ }
+ for key in keys
+ ]
+ return jsonify({
+ 'message': 'success',
+ 'data': {
+ 'keys': keys_json
+ }
+ })
\ No newline at end of file
diff --git a/routes/remote_device_wv.py b/routes/remote_device_wv.py
index 2339846..080bc02 100644
--- a/routes/remote_device_wv.py
+++ b/routes/remote_device_wv.py
@@ -11,6 +11,8 @@ from pywidevine.exceptions import (InvalidContext, InvalidInitData, InvalidLicen
InvalidSession, SignatureMismatch, TooManySessions)
import yaml
+from custom_functions.database.user_db import fetch_api_key, fetch_username_by_api_key
+from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
remotecdm_wv_bp = Blueprint('remotecdm_wv', __name__)
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
@@ -61,309 +63,307 @@ def remote_cdm_widevine_open(device):
'security_level': cdm.security_level,
}
}
- })
+ }), 200
+ if request.headers['X-Secret-Key'] and str(device).lower() != config['default_wv_cdm'].lower():
+ api_key = request.headers['X-Secret-Key']
+ user = fetch_username_by_api_key(api_key=api_key)
+ if user:
+ if user_allowed_to_use_device(device=device, username=user):
+ wv_device = widevineDevice.load(f'{os.getcwd()}/configs/CDMs/{user}/WV/{device}.wvd')
+ cdm = current_app.config["CDM"] = widevineCDM.from_device(wv_device)
+ session_id = cdm.open()
+ return jsonify({
+ 'status': 200,
+ 'message': 'Success',
+ 'data': {
+ 'session_id': session_id.hex(),
+ 'device': {
+ 'system_id': cdm.system_id,
+ 'security_level': cdm.security_level,
+ }
+ }
+ }), 200
+ else:
+ return jsonify({
+ 'message': f"Device '{device}' is not found or you are not authorized to use it.",
+ 'status': 403
+ }), 403
+ else:
+ return jsonify({
+ 'message': f"Device '{device}' is not found or you are not authorized to use it.",
+ 'status': 403
+ }), 403
else:
return jsonify({
- 'status': 400,
- 'message': 'Unauthorized'
- })
+ 'message': f"Device '{device}' is not found or you are not authorized to use it.",
+ 'status': 403
+ }), 403
+
@remotecdm_wv_bp.route('/remotecdm/widevine//close/', methods=['GET'])
def remote_cdm_widevine_close(device, session_id):
- if str(device).lower() == config['default_wv_cdm'].lower():
session_id = bytes.fromhex(session_id)
cdm = current_app.config["CDM"]
if not cdm:
return jsonify({
'status': 400,
'message': f'No CDM for "{device}" has been opened yet. No session to close'
- })
+ }), 400
try:
cdm.close(session_id)
except InvalidSession:
return jsonify({
'status': 400,
'message': f'Invalid session ID "{session_id.hex()}", it may have expired'
- })
+ }), 400
return jsonify({
'status': 200,
'message': f'Successfully closed Session "{session_id.hex()}".',
- })
- else:
- return jsonify({
- 'status': 400,
- 'message': f'Unauthorized'
- })
+ }), 200
@remotecdm_wv_bp.route('/remotecdm/widevine//set_service_certificate', methods=['POST'])
def remote_cdm_widevine_set_service_certificate(device):
- if str(device).lower() == config['default_wv_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id", "certificate"):
- if required_field == "certificate":
- has_field = required_field in body # it needs the key, but can be empty/null
- else:
- has_field = body.get(required_field)
- if not has_field:
- return jsonify({
- 'status': 400,
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
+ body = request.get_json()
+ for required_field in ("session_id", "certificate"):
+ if required_field == "certificate":
+ has_field = required_field in body # it needs the key, but can be empty/null
+ else:
+ has_field = body.get(required_field)
+ if not has_field:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Missing required field "{required_field}" in JSON body'
+ }), 400
- session_id = bytes.fromhex(body["session_id"])
+ session_id = bytes.fromhex(body["session_id"])
- cdm = current_app.config["CDM"]
- if not cdm:
- return jsonify({
- 'status': 400,
- 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
- })
-
- certificate = body["certificate"]
- try:
- provider_id = cdm.set_service_certificate(session_id, certificate)
- except InvalidSession:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid session id: "{session_id.hex()}", it may have expired'
- })
- except DecodeError as error:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Service Certificate, {error}'
- })
- except SignatureMismatch:
- return jsonify({
- 'status': 400,
- 'message': 'Signature Validation failed on the Service Certificate, rejecting'
- })
- return jsonify({
- 'status': 200,
- 'message': f"Successfully {['set', 'unset'][not certificate]} the Service Certificate.",
- 'data': {
- 'provider_id': provider_id,
- }
- })
- else:
+ cdm = current_app.config["CDM"]
+ if not cdm:
return jsonify({
'status': 400,
- 'message': f'Unauthorized'
- })
+ 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
+ }), 400
+
+ certificate = body["certificate"]
+ try:
+ provider_id = cdm.set_service_certificate(session_id, certificate)
+ except InvalidSession:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid session id: "{session_id.hex()}", it may have expired'
+ }), 400
+ except DecodeError as error:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Service Certificate, {error}'
+ }), 400
+ except SignatureMismatch:
+ return jsonify({
+ 'status': 400,
+ 'message': 'Signature Validation failed on the Service Certificate, rejecting'
+ }), 400
+ return jsonify({
+ 'status': 200,
+ 'message': f"Successfully {['set', 'unset'][not certificate]} the Service Certificate.",
+ 'data': {
+ 'provider_id': provider_id,
+ }
+ }), 200
@remotecdm_wv_bp.route('/remotecdm/widevine//get_service_certificate', methods=['POST'])
def remote_cdm_widevine_get_service_certificate(device):
- if str(device).lower() == config['default_wv_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id",):
- if not body.get(required_field):
- return jsonify({
- 'status': 400,
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
-
- session_id = bytes.fromhex(body["session_id"])
-
- cdm = current_app.config["CDM"]
-
- if not cdm:
+ body = request.get_json()
+ for required_field in ("session_id",):
+ if not body.get(required_field):
return jsonify({
'status': 400,
- 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
- })
+ 'message': f'Missing required field "{required_field}" in JSON body'
+ }), 400
- try:
- service_certificate = cdm.get_service_certificate(session_id)
- except InvalidSession:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
- })
- if service_certificate:
- service_certificate_b64 = base64.b64encode(service_certificate.SerializeToString()).decode()
- else:
- service_certificate_b64 = None
- return jsonify({
- 'status': 200,
- 'message': 'Successfully got the Service Certificate',
- 'data': {
- 'service_certificate': service_certificate_b64,
- }
- })
- else:
+ session_id = bytes.fromhex(body["session_id"])
+
+ cdm = current_app.config["CDM"]
+
+ if not cdm:
return jsonify({
'status': 400,
- 'message': f'Unauthorized'
- })
+ 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
+ }), 400
+
+ try:
+ service_certificate = cdm.get_service_certificate(session_id)
+ except InvalidSession:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
+ }), 400
+ if service_certificate:
+ service_certificate_b64 = base64.b64encode(service_certificate.SerializeToString()).decode()
+ else:
+ service_certificate_b64 = None
+ return jsonify({
+ 'status': 200,
+ 'message': 'Successfully got the Service Certificate',
+ 'data': {
+ 'service_certificate': service_certificate_b64,
+ }
+ }), 200
@remotecdm_wv_bp.route('/remotecdm/widevine//get_license_challenge/', methods=['POST'])
def remote_cdm_widevine_get_license_challenge(device, license_type):
- if str(device).lower() == config['default_wv_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id", "init_data"):
- if not body.get(required_field):
- return jsonify({
- 'status': 400,
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
- session_id = bytes.fromhex(body["session_id"])
- privacy_mode = body.get("privacy_mode", True)
- cdm = current_app.config["CDM"]
- if not cdm:
+ body = request.get_json()
+ for required_field in ("session_id", "init_data"):
+ if not body.get(required_field):
return jsonify({
'status': 400,
- 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
- })
- if current_app.config.get("force_privacy_mode"):
- privacy_mode = True
- if not cdm.get_service_certificate(session_id):
- return jsonify({
- 'status': 403,
- 'message': 'No Service Certificate set but Privacy Mode is Enforced.'
- })
-
- current_app.config['pssh'] = body['init_data']
- init_data = widevinePSSH(body['init_data'])
-
- try:
- license_request = cdm.get_license_challenge(
- session_id=session_id,
- pssh=init_data,
- license_type=license_type,
- privacy_mode=privacy_mode
- )
- except InvalidSession:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
- })
- except InvalidInitData as error:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Init Data, {error}'
- })
- except InvalidLicenseType:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid License Type {license_type}'
- })
- return jsonify({
- 'status': 200,
- 'message': 'Success',
- 'data': {
- 'challenge_b64': base64.b64encode(license_request).decode()
- }
- })
- else:
+ 'message': f'Missing required field "{required_field}" in JSON body'
+ }), 400
+ session_id = bytes.fromhex(body["session_id"])
+ privacy_mode = body.get("privacy_mode", True)
+ cdm = current_app.config["CDM"]
+ if not cdm:
return jsonify({
'status': 400,
- 'message': f'Unauthorized'
- })
+ 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
+ }), 400
+ if current_app.config.get("force_privacy_mode"):
+ privacy_mode = True
+ if not cdm.get_service_certificate(session_id):
+ return jsonify({
+ 'status': 403,
+ 'message': 'No Service Certificate set but Privacy Mode is Enforced.'
+ }), 403
+
+ current_app.config['pssh'] = body['init_data']
+ init_data = widevinePSSH(body['init_data'])
+
+ try:
+ license_request = cdm.get_license_challenge(
+ session_id=session_id,
+ pssh=init_data,
+ license_type=license_type,
+ privacy_mode=privacy_mode
+ )
+ except InvalidSession:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
+ }), 400
+ except InvalidInitData as error:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Init Data, {error}'
+ }), 400
+ except InvalidLicenseType:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid License Type {license_type}'
+ }), 400
+ return jsonify({
+ 'status': 200,
+ 'message': 'Success',
+ 'data': {
+ 'challenge_b64': base64.b64encode(license_request).decode()
+ }
+ }), 200
@remotecdm_wv_bp.route('/remotecdm/widevine//parse_license', methods=['POST'])
def remote_cdm_widevine_parse_license(device):
- if str(device).lower() == config['default_wv_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id", "license_message"):
- if not body.get(required_field):
- return jsonify({
- 'status': 400,
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
- session_id = bytes.fromhex(body["session_id"])
- cdm = current_app.config["CDM"]
- if not cdm:
+ body = request.get_json()
+ for required_field in ("session_id", "license_message"):
+ if not body.get(required_field):
return jsonify({
'status': 400,
- 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
- })
- try:
- cdm.parse_license(session_id, body['license_message'])
- except InvalidLicenseMessage as error:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid License Message, {error}'
- })
- except InvalidContext as error:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Context, {error}'
- })
- except InvalidSession:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
- })
- except SignatureMismatch:
- return jsonify({
- 'status': 400,
- 'message': f'Signature Validation failed on the License Message, rejecting.'
- })
- return jsonify({
- 'status': 200,
- 'message': 'Successfully parsed and loaded the Keys from the License message.',
- })
- else:
+ 'message': f'Missing required field "{required_field}" in JSON body'
+ }), 400
+ session_id = bytes.fromhex(body["session_id"])
+ cdm = current_app.config["CDM"]
+ if not cdm:
return jsonify({
'status': 400,
- 'message': 'Unauthorized'
- })
+ 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
+ }), 400
+ try:
+ cdm.parse_license(session_id, body['license_message'])
+ except InvalidLicenseMessage as error:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid License Message, {error}'
+ }), 400
+ except InvalidContext as error:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Context, {error}'
+ }), 400
+ except InvalidSession:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
+ }), 400
+ except SignatureMismatch:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Signature Validation failed on the License Message, rejecting.'
+ }), 400
+ return jsonify({
+ 'status': 200,
+ 'message': 'Successfully parsed and loaded the Keys from the License message.',
+ }), 200
@remotecdm_wv_bp.route('/remotecdm/widevine//get_keys/', methods=['POST'])
def remote_cdm_widevine_get_keys(device, key_type):
- if str(device).lower() == config['default_wv_cdm'].lower():
- body = request.get_json()
- for required_field in ("session_id",):
- if not body.get(required_field):
- return jsonify({
- 'status': 400,
- 'message': f'Missing required field "{required_field}" in JSON body'
- })
- session_id = bytes.fromhex(body["session_id"])
- key_type: Optional[str] = key_type
- if key_type == 'ALL':
- key_type = None
- cdm = current_app.config["CDM"]
- if not cdm:
+ body = request.get_json()
+ for required_field in ("session_id",):
+ if not body.get(required_field):
return jsonify({
'status': 400,
- 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
- })
- try:
- keys = cdm.get_keys(session_id, key_type)
- except InvalidSession:
- return jsonify({
- 'status': 400,
- 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
- })
- except ValueError as error:
- return jsonify({
- 'status': 400,
- 'message': f'The Key Type value "{key_type}" is invalid, {error}'
- })
- keys_json = [
- {
- "key_id": key.kid.hex,
- "key": key.key.hex(),
- "type": key.type,
- "permissions": key.permissions
- }
- for key in keys
- if not key_type or key.type == key_type
- ]
- for entry in keys_json:
- if config['database_type'].lower() != 'mariadb':
- from custom_functions.database.cache_to_db_sqlite import cache_to_db
- elif config['database_type'].lower() == 'mariadb':
- from custom_functions.database.cache_to_db_mariadb import cache_to_db
- if entry['type'] != 'SIGNING':
- cache_to_db(pssh=str(current_app.config['pssh']), kid=entry['key_id'], key=entry['key'])
-
-
+ 'message': f'Missing required field "{required_field}" in JSON body'
+ }), 400
+ session_id = bytes.fromhex(body["session_id"])
+ key_type: Optional[str] = key_type
+ if key_type == 'ALL':
+ key_type = None
+ cdm = current_app.config["CDM"]
+ if not cdm:
return jsonify({
- 'status': 200,
- 'message': 'Success',
- 'data': {
- 'keys': keys_json
- }
- })
\ No newline at end of file
+ 'status': 400,
+ 'message': f'No CDM session for "{device}" has been opened yet. No session to use'
+ }), 400
+ try:
+ keys = cdm.get_keys(session_id, key_type)
+ except InvalidSession:
+ return jsonify({
+ 'status': 400,
+ 'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
+ }), 400
+ except ValueError as error:
+ return jsonify({
+ 'status': 400,
+ 'message': f'The Key Type value "{key_type}" is invalid, {error}'
+ }), 400
+ keys_json = [
+ {
+ "key_id": key.kid.hex,
+ "key": key.key.hex(),
+ "type": key.type,
+ "permissions": key.permissions
+ }
+ for key in keys
+ if not key_type or key.type == key_type
+ ]
+ for entry in keys_json:
+ if config['database_type'].lower() != 'mariadb':
+ from custom_functions.database.cache_to_db_sqlite import cache_to_db
+ elif config['database_type'].lower() == 'mariadb':
+ from custom_functions.database.cache_to_db_mariadb import cache_to_db
+ if entry['type'] != 'SIGNING':
+ cache_to_db(pssh=str(current_app.config['pssh']), kid=entry['key_id'], key=entry['key'])
+
+ return jsonify({
+ 'status': 200,
+ 'message': 'Success',
+ 'data': {
+ 'keys': keys_json
+ }
+ }), 200
\ No newline at end of file
diff --git a/routes/user_changes.py b/routes/user_changes.py
new file mode 100644
index 0000000..ec4014c
--- /dev/null
+++ b/routes/user_changes.py
@@ -0,0 +1,54 @@
+import re
+from flask import Blueprint, request, jsonify, session
+from custom_functions.database.user_db import change_password, change_api_key
+
+user_change_bp = Blueprint('user_change_bp', __name__)
+
+# Define allowed characters regex (no spaces allowed)
+PASSWORD_REGEX = re.compile(r'^[A-Za-z0-9!@#$%^&*()_+\-=\[\]{};\'":\\|,.<>\/?`~]+$')
+
+@user_change_bp.route('/user/change_password', methods=['POST'])
+def change_password_route():
+ username = session.get('username')
+ if not username:
+ return jsonify({'message': 'False'}), 400
+
+ try:
+ data = request.get_json()
+ new_password = data.get('new_password', '')
+
+ if not PASSWORD_REGEX.match(new_password):
+ return jsonify({'message': 'Invalid password format'}), 400
+
+ change_password(username=username, new_password=new_password)
+ return jsonify({'message': 'True'}), 200
+
+ except Exception as e:
+ return jsonify({'message': 'False'}), 400
+
+
+@user_change_bp.route('/user/change_api_key', methods=['POST'])
+def change_api_key_route():
+ # Ensure the user is logged in by checking session for 'username'
+ username = session.get('username')
+ if not username:
+ return jsonify({'message': 'False', 'error': 'User not logged in'}), 400
+
+ # Get the new API key from the request body
+ new_api_key = request.json.get('new_api_key')
+
+ if not new_api_key:
+ return jsonify({'message': 'False', 'error': 'New API key not provided'}), 400
+
+ try:
+ # Call the function to update the API key in the database
+ success = change_api_key(username=username, new_api_key=new_api_key)
+
+ if success:
+ return jsonify({'message': 'True', 'success': 'API key changed successfully'}), 200
+ else:
+ return jsonify({'message': 'False', 'error': 'Failed to change API key'}), 500
+
+ except Exception as e:
+ # Catch any unexpected errors and return a response
+ return jsonify({'message': 'False', 'error': str(e)}), 500
\ No newline at end of file
diff --git a/routes/user_info.py b/routes/user_info.py
index 845af3c..e32611f 100644
--- a/routes/user_info.py
+++ b/routes/user_info.py
@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify, session
import os
import glob
import logging
+from custom_functions.database.user_db import fetch_api_key, fetch_styled_username
user_info_bp = Blueprint('user_info_bp', __name__)
@@ -19,7 +20,9 @@ def user_info():
return jsonify({
'Username': username,
'Widevine_Devices': wv_files,
- 'Playready_Devices': pr_files
+ 'Playready_Devices': pr_files,
+ 'API_Key': fetch_api_key(username),
+ 'Styled_Username': fetch_styled_username(username)
})
except Exception as e:
logging.exception("Error retrieving device files")