From c9efbb94e940616d06d50d123b5c2a2f0e348dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis=20Jansons?= Date: Tue, 28 Nov 2023 21:59:53 +0200 Subject: [PATCH] feat: support for forwardAuth --- overseerr-api.yml | 9 +++++++ server/middleware/auth.ts | 26 ++++++++++++++++++- src/pages/_app.tsx | 6 ++--- src/pages/collection/[collectionId]/index.tsx | 5 ++-- src/pages/movie/[movieId]/index.tsx | 5 ++-- src/pages/tv/[tvId]/index.tsx | 5 ++-- src/utils/localRequestHelper.ts | 18 +++++++++++++ 7 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 src/utils/localRequestHelper.ts diff --git a/overseerr-api.yml b/overseerr-api.yml index e070361be..fec887c55 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -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. @@ -171,6 +172,9 @@ components: defaultPermissions: type: number example: 32 + enableForwardAuth: + type: boolean + example: true PlexLibrary: type: object properties: @@ -1921,6 +1925,10 @@ components: type: apiKey in: header name: X-Api-Key + forwardAuth: + type: apiKey + in: header + name: X-Forwarded-User paths: /status: @@ -6804,3 +6812,4 @@ paths: security: - cookieAuth: [] - apiKey: [] + - forwardAuth: [] diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 326d460d8..d205be31e 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -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; } else if (req.session?.userId) { const userRepository = getRepository(User); @@ -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, diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 78199e08c..b9a9a4f2b 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -12,6 +12,7 @@ import { SettingsProvider } from '@app/context/SettingsContext'; import { UserContext } from '@app/context/UserContext'; import type { User } from '@app/hooks/useUser'; import '@app/styles/globals.css'; +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'; @@ -215,10 +216,7 @@ CoreApp.getInitialProps = async (initialProps) => { const response = await axios.get( `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), } ); user = response.data; diff --git a/src/pages/collection/[collectionId]/index.tsx b/src/pages/collection/[collectionId]/index.tsx index 5802577bb..5967e398a 100644 --- a/src/pages/collection/[collectionId]/index.tsx +++ b/src/pages/collection/[collectionId]/index.tsx @@ -1,4 +1,5 @@ import CollectionDetails from '@app/components/CollectionDetails'; +import { getRequestHeaders } from '@app/utils/localRequestHelper'; import type { Collection } from '@server/models/Collection'; import axios from 'axios'; import type { GetServerSideProps, NextPage } from 'next'; @@ -19,9 +20,7 @@ export const getServerSideProps: GetServerSideProps< ctx.query.collectionId }`, { - headers: ctx.req?.headers?.cookie - ? { cookie: ctx.req.headers.cookie } - : undefined, + headers: getRequestHeaders(ctx), } ); diff --git a/src/pages/movie/[movieId]/index.tsx b/src/pages/movie/[movieId]/index.tsx index 053ee3ff3..292bcf5e3 100644 --- a/src/pages/movie/[movieId]/index.tsx +++ b/src/pages/movie/[movieId]/index.tsx @@ -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 axios from 'axios'; import type { GetServerSideProps, NextPage } from 'next'; @@ -19,9 +20,7 @@ export const getServerSideProps: GetServerSideProps = async ( ctx.query.movieId }`, { - headers: ctx.req?.headers?.cookie - ? { cookie: ctx.req.headers.cookie } - : undefined, + headers: getRequestHeaders(ctx), } ); diff --git a/src/pages/tv/[tvId]/index.tsx b/src/pages/tv/[tvId]/index.tsx index a8a3cbd7d..fcc87b030 100644 --- a/src/pages/tv/[tvId]/index.tsx +++ b/src/pages/tv/[tvId]/index.tsx @@ -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 axios from 'axios'; import type { GetServerSideProps, NextPage } from 'next'; @@ -17,9 +18,7 @@ export const getServerSideProps: GetServerSideProps = async ( const response = await axios.get( `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), } ); diff --git a/src/utils/localRequestHelper.ts b/src/utils/localRequestHelper.ts new file mode 100644 index 000000000..0d55859e4 --- /dev/null +++ b/src/utils/localRequestHelper.ts @@ -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 = ( + ctx: NextPageContext | GetServerSidePropsContext +) => { + 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; +};