Implement OAuth2 server, link oauth:Client and auth::Oauth2Client, implement permission checks

This commit is contained in:
garrettmills
2020-05-16 23:55:08 -05:00
parent 6f621f5891
commit d558f21375
51 changed files with 2808 additions and 159 deletions

View File

@@ -1,8 +1,9 @@
const { Controller } = require('libflitter')
const zxcvbn = require('zxcvbn')
class AuthController extends Controller {
static get services() {
return [...super.services, 'models', 'auth', 'MFA', 'output', 'configs']
return [...super.services, 'models', 'auth', 'MFA', 'output', 'configs', 'utility']
}
async get_users(req, res, next) {
@@ -18,6 +19,20 @@ class AuthController extends Controller {
return res.api(data)
}
async get_groups(req, res, next) {
const Group = this.models.get('auth:Group')
const groups = await Group.find({active: true})
const data = []
for ( const group of groups ) {
if ( !req.user.can(`auth:group:${group.id}:view`) ) continue
data.push(await group.to_api())
}
return res.api(data)
}
async get_roles(req, res, next) {
const role_config = this.configs.get('auth.roles')
const data = []
@@ -32,6 +47,291 @@ class AuthController extends Controller {
return res.api(data)
}
async get_group(req, res, next) {
const Group = this.models.get('auth:Group')
const group = await Group.findById(req.params.id)
if ( !group || !group.active )
return res.status(404)
.message('Group not found with that ID.')
.api()
if ( !req.user.can(`auth:group:${group.id}:view`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
return res.api(await group.to_api())
}
async get_user(req, res, next) {
if ( req.params.id === 'me' )
return res.api(await req.user.to_api())
const User = this.models.get('auth:User')
const user = await User.findById(req.params.id)
if ( !user )
return res.status(404)
.message('User not found with that ID.')
.api()
if ( !req.user.can(`auth:user:${user.id}:view`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
return res.api(await user.to_api())
}
async create_group(req, res, next) {
if ( !req.user.can(`auth:group:create`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
if ( !req.body.name )
return res.status(400)
.message('Missing required field: name')
.api()
const Group = this.models.get('auth:Group')
// Make sure the name is free
const existing_group = await Group.findOne({ name: req.body.name })
if ( existing_group )
return res.status(400)
.message('A group with that name already exists.')
.api()
const group = new Group({ name: req.body.name })
// Validate user ids
const User = this.models.get('auth:User')
if ( req.body.user_ids ) {
const parsed = typeof req.body.user_ids === 'string' ? this.utility.infer(req.body.user_ids) : req.body.user_ids
const user_ids = Array.isArray(parsed) ? parsed : [parsed]
for ( const user_id of user_ids ) {
const user = await User.findById(user_id)
if ( !user )
return res.status(400)
.message('Invalid user_id.')
.api()
}
group.user_ids = user_ids
}
await group.save()
return res.api(await group.to_api())
}
async create_user(req, res, next) {
if ( !req.user.can('auth:user:create') )
return res.status(401)
.message('Insufficient permissions.')
.api()
const required_fields = ['uid', 'first_name', 'last_name', 'email', 'password']
for ( const field of required_fields ) {
if ( !req.body[field] )
return res.status(400)
.message(`Missing required field: ${field}`)
.api()
}
// Make sure uid & email are unique
const User = this.models.get('auth:User')
const unique_fields = ['uid', 'email']
for ( const field of unique_fields ) {
const filter = {}
filter[field] = req.body[field]
const existing_user = await User.findOne(filter)
if ( existing_user )
return res.status(400)
.message(`A user already exists with that ${field}`)
.api()
}
// Verify password complexity
const min_score = 3
const result = zxcvbn(req.body.password)
if ( result.score < min_score )
return res.status(400)
.message(`Password does not meet the minimum complexity score of ${min_score}.`)
.api()
const user = new User({
uid: req.body.uid,
email: req.body.email,
first_name: req.body.first_name,
last_name: req.body.last_name,
})
if ( req.body.tagline )
user.tagline = req.body.tagline
await user.reset_password(req.body.password, 'create')
await user.save()
return res.api(await user.to_api())
}
async update_group(req, res, next) {
const Group = this.models.get('auth:Group')
const User = this.models.get('auth:User')
const group = await Group.findById(req.params.id)
if ( !group )
return res.status(404)
.message('Group not found with that ID.')
.api()
if ( !req.user.can(`auth:group:${group.id}:update`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
if ( !req.body.name )
return res.status(400)
.message('Missing required field: name')
.api()
// Make sure the group name is unique
const existing_group = await Group.findOne({ name: req.body.name })
if ( existing_group && existing_group.id !== group.id )
return res.status(400)
.message('A group with that name already exists.')
.api()
// Validate user_ids
if ( req.body.user_ids ) {
const parsed = typeof req.body.user_ids === 'string' ? this.utility.infer(req.body.user_ids) : req.body.user_ids
const user_ids = Array.isArray(parsed) ? parsed : [parsed]
for ( const user_id of user_ids ) {
const user = await User.findById(user_id)
if ( !user )
return res.status(400)
.message('Invalid user_id.')
.api()
}
group.user_ids = user_ids
} else {
group.user_ids = []
}
group.name = req.body.name
await group.save()
return res.api()
}
async update_user(req, res, next) {
const User = this.models.get('auth:User')
const user = await User.findById(req.params.id)
if ( !user )
return res.status(404)
.message('User not found with that ID.')
.api()
if ( !req.user.can(`auth:user:${user.id}:update`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
const required_fields = ['uid', 'first_name', 'last_name', 'email']
for ( const field of required_fields ) {
if ( !req.body[field] )
return res.status(400)
.message(`Missing required field: ${field}`)
.api()
}
// Make sure the uid/email are unique
const unique_fields = ['uid', 'email']
for ( const field of unique_fields ) {
const filter = {}
filter[field] = req.body[field]
const existing_user = await User.findOne(filter)
if ( existing_user && existing_user.id !== user.id )
return res.status(400)
.message(`A user already exists with that ${field}`)
.api()
}
// Verify password complexity
if ( req.body.password ) {
const min_score = 3
const result = zxcvbn(req.body.password)
if (result.score < min_score)
return res.status(400)
.message(`Password does not meet the minimum complexity score of ${min_score}.`)
.api()
await user.reset_password(req.body.password, 'api')
}
user.first_name = req.body.first_name
user.last_name = req.body.last_name
user.uid = req.body.uid
user.email = req.body.email
if ( req.body.tagline )
user.tagline = req.body.tagline
else
user.tagline = ''
await user.save()
return res.api()
}
async delete_group(req, res, next) {
const Group = this.models.get('auth:Group')
const group = await Group.findById(req.params.id)
if ( !group )
return res.status(404)
.message('Group not found with that ID.')
.api()
if ( !req.user.can(`auth:group:${group.id}:delete`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
group.active = false
await group.save()
return res.api()
}
async delete_user(req, res, next) {
const User = this.models.get('auth:User')
const user = await User.findById(req.params.id)
if ( !user )
return res.status(404)
.message('User not found with that ID.')
.api()
if ( !req.user.can(`auth:user:${user.id}:delete`) )
return res.status(401)
.message('Insufficient permissions.')
.api()
// check if the user is an LDAP client. if so, delete the client
const Client = this.models.get('ldap:Client')
const matching_client = await Client.findOne({ user_id: user.id })
if ( matching_client ) {
matching_client.active = false
await matching_client.save()
}
user.active = false
await user.kickout()
await user.save()
return res.api()
}
async validate_username(req, res, next) {
let is_valid = true
@@ -73,6 +373,18 @@ class AuthController extends Controller {
success: false,
})
// Make sure the user can sign in.
// Sign-in is NOT allowed for LDAP clients
const Client = this.models.get('ldap:Client')
const client = await Client.findOne({ user_id: user.id })
if ( client )
return res.status(200)
.message(`Invalid username or password.`)
.api({
message: `Invalid username or password.`,
success: false,
})
if ( req.body.create_session )
await flitter.session(req, user)