mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: use common function to check for encryption and load api keys from existing configs
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { AddonDetail, Config } from '@aiostreams/types';
|
||||
import {
|
||||
addonDetails,
|
||||
isValueEncrypted,
|
||||
parseAndDecryptString,
|
||||
serviceDetails,
|
||||
Settings,
|
||||
unminifyConfig,
|
||||
@@ -98,20 +100,41 @@ export function validateConfig(config: Config): {
|
||||
);
|
||||
}
|
||||
// check for apiKey if Settings.API_KEY is set
|
||||
if (Settings.API_KEY && !config.apiKey) {
|
||||
return createResponse(
|
||||
false,
|
||||
'missingApiKey',
|
||||
'The AIOStreams API key is required'
|
||||
);
|
||||
} else if (Settings.API_KEY && config.apiKey !== Settings.API_KEY) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidApiKey',
|
||||
'Invalid AIOStreams API key. Please use the one defined in your environment variables'
|
||||
);
|
||||
if (Settings.API_KEY) {
|
||||
const { apiKey } = config;
|
||||
if (!apiKey) {
|
||||
return createResponse(
|
||||
false,
|
||||
'missingApiKey',
|
||||
'The AIOStreams API key is required'
|
||||
);
|
||||
}
|
||||
let decryptedApiKey = apiKey;
|
||||
if (isValueEncrypted(apiKey)) {
|
||||
const decryptionResult = parseAndDecryptString(apiKey);
|
||||
if (decryptionResult === null) {
|
||||
return createResponse(
|
||||
false,
|
||||
'decryptionFailed',
|
||||
'Failed to decrypt the AIOStreams API key'
|
||||
);
|
||||
} else if (decryptionResult === '') {
|
||||
return createResponse(
|
||||
false,
|
||||
'emptyDecryption',
|
||||
'Decrypted API key is empty'
|
||||
);
|
||||
}
|
||||
decryptedApiKey = decryptionResult;
|
||||
}
|
||||
if (decryptedApiKey !== Settings.API_KEY) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidApiKey',
|
||||
'Invalid AIOStreams API key. Please use the one defined in your environment variables'
|
||||
);
|
||||
}
|
||||
}
|
||||
// check for any duplicate addons where both the ID and options are the same
|
||||
const duplicateAddons = config.addons.filter(
|
||||
(addon, index) =>
|
||||
config.addons.findIndex(
|
||||
@@ -182,9 +205,23 @@ export function validateConfig(config: Config): {
|
||||
option.id.toLowerCase().includes('url') &&
|
||||
addon.options[option.id]
|
||||
) {
|
||||
const url = parseAndDecryptString(addon.options[option.id] ?? '');
|
||||
if (url === null) {
|
||||
return createResponse(
|
||||
false,
|
||||
'decryptionFailed',
|
||||
`Failed to decrypt URL for ${option.label}`
|
||||
);
|
||||
} else if (url === '') {
|
||||
return createResponse(
|
||||
false,
|
||||
'emptyDecryption',
|
||||
`Decrypted URL for ${option.label} is empty`
|
||||
);
|
||||
}
|
||||
if (
|
||||
Settings.DISABLE_TORRENTIO &&
|
||||
addon.options[option.id]?.match(/torrentio\.strem\.fun/) !== null
|
||||
url.match(/torrentio\.strem\.fun/) !== null
|
||||
) {
|
||||
// if torrentio is disabled, don't allow the user to set URLs with torrentio.strem.fun
|
||||
return createResponse(
|
||||
@@ -194,14 +231,13 @@ export function validateConfig(config: Config): {
|
||||
);
|
||||
} else if (
|
||||
Settings.DISABLE_TORRENTIO &&
|
||||
addon.options[option.id]?.match(/stremthru\.elfhosted\.com/) !==
|
||||
null
|
||||
url.match(/stremthru\.elfhosted\.com/) !== null
|
||||
) {
|
||||
// if torrentio is disabled, we need to inspect the stremthru URL to see if it's using torrentio
|
||||
try {
|
||||
const url = new URL(addon.options[option.id] as string);
|
||||
const parsedUrl = new URL(url);
|
||||
// get the component before manifest.json
|
||||
const pathComponents = url.pathname.split('/');
|
||||
const pathComponents = parsedUrl.pathname.split('/');
|
||||
if (pathComponents.includes('manifest.json')) {
|
||||
const index = pathComponents.indexOf('manifest.json');
|
||||
const componentBeforeManifest = pathComponents[index - 1];
|
||||
@@ -219,14 +255,9 @@ export function validateConfig(config: Config): {
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
} else if (
|
||||
addon.options[option.id]?.match(
|
||||
/^E-[0-9a-fA-F]{32}-[0-9a-fA-F]+$/
|
||||
) === null &&
|
||||
addon.options[option.id]?.match(/^E2-[^-]+-[^-]+$/) === null
|
||||
) {
|
||||
} else {
|
||||
try {
|
||||
new URL(addon.options[option.id] as string);
|
||||
new URL(url);
|
||||
} catch (_) {
|
||||
return createResponse(
|
||||
false,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
loadSecretKey,
|
||||
createLogger,
|
||||
getTimeTakenSincePoint,
|
||||
isValueEncrypted,
|
||||
} from '@aiostreams/utils';
|
||||
|
||||
const logger = createLogger('server');
|
||||
@@ -130,7 +131,7 @@ app.get('/:config/configure', (req, res) => {
|
||||
}
|
||||
try {
|
||||
let configJson = extractJsonConfig(config);
|
||||
if (config.startsWith('E-') || config.startsWith('E2-')) {
|
||||
if (isValueEncrypted(config)) {
|
||||
logger.info(`Encrypted config detected, encrypting credentials`);
|
||||
configJson = encryptInfoInConfig(configJson);
|
||||
}
|
||||
@@ -360,11 +361,10 @@ app.listen(Settings.PORT, () => {
|
||||
|
||||
function extractJsonConfig(config: string): Config {
|
||||
if (
|
||||
config.startsWith('E-') ||
|
||||
config.startsWith('eyJ') ||
|
||||
config.startsWith('eyI') ||
|
||||
config.startsWith('E2-') ||
|
||||
config.startsWith('B-')
|
||||
config.startsWith('B-') ||
|
||||
isValueEncrypted(config)
|
||||
) {
|
||||
return extractEncryptedOrEncodedConfig(config, 'Config');
|
||||
}
|
||||
@@ -459,6 +459,10 @@ function decryptEncryptedInfoFromConfig(config: Config): Config {
|
||||
decryptMediaFlowConfig(config.mediaFlowConfig);
|
||||
}
|
||||
|
||||
if (config.apiKey) {
|
||||
config.apiKey = decryptValue(config.apiKey, 'aioStreams apiKey');
|
||||
}
|
||||
|
||||
if (config.addons) {
|
||||
config.addons.forEach((addon) => {
|
||||
if (addon.options) {
|
||||
@@ -512,6 +516,10 @@ function encryptInfoInConfig(config: Config): Config {
|
||||
encryptMediaFlowConfig(config.mediaFlowConfig);
|
||||
}
|
||||
|
||||
if (config.apiKey) {
|
||||
config.apiKey = encryptValue(config.apiKey, 'aioStreams apiKey');
|
||||
}
|
||||
|
||||
if (config.addons) {
|
||||
config.addons.forEach((addon) => {
|
||||
if (addon.options) {
|
||||
@@ -597,13 +605,5 @@ function decryptValue(value: any, label: string): any {
|
||||
}
|
||||
}
|
||||
|
||||
function isValueEncrypted(value: string | undefined): boolean {
|
||||
if (!value) return false;
|
||||
const tests =
|
||||
/^E2-[^-]+-[^-]+$/.test(value) ||
|
||||
/^E-[0-9a-fA-F]{32}-[0-9a-fA-F]+$/.test(value);
|
||||
return tests;
|
||||
}
|
||||
|
||||
const rootUrl = (req: Request) =>
|
||||
`${req.protocol}://${req.hostname}${req.hostname === 'localhost' ? `:${Settings.PORT}` : ''}`;
|
||||
|
||||
@@ -28,7 +28,12 @@ import {
|
||||
allowedLanguages,
|
||||
validateConfig,
|
||||
} from '@aiostreams/config';
|
||||
import { addonDetails, serviceDetails, Settings } from '@aiostreams/utils';
|
||||
import {
|
||||
addonDetails,
|
||||
isValueEncrypted,
|
||||
serviceDetails,
|
||||
Settings,
|
||||
} from '@aiostreams/utils';
|
||||
|
||||
import Slider from '@/components/Slider';
|
||||
import CredentialInput from '@/components/CredentialInput';
|
||||
@@ -478,11 +483,7 @@ export default function Configure() {
|
||||
useEffect(() => {
|
||||
async function decodeConfig(config: string) {
|
||||
let decodedConfig: Config;
|
||||
if (
|
||||
config.startsWith('E-') ||
|
||||
config.startsWith('E2-') ||
|
||||
config.startsWith('B-')
|
||||
) {
|
||||
if (isValueEncrypted(config) || config.startsWith('B-')) {
|
||||
throw new Error('Encrypted Config Not Supported');
|
||||
} else {
|
||||
decodedConfig = JSON.parse(atob(decodeURIComponent(config)));
|
||||
@@ -573,6 +574,7 @@ export default function Configure() {
|
||||
setMediaFlowProxiedServices(
|
||||
decodedConfig.mediaFlowConfig?.proxiedServices || null
|
||||
);
|
||||
setApiKey(decodedConfig.apiKey || '');
|
||||
}
|
||||
|
||||
const path = window.location.pathname;
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import React from 'react';
|
||||
import styles from './CredentialInput.module.css';
|
||||
import { isValueEncrypted } from '@aiostreams/utils';
|
||||
|
||||
interface CredentialInputProps {
|
||||
credential: string;
|
||||
setCredential: (credential: string) => void;
|
||||
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
||||
}
|
||||
function isEncrypted(value: string): boolean {
|
||||
if (!value) return false;
|
||||
const tests =
|
||||
/^E2-[^-]+-[^-]+$/.test(value) ||
|
||||
/^E-[0-9a-fA-F]{32}-[0-9a-fA-F]+$/.test(value);
|
||||
return tests;
|
||||
}
|
||||
|
||||
const CredentialInput: React.FC<CredentialInputProps> = ({
|
||||
credential,
|
||||
@@ -25,18 +19,20 @@ const CredentialInput: React.FC<CredentialInputProps> = ({
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={
|
||||
isEncrypted(credential) ? '••••••••••••••••••••••••' : credential
|
||||
isValueEncrypted(credential) ? '••••••••••••••••••••••••' : credential
|
||||
}
|
||||
onChange={(e) => setCredential(e.target.value.trim())}
|
||||
className={styles.credentialInput}
|
||||
{...inputProps}
|
||||
disabled={isEncrypted(credential) ? true : inputProps.disabled || false}
|
||||
disabled={
|
||||
isValueEncrypted(credential) ? true : inputProps.disabled || false
|
||||
}
|
||||
/>
|
||||
{!isEncrypted(credential) && (
|
||||
{!isValueEncrypted(credential) && (
|
||||
<button
|
||||
className={styles.showHideButton}
|
||||
onClick={() => {
|
||||
if (!isEncrypted(credential)) {
|
||||
if (!isValueEncrypted(credential)) {
|
||||
setShowPassword(!showPassword);
|
||||
}
|
||||
}}
|
||||
@@ -123,7 +119,7 @@ const CredentialInput: React.FC<CredentialInputProps> = ({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isEncrypted(credential) && (
|
||||
{isValueEncrypted(credential) && (
|
||||
<button
|
||||
className={styles.resetCredentialButton}
|
||||
onClick={() => setCredential('')}
|
||||
|
||||
@@ -130,3 +130,11 @@ export function getTextHash(text: string): string {
|
||||
hash.update(text);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
export function isValueEncrypted(value?: string): boolean {
|
||||
if (!value) return false;
|
||||
const tests =
|
||||
/^E2-[^-]+-[^-]+$/.test(value) ||
|
||||
/^E-[0-9a-fA-F]{32}-[0-9a-fA-F]+$/.test(value);
|
||||
return tests;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user