lib/j1_app/j1_auth_manager/auth_manager.rb



# RuboCops - Documentation
# ------------------------------------------------------------------------------
# See: https://rubocop.readthedocs.io/en/latest/

# RuboCops - Disabled Cops
# ------------------------------------------------------------------------------
# rubocop:disable Metrics/BlockLength
# rubocop:disable Metrics/ClassLength
# rubocop:disable Metrics/LineLength
# rubocop:disable Style/StringLiterals
# rubocop:disable Style/Documentation
# rubocop:disable Metrics/BlockNesting
# rubocop:disable Layout/ClosingParenthesisIndentation
# rubocop:disable Layout/LeadingCommentSpace
# rubocop:disable Layout/EmptyLines
# rubocop:disable Layout/EmptyLinesAroundBlockBody
# rubocop:disable Layout/FirstParameterIndentation
# rubocop:disable Layout/CommentIndentation
# rubocop:disable Layout/AlignParameters
# rubocop:disable Layout/AlignHash
# rubocop:disable Layout/TrailingWhitespace
# rubocop:disable Layout/IndentHash
# rubocop:disable Layout/SpaceAroundOperators
# rubocop:disable Layout/ExtraSpacing
# rubocop:disable Style/UnlessElse
# rubocop:disable Style/HashSyntax
#
# rubocop:disable RubyLocalVariableNamingConvention

# ------------------------------------------------------------------------------
#  ~/lib/j1_auth_manager/auth_manager/.rb
#
#  Provides authentication services based on Warden|OmniAuth
#
#  Product/Info:
#  https://jekyll.one
#
#  Copyright (C) 2021 Juergen Adams
#
#  J1 Template is licensed under the MIT License.
#  See: https://github.com/jekyll-one-org/J1 Template/blob/master/LICENSE
#
# ------------------------------------------------------------------------------
# NOTES
#
# ------------------------------------------------------------------------------
# frozen_string_literal: true

module J1App
  class AuthManager < Sinatra::Base

    include J1App::Helpers
    include J1App::GithubHelpers

    # ==========================================================================
    # SET j1 project home folder
    # ==========================================================================
    # set project path (preferred set by ENV var J1_PROJECT_HOME )
    #
    project_path = J1App.project_path

    # ==========================================================================
    # CHECK|CREATE project log folder
    # ==========================================================================
    #
  	unless Dir.exist?(File.join(project_path, 'log'))
      FileUtils.mkdir_p(File.join(project_path, 'log/archived'))
    end

    # ==========================================================================
    # Initialize middleware resources
    # test data
    #   disqus_client_id = J1App.oauth_data['disqus']['client_id']
    # ==========================================================================
    oauth_data = J1App.oauth_data

    # ==========================================================================
    # SETUP (log4r) logger
    # ==========================================================================
    log4r_cfg = YamlConfigurator
    log4r_cfg.load_yaml_file(project_path + '/_data/modules/log4r.yml')

    # ==========================================================================
    # ??????????????????
    # ==========================================================================
    oauth_requester = ''

    # ==========================================================================
    # Initialize J1 logger settings
    # ==========================================================================
#   uuid = UUID.new.generate
#   page_id = uuid[25, 37]

    chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].map(&:to_a).flatten
    page_id = (0...11).map { chars[rand(chars.length)] }.join

    MDC.put('pageID', page_id)
    MDC.put('path', '/')

    j1_mw                             = Log4r::Logger['j1.mw']
    j1_mw_auth_mgr                    = Log4r::Logger['j1.mw.auth_mgr']
    j1_mw_auth_mgr_auth               = Log4r::Logger['j1.mw.auth_mgr.auth']
    j1_mw_auth_mgr_preflight          = Log4r::Logger['j1.mw.auth_mgr.preflight']
    j1_mw_auth_mgr_api_auth           = Log4r::Logger['j1.mw.auth_mgr.api.auth']
    j1_mw_auth_mgr_api_post_auth      = Log4r::Logger['j1.mw.auth_mgr.api.post_auth']
    j1_mw_auth_mgr_api_state          = Log4r::Logger['j1.mw.auth_mgr.api.state']
    j1_mw_auth_mgr_api_log2disk       = Log4r::Logger['j1.mw.auth_mgr.api.log2disk']
    j1_mw_auth_mgr_api_validate       = Log4r::Logger['j1.mw.auth_mgr.api.validate']

    j1_mw_auth_mgr.info 'middleware is being initialized'
    j1_mw_auth_mgr.info 'state: stated'
    j1_mw_auth_mgr.info "project path set to: #{project_path}"

    # ==========================================================================
    # Base App and Warden Framework settings
    # ==========================================================================
    j1_mw_auth_mgr.info 'setup base data'

    provider_site_url_default     = J1App.user_settings['provider_site_url']
    provider_home_url_default     = J1App.user_settings['provider_home_url']
    provider_blog_url_default     = J1App.user_settings['provider_blog_url']
    provider_member_url_default   = J1App.user_settings['provider_member_url']
    provider_privacy_url_default  = J1App.user_settings['provider_privacy_url']

    app_session_data  = {}
    user_session_data = {
        :authenticated        => 'false',
        :requested_page       => '/',
        :user_name            => 'visitor',
        :users_allowed        => 'all',
        :user_id              => 'unknown',
        :provider             => 'j1',
        :provider_membership  => 'guest',
        :provider_site_url    => "#{provider_site_url_default}",
        :provider_home_url    => "#{provider_home_url_default}",
        :provider_blog_url    => "#{provider_blog_url_default}",
        :provider_member_url  => "#{provider_member_url_default}",
        :provider_privacy_url => "#{provider_privacy_url_default}",
        :payment_info         => 'unknown',
        :provider_permissions => ['public'],
        :creator              => 'middleware',
        :writer               => 'middleware',
        :mode                 => 'app'
    }

    # Enable SSL for the rack session if configured
    # --------------------------------------------------------------------------
    require 'rack-ssl-enforcer' if J1App.ssl?
    j1_mw_auth_mgr.info 'enforce ssl encryption' if J1App.ssl?
    use Rack::SslEnforcer if J1App.ssl?

    # Set the session cookie used by Rack to track all relevant data
    # for the authentication service
    # --------------------------------------------------------------------------
    j1_mw_auth_mgr.info 'initialize web session cookie'
    use Rack::Session::Cookie,
        http_only: true,
        key: 'j1.app.session',
        same_site: 'Strict',
        secret: ENV['J1_SESSION_SECRET'] || SecureRandom.hex

    # use Rack::Cache do |config|
    #   #
    #   # ----------------------------------------------------------------------
    #   config.middleware.delete(Rack::Cache)
    # end

    # ==========================================================================
    # Warden Framework initialisation
    # ==========================================================================
    j1_mw_auth_mgr.info 'initialize web session (warden)'

    # Define what (user) data should be put (serialized) into the session
    # on requests and responses from Rack environment into the warden
    # environment (env['warden']).
    # --------------------------------------------------------------------------
    Warden::Manager.serialize_into_session do |user|
      user
    end
    Warden::Manager.serialize_from_session do |user|
      user
    end

    # ==========================================================================
    # Middleware that protects against typical web attacks like CSRF and others.
    # Use of all protections provided. For details, see:
    # https://github.com/sinatra/sinatra/tree/master/rack-protection
    # ==========================================================================
    j1_mw_auth_mgr.info 'initialize web attack protection strategies'
    use Rack::Protection

    # ==========================================================================
    # OmniAuth|Warden Framework initialisation
    # ==========================================================================

    # Set the 'default' authentication strategy and exception handler
    # (for warden) if the user was not explicitly signed in (signin dialog).
    # If 'signin' fails, the default exception 'signin_failure' is thrown
    # (used for all OmniAuth strategies registered).
    # --------------------------------------------------------------------------
    j1_mw_auth_mgr.info "initialize default authentication strategy as:  omni_#{J1App.default_provider}"
    signin_failure = ->(_e) { Rack::Response.new("Can't login", 401).finish }
    use Warden::Manager do |config|
      # OmniAuth strategies are name-spaced by 'omni' (see: warden_omniauth.rb)
      # ------------------------------------------------------------------------
      config.default_strategies :"omni_#{J1App.default_provider}"
      config.failure_app = signin_failure
    end

    j1_mw_auth_mgr.info 'detect and initialize configured authentication strategies (omniauth)'
    j1_mw_auth_mgr.info 'initialize oauth authentication strategy:  patreon' if J1App.active_providers.include? 'patreon'
    j1_mw_auth_mgr.info 'initialize oauth authentication strategy:  disqus' if J1App.active_providers.include? 'disqus'
    j1_mw_auth_mgr.info 'initialize oauth authentication strategy:  facebook' if J1App.active_providers.include? 'facebook'
    j1_mw_auth_mgr.info 'initialize oauth authentication strategy:  github' if J1App.active_providers.include? 'github'
    j1_mw_auth_mgr.info 'initialize oauth authentication strategy:  twitter' if J1App.active_providers.include? 'twitter'

    use OmniAuth::Builder do |config|
      # Rescue OmniAuth::Strategies::OAuth2::CallbackError
      # ------------------------------------------------------------------------
      config.on_failure do
        new_path = '/redirect_on_failure'
        Rack::Response.new(['302 Moved'], 302, 'Location' => new_path).finish
      end

      # Detect and set supported authentication strategies for OmniAuth
      # ------------------------------------------------------------------------

      # Additional (strategy) option skip_extra, default: true
      #
      # If true, skips the collection of raw data (extra) to NOT blow
      # up the session cookie (as it is limited to 4K)
      skip_extra = true

      if J1App.active_providers.include? 'disqus'
        scope             = J1App.auth_config['providers']['disqus']['scope'].join(',')
        data_collection   = J1App.auth_config['providers']['disqus']['data_fields'].join(',')
        skip_extra        = false if data_collection =~ /raw/i
        provider :disqus,
                 J1App.oauth_data['disqus']['client_id'],
                 J1App.oauth_data['disqus']['client_secret'],
                 scope: "#{scope}",
                 skip_extra: skip_extra
      end

      if J1App.active_providers.include? 'github'
        scope             = J1App.auth_config['providers']['github']['scope'].join(',')
        data_collection   = J1App.auth_config['providers']['github']['data_fields'].join(',')
        skip_extra        = false if data_collection =~ /raw/i
        provider :github,
                 J1App.oauth_data['github']['client_id'],
                 J1App.oauth_data['github']['client_secret'],
                 scope: "#{scope}",
                 skip_extra: skip_extra
      end

      if J1App.active_providers.include? 'patreon'
        scope             = J1App.auth_config['providers']['patreon']['scope'].join(',')
        data_collection   = J1App.auth_config['providers']['patreon']['data_fields'].join(',')
        skip_extra        = false if data_collection =~ /raw/i
        provider :patreon,
                 J1App.oauth_data['patreon']['client_id'],
                 J1App.oauth_data['patreon']['client_secret'],
                 scope: "#{scope}",
                 skip_extra: skip_extra
      end

      # if J1App.active_providers.include? 'facebook'
      #   scope             = J1App.auth_config['providers']['facebook']['scope'].join(',')
      #   data_collection   = J1App.auth_config['providers']['facebook']['data_fields'].join(',')
      #   skip_extra        = false if data_collection =~ /raw/i
      #   provider :facebook,
      #         J1App.oauth_data['facebook']['client_id'],
      #         J1App.oauth_data['facebook']['client_secret'],
      #            scope: "#{scope}",
      #            skip_extra: skip_extra
      # end

      # if J1App.active_providers.include? 'twitter'
      #   scope             = J1App.auth_config['providers']['twitter']['scope'].join(',')
      #   data_collection   = J1App.auth_config['providers']['twitter']['data_fields'].join(',')
      #   skip_extra        = false if data_collection =~ /raw/i
      #   provider :twitter,
      #         J1App.oauth_data['twitter']['client_id'],
      #         J1App.oauth_data['twitter']['client_secret'],
      #            scope: "#{scope}",
      #            skip_extra: skip_extra
      # end

    end

    j1_mw_auth_mgr.info 'register oauth authentication callback on: /post_authentication'
    # Set the (internal) endpoint if a user is successfully authenticated
    # --------------------------------------------------------------------------
    use J1WardenOmniAuth do |config|
      config.redirect_after_callback = '/post_authentication?verify=oauth_callback'
    end

    # Add the internal logger from Rack to the middleware's of the stack
    # --------------------------------------------------------------------------
    j1_mw_auth_mgr.info 'register rack internal logger to the middleware|s'
    use Rack::Logger

    # Load user profiles, permissions, conditions and strategies
    # --------------------------------------------------------------------------
    j1_mw_auth_mgr.info 'load user profiles, permissions, conditions and strategies'
    providers   = J1App.auth_config['providers']
    permissions = J1App.permissions

    j1_mw_auth_mgr.info 'middleware initialized successfully'
    j1_mw_auth_mgr.info 'state: finished'

    # ==========================================================================
    # Sinatra (before) FILTER to preprocess all page requests
    # ==========================================================================

    # before do
    #   response.headers['Access-Control-Allow-Origin'] = '*'
    # end

    # Prepare root (index) page for app detection
    # --------------------------------------------------------------------------
    before '/' do

      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr.info 'prepare page access'

      # read existing/current cookie 'j1.user.ession' to update all data
      # of user_session_data (hash) otherwise set initial data
      # ------------------------------------------------------------------------
      unless env['HTTP_COOKIE'] == nil
        j1_mw_auth_mgr.info 'read current user state data from cookie'
        user_session_data = readCookie('j1.user.session')
        j1_mw_auth_mgr.info 'update user session data for mode: app'
        user_session_data['mode']       ='app'
        timestamp                       = Time.now.strftime("%Y-%m-%d %H:%M:%S")
        user_session_data['timestamp']  = timestamp
        user_session_data['writer']     = 'middleware'
        session_json                    = user_session_data.to_json
        j1_mw_auth_mgr.info 'write user session data to cookie: ' + "#{session_json}"
        writeCookie('j1.user.session', session_json)
      else
        j1_mw_auth_mgr.info 'create user session data for mode: app'
        timestamp                       = Time.now.strftime("%Y-%m-%d %H:%M:%S")
        user_session_data['timestamp']  = timestamp
        user_session_data['writer']     = 'middleware'
        session_json                    = user_session_data.to_json
        j1_mw_auth_mgr.info 'write user session data to cookie: ' + "#{session_json}"
        writeCookie('j1.user.session', session_json)
        requested_page = env['REQUEST_URI']
        app_session_data['requested_page'] = "#{env['REQUEST_URI']}"
      end

      # Create|Initialize the J1 web session cookie
      # ------------------------------------------------------------------------
      if warden.authenticated?
        user = warden.user

        j1_mw_auth_mgr_auth.info 'user detected: ' + "#{user[:provider]}"
        j1_mw_auth_mgr_auth.info 'user detected as signed in'

        app_session_data['authenticated']         = 'true'
        app_session_data['user_name']             = user[:info]['nickname']
        app_session_data['users_allowed']         = providers["#{user[:provider]}"]['users']
        app_session_data['user_id']               = user[:uid]
        app_session_data['provider']              = user[:provider]
        app_session_data['provider_membership']   = 'member'
        app_session_data['provider_site_url']     = providers["#{user[:provider]}"]['provider_url']
        app_session_data['provider_permissions']  = providers["#{user[:provider]}"]['permissions']
        app_session_data['payment_status']        = user[:info][:payment_status]
      else
        j1_mw_auth_mgr_auth.info 'user detected as signed out'

        app_session_data['authenticated']         = 'false'
        app_session_data['users_allowed']         = 'all'
        app_session_data['user_name']             = 'visitor'
        app_session_data['user_id']               = 'unknown'
        app_session_data['payment_status']        = 'unknown'
        app_session_data['provider']              = 'j1'
        app_session_data['provider_membership']   = 'guest'
        app_session_data['provider_site_url']     = "#{provider_site_url_default}"
        app_session_data['provider_permissions']  = ['public']
      end
    end

    user_session_cookie = 'j1.user.session'
    user_state_cookie   = 'j1.user.state'


    # General content (type) detection (auth pre-flight)
    # --------------------------------------------------------------------------
    before '/(apps|pages|posts)/*' do

      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr_preflight.info 'initial checks initiated'

      # Read current J1 user SESSION cookie ???
      # ------------------------------------------------------------------------
      if existsCookie? user_session_cookie
        user_session_data = readCookie(user_session_cookie)
        j1_mw_auth_mgr_preflight.info 'read user session cookie'                #, "#{session_decoded}"
      else
        requested_page                      = env['REQUEST_URI']
        app_session_data['requested_page']  = "#{requested_page}"
      end

      # Create|Initialize the J1 web session cookie
      # ------------------------------------------------------------------------
      j1_mw_auth_mgr_preflight.info 'check authentication state'
      if warden.authenticated?
        user = warden.user
        app_session_data['authenticated']         = 'true'
        app_session_data['user_name']             = user[:info]['nickname']
        app_session_data['user_id']               = user[:uid]
        app_session_data['provider']              = user[:provider]
        app_session_data['provider_site_url']     = providers["#{user[:provider]}"]['provider_url']
        app_session_data['users_allowed']         = providers["#{user[:provider]}"]['users']#
        app_session_data['provider_permissions']  = providers["#{user[:provider]}"]['permissions']
        app_session_data['provider_membership']   = 'member'
        app_session_data['payment_status']        = user[:info][:payment_status]
        app_session_data['writer']                = 'middleware'

        user_session_data = merge( user_session_data, app_session_data )
        j1_mw_auth_mgr_preflight.info 'user authenticated: ' + "#{user[:info]['nickname']}"
      else
        j1_mw_auth_mgr_preflight.info 'user authenticated: false'
      end

      # User state|content detection for implicit authentication
      # ------------------------------------------------------------------------
      j1_mw_auth_mgr_preflight.info 'check config, found authentication: disabled' if authentication_enabled? == false
      j1_mw_auth_mgr_preflight.info 'pass for all pages' if authentication_enabled? == false

      pass if authentication_enabled? == false
      j1_mw_auth_mgr_preflight.info 'check config, authentication: enabled'
      j1_mw_auth_mgr_preflight.info 'check content, content detected of category: public' if public_content?
      j1_mw_auth_mgr_preflight.info 'check content, pass all content of category: public' if public_content?

      pass if public_content?

      # user_state_data = readCookie(user_state_cookie)

      # Read current J1 user STATE cookie
      # --------------------------------------------------------------------
      if existsCookie? user_state_cookie
        user_state_data = readCookie(user_state_cookie)
        j1_mw_auth_mgr_preflight.info 'read user state cookie'             #, "#{session_decoded}"
        j1_mw_auth_mgr_preflight.info 'cookie consent: ' + "#{user_state_data['cookies_accepted']}"
      else
        j1_mw_auth_mgr_preflight.error 'user session cookie missing: ' + "#{user_state_cookie}"
      end
      j1_mw_auth_mgr_preflight.info 'check content type'

      requested_page = env['REQUEST_URI']
      requested_page.scan(/(protected|private)/) do |match|

        category = match[0]
        j1_mw_auth_mgr_preflight.info 'content type detected: ' + "#{category}"
        j1_mw_auth_mgr_preflight.info 'check authorisation status'
        if warden.authenticated?
          j1_mw_auth_mgr_preflight.info 'user detected as: authenticated'
          user_name = user[:info]['nickname']
          j1_mw_auth_mgr_preflight.info 'user detected: ' +  "#{user_name}"

          current_provider  = warden.user[:provider]
          strategy          = providers["#{current_provider}"]['strategy']
          provider_strategy = :"#{strategy}"

          user_session_data['user_name']             = user_name
          user_session_data['provider_url']          = providers["#{current_provider}"]['provider_url']
          user_session_data['users_allowed']         = providers["#{current_provider}"]['users']
          user_session_data['provider_permissions']  = providers["#{user[:provider]}"]['permissions']
          user_session_data['requested_page']        = requested_page

          j1_mw_auth_mgr_preflight.info 'check permissions'

          if permissions[:"#{category}"].include? current_provider
            j1_mw_auth_mgr_preflight.info 'provider detected: ' + "#{current_provider}"
            j1_mw_auth_mgr_preflight.info 'category detected: ' + "#{category}"
            j1_mw_auth_mgr_preflight.info 'category support: enabled'

            # Check permissions
            #
            #log_info! 'Authorisation', 'ConditionCheck', 'Check permissions for provider', "#{current_provider}"
            #conditions = J1App.conditions current_provider
            # if conditions["#{category}"]
            #   log_info! 'Authorisation', 'ConditionCheck', 'Conditions detected', "#{category}"
            #   conditions["#{category}"].each do |k, v|
            #     case k
            #     when 'enabled'
            #       log_info! 'Authorisation', 'ConditionCheck', "#{k}", "#{v}"
            #     when 'users'
            #       log_info! 'Authorisation', 'ConditionCheck', 'users'
            #       v.each do |k, v|
            #         log_info! 'Authorisation', 'ConditionCheck', "users - #{k}", "#{v}"
            #       end
            #     when 'payment'
            #       log_info! 'Authorisation', 'ConditionCheck', 'payment'
            #       v.each do |k, v|
            #         case k
            #         when 'tiers'
            #           log_info! 'Authorisation', 'ConditionCheck', "payment - #{k}", "#{v}"
            #         when 'tier'
            #           v.each do |k, v|
            #             log_info! 'Authorisation', 'ConditionCheck', 'payment - tiers - tier : ' "#{k}", "#{v}"
            #           end
            #         end
            #       end
            #     end
            #   end
            # end
          else
            provider  = permissions[:"#{category}"][0]

            j1_mw_auth_mgr_preflight.info 'provider detected: ' + "#{current_provider}"
            j1_mw_auth_mgr_preflight.info 'category detected: ' + "#{category}"
            j1_mw_auth_mgr_preflight.info 'category support: disabled'
            j1_mw_auth_mgr_preflight.info 'authorisation failed for user: ' + "#{user_name}"

            # Update user session cookie for 'requested_page'
            # ------------------------------------------------------------------
            session_json = user_session_data.to_json

            # jadams, 2019-08-08: writeCookie of 'j1.user.session' is needed to
            # track the requested_page. This cause the potential risk to
            # corrupt the cookie (now, for unknown reason)
            # ------------------------------------------------------------------
            writeCookie(user_session_cookie, session_json)
            j1_mw_auth_mgr_preflight.info 'write user session data: ' + "#{session_json}"
            j1_mw_auth_mgr_preflight.info 'pass to error page: access_denied'

            description_title = "Access Denied"
            redirect "/access_denied?provider=#{current_provider}&user=#{user_name}&category=#{category}&title=#{description_title}"
          end

          timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
          j1_mw_auth_mgr_preflight.info 'pass to requested page: ' + "#{requested_page}"
          pass
        else
          j1_mw_auth_mgr_preflight.info 'user state detected: signed out'
          default_provider = permissions[:"#{category}"][0]
          j1_mw_auth_mgr_preflight.info 'set default provider: ' + "#{default_provider}"

          strategy          = providers["#{default_provider}"]['strategy']
          provider_strategy = :"#{strategy}"

          j1_mw_auth_mgr_preflight.info 'start processing provider: ' + "#{default_provider}"
          j1_mw_auth_mgr_preflight.info 'provider authentication strategy found: ' +  "#{strategy}"

          case provider_strategy

          when :org
            warden.authenticate!
            github_organization_authenticate! ENV['GITHUB_ORG_NAME']
            j1_mw_auth_mgr_preflight.info "Hi There, #{user_session_data[:user_name]}! You have access to the #{params['id']} organization"

          when :team
            warden.authenticate!
            github_team_authenticate! ENV['GITHUB_TEAM_ID']
            j1_mw_auth_mgr_preflight.info "Hi There, #{user_session_data[:user_name]}! You have access to the #{params['id']} team"

          when :teams
            warden.authenticate!
            github_teams_authenticate! ENV['GITHUB_TEAM_IDS'].split(',')
            j1_mw_auth_mgr_preflight.info "Hi There, #{user_session_data[:user_name]}! You have access to the #{params['id']} team"

          when :member
            j1_mw_auth_mgr_preflight.info 'process authentication strategy'

            if env['HTTP_COOKIE'].include? 'j1.user.session'
              session_encoded   = request.cookies['j1.user.session']
              session_decoded   = Base64.decode64(session_encoded)
              user_session_data = JSON.parse(session_decoded)
              j1_mw_auth_mgr_preflight.info 'read user session data from cookie' # "#{session_decoded}"
            end

            # Update user session cookie for 'requested_page'
            # ------------------------------------------------------------------
            user_session_data['requested_page'] = env['REQUEST_URI']
            user_session_data['writer']         = 'middleware'
            session_json                        = user_session_data.to_json

            # jadams, 2019-08-08: writeCookie of 'j1.user.session' is needed to
            # track the requested_page. This cause the potential risk to
            # corrupt the cookie (now, for unknown reason)
            # ------------------------------------------------------------------
            j1_mw_auth_mgr_preflight.info 'write user session data to cookie: ' + "#{session_decoded}"
            writeCookie(user_session_cookie, session_json)

            j1_mw_auth_mgr_preflight.info 'call api request for: ' + "/page_validation?page=#{user_session_data['requested_page']}"
            redirect "/page_validation?page=#{user_session_data['requested_page']}"
          else
            raise J1App::ConfigError
          end
        end
      end
    end


    # ==========================================================================
    # API ENDPOINTS (Sinatra HANDLERS)
    # ==========================================================================

    # ENDPOINT auth (called from Netlify CMS as an external oauth client)
    # --------------------------------------------------------------------------
    get '/auth' do
      provider        = 'github'
      oauth_requester = 'cc'

      MDC.put('path', env['REQUEST_URI'])

      j1_mw_auth_mgr_api_auth.info "authentication request received from: #{oauth_requester}"
      j1_mw_auth_mgr_api_auth.info 'check authentication status'

      if warden.authenticated?
        # process users only if authenticated at 'github'
        if warden.user[:provider] == provider
          nickname = warden.user[:info]['nickname']
          token    = warden.user[:credentials]['token']

          j1_mw_auth_mgr_api_auth.info "user detected as signed in: github, #{nickname}"
          j1_mw_auth_mgr_api_auth.info "pass authentication message to Netlify CMS for provider: #{provider}"

          signin_client = <<IIF
          <!DOCTYPE html>
          <html lang="en">
            <head>
              <!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
              <meta charset="utf-8" />
              <meta http-equiv="X-UA-Compatible" content="IE=edge" />
              <meta name="viewport" content="width=device-width, initial-scale=1" />
              <title>J1 Control Center | SignIn to Github</title>
              <style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */body,html{width:100%;height:100%;background-color:#21232a}body{color:#fff;text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.5);padding:0;min-height:100%;-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,.8);box-shadow:inset 0 0 100px rgba(0,0,0,.8);display:table;font-family:"Open Sans",Arial,sans-serif}h1{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;font-size:36px}h1 small{font-size:68%;font-weight:400;line-height:1;color:#777}a{text-decoration:none;color:#fff;font-size:inherit;border-bottom:dotted 1px #707070}.lead{color:silver;font-size:21px;line-height:1.4}.cover{display:table-cell;vertical-align:middle;padding:0 20px}footer{position:fixed;width:100%;height:40px;left:0;bottom:0;color:#a0a0a0;font-size:14px}</style>
            </head>
            <body>
              <div class="cover">
                <h1>Control Center <small>sign in</small></h1>
                <p class="lead">
                  The Control Center is accessing #{provider} for user #{nickname}.<br>
                  Providing authentication data ..</p>
              </div>

              <script>
              (function() {
                // Register an event handler to listen for messages of the child window
                window.addEventListener("message", receiveMessage, false);

                // Post a authorizing message to the parent window (Netlify CMS App)
                // as a handshake with the parent window
                window.opener.postMessage("authorizing:#{provider}", "*");

                function receiveMessage() {
                  // send message to main (parent) window (Netlify CMS App)
                  window.opener.postMessage(
                    'authorization:#{provider}:success:{"token":"#{token}","provider":"#{provider}"}'
                  )
                } // end receiveMessage
              })()
              </script>

            </body>
          </html>
IIF
        else
          provider =        'github'
          nickname          = warden.user[:info]['nickname']
          current_provider  = warden.user[:provider]

          j1_mw_auth_mgr_api_auth.info "user detected as signed in: #{nickname}"
          j1_mw_auth_mgr_api_auth.info "invalid provider detected: #{current_provider}"

          # Destroy current session to initiate a clean oauth authentication
          # ----------------------------------------------------------------
          j1_mw_auth_mgr_api_auth.info "sign out user from provider: #{current_provider}"
          j1_mw_auth_mgr_api_auth.info "initiate oauth authentication at provider: #{provider}"
          warden.logout
          session.clear
          warden.authenticate! :"omni_#{provider}"
        end
      else
        j1_mw_auth_mgr_api_auth.info 'user detected as signed out'
        j1_mw_auth_mgr_api_auth.info "initiate oauth authentication at provider: #{provider}"

        # Destroy current session to initiate a clean oauth authentication
        # ----------------------------------------------------------------
        warden.logout
        session.clear
        warden.authenticate! :"omni_#{provider}"
      end
    end


    # ENDPOINT authentication (called from WEB by auth client)
    # --------------------------------------------------------------------------
    get '/authentication' do
      request         = params.fetch('request')
      provider        = params.fetch('provider')
      oauth_requester = 'app'

      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr_api_auth.info "authentication request received from: #{oauth_requester}"

      # SignIn
      # ------------------------------------------------------------------------
      if request === 'signin'
        j1_mw_auth_mgr_api_auth.info 'sign in called for provider: ' + "#{provider}"

        # collect (additional) GET parameter|s
        # ----------------------------------------------------------------------
        allowed_users = params.fetch('allowed_users')

        if warden.authenticated?
          j1_mw_auth_mgr_api_auth.info 'sign in called but user already signed: ' + "#{warden.user[:info]['nickname']}"
          requested_page = user_session_data['requested_page']
          j1_mw_auth_mgr_api_auth.info 'pass user to requested page: ' + "#{requested_page}"
          redirect "#{requested_page}"
        else
          j1_mw_auth_mgr_api_auth.info 'initiate oauth authentication to sign in'

          # Make (really) sure that old session is cleared before login
          # --------------------------------------------------------------------
          warden.logout
          session.clear
          warden.authenticate! :"omni_#{provider}"
        end
        # SignOut
        # ------------------------------------------------------------------------
      elsif request === 'signout'
        # collect (additional) GET parameter|s
        provider_signout = params.fetch('provider_signout')
        j1_mw_auth_mgr_api_auth.info 'sign out called for provider: ' + "#{provider}"

        if warden.authenticated?
          user         = warden.user[:info]['nickname']
          provider     = warden.user[:provider]
          provider_url = user_session_data['provider_url']
          j1_mw_auth_mgr_api_auth.info 'sign out user: ' + "#{user}"
          warden.logout
          session.clear

          # Read current J1 user SESSION cookie
          # --------------------------------------------------------------------
          if existsCookie? user_session_cookie
            user_session_data = readCookie(user_session_cookie)
            j1_mw_auth_mgr_api_auth.info 'read user session cookie'             #, "#{session_decoded}"
          else
            j1_mw_auth_mgr_api_auth.error 'user session cookie missing: ' + "#{user_session_cookie}"
          end

          if provider_signout === 'true'
            j1_mw_auth_mgr_api_auth.info 'sign out user: ' + "#{user}"
            j1_mw_auth_mgr_api_auth.info 'sign out from: ' + "#{provider}"
            j1_mw_auth_mgr_api_auth.info 'pass to provider for sign out: ' + "#{provider_url}"
            redirect "#{provider_url}"
          else
            j1_mw_auth_mgr_api_auth.info 'sign out user: ' + "#{user}"
            j1_mw_auth_mgr_api_auth.info 'sign out from: session'

            # If signed out, redirect ONLY for PUBLIC pages
            # ------------------------------------------------------------------
            if redirect_whitelisted?user_session_data['requested_page']
              j1_mw_auth_mgr_api_auth.info 'check page permissions: access allowed'
              j1_mw_auth_mgr_api_auth.info 'pass to page: ' + "#{user_session_data['requested_page']}"
              redirect user_session_data['requested_page']
            else
              j1_mw_auth_mgr_api_auth.info 'check page permissions: access denied'
              j1_mw_auth_mgr_api_auth.info 'pass to page: /'
              redirect '/'
            end
          end
        else
          # THIS condition should NEVER REACHED because NO logout dialog
          # (modal) is provided by the auth client if a user isn't signed in.
          # Kept this alternative for cases something went wrong.
          # --------------------------------------------------------------------
          j1_mw_auth_mgr_api_auth.warn 'DEAD PATH - called for sign out but user state is: signed out'

          # Read current J1 SESSION cookie
          # --------------------------------------------------------------------
          if existsCookie? user_session_cookie
            user_session_data = readCookie(user_session_cookie)
            j1_mw_auth_mgr_api_auth.warn 'DEAD PATH - read user session cookie'  #, "#{session_decoded}"
          else
            j1_mw_auth_mgr_api_auth.error 'DEAD PATH - user session cookie missing: ' + "#{user_session_cookie}"
          end

          j1_mw_auth_mgr_api_auth.warn 'DEAD PATH - pass to requested page: ' + "#{user_session_data['requested_page']}"
          redirect user_session_data['requested_page']
        end
      else
        raise J1App::ConfigError
      end
    end
    # --------------------------------------------------------------------------
    # END: get '/authentication'

    # ENDPOINT post_authentication (called after a user is back from OAuth Provider)
    # --------------------------------------------------------------------------
    get '/post_authentication' do
      MDC.put('path', env['REQUEST_URI'])

      nickname  = warden.user[:info]['nickname']
      provider  = warden.user[:provider]
      token     = warden.user[:credentials]['token']

      j1_mw_auth_mgr_api_post_auth.info "user successfully authenticated: #{provider}, #{nickname}"
      j1_mw_auth_mgr_api_post_auth.info "authentication requester deteced as: #{oauth_requester}"

      if oauth_requester.include? 'cc'
        j1_mw_auth_mgr_api_post_auth.info 'auth request was requested by Netlify CMS'
        j1_mw_auth_mgr_api_post_auth.info 'prepare signin for Netlify CMS'

        signin_client = <<IIF
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
            <meta charset="utf-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <title>J1 Control Center | SignIn to Github</title>
            <style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */body,html{width:100%;height:100%;background-color:#21232a}body{color:#fff;text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.5);padding:0;min-height:100%;-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,.8);box-shadow:inset 0 0 100px rgba(0,0,0,.8);display:table;font-family:"Open Sans",Arial,sans-serif}h1{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;font-size:36px}h1 small{font-size:68%;font-weight:400;line-height:1;color:#777}a{text-decoration:none;color:#fff;font-size:inherit;border-bottom:dotted 1px #707070}.lead{color:silver;font-size:21px;line-height:1.4}.cover{display:table-cell;vertical-align:middle;padding:0 20px}footer{position:fixed;width:100%;height:40px;left:0;bottom:0;color:#a0a0a0;font-size:14px}</style>
          </head>
          <body>
            <div class="cover">
              <h1>Control Center <small>sign in</small></h1>
              <p class="lead">
                The Control Center is accessing #{provider} for user #{nickname}.<br>
                Providing authentication data ..</p>
            </div>

            <script>
            (function() {
              // Register an event handler to listen for messages of the child window
              window.addEventListener("message", receiveMessage, false);

              // Post a authorizing message to the parent window (Netlify CMS App)
              // as a handshake with the parent window
              window.opener.postMessage("authorizing:#{provider}", "*");

              function receiveMessage() {
                // send message to main (parent) window (Netlify CMS App)
                window.opener.postMessage(
                  'authorization:#{provider}:success:{"token":"#{token}","provider":"#{provider}"}'
                )
              } // end receiveMessage
            })()
            </script>

          </body>
        </html>
IIF

        j1_mw_auth_mgr_api_post_auth.info "pass authentication message to Netlify CMS for provider: #{provider}"
        # noinspection RubyJumpError
        return signin_client
      end

      reward = {
          :id    => 'unknown',
          :name  => 'unknown',
          :link  => '#'
      }
      campaign = {
          :id    => 'unknown',
          :link  => '#'
      }

       MDC.put('path', env['REQUEST_URI'])
        j1_mw_auth_mgr_api_post_auth.info 'identification request received'

      # Read current J1 SESSION cookie
      # ------------------------------------------------------------------------
      if existsCookie? user_session_cookie
        user_session_data = readCookie(user_session_cookie)
        j1_mw_auth_mgr_api_post_auth.info 'read user session cookie'
        j1_mw_auth_mgr_api_post_auth.info 'requested page found as: ' + "#{user_session_data['requested_page']}"
      else
        j1_mw_auth_mgr_api_post_auth.error 'user session cookie missing: ' + "#{user_session_cookie}"
      end

      user      = warden.user
      user_json = user.to_json

      if user[:provider] === 'disqus'
        user[:info][:urls][:site]       = "https://disqus.com"
        user[:info][:urls][:home]       = user[:info]['urls']['profileUrl']
        user[:info][:urls][:blog]       = "https://disqus.com/by/juergen_adams/"
        user[:info][:urls][:member]     = user[:info]['urls']['profileUrl']
      end

      if user[:provider] === 'github'
        user[:info][:urls][:site]       = "https://github.com"
        user[:info][:urls][:home]       = user[:info]['urls']['GitHub']
        user[:info][:urls][:blog]       = "https://github.com/jekyll-one"
        user[:info][:urls][:member]     = user[:info]['urls']['Blog']
      end

      if user[:provider] === 'patreon'

        user[:info][:urls][:site]       = "https://patreon.com"
        user[:info][:urls][:home]       = "https://patreon.com/home"
        user[:info][:urls][:blog]       = "https://patreon.com/jekyll_one"

        unless user[:info]['payment_info'].empty?
          reward_url                    = user[:info]['payment_info']['relationships']['reward']['links']['related']
          reward_json                   = RestClient.get "#{reward_url}", {:content_type => :json, :accept => :json}
          reward_data                   = JSON.parse(reward_json)
          user[:info][:urls][:member]   = "https://patreon.com" + reward_data['data']['attributes']['url']
          user[:info][:payment_status]  = user[:info]['payment_info']['attributes']['declined_since'].nil? ? 'true' : 'false'
        else
          reward_url                    = ""
          reward_json                   = ""
          reward_data                   = ""
          user[:info][:payment_status]  = 'false'
        end

        unless reward_data.empty?
          reward[:id]                   = reward_data['data']['id']
          reward[:name]                 = reward_data['data']['attributes']['title']
          reward[:link]                 = "https://patreon.com" + reward_data['data']['attributes']['url']
          campaign[:id]                 = reward_data['data']['relationships']['campaign']['data']['id']
          campaign[:link]               = reward_data['data']['relationships']['campaign']['links']['related']
        else
          reward[:id]                   = ""
          reward[:name]                 = "no tiers"
          reward[:link]                 = ""
          campaign[:id]                 = ""
          campaign[:link]               = ""
        end
      end

      user[:extra][:reward]             = reward
      user[:extra][:campaign]           = campaign

      # EXCEPTION: collection of session data failed (e.g cookie > 4K)
      # ------------------------------------------------------------------------
      if user.nil?
        j1_mw_auth_mgr_api_post_auth.error 'internal error: user identification failed'
        j1_mw_auth_mgr_api_post_auth.error 'check user session cookie for consistency'
        warden.logout
        session.clear
        j1_mw_auth_mgr_api_post_auth.info 'pass to error page: access denied'
        description_title = "Access Denied"
        redirect "/access_denied?provider=unknown&user=unknown&category=unknown&title=#{description_title}"
      else
        j1_mw_auth_mgr_api_post_auth.info 'user identified successfully'

        user_session_data['user_name']              = user[:info]['nickname']
        user_session_data['user_id']                = user[:uid]
        user_session_data['provider']               = user[:provider]
        user_session_data['provider_membership']    = 'member'
        user_session_data['provider_permissions']   = providers["#{user[:provider]}"]['permissions']
        user_session_data['users_allowed']          = providers["#{user[:provider]}"]['users']
        user_session_data['authenticated']          = 'true'
        user_session_data['payment_status']         = user[:info][:payment_status]
        user_session_data['writer']                 = 'middleware'

        current_user                                = user[:info]['nickname']
        current_provider                            = user[:provider]

        user_session_data['requested_page'].scan(/(protected|private)/) do |match|

          # Set category from requested page
          # --------------------------------------------------------------------
          category = match[0]
          j1_mw_auth_mgr_api_post_auth.info 'Processing content category: ' + "#{category}"

          # Check if user is allowed to access protected content in GENERAL
          # --------------------------------------------------------------------
          j1_mw_auth_mgr_api_post_auth.info 'check for users allowed'
          unless user_session_data['users_allowed'].include? 'all'
            unless user_session_data['users_allowed'].include? "#{current_user}"
              j1_mw_auth_mgr_api_post_auth.info 'user not allowed: ' + "#{current_user}"
              j1_mw_auth_mgr_api_post_auth.info 'allowed users: ' + "#{user_session_data['users_allowed']}"
              j1_mw_auth_mgr_api_post_auth.info 'pass to error page: access_denied'
              description_title = "Access Denied"
              redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}"
            end
          end
          j1_mw_auth_mgr_api_post_auth.info 'allowed users found: ' + "#{user_session_data['users_allowed']}"

          # Check conditions to access protected content (if any)
          #
          j1_mw_auth_mgr_api_post_auth.info 'check conditions for provider: ' + "#{current_provider}"
          check_conditions = providers["#{user[:provider]}"]['conditions'][category]['enabled']
          if check_conditions

            if providers["#{user[:provider]}"]['conditions'][category]['users']['whitelist'].nil?
              category_whitelist = 'all'
            else
              category_whitelist = providers["#{user[:provider]}"]['conditions'][category]['users']['whitelist']
            end

            # Check if user is BLACKLISTED
            #
            blacklist = providers["#{user[:provider]}"]['conditions'][category]['users']['blacklist']
            if blacklist.include? "#{current_user}"
              j1_mw_auth_mgr_api_post_auth.info 'check blacklisting'
              j1_mw_auth_mgr_api_post_auth.info 'found user blacklisted: ' + "#{current_user}"

              user[:info][:blacklisted] = 'true'
              j1_mw_auth_mgr_api_post_auth.info 'logout user from current session: ' + "#{current_user}"
              warden.logout
              session.clear
              j1_mw_auth_mgr_api_post_auth.info 'pass to error page: access_denied'
              description_title = "User blacklisted"
              redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}"
            end

            j1_mw_auth_mgr_api_post_auth.info 'check whitelisting'
            if category_whitelisted? category_whitelist, current_user
              user[:info][:whitelisted] = 'true'
              reward[:name]             = 'whitelisted'
              j1_mw_auth_mgr_api_post_auth.info 'found user whitelisted: ' + "#{current_user}"
              j1_mw_auth_mgr_api_post_auth.info 'set user rewards to: whitelisted'
            else
              j1_mw_auth_mgr_api_post_auth.info 'no whitelisting found for user: ' + "#{current_user}"
            end
            j1_mw_auth_mgr_api_post_auth.info 'check provider conditions'
            unless category_whitelisted? category_whitelist, current_user
              j1_mw_auth_mgr_api_post_auth.info 'check rewards for user: ' + "#{current_user}"
              payment_tiers = providers["#{user[:provider]}"]['conditions'][category]['payment']['activated']
              if payment_activated? payment_tiers
                j1_mw_auth_mgr_api_post_auth.info 'rewards found : ' + "#{reward[:name]}"

                # Check if any payment exists
                # --------------------------------------------------------------
                j1_mw_auth_mgr_api_post_auth.info 'check payment status'
                if user[:info]['payment_info'].empty?
                  j1_mw_auth_mgr_api_post_auth.info 'payment status not available for user: ' + "#{current_user}"
                  j1_mw_auth_mgr_api_post_auth.info 'logout user from current session'
                  warden.logout
                  session.clear
                  j1_mw_auth_mgr_api_post_auth.info 'pass to error page: access denied'
                  description_title = "Invalid funds"
                  redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}"
                end

                # Check for VALID payments (scope: pledge-to-me)
                # --------------------------------------------------------------
                payment_status = user[:info]['payment_info']['attributes']['declined_since']
                unless payment_valid? payment_status
                  j1_mw_auth_mgr_api_post_auth.info 'payment status invalid for user: ' + "#{current_user}"
                  j1_mw_auth_mgr_api_post_auth.info 'logout user from current session'
                  warden.logout
                  session.clear
                  j1_mw_auth_mgr_api_post_auth.info 'pass to error page: access denied'
                  description_title = "Invalid funds"
                  redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}"
                else
                  j1_mw_auth_mgr_api_post_auth.info 'payment status valid for user: ' + "#{current_user}"
                end
              end

            end
            # end category_whitelisted
          else
            category_condition_state = providers["#{user[:provider]}"]['conditions'][category]['enabled']

            j1_mw_auth_mgr_api_post_auth.info 'category check failed for provider: ' + "#{current_provider}"
            j1_mw_auth_mgr_api_post_auth.info 'category check failed for category: ' + "#{category}"
            j1_mw_auth_mgr_api_post_auth.info 'category support found as: ' + "#{category_condition_state}"
            j1_mw_auth_mgr_api_post_auth.info 'pass to error page: access denied'

            description_title = "Access Denied"
            redirect "/access_denied?provider=#{current_provider}&user=#{current_user}&category=#{category}&title=#{description_title}"
          end
          # end check conditions

        end
        # end protected content
      end
      # end user.nil?

      # jadams, 2019-08-08: writeCookie of 'j1.user.session' is needed to
      # track the requested_page. This cause the potential risk to
      # corrupt the cookie (now, for unknown reason)
      # ------------------------------------------------------------------
      timestamp                       = Time.now.strftime("%Y-%m-%d %H:%M:%S")
      user_session_data['timestamp']  = timestamp
      user_session_data['writer']     = 'middleware'
      session_json                    = user_session_data.to_json

      j1_mw_auth_mgr_preflight.info 'write user session data to cookie: ' + "#{session_json}"
      writeCookie(user_session_cookie, session_json)

      j1_mw_auth_mgr_api_post_auth.info 'provider detected: ' + "#{user[:provider]}"
      j1_mw_auth_mgr_api_post_auth.info 'user detected: ' + "#{user[:info]['nickname']}"
      j1_mw_auth_mgr_api_post_auth.info 'pass to requested page: ' + "#{user_session_data['requested_page']}"

      redirect user_session_data['requested_page']
    end
    # --------------------------------------------------------------------------
    # END: get /post_authentication

    # ENDPOINT status (called from WEB to get current state of an user)
    # --------------------------------------------------------------------------
    get '/status' do
      requested_page = params.fetch('page')
      category = 'public'

      requested_page.scan(/(public|protected|private)/) do |match|
        category = match[0]
      end

      # MDC.put('path', requested_page)
      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr_api_state.info 'status request received'
      j1_mw_auth_mgr_api_state.info 'page requested: ' + "#{requested_page}"
      j1_mw_auth_mgr_api_state.info 'content category found: ' + "#{category}"

      # if request.warden.user.respond_to?(:info)
      #
      if warden.authenticated?
        user_name               = warden.user[:info]['nickname']
        user_id                 = warden.user[:uid]
        users_allowed           = providers[warden.user[:provider]]['users']
        provider                = warden.user[:provider]
        provider_membership     = 'member'
        provider_permissions    = user_session_data['provider_permissions']

        if provider == 'github'
          provider_site_url       = warden.user[:info][:urls][:site]
          provider_home_url       = warden.user[:info][:urls][:blog]
          provider_blog_url       = warden.user[:info][:urls][:blog]
          provider_member_url     = warden.user[:info][:urls][:blog]
        end

        if provider == 'disqus'
          provider_site_url       = warden.user[:info][:urls][:site]
          provider_home_url       = warden.user[:info][:urls][:blog]
          provider_blog_url       = warden.user[:info][:urls][:blog]
          provider_member_url     = warden.user[:info][:urls][:blog]
        end

        if provider == 'patreon'
          provider_membership     = warden.user[:extra][:reward][:name]
          provider_site_url       = warden.user[:info][:urls][:site]
          provider_home_url       = warden.user[:info][:urls][:home]
          provider_blog_url       = warden.user[:info][:urls][:blog]
          provider_member_url     = warden.user[:extra][:reward][:link]
        end

        j1_mw_auth_mgr_api_state.info 'user detected as: ' + "#{user_name}"
        j1_mw_auth_mgr_api_state.info 'user state detected as: signed in'
      else
        user_name = 'unknown'
        j1_mw_auth_mgr_api_state.info 'user state detected as: signed out'
      end

      timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")

      # if request.warden.authenticated?
      #
      if user_name != 'unknown'
        j1_mw_auth_mgr_api_state.info 'send data for: signed in'
        content_type 'application/json'
        {
            user_name:                user_name,
            user_id:                  user_id,
            users_allowed:            users_allowed,
            provider:                 provider,
            provider_membership:      provider_membership,
            provider_permissions:     provider_permissions,
            provider_site_url:        provider_site_url,
            provider_home_url:        provider_home_url,
            provider_blog_url:        provider_blog_url,
            provider_member_url:      provider_member_url,
            provider_privacy_url:     provider_privacy_url_default,
            page_permission:          category,
            requested_page:           requested_page,
            authenticated:            'true',
            timestamp:                timestamp
        }.to_json
      else
        j1_mw_auth_mgr_api_state.info 'send data for: signed out'
        content_type 'application/json'
        {
            user_name:                'visitor',
            user_id:                  'unknown',
            users_allowed:            'all',
            provider:                 'j1',
            provider_membership:      'guest',
            provider_permissions:     ['public'],
            provider_site_url:        provider_site_url_default,
            provider_home_url:        provider_home_url_default,
            provider_blog_url:        provider_blog_url_default,
            provider_member_url:      provider_member_url_default,
            provider_privacy_url:     provider_privacy_url_default,
            page_permission:          category,
            requested_page:           requested_page,
            authenticated:            'false',
            timestamp:                timestamp
        }.to_json
      end
    end
    # --------------------------------------------------------------------------
    # END: get /status

    #  ENDPOINT access_denied (exception, called from the app|auth manager)
    # --------------------------------------------------------------------------
    get '/access_denied' do
      provider          = params.fetch('provider')
      category          = params.fetch('category')
      user              = params.fetch('user')
      description_title = params.fetch('title')

      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr_api_auth.info 'request received'
      j1_mw_auth_mgr_api_auth.warn 'access denied'

      # Read current J1 SESSION cookie
      # ------------------------------------------------------------------------
      if existsCookie? user_session_cookie
        j1_mw_auth_mgr_api_auth.info 'read user session cookie'
        user_session_data = readCookie(user_session_cookie)
      else
        j1_mw_auth_mgr_api_auth.error 'user session cookie missing: ' + "#{user_session_cookie}"
      end

      j1_mw_auth_mgr_api_auth.info 'pass to error page: access denied'

      # Capitalize first char
      provider =  provider.sub(/^./, &:upcase)
      route    = '/'

      @route             = route
      @provider          = provider
      @modal             = "centralModalInfo"
      @info_type         = "danger"
      @modal_icon        = "account-off"
      @modal_ok_text     = "Ok, understood"
      @modal_title       = "Authentication Manager"
      @modal_description = "<h4>#{description_title}</h4></br></br> User <b>#{user}</b> from provider <b>#{provider}</b> is not allowed to access <b>#{category}</b> pages."

      erb :auth_manager_ui
    end
    # --------------------------------------------------------------------------
    # END: get '/access_denied'

    # /page_validation ENDPOINT
    # --------------------------------------------------------------------------
    get '/page_validation' do
      requested_page  = params.fetch('page')
      category        = ''
      provider        = ''
      allowed_users   = ''

      # MDC.put('path', requested_page)
      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr_api_validate.info 'validate request received for page: ' + "#{requested_page}"

      requested_page.scan(/(protected|private)/) do |match|
        category      = match[0]
        provider      = permissions[:"#{category}"][0]
        allowed_users = providers["#{provider}"]['users'].join(',')
      end

      j1_mw_auth_mgr_api_validate.info 'category detected: ' + "#{category}"
      j1_mw_auth_mgr_api_validate.info 'provider detected: ' + "#{provider}"
      j1_mw_auth_mgr_api_validate.info 'users allowed detected: ' + "#{allowed_users}"


      if warden.authenticated?
        j1_mw_auth_mgr_api_validate.info 'validation: successful'
        j1_mw_auth_mgr_api_validate.info 'pass user to requested page: ' + "#{requested_page}"
        route = requested_page
      else
        j1_mw_auth_mgr_api_validate.info 'validation: failed: user not authenticated'
        j1_mw_auth_mgr_api_validate.info 'pass user to authentication: ' + "/authentication?request=signin&provider=#{provider}&allowed_users=#{allowed_users}"
        route = "/authentication?request=signin&provider=#{provider}&allowed_users=#{allowed_users}"
      end

      # Capitalize first char
      provider              =  provider.sub(/^./, &:upcase)

      @provider             = provider
      @route                = route
      @modal                = "signInProtectedContent"
      @modal_icon           = "login"
      @modal_agreed_text    = "Yes, please"
      @modal_disagreed_text = "No, thanks"
      @modal_title          = "SignIn"
      @modal_image          = "/assets/images/modules/attics/banner/admin-dashboard-bootstrap-1280x600.png"
      @modal_description    = "The page <b>#{requested_page}</b> you requested belongs to <b>#{category}</b> content. You'll be redirected to authenticate with the provider <b>#{provider}</b>. If signed in successfully, you get access to all <b>#{category} pages</b>."

      erb :auth_manager_ui
    end
    # --------------------------------------------------------------------------
    # END: get '/page_validation

    # Rescue OmniAuth::Strategies::OAuth2::CallbackError
    # ------------------------------------------------------------------------
    get '/redirect_on_failure' do

      MDC.put('path', env['REQUEST_URI'])
      j1_mw_auth_mgr_api_auth .error 'callback error on oauth redirect'
      j1_mw_auth_mgr_api_auth.info 'pass user to page: /'

      redirect '/'
    end

    post '/log2disk' do
#     payload   = params
#     request   = params.fetch('request')
      logger    = params.fetch('logger')
      epoch     = params.fetch('timestamp').to_i
      level     = params.fetch('level')
      url       = params.fetch('url')
      message   = params.fetch('message')
#     layout    = params.fetch('layout')
      timestamp = Time.at(epoch/1000.0).strftime('%Y-%m-%d %H:%M:%S.%3N')
      path      = ''

      # additional response header to allow 'log2disk' requests
      # from several origins
      # NOTE: See response header control for 'Sinatra'
      # with j1_site_manager
      # ------------------------------------------------------------------------
      response.headers['Access-Control-Allow-Origin'] = '*'

      url.scan(/((?:file|https?):.*?):(\d+)(.*)/) do |match|
        path = match[2]
      end

      if logger.include? 'j1.mw'
        MDC.put('id', page_id)
      else
        MDC.put('id', env['HTTP_X_PAGE_ID'])
      end

      MDC.put('path', path)
      MDC.put('level', level)
      MDC.put('timestamp', timestamp)
      MDC.put('message', message)
      MDC.put('logger', logger)

      j1_mw_auth_mgr_api_log2disk.info 'pass data'

      # return HTTP 200
      "finished"
    end
    # --------------------------------------------------------------------------
    # END: get /redirect_on_failure

    # ENDPOINT  invalid_funds (exception, called from the app|auth manager)
    # --------------------------------------------------------------------------
    # get '/invalid_funds' do
    #   provider          = params.fetch('provider')
    #   category          = params.fetch('category')
    #   user              = params.fetch('user')
    #   description_title = params.fetch('title')
    #
    #   log_info! 'API', 'ExceptionHandler', 'Request received'
    #   log_info! 'ExceptionHandler', 'ERROR', 'Invalid Funds'
    #
    #   session_encoded = request.cookies['j1.user.session']
    #   session_decoded = Base64.decode64(session_encoded)
    #   user_session_data  = JSON.parse(session_decoded)
    #
    #   log_info! 'ExceptionHandler', 'Redirect', 'Pass to error page', 'Invalid Funds'
    #
    #   # Capitalize first char
    #   provider  =  provider.sub(/^./, &:upcase)
    #   route     = '/'
    #
    #   @route                  = route
    #   @provider               = provider
    #   @modal                  = "centralModalInfo"
    #   @info_type              = "danger"
    #   @modal_icon             = "account-off"
    #   @modal_ok_text          = "Ok, understood"
    #   @modal_title            = "Authentication Manager"
    #   @modal_description      = "<h4>#{description_title}</h4></br></br> User <b>#{user}</b> from provider <b>#{provider}</b> is not allowed to access <b>#{category}</b> pages."
    #
    #   erb :auth_manager_ui
    # end
    # END: get /invalid_funds
    # --------------------------------------------------------------------------

    # ENDPOINT iframe
    # --------------------------------------------------------------------------
    # get '/iframe' do
    #   @website_url = "https://jekyll-one.github.io/"
    #   erb :iframe
    # end
    # END: get /iframe
    # --------------------------------------------------------------------------

  end
end