auth/Provider.js

/**
 * @module flitter-auth/Provider
 */
const ImplementationError = require('libflitter/errors/ImplementationError')
const { Injectable } = require('flitter-di')
const helpers = require('./Helpers')

/**
 * Base class for all Flitter auth providers.
 * @extends module:flitter-di/src/Injectable~Injectable
 */
class Provider extends Injectable {
    /**
     * Defines the services required by this provider.
     * Includes the 'app', 'auth', and 'models' services by default.
     * @returns {Array<string>}
     */
    static get services() {
        return [...super.services, 'app', 'auth', 'models']
    }

    /**
     * Instantiates the provider class.
     * @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
     * @param {object} config - the config for this provider
     */
    constructor(app, config){
        super()

        /**
         * The Flitter app.
         * @type {module:libflitter/app/FlitterApp~FlitterApp}
         */
        this.app = app

        /**
         * The provider's config.
         * @type {Object}
         */
        this.config = config

        /**
         * The User model's class.
         * @type {module:flitter-auth/model/User~User}
         */
        this.User = this.models.get('auth:User')
    }

    /**
     * Register a new user into whatever system the provider manages.
     * Should also create a valid User document.
     * @param {string} username - The UID of the user. Should be unique to Flitter.
     * @param {object} [attrs] - optionally, other model attributes to be merged in
     * @param {object} [data] - optionally, JSON data to be stored in the model
     * @return {Promise<User>} - should return a saved user model instance
     */
    async register(username, attrs, data){
        throw new ImplementationError('Providers must implement the register() method.')
    }

    /**
     * Ensure that registration form_data is valid. Checks for password and unique username.
     * @param {object} form_data
     * @returns {Promise<Array<string>>} - array of string errors. If empty array, no errors.
     */
    async validate_registration(form_data){
        const errors = []
        
        if ( !Object.keys(form_data).includes('username') || !form_data.username ){
            errors.push('Username field is required.')
        }
        
        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 {{username: string, password: string}} form_data
     * @returns {Promise<Array<*>>}
     */
    async get_registration_args(form_data){
        return [
            form_data.username,
            {
                password: form_data.password,
            }
        ]
    }

    /**
     * Get the Flitter canonical name of the registration view.
     * @returns {string}
     */
    registration_view(){
        return 'auth:register'
    }

    /**
     * Get the Flitter canonical name of the login view.
     * @returns {string}
     */
    login_view(){
        return 'auth:login'
    }

    /**
     * Handle a request to get the login view. By default, shows the view specified
     * by this.registration_view(), passing it title, heading_text, provider_name
     * @param {express/Request} req
     * @param {express/Response} res
     * @param {function} next
     * @returns {Promise<*>}
     */
    handle_login_get(req, res, next) {
        return res.page(this.login_view(), {
            title: 'Login',
            heading_text: 'Hi, there. Login to continue:',
            registration_enabled: req.auth_provider.config.registration,
            provider_name: req.auth_provider.config.name,
        })
    }

    /**
     * Handle a request to get the registration view. By default, shows the view specified
     * by this.registration_view(), passing it title, heading_text, provider_name
     * @param {express/Request} req
     * @param {express/Response} res
     * @param {function} next
     * @returns {Promise<*>}
     */
    handle_register_get(req, res, next) {
        return res.page(this.registration_view(), {
            title: 'Register',
            heading_text: 'Hi, there. Register to continue:',
            provider_name: req.auth_provider.config.name,
        })
    }

    /**
     * Get the Flitter canonical name of the logout view.
     * @returns {string}
     */
    logout_view(){
        return 'auth:form_page'
    }

    /**
     * Ensure that login form_data is valid. Checks for username.
     * @param {object} form_data
     * @returns {Promise<Array<string>>} - array of string errors. If empty array, no errors.
     */
    async validate_login(form_data){
        const errors = []
        
        if ( !Object.keys(form_data).includes('username') || !form_data.username ){
            errors.push('Username field is required.')
        }
        
        return errors
    }

    /**
     * From the form data, get the formatted arguments to be passed into the login function.
     * Should create the username and password params.
     * @param {{username: string, password: string}} form_data
     * @returns {Promise<Array<string>>}
     */
    async get_login_args(form_data){
        return [
            form_data.username,
            form_data.password,
        ]
    }

    /**
     * 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){
        throw new ImplementationError()
    }

    /**
     * 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){
        throw new ImplementationError()
    }

    /**
     * Bootstrap the session. Sets auth.user and auth.user_id.
     * @param {express/Request} request
     * @param {module:flitter-auth/model/User~User} user - the authenticated user
     * @returns {Promise<void>}
     */
    async session(request, user){
        if ( !request.session.auth ) request.session.auth = {}
        // request.session.auth.user = user
        request.user = user
        request.is_auth = true
        request.session.auth.user_id = user.id
    }

    /**
     * Log out the current user, if there is one, and clean the auth session.
     * @param {express/Request} request
     * @returns {Promise<object>} - the clean session
     */
    async logout(request){
        delete request.session.auth
        return helpers.guarantee_auth_session(request)
    }

    /**
     * Clean up resources used by this provider.
     * @param {module:libflitter/app/FlitterApp~FlitterApp} app - the current app
     * @returns {Promise<void>}
     */
    async cleanup(app){}
    
}

module.exports = exports = Provider