Skip to content

Commit

Permalink
feat(xo-server/authenticateUser): log failed attempts
Browse files Browse the repository at this point in the history
Related to zammad#16318
  • Loading branch information
julien-f authored and pdonias committed Sep 28, 2023
1 parent a1bd96d commit 73755e4
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [Host/Advanced] Display system disks health based on the _smartctl_ plugin. [#4458](https://github.com/vatesfr/xen-orchestra/issues/4458) (PR [#7060](https://github.com/vatesfr/xen-orchestra/pull/7060))
- [Authentication] Failed attempts are now logged as XO tasks (PR [#7061](https://github.com/vatesfr/xen-orchestra/pull/7061))

### Bug fixes

Expand Down
64 changes: 40 additions & 24 deletions packages/xo-server/src/xo-mixins/authentication.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { parseDuration } from '@vates/parse-duration'
import patch from '../patch.mjs'
import { Tokens } from '../models/token.mjs'
import { forEach, generateToken } from '../utils.mjs'
import { replace } from '../sensitive-values.mjs'

// ===================================================================

Expand Down Expand Up @@ -136,35 +137,50 @@ export default class {
}

async authenticateUser(credentials, userData) {
// don't even attempt to authenticate with empty password
const { password } = credentials
if (password === '') {
throw new Error('empty password')
}
const { tasks } = this._app
const task = await tasks.create({
type: 'xo:authentication:authenticateUser',
name: 'XO user authentication',
credentials: replace(credentials),
userData,
})

// TODO: remove when email has been replaced by username.
if (credentials.email) {
credentials.username = credentials.email
} else if (credentials.username) {
credentials.email = credentials.username
}
const result = await task.run(async () => {
// don't even attempt to authenticate with empty password
const { password } = credentials
if (password === '') {
throw new Error('empty password')
}

const failures = this._failures
// TODO: remove when email has been replaced by username.
if (credentials.email) {
credentials.username = credentials.email
} else if (credentials.username) {
credentials.email = credentials.username
}

const { username } = credentials
const now = Date.now()
let lastFailure
if (username && (lastFailure = failures[username]) && lastFailure + this._throttlingDelay > now) {
throw new Error('too fast authentication tries')
}
const failures = this._failures

const result = await this._authenticateUser(credentials, userData)
if (result === undefined) {
failures[username] = now
throw invalidCredentials()
}
const { username } = credentials
const now = Date.now()
let lastFailure
if (username && (lastFailure = failures[username]) && lastFailure + this._throttlingDelay > now) {
throw new Error('too fast authentication tries')
}

const result = await this._authenticateUser(credentials, userData)
if (result === undefined) {
failures[username] = now
throw invalidCredentials()
}

delete failures[username]
return result
})

// only keep trace of failed attempts
await tasks.deleteLog(task.id)

delete failures[username]
return result
}

Expand Down

0 comments on commit 73755e4

Please sign in to comment.