feat: add Emby Connect Authentication Flow

This flow allows users to authneticate to JS using Emby Connect login details.

#943
This commit is contained in:
Hermanus Engelbrecht
2025-09-10 20:03:42 +02:00
parent e39ad0acfa
commit d9f8d200d9
3 changed files with 26 additions and 26 deletions

3
.gitignore vendored
View File

@@ -71,3 +71,6 @@ tsconfig.tsbuildinfo
# Config Cache Directory
config/cache
# Mise
mise.toml

View File

@@ -1,5 +1,6 @@
import ExternalAPI from '@server/api/externalapi';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType } from '@server/constants/server';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { ApiError } from '@server/types/error';
@@ -52,6 +53,10 @@ class EmbyConnectAPI extends ExternalAPI {
{
headers: {
'X-Application': `Jellyseerr/${getAppVersion()}`,
'Content-Type': 'application/json',
Accept: 'application/json',
...(getSettings().main.mediaServerType === MediaServerType.EMBY &&
{}),
},
}
);
@@ -105,11 +110,9 @@ class EmbyConnectAPI extends ExternalAPI {
Password?: string
): Promise<ConnectAuthResponse> {
try {
const textResponse = await this.post<string>(
const response = await this.post<ConnectAuthResponse>(
'/service/user/authenticate',
{ nameOrEmail: Email, rawpw: Password },
{},
undefined,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
@@ -117,11 +120,12 @@ class EmbyConnectAPI extends ExternalAPI {
}
);
return JSON.parse(textResponse) as ConnectAuthResponse;
return response;
} catch (e) {
logger.debug(`Failed to authenticate using EmbyConnect: ${e.message}`, {
logger.debug(`Failed to authenticate using EmbyConnect:`, {
label: 'EmbyConnect API',
ip: this.ClientIP,
error: e.message,
});
throw new ApiError(
e.cause?.status ?? 401,
@@ -135,26 +139,19 @@ class EmbyConnectAPI extends ExternalAPI {
AccessToken: string
): Promise<LinkedServer[]> {
try {
const textResponse = await this.get<string>(
`/service/servers`,
{ userId: ConnectUserId },
undefined,
{
const response = await this.get<LinkedServer[]>(`/service/servers`, {
params: { userId: ConnectUserId },
headers: {
'X-Connect-UserToken': AccessToken,
},
}
);
return JSON.parse(textResponse) as LinkedServer[];
});
return response;
} catch (e) {
logger.error(
`Failed to retrieve EmbyConnect user server list: ${e.message}`,
{
logger.error(`Failed to retrieve EmbyConnect user server list: `, {
label: 'EmbyConnect API',
ip: this.ClientIP,
}
);
error: e.message,
});
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
}
}
@@ -205,7 +202,7 @@ class EmbyConnectAPI extends ExternalAPI {
return await response.json();
} catch (e) {
logger.debug('Failed local user auth exchange');
logger.debug('Failed local user auth exchange', e.cause);
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
}
}

View File

@@ -463,7 +463,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
message: 'Access denied.',
});
} else if (!user) {
// Emby Connect user with unlinked local account?
// Handle Emby Connect user with unlinked local account
if (
settings.main.mediaServerType === MediaServerType.EMBY &&
account.User.Email &&