Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
22 changes: 22 additions & 0 deletions config.sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ db:
db: wiki
ssl: false

# Optional - How often (in ms) to clean up expired sessions from the DB (default: 86400000 = 1 day)
# sessionCleanupInterval: 86400000

# Optional - How often (in ms) the /healthz endpoint checks the DB (default: 14400000 = 4 hours)
# healthCheckInterval: 14400000

# ---------------------------------------------------------------------
# Analytics
# ---------------------------------------------------------------------

# analytics:
# Optional - How long (in seconds) to cache analytics config (default: 86400 = 1 day)
# cacheTTL: 86400

# ---------------------------------------------------------------------
# Authentication
# ---------------------------------------------------------------------

# auth:
# Optional - How long (in seconds) to cache deserialized users (default: 14400 = 4 hours, 0 to disable)
# userCacheTTL: 14400

# Optional - PostgreSQL / MySQL / MariaDB only:
# -> Uncomment lines you need below and set `auto` to false
# -> Full list of accepted options: https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
Expand Down
8 changes: 8 additions & 0 deletions dev/build/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ db:
db: $(DB_NAME)
storage: $(DB_FILEPATH)
ssl: $(DB_SSL)
sessionCleanupInterval: $(DB_SESSION_CLEANUP_INTERVAL:86400000)
healthCheckInterval: $(DB_HEALTH_CHECK_INTERVAL:14400000)
pool:
min: $(DB_POOL_MIN:2)
ssl:
enabled: $(SSL_ACTIVE)
port: 3443
Expand All @@ -18,3 +22,7 @@ ssl:
logLevel: $(LOG_LEVEL:info)
logFormat: $(LOG_FORMAT:default)
ha: $(HA_ACTIVE)
analytics:
cacheTTL: $(ANALYTICS_CACHE_TTL:86400)
auth:
userCacheTTL: $(AUTH_USER_CACHE_TTL:14400)
7 changes: 6 additions & 1 deletion server/app/data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ defaults:
storage: ./db.sqlite
sslOptions:
auto: true
sessionCleanupInterval: 86400000
healthCheckInterval: 14400000
ssl:
enabled: false
pool:
min: 1
min: 2
bindIP: 0.0.0.0
logLevel: info
logFormat: default
Expand Down Expand Up @@ -61,6 +63,8 @@ defaults:
iconset: 'md'
darkMode: false
tocPosition: 'left'
analytics:
cacheTTL: 86400
auth:
autoLogin: false
enforce2FA: false
Expand All @@ -69,6 +73,7 @@ defaults:
audience: 'urn:wiki.js'
tokenExpiration: '30m'
tokenRenewal: '14d'
userCacheTTL: 14400
editShortcuts:
editFab: true
editMenuBar: false
Expand Down
10 changes: 0 additions & 10 deletions server/controllers/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,6 @@ router.get('/robots.txt', (req, res, next) => {
}
})

/**
* Health Endpoint
*/
router.get('/healthz', (req, res, next) => {
if (WIKI.models.knex.client.pool.numFree() < 1 && WIKI.models.knex.client.pool.numUsed() < 1) {
res.status(503).json({ ok: false }).end()
} else {
res.status(200).json({ ok: true }).end()
}
})

/**
* Administration
Expand Down
14 changes: 14 additions & 0 deletions server/core/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
groups: {},
validApiKeys: [],
revocationList: require('./cache').init(),
userCache: require('./cache').init(),

/**
* Initialize the authentication module
Expand All @@ -34,10 +35,20 @@ module.exports = {

passport.deserializeUser(async (id, done) => {
try {
const ttl = WIKI.config.auth.userCacheTTL
if (ttl > 0) {
const cached = this.userCache.get(id)
if (cached !== undefined) {
return done(null, cached)
}
}
const user = await WIKI.models.users.query().findById(id).withGraphFetched('groups').modifyGraph('groups', builder => {
builder.select('groups.id', 'permissions')
})
if (user) {
if (ttl > 0) {
this.userCache.set(id, user, ttl)
}
done(null, user)
} else {
done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
Expand Down Expand Up @@ -351,6 +362,9 @@ module.exports = {
const groupsArray = await WIKI.models.groups.query()
this.groups = _.keyBy(groupsArray, 'id')
WIKI.auth.guest.cacheExpiration = DateTime.utc().minus({ days: 1 })
// Cached deserialized users embed group permissions — drop them so
// group/permission changes take effect immediately, not after userCacheTTL.
this.userCache.flushAll()
},

/**
Expand Down
1 change: 1 addition & 0 deletions server/graph/resolvers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ module.exports = {
await WIKI.models.users.query().patch({ isActive: false }).findById(args.id)

WIKI.auth.revokeUserTokens({ id: args.id, kind: 'u' })
WIKI.auth.userCache.del(args.id)
WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' })

return {
Expand Down
28 changes: 27 additions & 1 deletion server/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,31 @@ module.exports = async () => {

app.use('/', ctrl.ssl)

// ----------------------------------------
// Health Endpoint (before Passport to avoid DB queries on healthcheck)
// ----------------------------------------

// A success is cached for the full interval (keeps the DB idle), but a failure
// is only cached briefly — /healthz is the ALB + ECS health check, and pinning
// one transient DB blip as 503 for the whole interval would cycle healthy tasks.
const HEALTH_FAIL_RECHECK_MS = 60 * 1000
let healthLastCheck = 0
let healthLastOk = false
app.get('/healthz', async (req, res) => {
const now = Date.now()
const maxAge = healthLastOk ? WIKI.config.db.healthCheckInterval : HEALTH_FAIL_RECHECK_MS
if (now - healthLastCheck > maxAge) {
try {
await WIKI.models.knex.raw('SELECT 1')
healthLastOk = true
} catch (err) {
healthLastOk = false
}
healthLastCheck = now
}
res.status(healthLastOk ? 200 : 503).json({ ok: healthLastOk }).end()
})

// ----------------------------------------
// Passport Authentication
// ----------------------------------------
Expand All @@ -81,7 +106,8 @@ module.exports = async () => {
resave: false,
saveUninitialized: false,
store: new KnexSessionStore({
knex: WIKI.models.knex
knex: WIKI.models.knex,
clearInterval: WIKI.config.db.sessionCleanupInterval
})
}))
app.use(WIKI.auth.passport.initialize())
Expand Down
2 changes: 1 addition & 1 deletion server/models/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ module.exports = class Analytics extends Model {
analyticsCode.bodyEnd += code.bodyEnd
}

await WIKI.cache.set('analytics', analyticsCode, 300)
await WIKI.cache.set('analytics', analyticsCode, WIKI.config.analytics.cacheTTL)

return analyticsCode
} catch (err) {
Expand Down
3 changes: 3 additions & 0 deletions server/models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,8 @@ module.exports = class User extends Model {
usrData.appearance = appearance
}
await WIKI.models.users.query().patch(usrData).findById(id)
// Drop the deserialize cache so group/permission changes apply immediately
WIKI.auth.userCache.del(id)
} else {
throw new WIKI.Error.UserNotFound()
}
Expand All @@ -752,6 +754,7 @@ module.exports = class User extends Model {

await WIKI.models.userKeys.query().delete().where('userId', id)
await WIKI.models.users.query().deleteById(id)
WIKI.auth.userCache.del(id)
} else {
throw new WIKI.Error.UserNotFound()
}
Expand Down