Compare commits

..

4 Commits

Author SHA1 Message Date
Gauthier
acc3599f1b perf: try to improve performance by removing rate limit 2024-11-06 17:00:30 +01:00
Gauthier
64f4610b9f fix: resolve error when setup on second attempt (#1061) 2024-11-06 15:21:19 +08:00
Ludovic Ortega
2d3b777daf docs: migrate to docker compose v2 (#1073)
Signed-off-by: Ludovic Ortega <ludovic.ortega@adminafk.fr>
2024-11-04 22:48:37 +08:00
Fallenbagel
cf59102ef9 fix(externalapi): extract basic auth and pass it through header (#1062)
This commit adds extraction of basic authentication credentials from the URL and then pass the
credentials as the `Authorization` header. And then credentials are removed from the URL before
being passed to fetch. This is done because fetch request cannot be constructed using a URL with
credentials

fix #1027
2024-11-03 14:35:20 +08:00
10 changed files with 95 additions and 66 deletions

View File

@@ -18,7 +18,7 @@ config/logs/*
config/*.json config/*.json
dist dist
Dockerfile* Dockerfile*
docker-compose.yml compose.yaml
docs docs
LICENSE LICENSE
node_modules node_modules

2
.gitattributes vendored
View File

@@ -40,7 +40,7 @@ docs export-ignore
.all-contributorsrc export-ignore .all-contributorsrc export-ignore
.editorconfig export-ignore .editorconfig export-ignore
Dockerfile.local export-ignore Dockerfile.local export-ignore
docker-compose.yml export-ignore compose.yaml export-ignore
stylelint.config.js export-ignore stylelint.config.js export-ignore
public/os_logo_filled.png export-ignore public/os_logo_filled.png export-ignore

View File

@@ -52,7 +52,7 @@ All help is welcome and greatly appreciated! If you would like to contribute to
pnpm dev pnpm dev
``` ```
- Alternatively, you can use [Docker](https://www.docker.com/) with `docker-compose up -d`. This method does not require installing NodeJS or Yarn on your machine directly. - Alternatively, you can use [Docker](https://www.docker.com/) with `docker compose up -d`. This method does not require installing NodeJS or Yarn on your machine directly.
5. Create your patch and test your changes. 5. Create your patch and test your changes.

View File

@@ -1,4 +1,3 @@
version: '3'
services: services:
jellyseerr: jellyseerr:
build: build:

View File

@@ -190,7 +190,7 @@ Caddy will automatically obtain and renew SSL certificates for your domain.
## Traefik (v2) ## Traefik (v2)
Add the following labels to the Jellyseerr service in your `docker-compose.yml` file: Add the following labels to the Jellyseerr service in your `compose.yaml` file:
```yaml ```yaml
labels: labels:

View File

@@ -71,7 +71,7 @@ You could also use [diun](https://github.com/crazy-max/diun) to receive notifica
For details on how to use Docker Compose, please [review the official Compose documentation](https://docs.docker.com/compose/reference/). For details on how to use Docker Compose, please [review the official Compose documentation](https://docs.docker.com/compose/reference/).
#### Installation: #### Installation:
Define the `jellyseerr` service in your `docker-compose.yml` as follows: Define the `jellyseerr` service in your `compose.yaml` as follows:
```yaml ```yaml
--- ---
services: services:
@@ -94,17 +94,17 @@ If you are using emby, make sure to set the `JELLYFIN_TYPE` environment variable
Then, start all services defined in the Compose file: Then, start all services defined in the Compose file:
```bash ```bash
docker-compose up -d docker compose up -d
``` ```
#### Updating: #### Updating:
Pull the latest image: Pull the latest image:
```bash ```bash
docker-compose pull jellyseerr docker compose pull jellyseerr
``` ```
Then, restart all services defined in the Compose file: Then, restart all services defined in the Compose file:
```bash ```bash
docker-compose up -d docker compose up -d
``` ```
:::tip :::tip
You may alternatively use a third-party mechanism like [dockge](https://github.com/louislam/dockge) to manage your docker compose files. You may alternatively use a third-party mechanism like [dockge](https://github.com/louislam/dockge) to manage your docker compose files.

2
next-env.d.ts vendored
View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View File

@@ -1,5 +1,5 @@
import type { RateLimitOptions } from '@server/utils/rateLimit'; import type { RateLimitOptions } from '@server/utils/rateLimit';
import rateLimit from '@server/utils/rateLimit'; // import rateLimit from '@server/utils/rateLimit';
import type NodeCache from 'node-cache'; import type NodeCache from 'node-cache';
// 5 minute default TTL (in seconds) // 5 minute default TTL (in seconds)
@@ -26,11 +26,12 @@ class ExternalAPI {
params: Record<string, string> = {}, params: Record<string, string> = {},
options: ExternalAPIOptions = {} options: ExternalAPIOptions = {}
) { ) {
if (options.rateLimit) { // if (options.rateLimit) {
this.fetch = rateLimit(fetch, options.rateLimit); // this.fetch = rateLimit(fetch, options.rateLimit);
} else { // } else {
// this.fetch = fetch;
// }
this.fetch = fetch; this.fetch = fetch;
}
const url = new URL(baseUrl); const url = new URL(baseUrl);

View File

@@ -1,6 +1,6 @@
import logger from '@server/logger'; import logger from '@server/logger';
import type { RateLimitOptions } from '@server/utils/rateLimit'; import type { RateLimitOptions } from '@server/utils/rateLimit';
import rateLimit from '@server/utils/rateLimit'; // import rateLimit from '@server/utils/rateLimit';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { promises } from 'fs'; import { promises } from 'fs';
import mime from 'mime/lite'; import mime from 'mime/lite';
@@ -150,13 +150,14 @@ class ImageProxy {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.key = key; this.key = key;
if (options.rateLimitOptions) { // if (options.rateLimitOptions) {
this.fetch = rateLimit(fetch, { // this.fetch = rateLimit(fetch, {
...options.rateLimitOptions, // ...options.rateLimitOptions,
}); // });
} else { // } else {
// this.fetch = fetch;
// }
this.fetch = fetch; this.fetch = fetch;
}
this.headers = options.headers || null; this.headers = options.headers || null;
} }

View File

@@ -299,14 +299,27 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
where: { jellyfinUserId: account.User.Id }, where: { jellyfinUserId: account.User.Id },
}); });
if (!user && !(await userRepository.count())) { const missingAdminUser = !user && !(await userRepository.count());
if (
missingAdminUser ||
settings.main.mediaServerType === MediaServerType.NOT_CONFIGURED
) {
// Check if user is admin on jellyfin // Check if user is admin on jellyfin
if (account.User.Policy.IsAdministrator === false) { if (account.User.Policy.IsAdministrator === false) {
throw new ApiError(403, ApiErrorCode.NotAdmin); throw new ApiError(403, ApiErrorCode.NotAdmin);
} }
if (
body.serverType !== MediaServerType.JELLYFIN &&
body.serverType !== MediaServerType.EMBY
) {
throw new Error('select_server_type');
}
settings.main.mediaServerType = body.serverType;
if (missingAdminUser) {
logger.info( logger.info(
'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Overseerr', 'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Jellyseerr',
{ {
label: 'API', label: 'API',
ip: req.ip, ip: req.ip,
@@ -316,10 +329,9 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
// User doesn't exist, and there are no users in the database, we'll create the user // User doesn't exist, and there are no users in the database, we'll create the user
// with admin permissions // with admin permissions
switch (body.serverType) {
case MediaServerType.EMBY:
settings.main.mediaServerType = MediaServerType.EMBY;
user = new User({ user = new User({
id: 1,
email: body.email || account.User.Name, email: body.email || account.User.Name,
jellyfinUsername: account.User.Name, jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id, jellyfinUserId: account.User.Id,
@@ -327,26 +339,44 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinAuthToken: account.AccessToken, jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN, permissions: Permission.ADMIN,
avatar: `/avatarproxy/${account.User.Id}`, avatar: `/avatarproxy/${account.User.Id}`,
userType: UserType.EMBY, userType:
body.serverType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY,
}); });
break; await userRepository.save(user);
case MediaServerType.JELLYFIN: } else {
settings.main.mediaServerType = MediaServerType.JELLYFIN; logger.info(
user = new User({ 'Sign-in attempt from Jellyfin user with access to the media server; editing admin user for Jellyseerr',
email: body.email || account.User.Name, {
label: 'API',
ip: req.ip,
jellyfinUsername: account.User.Name, jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id, }
jellyfinDeviceId: deviceId, );
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: `/avatarproxy/${account.User.Id}`,
userType: UserType.JELLYFIN,
});
break; // User alread exist but settings.json is not configured, we'll edit the admin user
default:
throw new Error('select_server_type'); user = await userRepository.findOne({
where: { id: 1 },
});
if (!user) {
throw new Error('Unable to find admin user to edit');
}
user.email = body.email || account.User.Name;
user.jellyfinUsername = account.User.Name;
user.jellyfinUserId = account.User.Id;
user.jellyfinDeviceId = deviceId;
user.jellyfinAuthToken = account.AccessToken;
user.permissions = Permission.ADMIN;
user.avatar = `/avatarproxy/${account.User.Id}`;
user.userType =
body.serverType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY;
await userRepository.save(user);
} }
// Create an API key on Jellyfin from this admin user // Create an API key on Jellyfin from this admin user
@@ -368,8 +398,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
settings.jellyfin.apiKey = apiKey; settings.jellyfin.apiKey = apiKey;
await settings.save(); await settings.save();
startJobs(); startJobs();
await userRepository.save(user);
} }
// User already exists, let's update their information // User already exists, let's update their information
else if (account.User.Id === user?.jellyfinUserId) { else if (account.User.Id === user?.jellyfinUserId) {