Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for forwardAuth #580

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ info:

- **Cookie Authentication**: A valid sign-in to the `/auth/plex` or `/auth/local` will generate a valid authentication cookie.
- **API Key Authentication**: Sign-in is also possible by passing an `X-Api-Key` header along with a valid API Key generated by Overseerr.
- **ForwardAuth Authentication**: Sign-in is also possible by passing an `X-Forwarded-User` header containing user's e-mail.
tags:
- name: public
description: Public API endpoints requiring no authentication.
Expand Down Expand Up @@ -186,6 +187,9 @@ components:
defaultPermissions:
type: number
example: 32
enableForwardAuth:
type: boolean
example: true
Comment on lines +190 to +192
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You wrote the setting here, but it is not available anywhere.

You should add this as a general setting, by adding a new properties in the MainSettings type in server/lib/settings.ts and adding a new input in the settings page in src/components/Settings/SettingsMain/index.tsx. You should also check that this setting is enabled before allowing login with forwardAuth.

Or did you intend to not make a setting and set it enabled by default?

PlexLibrary:
type: object
properties:
Expand Down Expand Up @@ -1939,6 +1943,10 @@ components:
type: apiKey
in: header
name: X-Api-Key
forwardAuth:
type: apiKey
in: header
name: X-Forwarded-User

paths:
/status:
Expand Down Expand Up @@ -6939,3 +6947,4 @@ paths:
security:
- cookieAuth: []
- apiKey: []
- forwardAuth: []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26 changes: 25 additions & 1 deletion server/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ export const checkUser: Middleware = async (req, _res, next) => {
}

user = await userRepository.findOne({ where: { id: userId } });
} else if (req.header('x-forwarded-user')) {
const userRepository = getRepository(User);
user = await userRepository.findOne({
where: { email: req.header('x-forwarded-user') },
});

if (user) {
req.user = user;
}

req.locale = user?.settings?.locale
? user.settings.locale
: settings.main.locale;
Comment on lines +30 to +36
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessary, it's a duplicate from below.

} else if (req.session?.userId) {
const userRepository = getRepository(User);

Expand All @@ -44,7 +57,18 @@ export const isAuthenticated = (
permissions?: Permission | Permission[],
options?: PermissionCheckOptions
): Middleware => {
const authMiddleware: Middleware = (req, res, next) => {
const authMiddleware: Middleware = async (req, res, next) => {
if (req.header('x-forwarded-user')) {
const userRepository = getRepository(User);
const user = await userRepository.findOne({
where: { email: req.header('x-forwarded-user') },
});

if (user) {
req.user = user;
}
}

if (!req.user || !req.user.hasPermission(permissions ?? 0, options)) {
res.status(403).json({
status: 403,
Expand Down
6 changes: 2 additions & 4 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { UserContext } from '@app/context/UserContext';
import type { User } from '@app/hooks/useUser';
import '@app/styles/globals.css';
import '@app/utils/fetchOverride';
import { getRequestHeaders } from '@app/utils/localRequestHelper';
import { polyfillIntl } from '@app/utils/polyfillIntl';
import { MediaServerType } from '@server/constants/server';
import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces';
Expand Down Expand Up @@ -227,10 +228,7 @@ CoreApp.getInitialProps = async (initialProps) => {
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/auth/me`,
{
headers:
ctx.req && ctx.req.headers.cookie
? { cookie: ctx.req.headers.cookie }
: undefined,
headers: getRequestHeaders(ctx),
}
);
if (!res.ok) throw new Error();
Expand Down
5 changes: 2 additions & 3 deletions src/pages/collection/[collectionId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CollectionDetails from '@app/components/CollectionDetails';
import { getRequestHeaders } from '@app/utils/localRequestHelper';
import type { Collection } from '@server/models/Collection';
import type { GetServerSideProps, NextPage } from 'next';

Expand All @@ -18,9 +19,7 @@ export const getServerSideProps: GetServerSideProps<
ctx.query.collectionId
}`,
{
headers: ctx.req?.headers?.cookie
? { cookie: ctx.req.headers.cookie }
: undefined,
headers: getRequestHeaders(ctx),
}
);
if (!res.ok) throw new Error();
Expand Down
5 changes: 2 additions & 3 deletions src/pages/movie/[movieId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import MovieDetails from '@app/components/MovieDetails';
import { getRequestHeaders } from '@app/utils/localRequestHelper';
import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
import type { GetServerSideProps, NextPage } from 'next';

Expand All @@ -18,9 +19,7 @@ export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
ctx.query.movieId
}`,
{
headers: ctx.req?.headers?.cookie
? { cookie: ctx.req.headers.cookie }
: undefined,
headers: getRequestHeaders(ctx),
}
);
if (!res.ok) throw new Error();
Expand Down
5 changes: 2 additions & 3 deletions src/pages/tv/[tvId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import TvDetails from '@app/components/TvDetails';
import { getRequestHeaders } from '@app/utils/localRequestHelper';
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
import type { GetServerSideProps, NextPage } from 'next';

Expand All @@ -16,9 +17,7 @@ export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/tv/${ctx.query.tvId}`,
{
headers: ctx.req?.headers?.cookie
? { cookie: ctx.req.headers.cookie }
: undefined,
headers: getRequestHeaders(ctx),
}
);
if (!res.ok) throw new Error();
Expand Down
18 changes: 18 additions & 0 deletions src/utils/localRequestHelper.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wdyt about renaming this file to something like serverSidePropsHelpers?

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { NextPageContext } from 'next/dist/shared/lib/utils';
import type { GetServerSidePropsContext, PreviewData } from 'next/types';
import type { ParsedUrlQuery } from 'querystring';

export const getRequestHeaders = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this function be called something like getAuthHeaders instead of getRequestHeaders?

ctx: NextPageContext | GetServerSidePropsContext<ParsedUrlQuery, PreviewData>
) => {
return ctx.req && ctx.req.headers
? {
...(ctx.req.headers.cookie && {
cookie: ctx.req.headers.cookie,
}),
...(ctx.req.headers['x-forwarded-user'] && {
'x-forwarded-user': ctx.req.headers['x-forwarded-user'] as string,
}),
}
: undefined;
};
Loading