auth/flitter/FlitterProvider.js

/**
 * @module flitter-auth/flitter/FlitterProvider
 */

const Provider = require('../Provider')
const bcrypt = require('bcrypt')

/**
 * Local database authentication provider for Flitter.
 * @extends module:flitter-auth/Provider~Provider
 */
class FlitterProvider extends Provider {

    /**
     * Register a new user.
     * @param {string} username - name of the new user
     * @param {object} attrs - collection of key-value pairs to set in the model
     * @param {object} data - additional data to save in the model's JSON
     * @returns {Promise<module:flitter-auth/model/User~User>}
     */
    async register(username, attrs, data) {
        const User = this.User
        let user_data = {
            uid: username,
        }
        
        if ( data ) user_data.data = JSON.stringify(data)
        if ( attrs ) user_data = {...user_data, ...attrs}
        
        const user = new User(user_data)
        
        await user.save()
        return user
    }

    /**
     * Validate the form input to register a new user.
     * @param {object} form_data - form data to validate
     * @returns {Promise<Array<string>>} - collection of error messages. If array is empty, input is valid.
     */
    async validate_registration(form_data) {
        const errors = await super.validate_registration(form_data)
        const min_pw_length = this.config.min_password_length

        if ( form_data.username ){
            const match = await this.User.findOne({ provider: this.config.name, uid: form_data.username })
            if ( match ) errors.push('Username is already taken.')
        }

        if ( !Object.keys(form_data).includes('password') || !form_data.password ){
            errors.push('Password field is required.')
        }
        else if ( form_data.password.length < min_pw_length ){
            errors.push(`Password must be at least ${min_pw_length} characters long.`)
        }
        
        return errors
    }

    /**
     * From the form data, get the formatted arguments to be passed into the registration function.
     * Should create the username and {password} objects.
     * @param {object} form_data - the form data from the request
     * @returns {Promise<Array<*>>}
     */
    async get_registration_args(form_data){
        const username = form_data.username
        delete form_data.username
        
        return [
            username,
            {
                password: await bcrypt.hash(form_data.password, 10)
            }
        ]
    }

    /**
     * Attempt to authenticate a user with the provided credentials. If it succeeds, return their User object.
     * @param {string} username
     * @param {string} password
     * @param [args] - not required
     * @returns {Promise<boolean|module:flitter-auth/model/User~User>} - false if the auth is unsuccessful, a User instance if it is
     */
    async login(username, password, args = {}){
        const User = this.User
        const user = await User.lookup({ uid: username, provider: this.config.name })

        if ( !user ) return false
        
        const success = await this.check_user_auth(user, password)
        return success ? user : false
    }

    /**
     * Check the validity of the provided credentials.
     * @param {string} user
     * @param {string} password
     * @returns {Promise<boolean>} - true if the credentials succeed, false otherwise
     */
    async check_user_auth(user, password){
        return await bcrypt.compare(password, user.password)
    }

}

module.exports = exports = FlitterProvider