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') this.output.debug('RADIUS attempt:') this.output.debug(req.body) 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('@') const password = String(req.body.password).replace(/\0/g, '') this.output.debug(`clientId: ${clientId}, username: ${username}, password: ${password}`) 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