feat(auth): support Plex home profile login#1594
Conversation
10e5838 to
f5de648
Compare
4b5cf4e to
fbd55ab
Compare
|
I don't know if I did not RTFM properly but I struggled a bit with these data migration files sorry (I am using a docker env btw)... |
We have a step-by-step guide in our contribution guide to generate these migrations: https://github.com/fallenbagel/jellyseerr/blob/develop/CONTRIBUTING.md#migrations |
Yep, I read this guide but I don't know why it generated unnecessary and incorrect things. Maybe I need to review my dev env. |
Migrations for SQLite can be quite messy yes. IIRC SQLite doesn't support editing columns, hence the long migration files for SQLite. |
|
Why does the cypress-run suddenly fails ? I didn't touch anything... |
87fa714 to
02a2d9c
Compare
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
|
I believe this PR is ready for review. I haven’t identified any issues so far, and there doesn’t appear to be any further work needed at this stage. Would it be appropriate to provide a tag for testing, or is it still too early ? |
I can push a preview tag tonight |
Awesome thanks 🔥 |
|
I'm not sure if this is the right place to ask this question (sorry if it's not) but does this pull request add the ability for each profile to request movies in jellyseerr thru their plex watchilsts ? This feature is already available but I was just wondering if this would add a "per profile" request thru each profile's watchlist. Thank you! |
I haven’t had the chance to review or test the watchlist feature yet, so I can’t confirm at the moment. However, if it doesn’t currently support this functionality, it would be great to consider integrating it. |
e762c9e to
6999dcd
Compare
What is the name of the tag ? I'm eager to test this |
I think fallenbagel forgot. |
Yeah sorry I got busy 😅 |
|
Preview should be available in half an hour as: |
… when multiple profiles exist
73688ba to
ce27178
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/routes/auth.ts (1)
319-333:⚠️ Potential issue | 🟡 MinorMisleading log message: says "Jellyseerr" but context is Plex access denial.
The log message at line 320 says "creating new Jellyseerr user" but this code path is actually denying access to a Plex user without media server access. The message is misleading and contradicts the actual behavior (returning 403).
🔧 Fix the log message
} else { - logger.info( - 'Sign-in attempt from Plex user with access to the media server; creating new Jellyseerr user', + logger.warn( + 'Sign-in attempt from Plex user without access to the media server', { label: 'API', ip: req.ip, email: account.email, plexId: account.id, plexUsername: account.username, } );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/routes/auth.ts` around lines 319 - 333, The logger.info message is misleading—inside the branch that returns next({ status: 403, error: ApiErrorCode.NewPlexLoginDisabled, ... }) it incorrectly says "creating new Jellyseerr user"; update the log text in the logger.info call (near the same block using logger.info, req.ip, account.email, account.id, account.username and the subsequent next(...) call) to accurately reflect that the Plex sign-in was denied due to lack of media server access (e.g., "Sign-in attempt from Plex user without access to the media server; denying access").
♻️ Duplicate comments (2)
server/routes/auth.ts (1)
649-668:⚠️ Potential issue | 🟠 MajorConsider returning an error instead of silently authenticating when profile matches a regular Plex user.
When an existing user has a different
plexIdand is not a profile user (lines 651-654), the code logs a warning but still authenticates the user (lines 664-667). This could lead to unexpected behavior where selecting a profile logs you into an unrelated user's account if theirplexProfileIdhappens to match.🔒 Proposed hardening
if ( profileUser.plexId && profileUser.plexId !== mainUser.plexId && profileUser.userType !== UserType.PLEX_PROFILE ) { logger.warn('Attempted to use a regular Plex user as a profile', { label: 'Auth', profileId, userId: profileUser.id, mainUserId: mainUser.id, }); - // Simply use their account without modifying it - if (req.session) { - req.session.userId = profileUser.id; - } - return res.status(200).json(profileUser.filter() ?? {}); + return next({ + status: 409, + message: 'This profile ID conflicts with an existing user account.', + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/routes/auth.ts` around lines 649 - 668, The current branch that handles an existing user with a different plexId and userType !== UserType.PLEX_PROFILE (symbols: profileUser, mainUser, UserType.PLEX_PROFILE, logger.warn, req.session.userId, profileId) should not silently authenticate; instead, stop setting req.session.userId and return a failure response (e.g., 403 Forbidden or 400) with a clear error message and relevant identifiers in the payload after logging the warning. Update the handler so when the condition matches you log the incident via logger.warn and then immediately return res.status(...).json({ error: 'Profile selection conflicts with an existing non-profile user', profileId, userId: profileUser.id }) without mutating session or proceeding to authenticate.server/api/plextv.ts (1)
376-384:⚠️ Potential issue | 🟡 MinorAdd timeout to the
switchProfileaxios call.This
axios.postcall is missing a timeout configuration. ThegetProfilesmethod (line 282) andgetUsersmethod (line 443) both havetimeout: 10000, but this call can hang indefinitely if the Plex API is unresponsive.🔧 Proposed fix
const response = await axios.post(urlPath, pin ? { pin } : {}, { baseURL: 'https://clients.plex.tv', + timeout: 10000, headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-Plex-Token': this.authToken, 'X-Plex-Client-Identifier': randomUUID(), }, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/api/plextv.ts` around lines 376 - 384, The axios.post call in switchProfile (the POST to urlPath with pin) lacks a timeout and can hang; update the request options passed to axios.post in the switchProfile method to include a timeout (e.g. timeout: 10000) alongside baseURL and headers so it matches getProfiles/getUsers behavior and fails fast if the Plex API is unresponsive.
🧹 Nitpick comments (6)
server/routes/auth.ts (2)
497-505: Redundant API call after successful PIN validation.The
getUser()call at line 498 is redundant. IfvalidateProfilePinreturnstrue(line 487), the profile switch already succeeded. This extra call adds unnecessary latency without providing additional security benefit.♻️ Remove redundant call
if (selectedProfile.protected && pin) { const isPinValid = await plextv.validateProfilePin(profileId, pin); if (!isPinValid) { return next({ status: 401, message: 'Invalid PIN.', error: ApiErrorCode.InvalidPin, }); } - - try { - await plextv.getUser(); - } catch (e) { - return next({ - status: 401, - message: 'Invalid PIN.', - error: ApiErrorCode.InvalidPin, - }); - } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/routes/auth.ts` around lines 497 - 505, Remove the redundant plextv.getUser() call and its surrounding try/catch in the route handler—if validateProfilePin(...) already returned true and profile switching succeeded, do not make an extra plextv.getUser() call; delete the block that calls plextv.getUser() and returns a 401 on catch, leaving the flow to continue after validateProfilePin succeeds (refer to validateProfilePin and plextv.getUser in auth.ts to locate the code).
604-619: Redundant/dead code: profile lookup already performed.The
exactProfileUserlookup at lines 605-607 is unreachable. The code only reaches this block whenprofileUseris null (line 553), meaning the earlier lookup byplexProfileIdat line 549-551 already returned no results. This second lookup with the sameplexProfileIdwill always return null.♻️ Remove redundant code
} else { - // Then check for any other potential matches - const exactProfileUser = await userRepository.findOne({ - where: { plexProfileId: profileId }, - }); - - if (exactProfileUser) { - logger.info('Found existing profile user with exact ID match', { - label: 'Auth', - profileId, - userId: exactProfileUser.id, - }); - - if (req.session) { - req.session.userId = exactProfileUser.id; - } - return res.status(200).json(exactProfileUser.filter() ?? {}); - } else { // Create a new profile user profileUser = new User({ // ... }); - } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/routes/auth.ts` around lines 604 - 619, Remove the redundant second lookup and its handling: delete the const exactProfileUser = await userRepository.findOne({ where: { plexProfileId: profileId } }); block and the subsequent if (exactProfileUser) { ... } return; since profileUser was already queried by plexProfileId earlier, this code is unreachable; if you intended to reuse a found user ensure you reference profileUser (and set req.session.userId = profileUser.id) instead.src/components/Login/index.tsx (1)
145-161: Optimize repeated profile lookups.The profile is looked up 5 times with
.find()for the sameprofileId. Cache the result for cleaner, more efficient code.♻️ Cache profile lookup
if (response.data?.status === 'REQUIRES_PIN') { + const profile = profiles.find((p) => p.id === profileId); setShowPinEntry(true); setPinProfileId(profileId); - setPinProfileName( - profiles.find((p) => p.id === profileId)?.title || - profiles.find((p) => p.id === profileId)?.username || - 'Profile' - ); - setPinProfileThumb( - profiles.find((p) => p.id === profileId)?.thumb || null - ); - setPinIsProtected( - profiles.find((p) => p.id === profileId)?.protected || false - ); - setPinIsMainUser( - profiles.find((p) => p.id === profileId)?.isMainUser || false - ); + setPinProfileName(profile?.title || profile?.username || 'Profile'); + setPinProfileThumb(profile?.thumb || null); + setPinIsProtected(profile?.protected || false); + setPinIsMainUser(profile?.isMainUser || false); setPinError(null); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Login/index.tsx` around lines 145 - 161, The code repeatedly calls profiles.find((p) => p.id === profileId) for the same profileId; cache the found profile in a local variable (e.g., const profile = profiles.find(p => p.id === profileId)) before the block and then replace the multiple finds used by setShowPinEntry, setPinProfileId, setPinProfileName, setPinProfileThumb, setPinIsProtected, and setPinIsMainUser with properties read from that cached profile (with the same fallback values), ensuring you handle the case where profile is undefined.server/api/plextv.ts (1)
396-410: Unreachable catch block invalidateProfilePin.The
catchblock will never execute becauseswitchProfilecatches all exceptions internally and returnsfalseinstead of rethrowing. The error logging at lines 404-408 is dead code.♻️ Simplify the method
public async validateProfilePin( profileId: string, pin: string ): Promise<boolean> { - try { - const success = await this.switchProfile(profileId, pin); - return success; - } catch (e) { - logger.error('Failed to validate Plex profile pin', { - label: 'Plex.tv API', - errorMessage: e.message, - }); - return false; - } + return this.switchProfile(profileId, pin); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/api/plextv.ts` around lines 396 - 410, The try/catch in validateProfilePin is dead because switchProfile already catches errors and returns false; remove the try/catch and simply return the result of this.switchProfile(profileId, pin) in validateProfilePin (leave switchProfile unchanged), and delete the unreachable logger.error block so validateProfilePin becomes a thin wrapper that returns await this.switchProfile(profileId, pin).src/components/UserList/PlexImportModal.tsx (1)
159-188: Add type safety forresponse.skippedproperty.The code accesses
response.skippedwhich isn't defined in the response type from the axios call. While the optional chaining handles undefined, explicitly typing the response would improve maintainability.♻️ Add explicit typing
- const { data: response } = await axios.post( + const { data: response } = await axios.post<{ + data: { userType: number }[]; + skipped?: { type: string }[]; + }>( '/api/v1/user/import-from-plex', { plexIds: selectedUsers, profileIds: selectedProfiles, } );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/UserList/PlexImportModal.tsx` around lines 159 - 188, Define an explicit TypeScript interface (e.g., PlexImportResponse { skipped?: { type: string }[]; ... }) for the API payload returned in PlexImportModal and apply it to the axios call (use axios.post<PlexImportResponse>(...) or type response.data as PlexImportResponse) so that response.skipped is strongly typed; update any local usages (response.skipped, skippedUsers, skippedProfiles) to rely on the typed response and keep existing optional checks (e.g., response.skipped && ...) to preserve runtime safety.seerr-api.yml (1)
117-117: ModeluserTypeas an explicit enum, not description-only mapping.Line 117 documents valid values, but schema validation/codegen won’t enforce them without an
enum.💡 Proposed OpenAPI refinement
userType: type: integer + enum: [1, 2, 3, 4, 5] example: 1 description: 'User type - 1=PLEX, 2=LOCAL, 3=JELLYFIN, 4=EMBY, 5=PLEX_PROFILE' readOnly: true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@seerr-api.yml` at line 117, The userType schema currently only documents allowed numeric values in its description; change it into an explicit enum so validation and codegen enforce the choices: update the userType property in the OpenAPI schema (the userType model/property) to include an enum array [1,2,3,4,5] (and optionally add a nullable/format/type: integer and a brief enum description or x-enum-descriptions mapping) so tools will validate the allowed values instead of relying on the prose-only description.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@seerr-api.yml`:
- Around line 3985-3987: The pin property is defined as a free-form string but
should be constrained to exactly four digits; update both occurrences of the pin
schema in seerr-api.yml (the request body schemas that currently show "pin:
type: string description: Optional 4-digit profile PIN") to enforce a 4-digit
format by adding validations such as pattern: '^[0-9]{4}$' (and optionally
minLength: 4 and maxLength: 4) so the schema only accepts exactly four numeric
digits.
In `@src/components/Login/PlexProfileSelector.tsx`:
- Around line 152-160: The Image src may be an empty string for some profiles
which can cause Next.js Image to error or render a broken image; in
PlexProfileSelector (where profile is used and the Image component is rendered)
guard against empty profile.thumb by providing a fallback source or skipping the
Image when profile.thumb is falsy—e.g., use profile.thumb ||
fallbackThumbnailUrl or render a plain placeholder element (same dimensions and
classes) when profile.thumb is empty so Image only receives a valid non-empty
src.
- Around line 50-60: The call to onProfileSelected(profile.id) is not awaited so
async errors slip past the try/catch and setIsSubmitting(false) runs too early;
make the containing handler function asynchronous (add async) and await
onProfileSelected(profile.id) inside the existing try block, leaving the catch
to call setError(intl.formatMessage(messages.selectProfileError)) and the
finally to call setIsSubmitting(false) so loading and error handling behave
correctly for async failures.
---
Outside diff comments:
In `@server/routes/auth.ts`:
- Around line 319-333: The logger.info message is misleading—inside the branch
that returns next({ status: 403, error: ApiErrorCode.NewPlexLoginDisabled, ...
}) it incorrectly says "creating new Jellyseerr user"; update the log text in
the logger.info call (near the same block using logger.info, req.ip,
account.email, account.id, account.username and the subsequent next(...) call)
to accurately reflect that the Plex sign-in was denied due to lack of media
server access (e.g., "Sign-in attempt from Plex user without access to the media
server; denying access").
---
Duplicate comments:
In `@server/api/plextv.ts`:
- Around line 376-384: The axios.post call in switchProfile (the POST to urlPath
with pin) lacks a timeout and can hang; update the request options passed to
axios.post in the switchProfile method to include a timeout (e.g. timeout:
10000) alongside baseURL and headers so it matches getProfiles/getUsers behavior
and fails fast if the Plex API is unresponsive.
In `@server/routes/auth.ts`:
- Around line 649-668: The current branch that handles an existing user with a
different plexId and userType !== UserType.PLEX_PROFILE (symbols: profileUser,
mainUser, UserType.PLEX_PROFILE, logger.warn, req.session.userId, profileId)
should not silently authenticate; instead, stop setting req.session.userId and
return a failure response (e.g., 403 Forbidden or 400) with a clear error
message and relevant identifiers in the payload after logging the warning.
Update the handler so when the condition matches you log the incident via
logger.warn and then immediately return res.status(...).json({ error: 'Profile
selection conflicts with an existing non-profile user', profileId, userId:
profileUser.id }) without mutating session or proceeding to authenticate.
---
Nitpick comments:
In `@seerr-api.yml`:
- Line 117: The userType schema currently only documents allowed numeric values
in its description; change it into an explicit enum so validation and codegen
enforce the choices: update the userType property in the OpenAPI schema (the
userType model/property) to include an enum array [1,2,3,4,5] (and optionally
add a nullable/format/type: integer and a brief enum description or
x-enum-descriptions mapping) so tools will validate the allowed values instead
of relying on the prose-only description.
In `@server/api/plextv.ts`:
- Around line 396-410: The try/catch in validateProfilePin is dead because
switchProfile already catches errors and returns false; remove the try/catch and
simply return the result of this.switchProfile(profileId, pin) in
validateProfilePin (leave switchProfile unchanged), and delete the unreachable
logger.error block so validateProfilePin becomes a thin wrapper that returns
await this.switchProfile(profileId, pin).
In `@server/routes/auth.ts`:
- Around line 497-505: Remove the redundant plextv.getUser() call and its
surrounding try/catch in the route handler—if validateProfilePin(...) already
returned true and profile switching succeeded, do not make an extra
plextv.getUser() call; delete the block that calls plextv.getUser() and returns
a 401 on catch, leaving the flow to continue after validateProfilePin succeeds
(refer to validateProfilePin and plextv.getUser in auth.ts to locate the code).
- Around line 604-619: Remove the redundant second lookup and its handling:
delete the const exactProfileUser = await userRepository.findOne({ where: {
plexProfileId: profileId } }); block and the subsequent if (exactProfileUser) {
... } return; since profileUser was already queried by plexProfileId earlier,
this code is unreachable; if you intended to reuse a found user ensure you
reference profileUser (and set req.session.userId = profileUser.id) instead.
In `@src/components/Login/index.tsx`:
- Around line 145-161: The code repeatedly calls profiles.find((p) => p.id ===
profileId) for the same profileId; cache the found profile in a local variable
(e.g., const profile = profiles.find(p => p.id === profileId)) before the block
and then replace the multiple finds used by setShowPinEntry, setPinProfileId,
setPinProfileName, setPinProfileThumb, setPinIsProtected, and setPinIsMainUser
with properties read from that cached profile (with the same fallback values),
ensuring you handle the case where profile is undefined.
In `@src/components/UserList/PlexImportModal.tsx`:
- Around line 159-188: Define an explicit TypeScript interface (e.g.,
PlexImportResponse { skipped?: { type: string }[]; ... }) for the API payload
returned in PlexImportModal and apply it to the axios call (use
axios.post<PlexImportResponse>(...) or type response.data as PlexImportResponse)
so that response.skipped is strongly typed; update any local usages
(response.skipped, skippedUsers, skippedProfiles) to rely on the typed response
and keep existing optional checks (e.g., response.skipped && ...) to preserve
runtime safety.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
seerr-api.ymlserver/api/plextv.tsserver/routes/auth.tsserver/routes/settings/index.tssrc/components/Login/PlexPinEntry.tsxsrc/components/Login/PlexProfileSelector.tsxsrc/components/Login/index.tsxsrc/components/UserList/PlexImportModal.tsxsrc/i18n/locale/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/Login/PlexPinEntry.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
server/api/plextv.ts (1)
376-384:⚠️ Potential issue | 🟠 MajorAdd a timeout to
switchProfileexternal call.This direct Plex request has no timeout; stalled upstream networking can hang the request path and cascade into auth latency/failures.
🔧 Suggested fix
const response = await axios.post(urlPath, pin ? { pin } : {}, { baseURL: 'https://clients.plex.tv', + timeout: 10000, headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-Plex-Token': this.authToken, 'X-Plex-Client-Identifier': randomUUID(), }, });#!/bin/bash # Verify whether switchProfile axios config includes a timeout. rg -n -A12 -B2 "axios\.post\(urlPath" server/api/plextv.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/api/plextv.ts` around lines 376 - 384, The axios.post call inside switchProfile is missing a request timeout which can hang; update the axios.post(urlPath, pin ? { pin } : {}, { baseURL: 'https://clients.plex.tv', headers: { ... } }) call to include a timeout property (e.g. timeout: 5000 or use an existing config value like this.requestTimeout) in the options object so the external Plex request fails fast on network stalls; ensure the timeout is applied to the same options object passed to axios.post in the switchProfile implementation.server/routes/auth.ts (1)
140-167:⚠️ Potential issue | 🔴 Critical
/auth/plexdirect profile login bypasses profile PIN flow.This path creates a session from
profileIdwithout enforcing PIN checks for protected profiles. Profile authentication should be centralized in/auth/plex/profile/select.🔒 Suggested hardening
- // Handle direct profile login - if (body.profileId) { - const profileUser = await userRepository.findOne({ - where: { plexProfileId: body.profileId }, - }); - - if (!profileUser) { - return next({ - status: 400, - message: 'Invalid profile selection.', - }); - } - if (!profiles.some((p) => p.id === body.profileId)) { - return next({ - status: 403, - error: 'Auth token does not have access to this profile.', - }); - } - - profileUser.plexToken = body.authToken; - await userRepository.save(profileUser); - - if (req.session) { - req.session.userId = profileUser.id; - } - - return res.status(200).json(profileUser.filter() ?? {}); - } + // Force all profile authentication through /auth/plex/profile/select + if (body.profileId) { + return next({ + status: 400, + message: 'Use /auth/plex/profile/select for profile authentication.', + }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/routes/auth.ts` around lines 140 - 167, The current /auth/plex handler directly creates a session and sets profileUser.plexToken when body.profileId is present, bypassing PIN checks; instead, do not perform session creation or token save in this branch—either remove this direct-profile block or delegate to the centralized profile auth flow (/auth/plex/profile/select) so PIN-protected profiles are validated before calling userRepository.save or setting req.session.userId; ensure you reuse the same profile selection/PIN verification logic that checks profiles.some((p) => p.id === body.profileId) and enforces PIN rules prior to updating profileUser or returning the filtered profile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/api/plextv.ts`:
- Around line 273-274: getProfiles() currently returns a synthetic main profile
with protected: false when Plex Home profile retrieval fails, which weakens auth
gating; change getProfiles() to fail closed by either throwing/propagating the
retrieval error or returning a failure result instead of the synthetic
unprotected profile (do not leave the profile object with protected: false).
Locate the code paths that construct the fallback profile (the object with
protected: false) in getProfiles() and in the similar blocks referenced later,
remove that permissive fallback, and ensure callers handle the propagated error
or explicit failure so PIN/profile-selection safeguards remain enforced.
In `@server/routes/auth.ts`:
- Around line 126-138: The current check only validates a PIN when body.pin
exists, allowing protected main profiles (mainUserProfile?.protected) to
authenticate with no PIN; update the logic in the block that uses
mainUserProfile and plextv.validateProfilePin so that if (!body.profileId &&
mainUserProfile?.protected) you first require a PIN (if no body.pin return
next({status:403, error:'PIN_REQUIRED.'})), then call
plextv.validateProfilePin(mainUserProfile.id, body.pin) and reject on invalid
PIN as before; apply the same change to the duplicate block around the 370-384
area to ensure protected main profiles cannot proceed without a PIN.
---
Duplicate comments:
In `@server/api/plextv.ts`:
- Around line 376-384: The axios.post call inside switchProfile is missing a
request timeout which can hang; update the axios.post(urlPath, pin ? { pin } :
{}, { baseURL: 'https://clients.plex.tv', headers: { ... } }) call to include a
timeout property (e.g. timeout: 5000 or use an existing config value like
this.requestTimeout) in the options object so the external Plex request fails
fast on network stalls; ensure the timeout is applied to the same options object
passed to axios.post in the switchProfile implementation.
In `@server/routes/auth.ts`:
- Around line 140-167: The current /auth/plex handler directly creates a session
and sets profileUser.plexToken when body.profileId is present, bypassing PIN
checks; instead, do not perform session creation or token save in this
branch—either remove this direct-profile block or delegate to the centralized
profile auth flow (/auth/plex/profile/select) so PIN-protected profiles are
validated before calling userRepository.save or setting req.session.userId;
ensure you reuse the same profile selection/PIN verification logic that checks
profiles.some((p) => p.id === body.profileId) and enforces PIN rules prior to
updating profileUser or returning the filtered profile.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
seerr-api.ymlserver/api/plextv.tsserver/routes/auth.tssrc/components/Login/PlexProfileSelector.tsxsrc/components/Login/index.tsxsrc/components/UserList/PlexImportModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/Login/PlexProfileSelector.tsx
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged. |
Description
This PR adds support for Plex Home profiles.
Authentication flow
flowchart TD A[User] -->|POST /auth/plex| B[Server] B -->|"single profile"| C["200 OK (session set)"] B -->|"multiple profiles"| D{{status: REQUIRES_PROFILE}} D --> E[Client shows profile selector] E -->|"POST /auth/plex/profile/select (profileId, pin?)"| F[Server] F -->|"needs PIN & none"| G{{status: REQUIRES_PIN}} F -->|"wrong PIN"| H[401 Invalid PIN] F -->|"imported profile"| I["200 OK (session set)"] F -->|"not imported & Allow new Plex logins OFF"| J[403 Access denied] F -->|"not imported & setting ON"| K[User created] K --> L[200 OK]Note
E‑mail fallback
<ownerPrefix>+<profile>@<ownerDomain>(plex.localif the owner address has no domain)P.S : Sorry for this "duplicate" PR (see #1503), but it's a complete rework.
How Has This Been Tested?
Screenshot (if UI-related)
Plex profile selector





PIN entry
Invalid PIN
Profile not imported (auto-import disabled)
Users imported
Plex Profile Import
In addition to the authentication flow, this PR now also adds support for importing Plex profiles from the admin interface:
Screenshots
Plex Profile Import UI


Duplicate Detection
To-Dos
Issues Fixed or Closed
Checklist:
pnpm buildpnpm i18n:extractSummary by CodeRabbit
New Features
Refactor
Documentation