forked from tpd94/CDRM-Project
		
	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