2021-10-24 18:12:58 +00:00
|
|
|
const { Controller } = require('libflitter')
|
|
|
|
|
|
|
|
class RadiusController extends Controller {
|
|
|
|
static get services() {
|
|
|
|
return [...super.services, 'models', 'output']
|
|
|
|
}
|
|
|
|
|
|
|
|
async attempt(req, res, next) {
|
|
|
|
const User = this.models.get('auth:User')
|
|
|
|
const Client = this.models.get('radius:Client')
|
|
|
|
|
2021-11-22 15:08:22 +00:00
|
|
|
this.output.debug('RADIUS attempt:')
|
|
|
|
this.output.debug(req.body)
|
|
|
|
|
2021-10-24 18:12:58 +00:00
|
|
|
if ( !req.body.username || !req.body.password ) {
|
|
|
|
this.output.error('RADIUS error: missing username or password')
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
const parts = String(req.body.username).split('@')
|
|
|
|
parts.reverse()
|
|
|
|
|
|
|
|
const clientId = parts.shift()
|
|
|
|
parts.reverse()
|
|
|
|
|
|
|
|
const username = parts.join('@')
|
2021-11-22 15:08:22 +00:00
|
|
|
const password = String(req.body.password).replace(/\0/g, '')
|
|
|
|
|
|
|
|
this.output.debug(`clientId: ${clientId}, username: ${username}, password: ${password}`)
|
2021-10-24 18:12:58 +00:00
|
|
|
|
|
|
|
const user = await User.findOne({ uid: username, active: true })
|
|
|
|
if ( !user ) {
|
|
|
|
this.output.error(`RADIUS error: invalid username: ${username}`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
const client = await Client.findById(clientId)
|
|
|
|
if ( !client || !client.active ) {
|
|
|
|
this.output.error(`RADIUS error: invalid client: ${clientId}`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the credentials are an app_password
|
|
|
|
const app_password_verified = Array.isArray(user.app_passwords)
|
|
|
|
&& user.app_passwords.length > 0
|
|
|
|
&& await user.check_app_password(password)
|
|
|
|
|
|
|
|
// Check if the user has MFA enabled.
|
|
|
|
// If so, split the incoming password to fetch the MFA code
|
|
|
|
// e.g. normalPassword:123456
|
|
|
|
if ( !app_password_verified && user.mfa_enabled ) {
|
|
|
|
const parts = password.split(':')
|
|
|
|
const mfa_code = parts.pop()
|
|
|
|
const actual_password = parts.join(':')
|
|
|
|
|
|
|
|
// Check the credentials
|
|
|
|
if ( !(await user.check_password(actual_password)) ) {
|
|
|
|
this.output.debug(`RADIUS error: user w/ MFA provided invalid credentials`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, check the MFA code
|
|
|
|
if ( !user.mfa_token.verify(mfa_code) ) {
|
|
|
|
this.output.debug(`RADIUS error: user w/ MFA provided invalid MFA token`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not MFA, just check the credentials
|
|
|
|
} else if (!app_password_verified && !await user.check_password(password)) {
|
|
|
|
this.output.debug(`RADIUS error: user w/ simple auth provided invalid credentials`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the user has any login interrupt traps set
|
|
|
|
if ( user.trap ) {
|
|
|
|
this.output.error(`RADIUS error: user has trap: ${user.trap}`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the appropriate IAM policy if this SAML SP is associated with an App
|
|
|
|
// If the SAML service provider has no associated application, just allow it
|
|
|
|
const associated_app = await client.application()
|
|
|
|
if ( associated_app ) {
|
|
|
|
const Policy = this.models.get('iam:Policy')
|
|
|
|
const can_access = await Policy.check_user_access(user, associated_app.id)
|
|
|
|
if ( !can_access ) {
|
|
|
|
this.output.error(`RADIUS error: user denied IAM access`)
|
|
|
|
return this.fail(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.output.info(`Authenticated RADIUS user: ${user.uid} to IAM ${associated_app.name}`)
|
|
|
|
return res.api({ success: true })
|
|
|
|
}
|
|
|
|
|
|
|
|
fail(res) {
|
|
|
|
return res.status(401).api({ success: false })
|
|
|
|
}
|
|
|
|
|
|
|
|
async get_clients(req, res, next) {
|
|
|
|
const Client = this.models.get('radius:Client')
|
|
|
|
const clients = await Client.find({ active: true })
|
|
|
|
const data = []
|
|
|
|
|
|
|
|
for ( const client of clients ) {
|
|
|
|
if ( req.user.can(`radius:client:${client.id}:view`) ) {
|
|
|
|
data.push(await client.to_api())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.api(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
async get_client(req, res, next) {
|
|
|
|
const Client = this.models.get('radius:Client')
|
|
|
|
const client = await Client.findById(req.params.id)
|
|
|
|
|
|
|
|
if ( !client || !client.active )
|
|
|
|
return res.status(404)
|
|
|
|
.message(req.T('api.client_not_found'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
if ( !req.user.can(`radius:client:${client.id}:view`) )
|
|
|
|
return res.status(401)
|
|
|
|
.message(req.T('api.insufficient_permissions'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
return res.api(await client.to_api())
|
|
|
|
}
|
|
|
|
|
|
|
|
async create_client(req, res, next) {
|
|
|
|
if ( !req.user.can('radius:client:create') )
|
|
|
|
return res.status(401)
|
|
|
|
.message(req.T('api.insufficient_permissions'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
if ( !req.body.name )
|
|
|
|
return res.status(400)
|
|
|
|
.message(`${req.T('api.missing_field')} name`)
|
|
|
|
.api()
|
|
|
|
|
|
|
|
const Client = this.models.get('radius:Client')
|
|
|
|
const client = new Client({
|
|
|
|
name: req.body.name,
|
|
|
|
})
|
|
|
|
|
|
|
|
await client.save()
|
|
|
|
return res.api(await client.to_api())
|
|
|
|
}
|
|
|
|
|
|
|
|
async update_client(req, res, next) {
|
|
|
|
const Client = this.models.get('radius:Client')
|
|
|
|
const client = await Client.findById(req.params.id)
|
|
|
|
|
|
|
|
if ( !client || !client.active )
|
|
|
|
return res.status(404)
|
|
|
|
.message(req.T('api.client_not_found'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
if ( !req.user.can(`radius:client:${client.id}:update`) )
|
|
|
|
return res.status(401)
|
|
|
|
.message(req.T('api.insufficient_permissions'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
if ( !req.body.name )
|
|
|
|
return res.status(400)
|
|
|
|
.message(`${req.T('api.missing_field')} name`)
|
|
|
|
.api()
|
|
|
|
|
|
|
|
client.name = req.body.name
|
|
|
|
await client.save()
|
|
|
|
return res.api()
|
|
|
|
}
|
|
|
|
|
|
|
|
async delete_client(req, res, next) {
|
|
|
|
const Client = this.models.get('radius:Client')
|
|
|
|
const client = await Client.findById(req.params.id)
|
|
|
|
|
|
|
|
if ( !client || !client.active )
|
|
|
|
return res.status(404)
|
|
|
|
.message(req.T('api.client_not_found'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
if ( !req.user.can(`radius:client:${client.id}:delete`) )
|
|
|
|
return res.status(401)
|
|
|
|
.message(req.T('api.insufficient_permissions'))
|
|
|
|
.api()
|
|
|
|
|
|
|
|
client.active = false
|
|
|
|
await client.save()
|
|
|
|
return res.api()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = exports = RadiusController
|