User updates
This commit is contained in:
parent
c218ae4cc6
commit
29b61668a4
4
cdrm-frontend/dist/index.html
vendored
4
cdrm-frontend/dist/index.html
vendored
@ -12,8 +12,8 @@
|
||||
<meta property='og:url' content="{{ data.opengraph_url }}" />
|
||||
<meta property='og:locale' content='en_US' />
|
||||
<title>{{ data.tab_title }}</title>
|
||||
<script type="module" crossorigin src="/assets/index-C2DUB5KK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BXlb7x7c.css">
|
||||
<script type="module" crossorigin src="/assets/index-DWCLK6jB.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DQNyIeaF.css">
|
||||
</head>
|
||||
<body class="w-full h-full">
|
||||
<div id="root" class="w-full h-full"></div>
|
||||
|
@ -6,6 +6,11 @@ function MyAccount() {
|
||||
const [prList, setPrList] = useState([]);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [username, setUsername] = useState('');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [newApiKey, setNewApiKey] = useState('');
|
||||
const [apiKeyError, setApiKeyError] = useState('');
|
||||
|
||||
// Fetch user info
|
||||
const fetchUserInfo = async () => {
|
||||
@ -13,7 +18,8 @@ function MyAccount() {
|
||||
const response = await axios.post('/userinfo');
|
||||
setWvList(response.data.Widevine_Devices || []);
|
||||
setPrList(response.data.Playready_Devices || []);
|
||||
setUsername(response.data.Username || '');
|
||||
setUsername(response.data.Styled_Username || '');
|
||||
setApiKey(response.data.API_Key || '');
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch user info', err);
|
||||
}
|
||||
@ -60,83 +66,193 @@ function MyAccount() {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle change password
|
||||
const handleChangePassword = async () => {
|
||||
if (passwordError || password === '') {
|
||||
alert('Please enter a valid password.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('/user/change_password', {
|
||||
new_password: password
|
||||
});
|
||||
|
||||
if (response.data.message === 'True') {
|
||||
alert('Password changed successfully.');
|
||||
setPassword('');
|
||||
} else {
|
||||
alert('Failed to change password.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data?.message === 'Invalid password format') {
|
||||
alert('Password format is invalid. Please try again.');
|
||||
} else {
|
||||
alert('Error occurred while changing password.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle change API key
|
||||
const handleChangeApiKey = async () => {
|
||||
if (apiKeyError || newApiKey === '') {
|
||||
alert('Please enter a valid API key.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('/user/change_api_key', {
|
||||
new_api_key: newApiKey,
|
||||
});
|
||||
if (response.data.message === 'True') {
|
||||
alert('API key changed successfully.');
|
||||
setApiKey(newApiKey);
|
||||
setNewApiKey('');
|
||||
} else {
|
||||
alert('Failed to change API key.');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error occurred while changing API key.');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="myaccount" className="flex flex-row w-full min-h-full overflow-y-auto p-4">
|
||||
<div className="flex flex-col w-full min-h-full lg:flex-row">
|
||||
{/* Left Panel */}
|
||||
<div className="border-2 border-yellow-500/50 lg:h-full lg:w-96 w-full rounded-2xl p-4 flex flex-col items-center overflow-y-auto">
|
||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
|
||||
{username ? `${username}` : 'My Account'}
|
||||
</h1>
|
||||
<div id="myaccount" className="flex flex-col lg:flex-row gap-4 w-full min-h-full overflow-y-auto p-4">
|
||||
<div className="flex-col w-full min-h-164 lg:h-full lg:w-96 border-2 border-yellow-500/50 rounded-2xl p-4 flex items-center overflow-y-auto">
|
||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
|
||||
{username ? `${username}` : 'My Account'}
|
||||
</h1>
|
||||
|
||||
{/* API Key Section */}
|
||||
<div className="w-full flex flex-col items-center">
|
||||
<label htmlFor="apiKey" className="text-white font-semibold mb-1">API Key</label>
|
||||
<input
|
||||
id="apiKey"
|
||||
type="text"
|
||||
value={apiKey}
|
||||
readOnly
|
||||
className="w-full p-2 mb-4 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
||||
/>
|
||||
|
||||
{/* New API Key Section */}
|
||||
<label htmlFor="newApiKey" className="text-white font-semibold mt-4 mb-1">New API Key</label>
|
||||
<input
|
||||
id="newApiKey"
|
||||
type="text"
|
||||
value={newApiKey}
|
||||
onChange={(e) => {
|
||||
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 && <p className="text-red-500 text-sm mb-3">{apiKeyError}</p>}
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||
onClick={handleChangeApiKey}
|
||||
>
|
||||
Log out
|
||||
Change API Key
|
||||
</button>
|
||||
|
||||
{/* Change Password Section */}
|
||||
<label htmlFor="password" className="text-white font-semibold mt-4 mb-1">Change Password</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => {
|
||||
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 && <p className="text-red-500 text-sm mb-3">{passwordError}</p>}
|
||||
<button
|
||||
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||
onClick={handleChangePassword}
|
||||
>
|
||||
Change Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right Panel */}
|
||||
<div className="flex flex-col grow lg:ml-2 mt-2 lg:mt-0">
|
||||
{/* Widevine Section */}
|
||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl lg:p-4 p-2 overflow-y-auto">
|
||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2">Widevine CDMs</h1>
|
||||
<div className="flex flex-col w-full grow p-2 bg-white/5 rounded-2xl mt-2 text-white text-left">
|
||||
{wvList.length === 0 ? (
|
||||
<div className="text-white text-center font-bold">No Widevine CDMs uploaded.</div>
|
||||
) : (
|
||||
wvList.map((filename, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-center font-bold text-white p-2 rounded ${
|
||||
i % 2 === 0 ? 'bg-black/30' : 'bg-black/60'
|
||||
}`}
|
||||
>
|
||||
{filename}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<label className="bg-yellow-500 text-white w-full h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
||||
{uploading ? 'Uploading...' : 'Upload CDM'}
|
||||
<input
|
||||
type="file"
|
||||
accept=".wvd"
|
||||
hidden
|
||||
onChange={(e) => handleUpload(e, 'WV')}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||
>
|
||||
Log out
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Playready Section */}
|
||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl p-2 mt-2 lg:mt-2 overflow-y-auto">
|
||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2">Playready CDMs</h1>
|
||||
<div className="flex flex-col w-full bg-white/5 grow rounded-2xl mt-2 text-white text-left p-2">
|
||||
{prList.length === 0 ? (
|
||||
<div className="text-white text-center font-bold">No Playready CDMs uploaded.</div>
|
||||
) : (
|
||||
prList.map((filename, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-center font-bold text-white p-2 rounded ${
|
||||
i % 2 === 0 ? 'bg-black/30' : 'bg-black/60'
|
||||
}`}
|
||||
>
|
||||
{filename}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<label className="bg-yellow-500 text-white w-full h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
||||
{uploading ? 'Uploading...' : 'Upload CDM'}
|
||||
<input
|
||||
type="file"
|
||||
accept=".prd"
|
||||
hidden
|
||||
onChange={(e) => handleUpload(e, 'PR')}
|
||||
/>
|
||||
</label>
|
||||
<div className="flex flex-col w-full lg:ml-2 mt-2 lg:mt-0">
|
||||
{/* Widevine Section */}
|
||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl lg:p-4 p-2 overflow-y-auto">
|
||||
<h1 className="bg-black text-2xl font-bold text-white border-b-2 border-white p-2">Widevine CDMs</h1>
|
||||
<div className="flex flex-col w-full grow p-2 bg-white/5 rounded-2xl mt-2 text-white text-left">
|
||||
{wvList.length === 0 ? (
|
||||
<div className="text-white text-center font-bold">No Widevine CDMs uploaded.</div>
|
||||
) : (
|
||||
wvList.map((filename, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-center font-bold text-white p-2 rounded ${i % 2 === 0 ? 'bg-black/30' : 'bg-black/60'}`}
|
||||
>
|
||||
{filename}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
||||
{uploading ? 'Uploading...' : 'Upload CDM'}
|
||||
<input
|
||||
type="file"
|
||||
accept=".wvd"
|
||||
hidden
|
||||
onChange={(e) => handleUpload(e, 'WV')}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Playready Section */}
|
||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl p-2 mt-2 lg:mt-2 overflow-y-auto">
|
||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 bg-black">Playready CDMs</h1>
|
||||
<div className="flex flex-col w-full bg-white/5 grow rounded-2xl mt-2 text-white text-left p-2">
|
||||
{prList.length === 0 ? (
|
||||
<div className="text-white text-center font-bold">No Playready CDMs uploaded.</div>
|
||||
) : (
|
||||
prList.map((filename, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-center font-bold text-white p-2 rounded ${i % 2 === 0 ? 'bg-black/30' : 'bg-black/60'}`}
|
||||
>
|
||||
{filename}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
||||
{uploading ? 'Uploading...' : 'Upload CDM'}
|
||||
<input
|
||||
type="file"
|
||||
accept=".prd"
|
||||
hidden
|
||||
onChange={(e) => handleUpload(e, 'PR')}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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',
|
||||
|
@ -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
|
2
main.py
2
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')
|
@ -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'
|
||||
})
|
||||
'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
|
||||
|
@ -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/<device>/close/<session_id>', 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/<device>/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("<WRMHEADER"):
|
||||
try:
|
||||
pssh = PlayReadyPSSH(init_data)
|
||||
if pssh.wrm_headers:
|
||||
init_data = pssh.wrm_headers[0]
|
||||
except InvalidPssh as e:
|
||||
return jsonify({
|
||||
'message': f'Unable to parse base64 PSSH, {e}'
|
||||
})
|
||||
body = request.get_json()
|
||||
for required_field in ("session_id", "init_data"):
|
||||
if not body.get(required_field):
|
||||
return jsonify({
|
||||
'message': f'Missing required field "{required_field}" in JSON body'
|
||||
}), 400
|
||||
cdm = current_app.config["CDM"]
|
||||
session_id = bytes.fromhex(body["session_id"])
|
||||
init_data = body["init_data"]
|
||||
if not init_data.startswith("<WRMHEADER"):
|
||||
try:
|
||||
license_request = cdm.get_license_challenge(
|
||||
session_id=session_id,
|
||||
wrm_header=init_data
|
||||
)
|
||||
except InvalidSession:
|
||||
pssh = PlayReadyPSSH(init_data)
|
||||
if pssh.wrm_headers:
|
||||
init_data = pssh.wrm_headers[0]
|
||||
except InvalidPssh as e:
|
||||
return jsonify({
|
||||
'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'message': f'Error, {e}'
|
||||
'message': f'Unable to parse base64 PSSH, {e}'
|
||||
})
|
||||
try:
|
||||
license_request = cdm.get_license_challenge(
|
||||
session_id=session_id,
|
||||
wrm_header=init_data
|
||||
)
|
||||
except InvalidSession:
|
||||
return jsonify({
|
||||
'message': 'success',
|
||||
'data': {
|
||||
'challenge': license_request
|
||||
}
|
||||
'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'message': f'Error, {e}'
|
||||
})
|
||||
return jsonify({
|
||||
'message': 'success',
|
||||
'data': {
|
||||
'challenge': license_request
|
||||
}
|
||||
})
|
||||
|
||||
@remotecdm_pr_bp.route('/remotecdm/playready/<device>/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/<device>/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
|
||||
}
|
||||
})
|
||||
'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
|
||||
}
|
||||
})
|
@ -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/<device>/close/<session_id>', 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/<device>/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/<device>/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/<device>/get_license_challenge/<license_type>', 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/<device>/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/<device>/get_keys/<key_type>', 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
|
||||
}
|
||||
})
|
||||
'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
|
54
routes/user_changes.py
Normal file
54
routes/user_changes.py
Normal file
@ -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
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user