AccessControl.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. const db = require('../plugin/DataBase/db')
  2. const Redis = require('../plugin/DataBase/Redis')
  3. const {
  4. DEFAULT_PERMISSION_POINTS,
  5. DEFAULT_PERMISSION_RESOURCE_RULES,
  6. DEFAULT_BASIC_USER_PERMISSION_CODES,
  7. LEGACY_ROLE_PERMISSION_MAP
  8. } = require('./PermissionCatalog')
  9. class AccessControl {
  10. constructor() {
  11. this.schemaReady = false
  12. }
  13. parseArray(value) {
  14. if (Array.isArray(value)) return value
  15. if (!value) return []
  16. if (typeof value !== 'string') return []
  17. try {
  18. const parsed = JSON.parse(value)
  19. return Array.isArray(parsed) ? parsed : []
  20. } catch (_) {
  21. return []
  22. }
  23. }
  24. normalizeCodes(codes) {
  25. if (!codes) return []
  26. const list = Array.isArray(codes) ? codes : [codes]
  27. return [...new Set(list.map(code => String(code || '').trim()).filter(Boolean))]
  28. }
  29. async ensurePermissionSchema() {
  30. if (this.schemaReady) return
  31. for (const point of DEFAULT_PERMISSION_POINTS) {
  32. await db.query(
  33. `INSERT INTO permission_points
  34. (code, name, category, scope_type, page_route_name, enabled, remark)
  35. VALUES (?, ?, ?, ?, ?, 1, ?)
  36. ON DUPLICATE KEY UPDATE
  37. name = VALUES(name),
  38. category = VALUES(category),
  39. scope_type = VALUES(scope_type),
  40. page_route_name = VALUES(page_route_name),
  41. remark = VALUES(remark)`,
  42. [point.code, point.name, point.category, point.scope_type, point.page_route_name || null, point.remark || '']
  43. )
  44. }
  45. for (const rule of DEFAULT_PERMISSION_RESOURCE_RULES) {
  46. const requiredCodes = JSON.stringify(this.normalizeCodes(rule.required_codes))
  47. await db.query(
  48. `INSERT INTO permission_resource_rules
  49. (resource_type, resource_key, api_method, api_path, required_codes, enabled, remark)
  50. VALUES (?, ?, ?, ?, ?, 1, ?)
  51. ON DUPLICATE KEY UPDATE
  52. api_method = VALUES(api_method),
  53. api_path = VALUES(api_path),
  54. required_codes = VALUES(required_codes),
  55. remark = VALUES(remark)`,
  56. [
  57. rule.resource_type,
  58. rule.resource_key,
  59. rule.api_method || null,
  60. rule.api_path || null,
  61. requiredCodes,
  62. rule.remark || ''
  63. ]
  64. )
  65. }
  66. this.schemaReady = true
  67. }
  68. async checkSession(uuid, session) {
  69. return (await Redis.get(`userSession:${uuid}`)) === session
  70. }
  71. async isBanned(uuid) {
  72. const sql = 'SELECT COALESCE(is_banned, 0) AS is_banned FROM users WHERE uuid = ?'
  73. const rows = await db.query(sql, [uuid])
  74. return Number(rows[0]?.is_banned) === 1
  75. }
  76. async invalidateSession(uuid) {
  77. await Redis.del(`userSession:${uuid}`)
  78. }
  79. async getPermission(uuid) {
  80. const sql = 'SELECT permission FROM users WHERE uuid = ?'
  81. const rows = await db.query(sql, [uuid])
  82. return this.parseArray(rows?.[0]?.permission)
  83. }
  84. isSuperPermission(permission) {
  85. return this.parseArray(permission).includes('admin')
  86. }
  87. async isSuperAdmin(uuid) {
  88. const permission = await this.getPermission(uuid)
  89. return this.isSuperPermission(permission)
  90. }
  91. async getPermissionPoints() {
  92. await this.ensurePermissionSchema()
  93. const rows = await db.query(`
  94. SELECT id, code, name, category, scope_type, page_route_name, enabled, remark
  95. FROM permission_points
  96. ORDER BY category, id
  97. `)
  98. return rows || []
  99. }
  100. async getUserDirectPermissionCodes(uuid) {
  101. await this.ensurePermissionSchema()
  102. const rows = await db.query(
  103. `SELECT permission_code FROM user_permission_points WHERE user_uuid = ? ORDER BY permission_code`,
  104. [uuid]
  105. )
  106. return (rows || []).map(row => row.permission_code)
  107. }
  108. async getUserDeniedBasicPermissionCodes(uuid) {
  109. await this.ensurePermissionSchema()
  110. const rows = await db.query(
  111. `SELECT permission_code FROM user_basic_permission_denials WHERE user_uuid = ? ORDER BY permission_code`,
  112. [uuid]
  113. )
  114. const denied = (rows || []).map(row => row.permission_code)
  115. const basicSet = new Set(DEFAULT_BASIC_USER_PERMISSION_CODES)
  116. return this.normalizeCodes(denied.filter(code => basicSet.has(code)))
  117. }
  118. getEnabledBasicPermissionCodes(deniedBasicCodes = []) {
  119. const deniedSet = new Set(this.normalizeCodes(deniedBasicCodes))
  120. return DEFAULT_BASIC_USER_PERMISSION_CODES.filter(code => !deniedSet.has(code))
  121. }
  122. async getUserPermissionCodes(uuid) {
  123. await this.ensurePermissionSchema()
  124. const legacyRoles = await this.getPermission(uuid)
  125. const directCodes = await this.getUserDirectPermissionCodes(uuid)
  126. const deniedBasicCodes = await this.getUserDeniedBasicPermissionCodes(uuid)
  127. const roleCodes = legacyRoles.flatMap(role => LEGACY_ROLE_PERMISSION_MAP[role] || [])
  128. return this.normalizeCodes([
  129. ...this.getEnabledBasicPermissionCodes(deniedBasicCodes),
  130. ...legacyRoles,
  131. ...roleCodes,
  132. ...directCodes
  133. ])
  134. }
  135. async setUserPermissionCodes(uuid, codes) {
  136. await this.ensurePermissionSchema()
  137. const permissionCodes = this.normalizeCodes(codes)
  138. const points = await this.getPermissionPoints()
  139. const validCodes = new Set(points.map(point => point.code))
  140. const invalidCodes = permissionCodes.filter(code => !validCodes.has(code))
  141. if (invalidCodes.length > 0)
  142. throw new Error(`存在无效权限点:${invalidCodes.join(', ')}`)
  143. const conn = await db.connect()
  144. await conn.beginTransaction()
  145. try {
  146. await conn.execute(`DELETE FROM user_permission_points WHERE user_uuid = ?`, [uuid])
  147. for (const code of permissionCodes) {
  148. await conn.execute(
  149. `INSERT INTO user_permission_points (user_uuid, permission_code) VALUES (?, ?)`,
  150. [uuid, code]
  151. )
  152. }
  153. await conn.commit()
  154. } catch (error) {
  155. try { await conn.rollback() } catch (_) { }
  156. throw error
  157. }
  158. }
  159. async setUserDeniedBasicPermissionCodes(uuid, codes) {
  160. await this.ensurePermissionSchema()
  161. const basicSet = new Set(DEFAULT_BASIC_USER_PERMISSION_CODES)
  162. const deniedCodes = this.normalizeCodes(codes).filter(code => basicSet.has(code))
  163. const invalidCodes = this.normalizeCodes(codes).filter(code => !basicSet.has(code))
  164. if (invalidCodes.length > 0)
  165. throw new Error(`仅可关闭基础权限:${invalidCodes.join(', ')}`)
  166. const conn = await db.connect()
  167. await conn.beginTransaction()
  168. try {
  169. await conn.execute(`DELETE FROM user_basic_permission_denials WHERE user_uuid = ?`, [uuid])
  170. for (const code of deniedCodes) {
  171. await conn.execute(
  172. `INSERT INTO user_basic_permission_denials (user_uuid, permission_code) VALUES (?, ?)`,
  173. [uuid, code]
  174. )
  175. }
  176. await conn.commit()
  177. } catch (error) {
  178. try { await conn.rollback() } catch (_) { }
  179. throw error
  180. }
  181. }
  182. async getResourceRules() {
  183. await this.ensurePermissionSchema()
  184. const rows = await db.query(`
  185. SELECT id, resource_type, resource_key, api_method, api_path, required_codes, enabled, remark
  186. FROM permission_resource_rules
  187. ORDER BY FIELD(resource_type, 'page', 'action', 'api'), id
  188. `)
  189. return (rows || []).map(row => ({
  190. ...row,
  191. required_codes: this.parseArray(row.required_codes)
  192. }))
  193. }
  194. async getResourceRequiredCodes({ resourceType, resourceKey, method, path }) {
  195. await this.ensurePermissionSchema()
  196. let rows = []
  197. if (resourceType && resourceKey) {
  198. rows = await db.query(
  199. `SELECT required_codes
  200. FROM permission_resource_rules
  201. WHERE resource_type = ? AND resource_key = ? AND enabled = 1
  202. LIMIT 1`,
  203. [resourceType, resourceKey]
  204. )
  205. } else if (method && path) {
  206. rows = await db.query(
  207. `SELECT required_codes
  208. FROM permission_resource_rules
  209. WHERE resource_type = 'api' AND api_method = ? AND api_path = ? AND enabled = 1
  210. LIMIT 1`,
  211. [String(method).toUpperCase(), path]
  212. )
  213. }
  214. return this.parseArray(rows?.[0]?.required_codes)
  215. }
  216. async updateResourceRule({ id, required_codes, enabled }) {
  217. await this.ensurePermissionSchema()
  218. const requiredCodes = this.normalizeCodes(required_codes)
  219. const points = await this.getPermissionPoints()
  220. const validCodes = new Set(points.map(point => point.code))
  221. const invalidCodes = requiredCodes.filter(code => !validCodes.has(code))
  222. if (invalidCodes.length > 0)
  223. throw new Error(`存在无效权限点:${invalidCodes.join(', ')}`)
  224. const rows = await db.query(
  225. `UPDATE permission_resource_rules
  226. SET required_codes = ?, enabled = ?
  227. WHERE id = ?`,
  228. [JSON.stringify(requiredCodes), Number(enabled) === 0 ? 0 : 1, id]
  229. )
  230. return rows?.affectedRows === 1
  231. }
  232. async canAccess(uuid, requiredCodes) {
  233. const codes = this.normalizeCodes(requiredCodes)
  234. if (codes.length === 0) return true
  235. if (await this.isSuperAdmin(uuid)) return true
  236. const userCodes = await this.getUserPermissionCodes(uuid)
  237. return codes.some(code => userCodes.includes(code))
  238. }
  239. async checkJwAccount(uuid, username) {
  240. const sql = 'SELECT password FROM jw_account WHERE create_user = ? AND state = 1 AND username = ?'
  241. const rows = await db.query(sql, [uuid, username]);
  242. if (!rows || rows.length !== 1 || !rows[0].password)
  243. return false
  244. return rows[0]?.password
  245. }
  246. async getVerifiedJwAccount(uuid, accountId) {
  247. const id = Number(accountId)
  248. if (!id) return null
  249. const rows = await db.query(
  250. 'SELECT id, username, password, realname FROM jw_account WHERE id = ? AND create_user = ? AND state = 1',
  251. [id, uuid]
  252. )
  253. if (!rows?.length) return null
  254. return rows[0]
  255. }
  256. async listVerifiedJwAccounts(uuid) {
  257. const rows = await db.query(
  258. 'SELECT id, username, realname, deptName, className FROM jw_account WHERE create_user = ? AND state = 1 ORDER BY create_time DESC',
  259. [uuid]
  260. )
  261. return rows || []
  262. }
  263. }
  264. module.exports = new AccessControl();