Repository navigation
Description
Description
Since 1.12.1, every OAuth2 (and presumably every other passport-based social/SSO) login fails with a 500 after the provider redirects back to /auth/oauth2/callback. Email/password login is unaffected. 1.12.0 works fine with the same config.
The cause is a circular import introduced in 3219964 ("fix(login): avoid email check using timing attack"), which added this to lib/models/user.js:
const { verifyPassword } = require('../web/auth/utils')This results in the following load order:
lib/models/index.jsstarts loading and requireslib/models/user.jsuser.jsnow requireslib/web/auth/utils.jsutils.jsrunsconst models = require('../../models')at module load, whilemodels/index.jsis still loading, so it receives the partial (empty) exports objectmodels/index.jsfinishes withmodule.exports = db, bututils.jskeeps the stale object
As a result, models.User is undefined in passportGeneralCallback, and the callback crashes on models.User.findOrCreate.
Steps to reproduce
- Run
quay.io/hedgedoc/hedgedoc:1.12.1with generic OAuth2 configured (Keycloak in my case) - Click "Sign in via OAuth2" and authenticate at the provider
- The callback returns HTTP 500
Expected behaviour
The user is logged in, as on 1.12.0.
Logs
With `NODE_OPTIONS=--trace-warnings`:
Nov 04 23:26:30 pad docker[26393]: 2026-11-04T22:26:30.308Z info: 84.151.78.234 - - [04/Nov/2026:22:26:30 +0000] "GET /auth/oauth2 HTTP/1.1" 302 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"
Nov 04 23:26:30 pad docker[26393]: (node:1) Warning: Accessing non-existent property 'User' of module exports inside circular dependency
Nov 04 23:26:30 pad docker[26393]: at emitCircularRequireWarning (node:internal/modules/cjs/loader:1044:11)
Nov 04 23:26:30 pad docker[26393]: at Object.get (node:internal/modules/cjs/loader:1060:5)
Nov 04 23:26:30 pad docker[26393]: at OAuth2CustomStrategy.callback [as _verify] (file:///hedgedoc/lib/web/auth/utils.js:12:10)
Nov 04 23:26:30 pad docker[26393]: at /hedgedoc/node_modules/passport-oauth2/lib/strategy.js:205:24
Nov 04 23:26:30 pad docker[26393]: at /hedgedoc/lib/web/auth/oauth2/index.js:131:5
Nov 04 23:26:30 pad docker[26393]: at passBackControl (/hedgedoc/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:134:9)
Nov 04 23:26:30 pad docker[26393]: at IncomingMessage.<anonymous> (/hedgedoc/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:157:7)
Nov 04 23:26:30 pad docker[26393]: at IncomingMessage.emit (node:events:526:24)
Nov 04 23:26:30 pad docker[26393]: at endReadableNT (node:internal/streams/readable:1783:12)
Nov 04 23:26:30 pad docker[26393]: at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
Nov 04 23:26:30 pad docker[26393]: TypeError: Cannot read properties of undefined (reading 'findOrCreate')
Nov 04 23:26:30 pad docker[26393]: at OAuth2CustomStrategy.callback [as _verify] (file:///hedgedoc/lib/web/auth/utils.js:12:15)
Nov 04 23:26:30 pad docker[26393]: at /hedgedoc/node_modules/passport-oauth2/lib/strategy.js:205:24
Nov 04 23:26:30 pad docker[26393]: at /hedgedoc/lib/web/auth/oauth2/index.js:131:5
Nov 04 23:26:30 pad docker[26393]: at passBackControl (/hedgedoc/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:134:9)
Nov 04 23:26:30 pad docker[26393]: at IncomingMessage.<anonymous> (/hedgedoc/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:157:7)
Nov 04 23:26:30 pad docker[26393]: at IncomingMessage.emit (node:events:526:24)
Nov 04 23:26:30 pad docker[26393]: at endReadableNT (node:internal/streams/readable:1783:12)
Nov 04 23:26:30 pad docker[26393]: at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
Nov 04 23:26:30 pad docker[26393]: 2026-11-04T22:26:30.575Z info: 84.151.78.234 - - [04/Nov/2026:22:26:30 +0000] "GET /auth/oauth2/callback?state=… HTTP/1.1" 500 148 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"Config
-e CMD_DB_URL=postgres://hedgedoc:xxx@database:5432/hedgedoc \
-e CMD_DOMAIN=xxx \
-e CMD_URL_ADDPORT=false \
-e CMD_PROTOCOL_USESSL=true \
-e CMD_OAUTH2_USER_PROFILE_URL=https://xxx/realms/xxx/protocol/openid-connect/userinfo -e CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=preferred_username -e CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR=name -e CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR=email -e CMD_OAUTH2_TOKEN_URL=https://xxx/realms/xxx/protocol/openid-connect/token -e CMD_OAUTH2_AUTHORIZATION_URL=https://xxx/realms/xxx/protocol/openid-connect/auth -e CMD_OAUTH2_CLIENT_ID=pad -e CMD_OAUTH2_CLIENT_SECRET=xxx -e CMD_OAUTH2_PROVIDERNAME="xxx" -e CMD_OAUTH2_SCOPE="openid email profile" -e CMD_EMAIL=false -e CMD_ALLOW_ANONYMOUS=false -e CMD_ALLOW_ANONYMOUS_EDITS=true -e CMD_SESSION_SECRET=xxx -e CMD_ENABLE_UPLOADS=registered \
-e NODE_OPTIONS=--trace-warningsYour Setup
- HedgeDoc version: 1.12.1 (official image
quay.io/hedgedoc/hedgedoc:1.12.1) - NodeJS version: the one shipped in the image
- Database: PostgreSQL
- Auth: generic OAuth2 (Keycloak)
Additional context
Possible fixes:
- Move
verifyPasswordinto a module that doesn't depend onmodels(e.g.lib/utils/password.js) and import it from bothlib/models/user.jsandlib/web/auth/email/index.js, or - Resolve
modelslazily inlib/web/auth/utils.js.
Workaround I'm using right now (bind-mounted patched utils.js), replacing line 7:
const models = new Proxy({}, { get: (_, prop) => require('../../models')[prop] })Rolling back to 1.12.0 also works, but that drops the security fixes in 1.12.1, so I'd rather not.
Activity