mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: allow logging in with aliases
This commit is contained in:
@@ -181,10 +181,10 @@ const userAgent = makeValidator((x) => {
|
||||
});
|
||||
|
||||
// comma separated list of alias:uuid
|
||||
const aliasedUUIDs = makeValidator((x) => {
|
||||
const aliasedUUIDs = makeExactValidator((x) => {
|
||||
try {
|
||||
const aliases: Record<string, { uuid: string; password: string }> = {};
|
||||
const parsed = x.split(',').map((x) => {
|
||||
const aliases: Map<string, { uuid: string; password: string }> = new Map();
|
||||
x.split(',').forEach((x) => {
|
||||
const [alias, uuid, password] = x.split(':');
|
||||
if (!alias || !uuid || !password) {
|
||||
throw new Error('Invalid alias:uuid:password pair');
|
||||
@@ -195,7 +195,7 @@ const aliasedUUIDs = makeValidator((x) => {
|
||||
) {
|
||||
throw new Error('Invalid UUID');
|
||||
}
|
||||
aliases[alias] = { uuid, password };
|
||||
aliases.set(alias, { uuid, password });
|
||||
});
|
||||
return aliases;
|
||||
} catch (e) {
|
||||
@@ -393,7 +393,7 @@ export const Env = cleanEnv(process.env, {
|
||||
desc: 'Mapping of URLs to another, converts requests to the original URL to the mapped URL',
|
||||
}),
|
||||
ALIASED_CONFIGURATIONS: aliasedUUIDs({
|
||||
default: {},
|
||||
default: new Map(),
|
||||
desc: 'Comma separated list of alias:uuid:encryptedPassword pairs. Can then access at /stremio/u/alias/manifest.json ',
|
||||
}),
|
||||
TRUSTED_UUIDS: str({
|
||||
|
||||
@@ -300,8 +300,13 @@ function Content() {
|
||||
toast.error('Failed to export configuration');
|
||||
}
|
||||
};
|
||||
|
||||
const manifestUrl = `${baseUrl}/stremio/${uuid}/${encryptedPassword}/manifest.json`;
|
||||
const uuidRegex =
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
||||
const manifestUrl = uuid
|
||||
? uuidRegex.test(uuid)
|
||||
? `${baseUrl}/stremio/${uuid}/${encryptedPassword}/manifest.json`
|
||||
: `${baseUrl}/stremio/u/${uuid}/manifest.json`
|
||||
: '';
|
||||
const encodedManifest = encodeURIComponent(manifestUrl);
|
||||
|
||||
const copyManifestUrl = async () => {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Env } from '@aiostreams/core';
|
||||
|
||||
// Resolves alias to UUID for user API routes.
|
||||
// If the provided value is not a UUID and matches a known alias, replaces it with the real UUID.
|
||||
export function resolveUuidAliasForUserApi(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const uuidRegex =
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
||||
|
||||
const method = req.method.toUpperCase();
|
||||
|
||||
if (method === 'GET' || method === 'HEAD') {
|
||||
const value = req.query.uuid;
|
||||
if (typeof value === 'string' && !uuidRegex.test(value)) {
|
||||
const configuration = Env.ALIASED_CONFIGURATIONS.get(value);
|
||||
if (configuration?.uuid) {
|
||||
req.uuid = configuration?.uuid;
|
||||
}
|
||||
}
|
||||
} else if (method === 'PUT' || method === 'DELETE') {
|
||||
const value = (req.body ?? {}).uuid;
|
||||
if (typeof value === 'string' && !uuidRegex.test(value)) {
|
||||
const configuration = Env.ALIASED_CONFIGURATIONS.get(value);
|
||||
if (configuration?.uuid) {
|
||||
req.uuid = configuration.uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Resource,
|
||||
StremioTransformer,
|
||||
UserRepository,
|
||||
Env,
|
||||
} from '@aiostreams/core';
|
||||
|
||||
const logger = createLogger('server');
|
||||
@@ -20,10 +21,10 @@ export const userDataMiddleware = async (
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const { uuid, encryptedPassword } = req.params;
|
||||
const { uuid: uuidOrAlias, encryptedPassword } = req.params;
|
||||
|
||||
// Both uuid and encryptedPassword should be present since we mounted the router on this path
|
||||
if (!uuid || !encryptedPassword) {
|
||||
if (!uuidOrAlias || !encryptedPassword) {
|
||||
next(new APIError(constants.ErrorCode.USER_INVALID_DETAILS));
|
||||
return;
|
||||
}
|
||||
@@ -37,11 +38,19 @@ export const userDataMiddleware = async (
|
||||
}
|
||||
|
||||
// Second check - validate UUID format (simpler regex that just checks UUID format)
|
||||
let uuid: string | undefined;
|
||||
const uuidRegex =
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
||||
if (!uuidRegex.test(uuid)) {
|
||||
next(new APIError(constants.ErrorCode.USER_INVALID_DETAILS));
|
||||
return;
|
||||
if (!uuidRegex.test(uuidOrAlias)) {
|
||||
const alias = Env.ALIASED_CONFIGURATIONS.get(uuidOrAlias);
|
||||
if (alias) {
|
||||
uuid = alias.uuid;
|
||||
} else {
|
||||
next(new APIError(constants.ErrorCode.USER_INVALID_DETAILS));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
uuid = uuidOrAlias;
|
||||
}
|
||||
|
||||
const resource = resourceMatch[1];
|
||||
|
||||
@@ -7,16 +7,18 @@ import {
|
||||
UserRepository,
|
||||
} from '@aiostreams/core';
|
||||
import { userApiRateLimiter } from '../../middlewares/ratelimit.js';
|
||||
import { resolveUuidAliasForUserApi } from '../../middlewares/alias.js';
|
||||
import { createResponse } from '../../utils/responses.js';
|
||||
const router: Router = Router();
|
||||
|
||||
const logger = createLogger('server');
|
||||
|
||||
router.use(userApiRateLimiter);
|
||||
router.use(resolveUuidAliasForUserApi);
|
||||
|
||||
// checking existence of a user
|
||||
router.head('/', async (req, res, next) => {
|
||||
const { uuid } = req.query;
|
||||
const uuid = req.uuid || req.query.uuid;
|
||||
if (typeof uuid !== 'string') {
|
||||
next(
|
||||
new APIError(
|
||||
@@ -55,7 +57,10 @@ router.head('/', async (req, res, next) => {
|
||||
|
||||
// getting user details
|
||||
router.get('/', async (req, res, next) => {
|
||||
const { uuid, password } = req.query;
|
||||
const { uuid, password } = {
|
||||
uuid: req.uuid || req.query.uuid,
|
||||
password: req.query.password,
|
||||
};
|
||||
if (typeof uuid !== 'string' || typeof password !== 'string') {
|
||||
next(
|
||||
new APIError(
|
||||
@@ -144,7 +149,10 @@ router.post('/', async (req, res, next) => {
|
||||
|
||||
// updating user details
|
||||
router.put('/', async (req, res, next) => {
|
||||
const { uuid, password, config } = req.body;
|
||||
const { uuid, password, config } = {
|
||||
...req.body,
|
||||
uuid: req.uuid || req.body.uuid,
|
||||
};
|
||||
if (!uuid || !password || !config) {
|
||||
next(
|
||||
new APIError(
|
||||
@@ -180,7 +188,10 @@ router.put('/', async (req, res, next) => {
|
||||
});
|
||||
|
||||
router.delete('/', async (req, res, next) => {
|
||||
const { uuid, password } = req.body;
|
||||
const { uuid, password } = {
|
||||
...req.body,
|
||||
uuid: req.uuid || req.body.uuid,
|
||||
};
|
||||
if (!uuid || !password) {
|
||||
next(new APIError(constants.ErrorCode.MISSING_REQUIRED_FIELDS));
|
||||
return;
|
||||
|
||||
@@ -18,7 +18,7 @@ router.get(
|
||||
wildcardPath = wildcardPath.join('/');
|
||||
}
|
||||
|
||||
const configuration = Env.ALIASED_CONFIGURATIONS[alias];
|
||||
const configuration = Env.ALIASED_CONFIGURATIONS.get(alias);
|
||||
if (!configuration || !configuration.uuid || !configuration.password) {
|
||||
throw new APIError(constants.ErrorCode.USER_INVALID_DETAILS);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user