Source: lib/general_auth_session_lite.js


const AppLifeCycle = require("./general_lifecyle")
const LocalSessionTokens = require("session_tokens").optimal   // session token management has been isolated
const { 
    TransitionObject, 
    LoginTransitionObject, 
    LogoutTransitionObject, 
    RegistrationTransitionObject, 
    ProcessTransitionObject
} = require("state-tokens")


const SessionTokenManager = require("./session_token_manager")

/**
 * Provides a subset of methods required for authorization and authorized transition processing.
 * Makes use of established sessions, transition tokens in order to set actual transition operations into action.
 * 
 * Not to forget that this is a client facing class. The purpose of this class is to provide the basic methods (abstractly in most cases)
 * for the management of authorization and allowing access to authorized processes. This class provide guards, matching, and calls out to 
 * backend (e.g. transitions engine) processes such as finalization of the state transition, queries as to the feasibility of
 * state transitions, and setting up transitions or providing access to assets, either static or computed (dynamic).
 * 
 * As this is client facing, access is provided to the middleware and the web application class instance.
 * 
 * This class has access to the business class (which may be useful in some applications) and to the transition engine.
 * Most applications will have some specialization of the transition engine. The transition engine will most likely be called upon
 * during transition finalization, but may be use to prepare for the transition as well.
 * 
 * For the most part, the class manages the lifecycle of a transition object. The associated transition token may be of use, but
 * the token is managed by the contractual classes utilized by the class CopiousTransitions found in `user_service_class.js`.
 * 
 * NOTE: all the base classes in /lib return the class and do not return an instance. Explore the applications and 
 * the reader will find that the descendant modules export instances. The classes provided by copious-transitions must
 * be extended by an application.
 * 
 * @memberof base
 */
class SessionManager_Lite extends SessionTokenManager {

    //
    constructor(exp_app,db_obj,business,transition_engine,tokenStorage) {  // reference to the DB and initializes the a middleware vector
        
        let conf = exp_app._extract_conf()

        super(conf,db_obj,tokenStorage)

        //
        this.app = exp_app          // not used generally,, but will be available to applications
        this.middle_ware = null
        if ( conf ) {
            this.middle_ware = conf.middleware["session"]
        }
        if ( this.middle_ware === undefined ) {
            this.middle_ware = []
        }
        this.conf = conf
        //
        this.business = business
        this.trans_engine = transition_engine
        //
        this.user_cookie = conf.user_cookie
        this.max_age_user_cookie = 90000
        this.require_secure_transfer = false
        //
        if ( conf ) {
            if ( conf.use_secure_transfer ) {
                this.require_secure_transfer = true
            }
        }
    }

    
    /**
     * The transition type is passed in as a parameter to the 
     * transition object constructor.
     * 
     * The subtype parameter is used to identify the class of the constructor.
     * Take note that just a few types have their own classes. These are mostly for session management transitions.
     * 
     * The `true transition` subtype is for actual state machine or Petri net travel occuring in generalized transition engines.
     * All others are application specific with subtypes developed separately for each process.
     * 
     * @param {string} t_type 
     * @param {string} sub_type 
     * @returns {object}  - the transition object
     */
    create_transition_record(t_type,sub_type) {
        let tor = false
        if ( sub_type ) {
            switch ( sub_type ) {
                case 'login' : {
                    tor = new LoginTransitionObject(t_type)
                    break
                }
                case 'logout' : {
                    tor = new LogoutTransitionObject(t_type)
                    break                    
                }
                case 'register' : {
                    tor = new RegistrationTransitionObject(t_type)
                    break;
                }
                case 'true-transition': {
                    tor = new ProcessTransitionObject(t_type)
                    break;
                }
                default : {
                    tor = new TransitionObject(t_type)
                }
            }
        } else {
            tor = new TransitionObject(t_type)
        }
        return tor
    }

    /**
     * 
     * @param {object} user - a DB record representing a user
     * @param {object} info - application specific information that may be used with the user data to extract information.
     * @returns - returns some user information -- default is the name
     */
    extract_exposable_user_info(user,info) {
        return(user.name)
    }

    //
    /**
     * A method that returns a wrapped key after generating it.
     * This method is left as abstract. 
     * 
     * @param {object} wrapper_public_key  - a wrapper key
     * @returns {string} - an empty string by default - the application sould override this method.
     */
    async gen_wrapped_key(wrapper_public_key) {  // generate a wrapped aes key...
        return ""  // let descendants implement
    }

    /**
     * Override this method to use the application specific key format and run 
     * the cipher picked by the application. 
     * 
     * The method is left abstract. It does not place any condition on the kind of cipher nor the format in which the cipher is passed.
     * 
     * @param {string} clear_text 
     * @param {object} aes_key - an AES key (or other) in a format that may be consumed by descendant library users 
     * @returns {string} - the clear (deciphered) text
     */
    async cipher(clear_text,aes_key) {
        return clear_text               // let descendants implement
    }

    //
    /**
     * This method intercepts requests that require processing, including dynamically generated assests and 
     * state transitions.
     * 
     * The guard method at a minimum will check to see if the CopiousTransitions innstance is checking for https
     * and provide the check if it is. 
     * 
     * There may be applications that provide a complex and calculated checks. But, in most cases, the checks wil be rapid
     * and sufficient for the request to be accepted or rejected in a timely manner for the client.
     * 
     * Some applications may want to provide use of an ACL subprocess at this point.
     * 
     * @param {string} asset - idenitifies the asset that is being accessed.
     * @param {object} body - This is the request object sent by the client
     * @param {object} req - This is request object derived from the HTTP header
     * @returns {boolean} - true allows passage
     */
    async guard(asset,body,req) {
        if ( this.require_secure_transfer ) {
            if ( req.protocol === "https" ) {
                return true
            }
            return false
        }
        return(true)    // true by default
    }



    /**
     * If a client request has access to a transition (state machine, Petri net), then one more kind of check will
     * be employed. In this case, the transition is checked for feasibility; that is, the transition request may be limited in
     * resoures it may use or it may not be supported computationally by the host machine.
     * 
     * Many types of applications may check feasibility in particular ways. For example, one application may check on rate limiting
     * for certain transitions. Another, might check a balance. A fairly general check might be that a requested state transition targets
     * an existing final state. One use already being used is to check if a signature can be verified with server side session data.
     * 
     * @param {string} transition 
     * @param {object} post_body - This is the request object sent by the client
     * @param {object} req - This is request object derived from the HTTP header 
     * @returns {boolean} - true indicates feasibility
     */
    feasible(transition,post_body,req) {            // examine the session state to see if the transition can take place
        return(false)
    }



    /**
     * This guard, the static guard, is similar to the more general `guard` method. But, some implementations may be paired down
     * considerably knowing that the asset being accessed will be some static file. In particular, the file might have been preloaded.
     * Rate limiting might also be checked in the guard.
     * 
     * Usually, this method will access the static assets module to make queries about assets.
     * 
     * @param {string} asset 
     * @param {object} body 
     * @param {object} req 
     * @returns {boolean} - true allows passage
     */
    async guard_static(asset,body,req) {
        return(true)
    }

    /**
     * Decodes fields from URI encoded to regular text
     * @param {object} udata 
     * @returns {object} - the updated (decoded fields) of the user data object passed
     */
    post_body_decode(udata) {
        for ( let key in udata ) {
            let field = udata[key]
            if ( field ) {
                field = decodeURIComponent(field)
                udata[key] = field.trim()
            }
        }
        return(udata)
    }


    /**
     * This is a key that may be set by the application. Generic code will not know the name of a field in a DB
     * nor in objects received from the client. An application can override this method and let the generic methods operated with ti.
     * @returns {string} - a field name for accessing keys to user data
     */
    key_for_user() {    // override this for tracking the user across of few user transitions
        return('_id')
    }

    //
    /**
     * The match method is employed during a secondary transition phase of a transition or in secondary user processing (access management).
     * 
     * The default case is for the client to post a body with a field `_t_match_field`. This field is compared to a `match` field stored 
     * in the elements sub-object of a chached transition object, one that has been created during the first phase of transition 
     * processing.
     * 
     * @param {object} post_body 
     * @param {object} transtion_object 
     * @returns {boolean} - true for successful matching
     */
    match(post_body,transtion_object)  {
        if ( post_body._t_match_field ) {
            let t_match = transtion_object.elements.match;
            if ( t_match === post_body._t_match_field ) {
                return true
            }
        }
        return false
    }


    //
    /**
     * This method generates a transition object for tracking the access to a guarded asset of some time.
     * The transition object generated is not applied to a transition.
     * 
     * @param {string} asset_id 
     * @param {object} post_body 
     * @returns {object} - the transition object required to continue accessing the mime type asset
     */
    process_asset(asset_id,post_body) {
        let token = this.generate_transition_token(asset_id)
        let transition_object = this.create_transition_record('static_asset')
        transition_object.set_token(token)
        return(transition_object)
    }
    
    //
    /**
     * This method generates a transition object for a kind of transition. 
     * The transition object remains available for the duration fo the transition.
     * There are two types of transition processes, one, those which work with one request from the client
     * and two, those that work with more than one request from the client, one requiring secondary action. 
     * 
     * When an application wishes to use a secondary action, it must set the `secondary_action` in the transition object.
     * 
     * 
     * @param {string} transition 
     * @param {object} post_body 
     * @param {object} req 
     * @returns {object} - the transition object require to continue processing the transition step
     */
    process_transition(transition,post_body,req) {  // req for any session cookies, etc.
        //
        let token = this.generate_transition_token(post_body._token_prefix)
        let transition_object = this.create_transition_record(transition,'true-transition')
        transition_object.set_token(token)
        return(transtion_object)
    }

    /**
     * Primary transitions that do not require seconday responses from the client and secondary transition processing may 
     * determine that a transition may be finalized. That is, a transition may be recorded as having completed allowing
     * for the process to settle into a new state.
     * 
     * This method returns an object that indicates the state of the machine. The entirety of this object will be passed on 
     * to the client.
     * 
     * @param {string} transition 
     * @param {object} post_body 
     * @param {object} elements 
     * @param {object} req 
     * @returns {object} data destined to the client and with a report as to the state of the machine relative to the client
     */
    finalize_transition(transition,post_body,elements,req)  {
        let finalization_state = {
            "state" : "UP",
            "OK" : "true"
        }    
        return(finalization_state)   // finalization state more likely some objecg
    }

    // --
    /**
     * Implementations of this method will keep track of errors accrued while processing a transition.
     * 
     * 
     * @param {*} category 
     * @param {*} data 
     * @param {*} err 
     */
    session_accrue_errors(category,data,err) {}

    /**
     * Often called by `finalize_transition`. This is an abstract placeholder for applications that choose to implement it. 
     * It is provided allow clients to set session variables and to figure a status to return from `finalize_transition`.
     * 
     * This method suggests a set of parameter to such an update call. But, it does not indicate a way that it should be called by 
     * `finalize_transition`.
     * 
     * @param {string} transition 
     * @param {object} post_body 
     * @param {object} req 
     * @returns {boolean} - true for successful update of the session state
     */
    update_session_state(transition,post_body,req) {    // req for session cookies if any
        return true
    }

    /**
     * These method allow for implementations to manage cookies when dealing with browsers
     * @param {object} res 
     * @param {string} cookie_id 
     * @param {object} value 
     * @param {Number} age 
     */
    set_cookie(res,cookie_id,value,age) {
        // application overried 
    }

    /**
     * These method allow for implementations to manage cookies when dealing with browsers
     * @param {object} res 
     * @param {string} cookie_id 
     */
    release_cookie(res,cookie_id) {}

    /**
     * 
     * These method allow for implementations to manage cookies when dealing with browsers
     * @param {object} req 
     * @param {string} session_token 
     */
    app_user_check_cookie(req,session_token) {/* application only */}

    /**
     * 
     * These method allow for implementations to manage cookies when dealing with browsers
     * @param {object} result 
     * @param {object} res 
     * @param {object} transitionObj 
     */
    handle_cookies(result,res,transitionObj) {/* application only */}

}


/** 
 * Provides an interface to the top level transition prrocesing and module initialization.
 * Takes in referneces to the database, web app handlers, the transition engine and optionally 
 * a custom token storage class.
 * 
 * 
 * @memberof base
 */
class GeneralAuth extends AppLifeCycle {

    constructor(sessClass,tokenStorageClass) {
        super()
        //
        this.db = null
        this.trans_engine = null
        this.sessionClass = sessClass ? sessClass : SessionManager_Lite
        this.tokenStorageClass = tokenStorageClass ? tokenStorageClass : LocalSessionTokens
        if ( tokenStorageClass !== undefined ) {
            if ( (typeof tokenStorageClass.token_maker) === 'function' ) {
                _l_token_maker = token_maker
            }
        }
    }

    sessions(exp_app,db_obj,bussiness,transition_engine) {
        let sess_m = new this.sessionClass(exp_app,db_obj,bussiness,transition_engine,this.tokenStorageClass);
        this.db = db_obj
        return(sess_m)
    }
}



module.exports = SessionManager_Lite
module.exports.SessionManager_Lite = SessionManager_Lite
module.exports.GeneralAuth = GeneralAuth