mirror of
https://github.com/seerr-team/seerr.git
synced 2026-04-19 06:48:25 -04:00
feat: fixing merge conflicts, adding user linking for emby connect
#943
This commit is contained in:
1717
pnpm-lock.yaml
generated
1717
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,7 @@ class EmbyConnectAPI extends ExternalAPI {
|
||||
return {
|
||||
User: {
|
||||
Name: connectAuthResponse.User.Name,
|
||||
Email: connectAuthResponse.User.Email,
|
||||
ServerId: matchingServer.SystemId,
|
||||
ServerName: matchingServer.Name,
|
||||
Id: localUserExchangeResponse.LocalUserId,
|
||||
@@ -122,7 +123,10 @@ class EmbyConnectAPI extends ExternalAPI {
|
||||
label: 'EmbyConnect API',
|
||||
ip: this.ClientIP,
|
||||
});
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidCredentials);
|
||||
throw new ApiError(
|
||||
e.cause?.status ?? 401,
|
||||
ApiErrorCode.InvalidCredentials
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,10 @@ class ExternalAPI {
|
||||
ttl?: number,
|
||||
config?: RequestInit
|
||||
): Promise<T> {
|
||||
const headers = { ...this.defaultHeaders, ...config?.headers };
|
||||
const headers = new Headers({
|
||||
...this.defaultHeaders,
|
||||
...(config?.headers || {}),
|
||||
});
|
||||
const cacheKey = this.serializeCacheKey(endpoint, {
|
||||
config: { ...this.params, ...params },
|
||||
headers,
|
||||
@@ -125,10 +128,6 @@ class ExternalAPI {
|
||||
}
|
||||
|
||||
const url = this.formatUrl(endpoint, params);
|
||||
const headers = new Headers({
|
||||
...this.defaultHeaders,
|
||||
...(config?.headers || {}),
|
||||
});
|
||||
|
||||
const isFormUrlEncoded = headers
|
||||
.get('Content-Type')
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as EmailValidator from 'email-validator';
|
||||
|
||||
export interface JellyfinUserResponse {
|
||||
Name: string;
|
||||
Email?: string;
|
||||
ServerId: string;
|
||||
ServerName: string;
|
||||
Id: string;
|
||||
@@ -141,6 +142,31 @@ class JellyfinAPI extends ExternalAPI {
|
||||
);
|
||||
};
|
||||
|
||||
if (
|
||||
getSettings().main.mediaServerType === MediaServerType.EMBY &&
|
||||
Username &&
|
||||
EmailValidator.validate(Username)
|
||||
) {
|
||||
try {
|
||||
const connectApi = new EmbyConnectAPI({
|
||||
ClientIP: ClientIP,
|
||||
DeviceId: this.deviceId,
|
||||
});
|
||||
return await connectApi.authenticateConnectUser(Username, Password);
|
||||
} catch (e) {
|
||||
// Possible local Emby user with email as username
|
||||
logger.warn(
|
||||
`Emby Connect authentication failed: ${e}, attempting local Emby server authentication`,
|
||||
{
|
||||
label: 'Jellyfin API',
|
||||
error:
|
||||
e.cause?.message ?? e.cause?.statusText ?? ApiErrorCode.Unknown,
|
||||
ip: ClientIP,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await authenticate(true);
|
||||
} catch (e) {
|
||||
@@ -164,39 +190,18 @@ class JellyfinAPI extends ExternalAPI {
|
||||
} catch (e) {
|
||||
if (e.cause.status === 401) {
|
||||
throw new ApiError(e.cause.status, ApiErrorCode.InvalidCredentials);
|
||||
} else {
|
||||
logger.error(
|
||||
'Something went wrong while authenticating with the Jellyfin server',
|
||||
{
|
||||
label: 'Jellyfin API',
|
||||
error: e.cause.message ?? e.cause.statusText,
|
||||
ip: ClientIP,
|
||||
}
|
||||
);
|
||||
throw new ApiError(e.cause.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
|
||||
if (
|
||||
settings.main.mediaServerType === MediaServerType.EMBY &&
|
||||
Username &&
|
||||
EmailValidator.validate(Username)
|
||||
) {
|
||||
try {
|
||||
const connectApi = new EmbyConnectAPI({
|
||||
ClientIP: ClientIP,
|
||||
DeviceId: this.deviceId,
|
||||
});
|
||||
|
||||
return await connectApi.authenticateConnectUser(Username, Password);
|
||||
} catch (e) {
|
||||
logger.debug(`Emby Connect authentication failed: ${e}`);
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidCredentials);
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
'Something went wrong while authenticating with the Jellyfin server',
|
||||
{
|
||||
label: 'Jellyfin API',
|
||||
error: e.cause.message ?? e.cause.statusText,
|
||||
ip: ClientIP,
|
||||
}
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
public setUserId(userId: string): void {
|
||||
@@ -233,9 +238,9 @@ class JellyfinAPI extends ExternalAPI {
|
||||
|
||||
public async getUsers(): Promise<JellyfinUserListResponse> {
|
||||
try {
|
||||
const userReponse = await this.get<JellyfinUserResponse[]>(`/Users`);
|
||||
const userResponse = await this.get<JellyfinUserResponse[]>(`/Users`);
|
||||
|
||||
return { users: userReponse };
|
||||
return { users: userResponse };
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the account from the Jellyfin server',
|
||||
@@ -248,10 +253,10 @@ class JellyfinAPI extends ExternalAPI {
|
||||
|
||||
public async getUser(): Promise<JellyfinUserResponse> {
|
||||
try {
|
||||
const userReponse = await this.get<JellyfinUserResponse>(
|
||||
const userResponse = await this.get<JellyfinUserResponse>(
|
||||
`/Users/${this.userId ?? 'Me'}`
|
||||
);
|
||||
return userReponse;
|
||||
return userResponse;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the account from the Jellyfin server',
|
||||
|
||||
@@ -39,7 +39,6 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => {
|
||||
user.warnings.push('userEmailRequired');
|
||||
logger.warn(`User ${user.username} has no valid email address`);
|
||||
}
|
||||
|
||||
return res.status(200).json(user);
|
||||
});
|
||||
|
||||
@@ -402,25 +401,34 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
}
|
||||
// User already exists, let's update their information
|
||||
else if (account.User.Id === user?.jellyfinUserId) {
|
||||
const serverType =
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: ServerType.EMBY;
|
||||
|
||||
const userType =
|
||||
serverType === ServerType.JELLYFIN ? UserType.JELLYFIN : UserType.EMBY;
|
||||
|
||||
logger.info(
|
||||
`Found matching ${
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: ServerType.EMBY
|
||||
} user; updating user with ${
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: ServerType.EMBY
|
||||
}`,
|
||||
`Found matching ${serverType} user; updating user with ${userType}`,
|
||||
{
|
||||
label: 'API',
|
||||
ip: req.ip,
|
||||
jellyfinUsername: account.User.Name,
|
||||
}
|
||||
);
|
||||
|
||||
user.userType = userType;
|
||||
user.avatar = `/avatarproxy/${account.User.Id}`;
|
||||
user.jellyfinUsername = account.User.Name;
|
||||
|
||||
if (
|
||||
account.User.Email !== undefined &&
|
||||
user.email !== account.User.Email
|
||||
) {
|
||||
user.email = account.User.Email;
|
||||
}
|
||||
|
||||
if (user.username === account.User.Name) {
|
||||
user.username = '';
|
||||
}
|
||||
@@ -441,36 +449,60 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
message: 'Access denied.',
|
||||
});
|
||||
} else if (!user) {
|
||||
logger.info(
|
||||
'Sign-in attempt from Jellyfin user with access to the media server; creating new Overseerr user',
|
||||
{
|
||||
label: 'API',
|
||||
ip: req.ip,
|
||||
jellyfinUsername: account.User.Name,
|
||||
}
|
||||
);
|
||||
|
||||
user = new User({
|
||||
email: body.email,
|
||||
jellyfinUsername: account.User.Name,
|
||||
jellyfinUserId: account.User.Id,
|
||||
jellyfinDeviceId: deviceId,
|
||||
permissions: settings.main.defaultPermissions,
|
||||
avatar: `/avatarproxy/${account.User.Id}`,
|
||||
userType:
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? UserType.JELLYFIN
|
||||
: UserType.EMBY,
|
||||
});
|
||||
|
||||
//initialize Jellyfin/Emby users with local login
|
||||
const passedExplicitPassword = body.password && body.password.length > 0;
|
||||
if (passedExplicitPassword) {
|
||||
await user.setPassword(body.password ?? '');
|
||||
// Emby Connect user with unlinked local account?
|
||||
if (
|
||||
settings.main.mediaServerType === MediaServerType.EMBY &&
|
||||
account.User.Email &&
|
||||
account.User.Email.trim() !== ''
|
||||
) {
|
||||
user = await userRepository.findOne({
|
||||
where: { email: account.User.Email },
|
||||
});
|
||||
}
|
||||
await userRepository.save(user);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
logger.info(
|
||||
`Sign in attempt from EmbyConnect user with access to the media server, linking users`
|
||||
);
|
||||
user.avatar = `/avatarproxy/${account.User.Id}`;
|
||||
user.jellyfinUserId = account.User.Id;
|
||||
user.userType = UserType.EMBY;
|
||||
user.username = account.User.Name;
|
||||
await userRepository.save(user);
|
||||
|
||||
// No user, create new
|
||||
} else {
|
||||
logger.info(
|
||||
'Sign-in attempt from Jellyfin/Emby user with access to the media server; creating new Jellyseerr user',
|
||||
{
|
||||
label: 'API',
|
||||
ip: req.ip,
|
||||
jellyfinUsername: account.User.Name,
|
||||
}
|
||||
);
|
||||
|
||||
user = new User({
|
||||
email: body.email,
|
||||
jellyfinUsername: account.User.Name,
|
||||
jellyfinUserId: account.User.Id,
|
||||
jellyfinDeviceId: deviceId,
|
||||
permissions: settings.main.defaultPermissions,
|
||||
avatar: `/avatarproxy/${account.User.Id}`,
|
||||
userType:
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? UserType.JELLYFIN
|
||||
: UserType.EMBY,
|
||||
});
|
||||
|
||||
//initialize Jellyfin/Emby users with local login
|
||||
const passedExplicitPassword =
|
||||
body.password && body.password.length > 0;
|
||||
if (passedExplicitPassword) {
|
||||
await user.setPassword(body.password ?? '');
|
||||
}
|
||||
await userRepository.save(user);
|
||||
}
|
||||
}
|
||||
// Set logged in session
|
||||
if (req.session) {
|
||||
req.session.userId = user?.id;
|
||||
|
||||
@@ -437,7 +437,11 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
<Form>
|
||||
<div className="sm:border-t sm:border-gray-800">
|
||||
<label htmlFor="username" className="text-label">
|
||||
{intl.formatMessage(messages.username)}
|
||||
{serverType === MediaServerType.EMBY
|
||||
? `Emby Connect ${intl.formatMessage(
|
||||
messages.email
|
||||
)} / ${intl.formatMessage(messages.username)}`
|
||||
: intl.formatMessage(messages.username)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
@@ -445,7 +449,13 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.username)}
|
||||
placeholder={
|
||||
serverType === MediaServerType.EMBY
|
||||
? `Emby Connect ${intl.formatMessage(
|
||||
messages.email
|
||||
)} / ${intl.formatMessage(messages.username)}`
|
||||
: intl.formatMessage(messages.username)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{errors.username && touched.username && (
|
||||
|
||||
@@ -175,7 +175,10 @@ const Login = () => {
|
||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
||||
/>
|
||||
) : (
|
||||
<JellyfinLogin revalidate={revalidate} />
|
||||
<JellyfinLogin
|
||||
revalidate={revalidate}
|
||||
serverType={settings.currentSettings.mediaServerType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
|
||||
Reference in New Issue
Block a user