Desktop: Resolves #14372: Enable OAuth for Joplin Server#14888
Desktop: Resolves #14372: Enable OAuth for Joplin Server#14888Kaushalendra-Marcus wants to merge 8 commits intolaurent22:devfrom
Conversation
📝 WalkthroughWalkthroughConfig and login flows were made sync-target aware: settings are saved before auth checks; sync targets expose an auth route name and session-based auth checks; login screens and polling accept a syncTargetId; Root adds a Joplin Server login route and Config shows a server login button. Changes
Sequence DiagramsequenceDiagram
actor User
participant ConfigScreen as ConfigScreen
participant SettingsManager as SettingsManager
participant SyncTarget as SyncTarget
participant LoginScreen as LoginScreen
participant JoplinServerApi as JoplinServerApi
User->>ConfigScreen: Select sync target & click Login
ConfigScreen->>SettingsManager: Save settings
SettingsManager-->>ConfigScreen: Save result
ConfigScreen->>SyncTarget: isAuthenticated()
SyncTarget->>JoplinServerApi: fileApi -> sessionId()
JoplinServerApi-->>SyncTarget: sessionId (or empty)
alt Not authenticated
SyncTarget-->>ConfigScreen: false
ConfigScreen->>ConfigScreen: authRouteName()
ConfigScreen->>LoginScreen: Navigate (syncTargetId)
LoginScreen->>User: Show auth UI
User->>LoginScreen: Complete OAuth flow
LoginScreen->>SettingsManager: Persist credentials (sync.<id>.*)
SettingsManager-->>LoginScreen: Persisted
else Authenticated
SyncTarget-->>ConfigScreen: true
ConfigScreen->>SettingsManager: Proceed with sync config check
end
Suggested labels
🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx`:
- Around line 69-81: The auth gate in checkSyncConfig_ uses the persisted
reg.syncTarget() and its isAuthenticated() which reads Setting.value(...),
causing stale validation when the user has unsaved edits in this.state.settings;
fix by using the draft settings or persisting them before running the auth
check: either (A) persist the draft settings first (call the existing
save/persist method used by this screen) and then re-read
reg.syncTarget()/isAuthenticated(), or (B) construct a sync target instance from
this.state.settings (instead of reg.syncTarget()) and call isAuthenticated() on
that instance, then call shared.checkSyncConfig(this, this.state.settings) as
before; update checkSyncConfig_ to use one of these approaches and keep
references to reg.syncTarget(), isAuthenticated(), shared.checkSyncConfig(this,
this.state.settings), and this.state.settings to locate the change.
- Around line 272-277: The handler goToJoplinServerLogin currently awaits
shared.saveSettings(this) but ignores its boolean result, allowing navigation
even when saveSettings failed; change the flow in goToJoplinServerLogin to store
the result of await shared.saveSettings(this), and if it returns false (or
otherwise indicates failure), abort and do not dispatch the NAV_GO action to
'JoplinServerLogin'—only dispatch when saveSettings succeeds.
In `@packages/app-desktop/gui/JoplinCloudLoginScreen.tsx`:
- Line 105: state.message() is translated and should not be mutated with
.replace(); instead determine the target label from props.syncTargetId (e.g.,
label = props.syncTargetId === 10 ? _('Joplin Cloud') : _('Joplin Server')) and
pass that label into the localized message when calling the translation function
(i.e., build the string via the i18n call used by state.message() or change
state.message() to accept a targetLabel parameter) so you inject the correct
localized target before rendering rather than replacing the English text after
the fact.
In `@packages/lib/services/joplinCloudUtils.ts`:
- Around line 115-119: The login flow writes credentials for syncTargetId but
doesn't update the active target or use the target-specific syncTarget instance;
after persisting the username/password via
Setting.setValue(`sync.${syncTargetId}.*`) you must also persist the active
target with Setting.setValue('sync.target', syncTargetId) and instantiate/use
the target-specific registry (e.g. call reg.syncTarget(syncTargetId) and assign
it to a variable) before calling fileApi.driver().api().loadSession() so the
session load and subsequent sync operate against the newly-logged-in target
rather than the previously saved one.
In `@packages/lib/SyncTargetJoplinServer.ts`:
- Around line 72-74: Update the failing test expectation in
shouldShowMissingPasswordWarning.test to reflect that
SyncTargetJoplinServer.requiresPassword() now returns false: change the
'joplinServer' expected value from true to false (or otherwise adjust the
expected object) so the test aligns with the OAuth-only flow; verify the
assertion that references the 'joplinServer' key and update it accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 639fb9fe-bc15-4ce0-a16e-2a981db038ea
📒 Files selected for processing (8)
packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsxpackages/app-desktop/gui/JoplinCloudLoginScreen.tsxpackages/app-desktop/gui/Root.tsxpackages/lib/BaseSyncTarget.tspackages/lib/JoplinServerApi.tspackages/lib/SyncTargetJoplinCloud.tspackages/lib/SyncTargetJoplinServer.tspackages/lib/services/joplinCloudUtils.ts
| public static requiresPassword() { | ||
| return true; | ||
| return false; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect every caller of the shared missing-password warning helper.
rg -n -C2 '\bshouldShowMissingPasswordWarning\s*\(' packages
# Inspect every Joplin Server OAuth/login entry point added in the UI.
rg -n -C2 "routeName:\s*'JoplinServerLogin'|Login with Joplin Server|Joplin Server Login" packagesRepository: laurent22/joplin
Length of output: 25297
🏁 Script executed:
cat -n packages/lib/components/shared/config/shouldShowMissingPasswordWarning.tsRepository: laurent22/joplin
Length of output: 588
🏁 Script executed:
# Find the sync target ID for Joplin Server
rg -n "class SyncTargetJoplinServer|Joplin Server" packages/lib/SyncTargetJoplinServer.ts | head -5Repository: laurent22/joplin
Length of output: 298
🏁 Script executed:
# Check if there are any other potential surfaces consuming the warning
rg -n "hasMissingSyncCredentials|shouldShowMissingPasswordWarning" packages --type tsx --type ts | grep -v test | grep -v localeRepository: laurent22/joplin
Length of output: 87
🏁 Script executed:
# Find the sync target ID for Joplin Server (should be 9)
rg -A 5 "class SyncTargetJoplinServer" packages/lib/SyncTargetJoplinServer.ts | head -20Repository: laurent22/joplin
Length of output: 167
🏁 Script executed:
# Check how hasMissingSyncCredentials is used in MainScreen
rg -B 3 -A 3 "hasMissingSyncCredentials" packages/app-desktop/gui/MainScreen.tsxRepository: laurent22/joplin
Length of output: 1422
🏁 Script executed:
# Look for other surfaces that might use the missing password warning or require password flow
rg -n "shouldShowMissingPasswordWarning\|hasMissingSyncCredentials" packages --type ts --type jsxRepository: laurent22/joplin
Length of output: 87
🏁 Script executed:
# Check if web/CLI have their own handling for sync target 9
rg -n "sync\.target\|SyncTarget" packages/app-web packages/app-cli --type ts 2>/dev/null | head -20Repository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Search for any password requirement logic specific to Joplin Server
rg -n "JoplinServer\|syncTargetId.*9\|sync\.9\." packages/lib --type ts | head -30Repository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Check if there are other mobile or non-desktop surfaces
fd "SsoLoginScreen\|OAuthFlow\|JoplinServerLogin" packages --type fRepository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Check SyncTargetRegistry to see which targets are available in each surface
rg -n "register\|add" packages/lib/SyncTargetRegistry.ts | head -20Repository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Look at the full ConfigScreen context around the Joplin Server login button
sed -n '260,295p' packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsxRepository: laurent22/joplin
Length of output: 1283
🏁 Script executed:
# Check if mobile SsoLoginScreen handles both OAuth and password-based login
sed -n '1,100p' packages/app-mobile/components/screens/SsoLoginScreen.tsxRepository: laurent22/joplin
Length of output: 2886
🏁 Script executed:
# Check the test expectations for Joplin Server (sync target 9)
cat -n packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.tsRepository: laurent22/joplin
Length of output: 1763
🏁 Script executed:
# Verify sync target name to ID mapping
rg -B 2 -A 2 "joplinServer\|nameToId" packages/lib/SyncTargetRegistry.ts | head -30Repository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Check if the test file was modified in this PR
git diff HEAD~1 packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.ts 2>/dev/null | head -50Repository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Check the git status to see what files were modified
git diff --name-only HEAD~1 2>/dev/null | grep -i testRepository: laurent22/joplin
Length of output: 42
🏁 Script executed:
# Look at the full SyncTargetJoplinServer implementation to understand the change
cat -n packages/lib/SyncTargetJoplinServer.tsRepository: laurent22/joplin
Length of output: 6248
Test expectations need updating for the requiresPassword() change.
The wider-impact concern is addressed: both desktop and mobile surfaces provide OAuth alternatives (ConfigScreen's "Login with Joplin Server" button and SsoLoginScreen's browser-based code flow). However, the test file packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.ts still expects 'joplinServer': true on line 10, which will cause failures since shouldShowMissingPasswordWarning() now returns false for sync target 9. Update the test expectations to reflect the OAuth-only flow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/lib/SyncTargetJoplinServer.ts` around lines 72 - 74, Update the
failing test expectation in shouldShowMissingPasswordWarning.test to reflect
that SyncTargetJoplinServer.requiresPassword() now returns false: change the
'joplinServer' expected value from true to false (or otherwise adjust the
expected object) so the test aligns with the OAuth-only flow; verify the
assertion that references the 'joplinServer' key and update it accordingly.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx (1)
72-83:⚠️ Potential issue | 🟠 MajorRe-indent these blocks with tabs only.
Line 72-83 and Line 274-290 mix spaces and tabs, which triggers
no-mixed-spaces-and-tabsand can fail CI. Please normalise both blocks to tab indentation only.As per coding guidelines,
**/*.{ts,tsx,js,jsx}: Use tabs for indentation.Also applies to: 274-290
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx` around lines 72 - 83, The highlighted blocks using syncTarget/authRouteName/isAuthenticated and the later block around shared.checkSyncConfig are mixing spaces and tabs; fix by re-indenting both code blocks to use tabs only (remove any leading spaces and replace with tabs) so they conform to the repository's tab-only indentation rule and eliminate the no-mixed-spaces-and-tabs lint error; ensure the indentation for the if/await/return lines and any nested lines inside the syncTarget/authRouteName checks and the block containing shared.checkSyncConfig is consistently tab-indented.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app-desktop/gui/JoplinCloudLoginScreen.tsx`:
- Around line 120-126: Indentation in mapStateToProps is using spaces which
triggers lint rules (indent/@typescript-eslint/indent); update the block that
declares syncTargetId, isCloud and the returned object so all leading
indentation uses tabs instead of spaces, keeping the same code and alignment for
the expressions joplinCloudWebsite, joplinCloudApi and syncTargetId (look for
the const syncTargetId = ownProps.syncTargetId ?? 10; / const isCloud =
syncTargetId === 10; and the return { ... } lines) to satisfy the project's
tabs-for-indentation rule.
---
Duplicate comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx`:
- Around line 72-83: The highlighted blocks using
syncTarget/authRouteName/isAuthenticated and the later block around
shared.checkSyncConfig are mixing spaces and tabs; fix by re-indenting both code
blocks to use tabs only (remove any leading spaces and replace with tabs) so
they conform to the repository's tab-only indentation rule and eliminate the
no-mixed-spaces-and-tabs lint error; ensure the indentation for the
if/await/return lines and any nested lines inside the syncTarget/authRouteName
checks and the block containing shared.checkSyncConfig is consistently
tab-indented.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a42df756-81d9-4305-b3a7-fa6a3c86bdd7
📒 Files selected for processing (6)
packages/app-cli/app/command-sync.tspackages/app-desktop/gui/ConfigScreen/ConfigScreen.tsxpackages/app-desktop/gui/JoplinCloudLoginScreen.tsxpackages/app-mobile/components/screens/JoplinCloudLoginScreen.tsxpackages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.tspackages/lib/services/joplinCloudUtils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/lib/services/joplinCloudUtils.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/lib/BaseSyncTarget.ts (1)
88-100: Missing blank line between methods.For consistency with the rest of the file (e.g., between lines 86–87, 102–103), add a blank line after the closing brace of
isAuthenticatedViaJoplinServerCompatibleSession()and beforeauthRouteName().🔧 Suggested fix
} } + public authRouteName(): string {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/lib/BaseSyncTarget.ts` around lines 88 - 100, Add a blank line between the end of the isAuthenticatedViaJoplinServerCompatibleSession() method and the start of authRouteName(): insert an empty line after the closing brace of isAuthenticatedViaJoplinServerCompatibleSession() so the method separation matches the file's style/consistency (look for the closing brace of isAuthenticatedViaJoplinServerCompatibleSession and the declaration of authRouteName to place the blank line).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/lib/BaseSyncTarget.ts`:
- Around line 88-100: Add a blank line between the end of the
isAuthenticatedViaJoplinServerCompatibleSession() method and the start of
authRouteName(): insert an empty line after the closing brace of
isAuthenticatedViaJoplinServerCompatibleSession() so the method separation
matches the file's style/consistency (look for the closing brace of
isAuthenticatedViaJoplinServerCompatibleSession and the declaration of
authRouteName to place the blank line).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 706e4067-8c2a-41ca-a9cb-1d89d725fa0e
📒 Files selected for processing (4)
packages/app-desktop/gui/Root.tsxpackages/lib/BaseSyncTarget.tspackages/lib/SyncTargetJoplinCloud.tspackages/lib/SyncTargetJoplinServer.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/lib/SyncTargetJoplinCloud.ts
- packages/app-desktop/gui/Root.tsx
- packages/lib/SyncTargetJoplinServer.ts
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/app-desktop/gui/JoplinCloudLoginScreen.tsx (1)
119-127: Consider renaming props to be target-agnostic.The prop names
joplinCloudWebsiteandjoplinCloudApiare now used for both Joplin Cloud and Joplin Server, which may confuse future maintainers. Consider renaming to more generic names likesyncTargetWebsiteandsyncTargetApi.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app-desktop/gui/JoplinCloudLoginScreen.tsx` around lines 119 - 127, The prop names joplinCloudWebsite and joplinCloudApi in mapStateToProps are misleading because they are used for both Joplin Cloud and Joplin Server; rename them to neutral names such as syncTargetWebsite and syncTargetApi across mapStateToProps return object and any consumers (components/props interfaces) that reference these props so the values remain the same but the identifiers are target-agnostic; update mapStateToProps (and corresponding prop types/usages) to use syncTargetWebsite and syncTargetApi and ensure any imports/prop destructuring in JoplinCloudLoginScreen and related components are adjusted accordingly.packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx (1)
255-270: Consider aligninggoToJoplinCloudLoginwith the save-before-navigate pattern.The existing
goToJoplinCloudLoginhandler navigates directly without saving settings first, whereas the newgoToJoplinServerLoginsaves settings before navigation. For consistency and to prevent potential data loss if users have unsaved changes, consider updating the Cloud handler to follow the same pattern:Suggested change
if (settings['sync.target'] === SyncTargetRegistry.nameToId('joplinCloud')) { - const goToJoplinCloudLogin = () => { + const goToJoplinCloudLogin = async () => { + const ok = await shared.saveSettings(this); + if (!ok) return; this.props.dispatch({ type: 'NAV_GO', routeName: 'JoplinCloudLogin', }); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx` around lines 255 - 270, Update the goToJoplinCloudLogin handler to follow the same save-before-navigate pattern used by goToJoplinServerLogin: instead of directly dispatching NAV_GO, call the existing save settings helper (the same function used by the server flow, e.g. saveSettingsAndThen or the method invoked by goToJoplinServerLogin) and pass a callback that dispatches { type: 'NAV_GO', routeName: 'JoplinCloudLogin' } so settings are saved before navigation; keep the handler name goToJoplinCloudLogin and ensure it still uses this.props.dispatch inside the callback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx`:
- Around line 255-270: Update the goToJoplinCloudLogin handler to follow the
same save-before-navigate pattern used by goToJoplinServerLogin: instead of
directly dispatching NAV_GO, call the existing save settings helper (the same
function used by the server flow, e.g. saveSettingsAndThen or the method invoked
by goToJoplinServerLogin) and pass a callback that dispatches { type: 'NAV_GO',
routeName: 'JoplinCloudLogin' } so settings are saved before navigation; keep
the handler name goToJoplinCloudLogin and ensure it still uses
this.props.dispatch inside the callback.
In `@packages/app-desktop/gui/JoplinCloudLoginScreen.tsx`:
- Around line 119-127: The prop names joplinCloudWebsite and joplinCloudApi in
mapStateToProps are misleading because they are used for both Joplin Cloud and
Joplin Server; rename them to neutral names such as syncTargetWebsite and
syncTargetApi across mapStateToProps return object and any consumers
(components/props interfaces) that reference these props so the values remain
the same but the identifiers are target-agnostic; update mapStateToProps (and
corresponding prop types/usages) to use syncTargetWebsite and syncTargetApi and
ensure any imports/prop destructuring in JoplinCloudLoginScreen and related
components are adjusted accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 68865619-786c-497e-ae65-c374aeb3daa8
📒 Files selected for processing (2)
packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsxpackages/app-desktop/gui/JoplinCloudLoginScreen.tsx
|
There are still CodeRabbit comments |
got it, let me fix |
|
All CodeRabbit comments have been addressed. Ready for review. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/lib/SyncTargetJoplinServer.ts`:
- Around line 72-74: The method requiresPassword in class SyncTargetJoplinServer
has mixed spaces and tabs; fix by normalizing indentation to use tabs only for
all leading whitespace in the method body and signature (e.g., adjust the
requiresPassword() { return false; } block) so it conforms to the TypeScript
file indentation standard.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: dfab280d-a037-41f2-a759-455c8c679449
📒 Files selected for processing (2)
packages/lib/SyncTargetJoplinServer.tspackages/lib/services/joplinCloudUtils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/lib/services/joplinCloudUtils.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/lib/SyncTargetJoplinServer.ts`:
- Line 69: The current isAuthenticated() call returns only
isAuthenticatedViaJoplinServerCompatibleSession(), which can false-negative on
cold start because newFileApi() uses a session provider that returns null;
update newFileApi() to provide a persisted session provider (read stored OAuth
session/token from storage and return a Session object) or, alternatively, add a
fallback path inside isAuthenticated() to check for stored OAuth credentials
(e.g., inspect persisted OAuth token/refresh token) and treat that as
authenticated until verification; locate newFileApi() and
isAuthenticatedViaJoplinServerCompatibleSession() and implement the persisted
session retrieval or the fallback credential check so auth status is accurate
after restart.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8f4009cc-9020-424b-92bc-0063e2f3f75a
📒 Files selected for processing (1)
packages/lib/SyncTargetJoplinServer.ts
|
Hi @laurent22, all issues have been addressed:
Tested locally - OAuth flow works end-to-end with a local Joplin Server instance. |
|
OAuth is optional for Joplin Server. Could you please also provide a video showing that password login still works as expected? |
Hi @laurent22, here's a video showing that password login still works as expected. 2026-04-03-11-23-40_0NwdQklz.mp4 |
|
I'm not sure this is working as expected. What is this UUID in the email field? And you didn't demonstrate auth with email/password in that video so I'm wondering if it's still working. It would be a disaster if we break this and people can't sync anymore |
Hi @laurent22, to clarify what's shown in the video: The UUID in the email field ( Password login still works - if a user enters their email and password manually in the fields and clicks "Check synchronisation configuration", it works as before. The OAuth flow is an additional option, not a replacement. I can record a separate video showing manual password login if needed. |
|
Yes that's the video I meant, that shows that password login is still working |


fixes #14372
AI Usage
I used AI assistance (Claude) for exploring the codebase and debugging.
Problem
Joplin Server users could not use OAuth to authenticate - they were forced to manually enter email and password. The OAuth flow was only enabled for Joplin Cloud, even though Joplin Server backend supports it.
Solution
Made the existing Joplin Cloud OAuth login components generic so both sync targets share the same authentication flow.
Changes:
BaseSyncTarget.ts- Added sharedisAuthenticatedViaJoplinServerCompatibleSession()methodSyncTargetJoplinServer.ts- Uses shared auth method, addedauthRouteName(), setrequiresPassword()to falseSyncTargetJoplinCloud.ts- Refactored to use shared auth methodJoplinServerApi.ts- MadesessionId()publicjoplinCloudUtils.ts- MadesyncTargetIda required parameter, API key header only sent for Joplin CloudJoplinCloudLoginScreen.tsx- Made screen generic for both targetsConfigScreen.tsx- Added "Login with Joplin Server" button, madecheckSyncConfig_genericRoot.tsx- AddedJoplinServerLoginrouteTest Plan
2026-03-24.14-37-53.mp4