WWebSso.mjs

import path from 'path'
import fs from 'fs'
import get from 'lodash-es/get.js'
import iseobj from 'wsemi/src/iseobj.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import ispint from 'wsemi/src/ispint.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import ispnum from 'wsemi/src/ispnum.mjs'
import isErr from 'wsemi/src/isErr.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import cint from 'wsemi/src/cint.mjs'
import strleft from 'wsemi/src/strleft.mjs'
import strright from 'wsemi/src/strright.mjs'
import strdelright from 'wsemi/src/strdelright.mjs'
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
import replace from 'wsemi/src/replace.mjs'
import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
import ds from '../src/schema/index.mjs'
import procCore from './procCore.mjs'
import procProtect from './procProtect.mjs'
import procSettings from './procSettings.mjs'


/**
 * 權限伺服器
 *
 * @class
 * @param {Function} WOrm 輸入資料庫ORM函數
 * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
 * @param {String} db 輸入資料庫名稱字串
 * @param {String} [pathSettings='./settings'] 輸入設定檔案路徑字串,預設'./settings'
 * @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
 * @example
 *
 *
 *
 */
function WWebSso(WOrm, url, db, pathSettings) {
    let instWServHapiServer = null


    //check WOrm
    if (!isfun(WOrm)) {
        console.log('invalid WOrm', WOrm)
        throw new Error('invalid WOrm')
    }


    //check url
    if (!isestr(url)) {
        console.log('invalid url', url)
        throw new Error('invalid url')
    }


    //check db
    if (!isestr(db)) {
        console.log('invalid db', db)
        throw new Error('invalid db')
    }


    //check pathSettings
    if (!fsIsFile(pathSettings)) {
        pathSettings = './settings.json'
    }


    //setFilepath
    procSettings.setFilepath(pathSettings)


    //getSettings
    let opt = procSettings.getSettings()
    // console.log('opt', opt)
    // * @param {Object|String} [opt={}] 輸入設定物件或設定檔檔案路徑字串,若給予檔案路徑則會預設檔案為json格式且讀入成為opt物件,預設{}
    // * @param {Integer} [opt.serverPort=11007] 輸入伺服器通訊port整數,預設11007
    // * @param {Boolean} [opt.useCheckUser=false] 輸入是否檢查使用者資訊布林值,預設false
    // * @param {Function} [opt.getUserById=null] 輸入當useCheckUser=true時依照使用者ID取得使用者資訊物件函數,預設null
    // * @param {Boolean} [opt.useExcludeWhenNotAdmin=false] 輸入使用ORM的select方法時是否自動刪除數據內isActive欄位之布林值,預設false
    // * @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
    // * @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
    // * @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
    // * @param {Array} [opt.webBackgoundGradientColors=['#FFE0B2', '#FFCC80', '#FFF59D', '#F2D6A2', '#F0CC88']] 輸入使用者登入頁顏色陣列,前3色為背景線性linear-gradient,第4色為登入區塊背景色,第5色為站台logo背景色,若不給則預設白色。預設['#FFE0B2', '#FFCC80', '#FFF59D', '#F2D6A2', '#F0CC88']
    // * @param {String} [opt.webKey=''] 輸入站台識別字串,預設''
    // * @param {String} [opt.salt=''] 輸入密碼加鹽字串,預設''
    // * @param {Integer} [opt.minExpired=30] 輸入創建或更新金鑰有效時間整數,單位分鐘(min),預設30
    // * @param {Integer} [opt.minForAccountLoginFailed=10] 輸入限制帳號最大登入失敗(密碼錯誤)次數之判準時間整數,單位分鐘(min),預設10
    // * @param {Integer} [opt.numForAccountLoginFailed=3] 輸入限制帳號最大登入失敗(密碼錯誤)之次數整數,預設3
    // * @param {Integer} [opt.minBlockForAccountLoginFailed=30] 輸入限制帳號最大登入失敗(密碼錯誤)次數之觸發後封鎖時間整數,單位分鐘(min),預設30
    // * @param {Integer} [opt.minForTokenCallApi=10] 輸入限制token最大調用API次數之判準時間整數,單位分鐘(min),預設10
    // * @param {Integer} [opt.numForTokenCallApi=1000] 輸入限制token最大調用API之次數整數,預設1000
    // * @param {Integer} [opt.minBlockForTokenCallApi=30] 輸入限制token最大調用API次數之觸發後封鎖時間整數,單位分鐘(min),預設30
    // * @param {Integer} [opt.minForIpCallApi=10] 輸入限制IP最大調用API次數之判準時間整數,單位分鐘(min),預設10
    // * @param {Integer} [opt.numForIpCallApi=10000] 輸入限制IP最大調用API之次數整數,預設10000
    // * @param {Integer} [opt.minBlockForIpCallApi=30] 輸入限制IP最大調用API次數之觸發後封鎖時間整數,單位分鐘(min),預設30
    // * @param {String} [opt.userLogo=''] 輸入使用者logo字串,採base64格式,預設''
    // * @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
    // * @param {String} [opt.mappingBy='email'] 輸入外部系統識別使用者token後所提供之資料物件,與權限系統之使用者資料物件,兩者間查找之對應欄位,可選'id'、'email'、'name',預設'email'


    //serverPort
    let serverPort = get(opt, 'serverPort')
    if (!ispint(serverPort)) {
        serverPort = 11007
    }
    serverPort = cint(serverPort)


    //useCheckUser
    let useCheckUser = get(opt, 'useCheckUser', false)


    //getUserById
    let getUserById = get(opt, 'getUserById', null)


    //useExcludeWhenNotAdmin
    let useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin', false)


    //webName
    let webName = get(opt, 'webName', {})


    //webDescription
    let webDescription = get(opt, 'webDescription', {})


    //webLogo
    let webLogo = get(opt, 'webLogo', '')


    //webBackgoundGradientColors
    let webBackgoundGradientColors = get(opt, 'webBackgoundGradientColors', [])
    if (!isearr(webBackgoundGradientColors)) {
        webBackgoundGradientColors = ['#FFE0B2', '#FFCC80', '#FFF59D', '#F2D6A2', '#F0CC88']
    }


    //webKey
    let webKey = get(opt, 'webKey', '')


    //salt
    let salt = get(opt, 'salt', '')


    //minExpired, 使用者成功登入後產生token之有效時間(分鐘)
    let minExpired = get(opt, 'minExpired', '')
    if (!ispnum(minExpired)) {
        minExpired = 30
    }


    //minForAccountLoginFailed, 限制帳號最大登入失敗(密碼錯誤)次數之判準時間(分鐘)
    let minForAccountLoginFailed = get(opt, 'minForAccountLoginFailed', '')
    if (!ispnum(minForAccountLoginFailed)) {
        minForAccountLoginFailed = 10
    }


    //numForAccountLoginFailed, 限制帳號最大登入失敗(密碼錯誤)之次數
    let numForAccountLoginFailed = get(opt, 'numForAccountLoginFailed', '')
    if (!ispnum(numForAccountLoginFailed)) {
        numForAccountLoginFailed = 3
    }


    //minBlockForAccountLoginFailed, 限制帳號最大登入失敗(密碼錯誤)次數之觸發後封鎖時間(分鐘)
    let minBlockForAccountLoginFailed = get(opt, 'minBlockForAccountLoginFailed', '')
    if (!ispnum(minBlockForAccountLoginFailed)) {
        minBlockForAccountLoginFailed = 30
    }


    //minForTokenCallApi, 限制token最大調用API次數之判準時間(分鐘)
    let minForTokenCallApi = get(opt, 'minForTokenCallApi', '')
    if (!ispnum(minForTokenCallApi)) {
        minForTokenCallApi = 10
    }


    //numForTokenCallApi, 限制token最大調用API之次數
    let numForTokenCallApi = get(opt, 'numForTokenCallApi', '')
    if (!ispnum(numForTokenCallApi)) {
        numForTokenCallApi = 1000
    }


    //minBlockForTokenCallApi, 限制token最大調用API次數之觸發後封鎖時間(分鐘)
    let minBlockForTokenCallApi = get(opt, 'minBlockForTokenCallApi', '')
    if (!ispnum(minBlockForTokenCallApi)) {
        minBlockForTokenCallApi = 30
    }


    //minForIpCallApi, 限制IP最大調用API次數之判準時間(分鐘)
    let minForIpCallApi = get(opt, 'minForIpCallApi', '')
    if (!ispnum(minForIpCallApi)) {
        minForIpCallApi = 10
    }


    //numForIpCallApi, 限制IP最大調用API之次數
    let numForIpCallApi = get(opt, 'numForIpCallApi', '')
    if (!ispnum(numForIpCallApi)) {
        numForIpCallApi = 10000
    }


    //minBlockForIpCallApi, 限制IP最大調用API次數之觸發後封鎖時間(分鐘)
    let minBlockForIpCallApi = get(opt, 'minBlockForIpCallApi', '')
    if (!ispnum(minBlockForIpCallApi)) {
        minBlockForIpCallApi = 30
    }


    //是否啟用要求使用者定期更換密碼
    // 若是,預設6個月更換密碼,是否允許使用同樣密碼


    //userLogo
    let userLogo = get(opt, 'userLogo', '')


    //subfolder
    let subfolder = get(opt, 'subfolder', '')
    if (isestr(subfolder)) {
        if (strright(subfolder, 1) === '/') { //右邊不需要給「/」
            subfolder = strdelright(subfolder, 1)
        }
        if (strleft(subfolder, 1) !== '/') { //左邊需要給「/」
            subfolder = `/${subfolder}`
        }
    }


    //params
    let showLanguage = get(opt, 'showLanguage', 'n')
    let language = get(opt, 'language', 'eng')
    let showModeEditUsers = get(opt, 'showModeEditUsers', 'n')
    let modeEditUsers = get(opt, 'modeEditUsers', 'n')
    let showModeEditTokens = get(opt, 'showModeEditTokens', 'n')
    let modeEditTokens = get(opt, 'modeEditTokens', 'n')
    let showModeEditIps = get(opt, 'showModeEditIps', 'n')
    let modeEditIps = get(opt, 'modeEditIps', 'n')

    //是否可於登入頁提供申請註冊使用者按鈕
    //是否使用圖形驗證碼模組

    //email模板
    // 註冊驗證信模板
    // 變更使用者密碼驗證信模板
    // 註冊請求通知(給SSO系統管理員)信模板
    // 註冊核可成功通知信模板
    // 變更使用者資訊通知信模板


    //mappingBy
    let mappingBy = get(opt, 'mappingBy', '')
    if (mappingBy !== 'id' && mappingBy !== 'email' && mappingBy !== 'name') {
        mappingBy = 'email'
    }
    // console.log('mappingBy', mappingBy)


    //kpLangExt
    let kpLangExt = get(opt, 'kpLangExt', null)


    //WServOrm
    let optWServOrm = {
        useCheckUser,
        getUserById,
        useExcludeWhenNotAdmin,
    }
    let wp = {}
    try {
        wp = WServOrm(ds, WOrm, url, db, optWServOrm)
    }
    catch (err) {
        console.log(err)
    }
    let { woItems, procOrm } = wp


    //getWebInfor
    let getWebInfor = () => {
        return {

            webName,
            webDescription,
            webLogo,
            webBackgoundGradientColors,
            webKey,
            userLogo,

            showLanguage,
            language,

            showModeEditUsers,
            modeEditUsers,
            showModeEditTokens,
            modeEditTokens,
            showModeEditIps,
            modeEditIps,

            kpLangExt,

        }
    }


    //procCore, procProtect
    let p = procCore(woItems, procOrm, { salt, minExpired })
    let pp = procProtect(woItems, p, {
        minForAccountLoginFailed,
        numForAccountLoginFailed,
        minBlockForAccountLoginFailed,
        minForTokenCallApi,
        numForTokenCallApi,
        minBlockForTokenCallApi,
        minForIpCallApi,
        numForIpCallApi,
        minBlockForIpCallApi,
    })


    // //parseToken
    // let parseToken = (req) => {

    //     //token
    //     let token = get(req, 'query.token', '')
    //     // console.log('token', token)

    //     return token
    // }


    // //getTokenUser
    // let getTokenUser = async(token) => {

    //     //userSelf
    //     let userSelf = null
    //     if (isestr(token)) {
    //         userSelf = getUserByToken(token)
    //         if (ispm(userSelf)) {
    //             userSelf = await userSelf
    //         }
    //     }
    //     // console.log('userSelf', userSelf)

    //     //check
    //     if (!iseobj(userSelf)) {
    //         console.log(`token`, token)
    //         console.log(`can not find the user from token`)
    //         return Promise.reject(`can not find the user from token`)
    //     }

    //     //check userSelf
    //     if (true) {
    //         let id = get(userSelf, 'id', '')
    //         if (!isestr(id)) {
    //             console.log('userSelf', userSelf)
    //             console.log('can not get the userId')
    //             return Promise.reject(`can not get the userId`)
    //         }
    //         let email = get(userSelf, 'email', '')
    //         if (!isestr(email)) {
    //             console.log('userSelf', userSelf)
    //             console.log('can not get the email of user')
    //             return Promise.reject(`can not get the email of user`)
    //         }
    //         let name = get(userSelf, 'name', '')
    //         if (!isestr(name)) {
    //             console.log('userSelf', userSelf)
    //             console.log('can not get userName')
    //             return Promise.reject(`can not get userName`)
    //         }
    //         let isAdmin = get(userSelf, 'isAdmin', '')
    //         if (isAdmin !== 'y' && isAdmin !== 'n') {
    //             console.log('userSelf', userSelf)
    //             console.log('userSelf.isAdmin is not y or n', userSelf.isAdmin)
    //             console.log('can not get the role of user')
    //             return Promise.reject(`can not get the role of user`)
    //         }
    //     }

    //     //須反查sso內users, 提供正規化屬性
    //     let vSelf = get(userSelf, mappingBy, '')
    //     // console.log('vSelf', vSelf)

    //     //check
    //     if (!isestr(vSelf)) {
    //         console.log('userSelf', userSelf)
    //         console.log('mappingBy', mappingBy)
    //         console.log('can not get the prop of user by mappingBy')
    //         return Promise.reject(`can not get the prop of user by mappingBy`)
    //     }

    //     //userFind
    //     let userFind = await woItems.users.select({ [mappingBy]: vSelf, isActive: 'y' })
    //     userFind = get(userFind, 0, null)
    //     // console.log('userFind', userFind)

    //     //check
    //     if (!iseobj(userFind)) {
    //         console.log('userSelf', userSelf)
    //         console.log('mappingBy', mappingBy)
    //         console.log('can not get the user from sso')
    //         return Promise.reject(`can not get the user from sso`)
    //     }

    //     //複寫isAdmin
    //     let isAdminSrc = get(userSelf, 'isAdmin', '')
    //     let isAdminSelf = get(userFind, 'isAdmin', '')
    //     if (isAdminSrc !== isAdminSelf) {
    //         userFind.isAdmin = isAdminSelf
    //     }
    //     // console.log('userFind(isAdmin)', userFind)

    //     return userFind
    // }


    // //getAndVerifyBrowserTokenUser
    // let getAndVerifyBrowserTokenUser = async (token, caller = '') => {

    //     //getTokenUser
    //     let userSelf = await getTokenUser(token)

    //     //verifyClientUser
    //     let b = verifyClientUser(userSelf, caller)
    //     if (ispm(b)) {
    //         b = await b
    //     }

    //     //check
    //     if (!b) {
    //         console.log('userSelf', userSelf)
    //         console.log(`user does not have permission`)
    //         return Promise.reject(`user does not have permission`)
    //     }

    //     return userSelf
    // }


    // //getAndVerifyAppTokenUser
    // let getAndVerifyAppTokenUser = async (token, caller = '') => {

    //     //getTokenUser
    //     let userSelf = await getTokenUser(token)

    //     //verifyAppUser
    //     let b = verifyAppUser(userSelf, caller)
    //     if (ispm(b)) {
    //         b = await b
    //     }

    //     //check
    //     if (!b) {
    //         console.log('userSelf', userSelf)
    //         console.log(`user does not have permission`)
    //         return Promise.reject(`user does not have permission`)
    //     }

    //     return userSelf
    // }


    // //parsePayload
    // let parsePayload = async (req, key) => {

    //     //inp
    //     let inp = get(req, 'payload')

    //     //to obj
    //     if (isestr(inp)) {
    //         inp = j2o(inp)
    //     }

    //     //check
    //     if (!iseobj(inp)) {
    //         console.log('inp', inp)
    //         console.log('invalid inp from req')
    //         return Promise.reject(`invalid inp from req`)
    //     }

    //     //from
    //     let from = get(inp, 'from', '')

    //     //check
    //     if (!isestr(from)) {
    //         console.log('inp', inp)
    //         console.log('from', from)
    //         console.log('invalid from from inp')
    //         return Promise.reject(`invalid from from inp`)
    //     }


    //     //vs
    //     let vs = get(inp, key, [])

    //     //check
    //     if (!isearr(vs)) {
    //         console.log('inp', inp)
    //         console.log(key, vs)
    //         console.log(`invalid ${key} from inp`)
    //         return Promise.reject(`invalid ${key} from inp`)
    //     }

    //     //resave
    //     inp = {
    //         from,
    //         [key]: vs,
    //     }

    //     return inp
    // }


    // //syncAndReplaceRows
    // let syncAndReplaceRows = async (params, key) => {

    //     //from
    //     let from = get(params, 'from', '')
    //     // console.log('from', from)

    //     //check
    //     if (!isestr(from)) {
    //         console.log('params', params)
    //         console.log('from', from)
    //         console.log(`invalid from`)
    //         return Promise.reject(`invalid from`)
    //     }

    //     //vs
    //     let vs = get(params, key, [])
    //     // console.log(key, vs)

    //     //check
    //     if (!isearr(vs)) {
    //         console.log('params', params)
    //         console.log(key, vs)
    //         console.log(`invalid ${key}`)
    //         return Promise.reject(`invalid ${key}`)
    //     }

    //     //save from
    //     each(vs, (v, k) => {
    //         vs[k].from = from
    //     })

    //     //delAll from
    //     await woItems[key].delAll({ from })

    //     //insert
    //     let r = await woItems[key].insert(vs)

    //     return r
    // }


    //pathStaticFiles
    let pathStaticFiles = 'dist'
    let npmPathStaticFiles = './node_modules/w-web-sso/dist'
    if (fsIsFolder(npmPathStaticFiles)) {
        pathStaticFiles = npmPathStaticFiles
    }
    // console.log('pathStaticFiles', pathStaticFiles)


    //subfolder
    let fnEntryIn = 'index.tmp'
    let fnEntryOut = 'index.html'
    try {
        let fpEntryIn = path.resolve(pathStaticFiles, fnEntryIn)
        if (!fsIsFile(fpEntryIn)) {
            fpEntryIn = path.resolve(pathStaticFiles, fnEntryOut) //本機開發另使用html替代tmp
        }
        if (!fsIsFile(fpEntryIn)) {
            console.log('fpEntryIn', fpEntryIn)
            throw new Error(`invalid fpEntryIn`)
        }
        let fpEntryOut = path.resolve(pathStaticFiles, fnEntryOut)
        let c = fs.readFileSync(fpEntryIn, 'utf8')
        c = replace(c, '/msso/', '{sfd}/') //方法同genEntry
        c = replace(c, '{sfd}', subfolder)
        c = replace(c, '{language}', language)
        fs.writeFileSync(fpEntryOut, c, 'utf8')
    }
    catch (err) {
        console.log(err)
        console.log(`can not generate ${fnEntryOut}`)
    }


    //apis
    let apis = [
        // {
        //     method: 'GET',
        //     path: '/api/someAPI',
        //     handler: async function (req, res) {

        //         // //token
        //         // let token = get(req, 'query.token', '')

        //         return 'someAPI'
        //     },
        // },

        {
            method: 'GET',
            path: '/api/logoutSsoUser',
            handler: async function (req, res) {
                // console.log('logoutSsoUser', req)

                async function core() {

                    //token
                    let token = get(req, 'query.token', '')
                    // console.log('token', token)

                    //logOutByToken
                    let b = await p.logOutByToken(token)
                    // console.log('b', b)

                    return b
                }

                //pm2resolve core
                let r = await pm2resolve(core)()
                if (isErr(r.msg)) {
                    r.msg = r.msg.message
                }
                // console.log('logoutSsoUser', r)

                return r
            },
        },

        {
            method: 'GET',
            path: '/api/checkToken',
            handler: async function (req, res) {
                // console.log('checkToken', req)

                async function core() {

                    //token
                    let token = get(req, 'query.token', '')
                    // console.log('token', token)

                    //callApiByToken
                    pp.callApiByToken(token)

                    //checkToken
                    let b = await p.checkToken(token)
                    // console.log('b', b)

                    return b
                }

                //pm2resolve core
                let r = await pm2resolve(core)()
                if (isErr(r.msg)) {
                    r.msg = r.msg.message
                }
                // console.log('checkToken', r)

                return r
            },
        },

        {
            method: 'GET',
            path: '/api/refreshToken',
            handler: async function (req, res) {
                // console.log('refreshToken', req)

                async function core() {

                    //token
                    let token = get(req, 'query.token', '')
                    // console.log('token', token)

                    //refreshToken
                    let b = await p.refreshToken(token)
                    // console.log('b', b)

                    return b
                }

                //pm2resolve core
                let r = await pm2resolve(core)()
                if (isErr(r.msg)) {
                    r.msg = r.msg.message
                }
                // console.log('refreshToken', r)

                return r
            },
        },

        {
            method: 'GET',
            path: '/api/logOutByToken',
            handler: async function (req, res) {
                // console.log('logOutByToken', req)

                async function core() {

                    //token
                    let token = get(req, 'query.token', '')
                    // console.log('token', token)

                    //logOutByToken
                    let b = await p.logOutByToken(token)
                    // console.log('b', b)

                    return b
                }

                //pm2resolve core
                let r = await pm2resolve(core)()
                if (isErr(r.msg)) {
                    r.msg = r.msg.message
                }
                // console.log('logOutByToken', r)

                return r
            },
        },

        {
            method: 'GET',
            path: '/api/getSsoUsersList',
            handler: async function (req, res) {
                // console.log('getSsoUsersList', req)

                async function core() {

                    //token
                    let token = get(req, 'query.token', '')
                    // console.log('token', token)

                    //callApiByToken
                    pp.callApiByToken(token)

                    //checkTokenAndGetUsersList
                    let us = await p.checkTokenAndGetUsersList(token)
                    // console.log('us', us)

                    return us
                }

                //pm2resolve core
                let r = await pm2resolve(core)()
                if (isErr(r.msg)) {
                    r.msg = r.msg.message
                }
                // console.log('getSsoUsersList', r)

                return r
            },
        },

        {
            method: 'GET',
            path: '/api/getSsoUserInfor',
            handler: async function (req, res) {
                // console.log('getSsoUserInfor', req)

                async function core() {

                    //token
                    let token = get(req, 'query.token', '')
                    // console.log('token', token)

                    //key
                    let key = get(req, 'query.key', '')
                    // console.log('key', key)

                    //value
                    let value = get(req, 'query.value', '')
                    // console.log('value', value)

                    //callApiByToken
                    pp.callApiByToken(token)

                    //userTarget
                    let userTarget = null
                    if (key === 'token') {
                        userTarget = await p.checkTokenAndGetUserByToken(token, value)
                        // console.log('checkTokenAndGetUserByToken userTarget', userTarget)
                    }
                    else {
                        userTarget = await p.checkTokenAndGetUserInfor(token, key, value)
                        // console.log('checkTokenAndGetUserInfor userTarget', userTarget)
                    }

                    //check
                    if (!iseobj(userTarget)) {
                        console.log('token', token)
                        console.log('key', key, 'value', value)
                        console.log(`token does not have permission`)
                        return Promise.reject(`token does not have permission`)
                    }

                    return userTarget
                }

                //pm2resolve core
                let r = await pm2resolve(core)()
                if (isErr(r.msg)) {
                    r.msg = r.msg.message
                }
                // console.log('getSsoUserInfor', r)

                return r
            },
        },

    ]


    //WServHapiServer
    instWServHapiServer = new WServHapiServer({
        port: opt.serverPort,
        pathStaticFiles,
        apiName: 'api',
        apis,
        tokenType: 'Bearer',
        verifyConn: async ({ apiType, authorization, headers, query }) => {
            // console.log('headers', headers)
            // let origin = headers.origin
            // let referer = headers.referer

            //getIpByHeaders
            let ip = pp.getIpByHeaders(headers)
            // console.log('ip', ip)

            //callApiByIp
            pp.callApiByIp(ip)

            //getBlockedByIp
            let b = true
            await pp.getBlockedByIp(ip)
                .then((res) => {
                    // console.log('pp.getBlockedByIp(ip) then', res)
                    b = res
                })
                .catch(() => {
                    // console.log('pp.getBlockedByIp(ip) catch', err)
                    b = true
                })
            // console.log('pp.getBlockedByIp(ip)', b)

            //b改為代表通行(未封鎖)狀態
            b = !b

            // //getInforsByIpCallApi
            // let rs = await pp.getInforsByIpCallApi(ip)
            // console.log('getInforsByIpCallApi', [
            //     rs[0],
            //     rs[rs.length - 1],
            // ], rs.length)

            //logshow
            if (!b) {
                console.log(`block ip[${ip}]`) //[tag:測試:顯示被封鎖ip]
            }
            // b = true //強制通行

            return b
        },
        getUserIdByToken: async (token) => { //可使用async或sync函數
            return '' //不檢核, 查找與變更數據皆由kpFunExt內各函數各自檢核
        },
        corsOrigins: ['*'],
        useDbOrm: false,
        // kpOrm: woItems,
        // operOrm: procOrm, //procOrm的輸入為: userId, tableName, methodName, input
        tableNamesExec: [],
        methodsExec: [], //['select', 'insert', 'save', 'del', 'delAll'],
        tableNamesSync: [],
        kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數

            getWebInfor: async (_t) => {
                return getWebInfor()
            },

            checkToken: async(_t, token) => { //sso前端通過$fapi.checkToken調用
                return await p.checkToken(token)
            },
            refreshToken: async(_t, token) => { //sso前端通過$fapi.refreshToken調用
                return await p.refreshToken(token)
            },
            loginByAccountAndPassword: async (_t, account, password) => {
                return await pp.loginByAccountAndPassword(account, password)
            },

            getUserByToken: async (_t, token) => {
                return await p.checkTokenAndGetUserByToken(token, token)
            },
            getUserInfor: async (_t, token, key, value) => {
                return await p.checkTokenAndGetUserInfor(token, key, value)
            },
            getUsersList: async (_t, token) => {
                return await p.checkTokenAndGetUsersList(token)
            },
            updateUsersList: async(_t, token, rows) => {
                return await p.checkTokenAndUpdateUsersList(token, rows)
            },

            getTokensList: async (_t, token) => {
                return await p.checkTokenAndGetTokensList(token)
            },
            updateTokensList: async(_t, token, rows) => {
                return await p.checkTokenAndUpdateTokensList(token, rows)
            },

            getIpsList: async (_t, token) => {
                return await p.checkTokenAndGetIpsList(token)
            },
            updateIpsList: async(_t, token, rows) => {
                return await p.checkTokenAndUpdateIpsList(token, rows)
            },

            // getSettings: async (_t, token) => {

            //     //checkToken
            //     await p.checkToken(token)

            //     return procSettings.getSettings()
            // },
            // updateSettings: async (_t, token, st) => {

            //     //checkToken
            //     await p.checkToken(token)

            //     return procSettings.setSettings(st)
            // },

        },
        fnTableTags: 'tableTags-web-sso.json',
    })


    return instWServHapiServer
}


export default WWebSso