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 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 procSettings from './procSettings.mjs'
/**
* 權限伺服器
*
* @class
* @param {Function} WOrm 輸入資料庫ORM函數
* @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://sername:password@$127.0.0.1:27017'
* @param {String} db 輸入資料庫名稱字串
* @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
* @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
* @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
* @param {String} [pathSettings='./settings'] 輸入設定檔案路徑字串,預設'./settings'
* @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
* @example
*
*
*
*/
function WWebSso(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, 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 getUserByToken
if (!isfun(getUserByToken)) {
console.log('invalid getUserByToken', getUserByToken)
throw new Error('invalid getUserByToken')
}
//check verifyClientUser
if (!isfun(verifyClientUser)) {
console.log('invalid verifyClientUser', verifyClientUser)
throw new Error('invalid verifyClientUser')
}
//check verifyAppUser
if (!isfun(verifyAppUser)) {
console.log('invalid verifyAppUser', verifyAppUser)
throw new Error('invalid verifyAppUser')
}
//check pathSettings
if (!fsIsFile(pathSettings)) {
pathSettings = './settings.json'
}
//setFilepath
procSettings.setFilepath(pathSettings)
//getSettings
let opt = procSettings.getSettings()
// * @param {Object|String} [opt={}] 輸入設定物件或設定檔檔案路徑字串,若給予檔案路徑則會預設檔案為json格式且讀入成為opt物件,預設{}
// * @param {Integer} [opt.serverPort=11007] 輸入伺服器通訊port,預設11006
// * @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 {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']
}
//webKey
let webKey = get(opt, 'webKey', '')
//salt
let salt = get(opt, 'salt', '')
//timeExpired
let timeExpired = get(opt, 'timeExpired', '')
if (ispnum(timeExpired)) {
timeExpired = 30
}
//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')
//是否可於登入頁提供申請註冊使用者按鈕 bbb
//是否使用圖形驗證碼模組
// - 使用者成功登入後產生token之有效(過期)時間m(30)分鐘
// - 使用者n(10)分鐘內最大登入失敗次數m(3),若觸發則進入封鎖狀態p(30)分鐘
// - 使用者n(10)分鐘內最大調用API次數m(1000),若觸發則進入封鎖狀態p(30)分鐘
// - IP使用者n(10)分鐘內最大調用API次數m(10000),若觸發則IP進入封鎖狀態p(30)分鐘
// - 清除(移動至備份表)紀錄使用者與IP連線log機制,偵測時間n(1)天,保留最新資料時間m(60)天
//是否啟用要求使用者定期更換密碼
// 若是,預設6個月更換密碼,是否允許使用同樣密碼
//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 = (_t) => {
return {
webName,
webDescription,
webLogo,
webBackgoundGradientColors,
webKey,
showLanguage,
language,
showModeEditUsers,
modeEditUsers,
kpLangExt,
}
}
//procCore
let p = procCore(woItems, procOrm, { salt, timeExpired })
//detect
let stCleanTokens = 'none'
setInterval(() => {
if (stCleanTokens === 'doing') {
return
}
stCleanTokens = 'doing'
p.cleanTokens()
.finally(() => {
stCleanTokens = 'none'
})
}, 1000)
// //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: '/getWebInfor',
// handler: async function (req, res) {
// return getWebInfor() //bbb 應該已不用, 前端載入通訊器後由$fapi.getWebInfor呼叫
// },
// },
{
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)()
// 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)
//checkToken
let b = await p.checkToken(token)
// console.log('b', b)
return b
}
//pm2resolve core
let r = await pm2resolve(core)()
// console.log('checkToken', 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)
//getUsersList
let us = await p.getUsersList(token)
// console.log('us', us)
return us
}
//pm2resolve core
let r = await pm2resolve(core)()
// 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)
//userTarget
let userTarget = null
if (key === 'token') {
userTarget = await p.getUserByToken(value)
// console.log('getUserByToken userTarget', userTarget)
}
else {
userTarget = await p.getUserInfor(token, key, value)
// console.log('getUserInfor 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)()
// console.log('getSsoUserInfor', r)
return r
},
},
]
// //tableNamesExec, tableNamesSync
// let tableNamesExec = [] //keys(ds)
// // let tableNamesSync = filter(tableNamesExec, (v) => {
// // return strright(v, 5) !== 'Items'//不同步數據
// // })
// let tableNamesSync = []
//WServHapiServer
instWServHapiServer = new WServHapiServer({
port: opt.serverPort,
pathStaticFiles,
apis,
verifyConn: () => {
return true
},
getUserIdByToken: async (token) => { //可使用async或sync函數
return ''
},
corsOrigins: ['*'],
useDbOrm: false,
// kpOrm: woItems,
// operOrm: procOrm, //procOrm的輸入為: userId, tableName, methodName, input
tableNamesExec: [],
methodsExec: [], //['select', 'insert', 'save', 'del', 'delAll'],
tableNamesSync: [],
kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數
getWebInfor,
getUserByToken: (_t, token) => {
return p.getUserByToken(token)
},
loginByAccountAndPassword: (_t, account, password) => {
return p.loginByAccountAndPassword(account, password)
},
checkToken: (_t, token) => {
return p.checkToken(token)
},
refreshToken: (_t, token) => {
return p.refreshToken(token)
},
logOutByToken: (_t, token) => {
return p.logOutByToken(token)
},
getUsersList: (_t, token) => {
return p.getUsersList(token)
},
updateUsersList: (_t, token, rows) => {
return p.updateUsersList(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