AccessControl.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. remark = VALUES(remark)`,
  55. [
  56. rule.resource_type,
  57. rule.resource_key,
  58. rule.api_method || null,
  59. rule.api_path || null,
  60. requiredCodes,
  61. rule.remark || ''
  62. ]
  63. )
  64. }
  65. this.schemaReady = true
  66. }
  67. async checkSession(uuid, session) {
  68. return (await Redis.get(`userSession:${uuid}`)) === session
  69. }
  70. async isBanned(uuid) {
  71. const sql = 'SELECT COALESCE(is_banned, 0) AS is_banned FROM users WHERE uuid = ?'
  72. const rows = await db.query(sql, [uuid])
  73. return Number(rows[0]?.is_banned) === 1
  74. }
  75. async invalidateSession(uuid) {
  76. await Redis.del(`userSession:${uuid}`)
  77. }
  78. async getPermission(uuid) {
  79. const sql = 'SELECT permission FROM users WHERE uuid = ?'
  80. const rows = await db.query(sql, [uuid])
  81. return this.parseArray(rows?.[0]?.permission)
  82. }
  83. isSuperPermission(permission) {
  84. return this.parseArray(permission).includes('admin')
  85. }
  86. async isSuperAdmin(uuid) {
  87. const permission = await this.getPermission(uuid)
  88. return this.isSuperPermission(permission)
  89. }
  90. async getPermissionPoints() {
  91. await this.ensurePermissionSchema()
  92. const rows = await db.query(`
  93. SELECT id, code, name, category, scope_type, page_route_name, enabled, remark
  94. FROM permission_points
  95. ORDER BY category, id
  96. `)
  97. return rows || []
  98. }
  99. async getUserDirectPermissionCodes(uuid) {
  100. await this.ensurePermissionSchema()
  101. const rows = await db.query(
  102. `SELECT permission_code FROM user_permission_points WHERE user_uuid = ? ORDER BY permission_code`,
  103. [uuid]
  104. )
  105. return (rows || []).map(row => row.permission_code)
  106. }
  107. async getUserDeniedBasicPermissionCodes(uuid) {
  108. await this.ensurePermissionSchema()
  109. const rows = await db.query(
  110. `SELECT permission_code FROM user_basic_permission_denials WHERE user_uuid = ? ORDER BY permission_code`,
  111. [uuid]
  112. )
  113. const denied = (rows || []).map(row => row.permission_code)
  114. const basicSet = new Set(DEFAULT_BASIC_USER_PERMISSION_CODES)
  115. return this.normalizeCodes(denied.filter(code => basicSet.has(code)))
  116. }
  117. getEnabledBasicPermissionCodes(deniedBasicCodes = []) {
  118. const deniedSet = new Set(this.normalizeCodes(deniedBasicCodes))
  119. return DEFAULT_BASIC_USER_PERMISSION_CODES.filter(code => !deniedSet.has(code))
  120. }
  121. async getUserPermissionCodes(uuid) {
  122. await this.ensurePermissionSchema()
  123. const legacyRoles = await this.getPermission(uuid)
  124. const directCodes = await this.getUserDirectPermissionCodes(uuid)
  125. const deniedBasicCodes = await this.getUserDeniedBasicPermissionCodes(uuid)
  126. const roleCodes = legacyRoles.flatMap(role => LEGACY_ROLE_PERMISSION_MAP[role] || [])
  127. return this.normalizeCodes([
  128. ...this.getEnabledBasicPermissionCodes(deniedBasicCodes),
  129. ...legacyRoles,
  130. ...roleCodes,
  131. ...directCodes
  132. ])
  133. }
  134. async setUserPermissionCodes(uuid, codes) {
  135. await this.ensurePermissionSchema()
  136. const permissionCodes = this.normalizeCodes(codes)
  137. const points = await this.getPermissionPoints()
  138. const validCodes = new Set(points.map(point => point.code))
  139. const invalidCodes = permissionCodes.filter(code => !validCodes.has(code))
  140. if (invalidCodes.length > 0)
  141. throw new Error(`存在无效权限点:${invalidCodes.join(', ')}`)
  142. const conn = await db.connect()
  143. await conn.beginTransaction()
  144. try {
  145. await conn.execute(`DELETE FROM user_permission_points WHERE user_uuid = ?`, [uuid])
  146. for (const code of permissionCodes) {
  147. await conn.execute(
  148. `INSERT INTO user_permission_points (user_uuid, permission_code) VALUES (?, ?)`,
  149. [uuid, code]
  150. )
  151. }
  152. await conn.commit()
  153. } catch (error) {
  154. try { await conn.rollback() } catch (_) { }
  155. throw error
  156. }
  157. }
  158. async setUserDeniedBasicPermissionCodes(uuid, codes) {
  159. await this.ensurePermissionSchema()
  160. const basicSet = new Set(DEFAULT_BASIC_USER_PERMISSION_CODES)
  161. const deniedCodes = this.normalizeCodes(codes).filter(code => basicSet.has(code))
  162. const invalidCodes = this.normalizeCodes(codes).filter(code => !basicSet.has(code))
  163. if (invalidCodes.length > 0)
  164. throw new Error(`仅可关闭基础权限:${invalidCodes.join(', ')}`)
  165. const conn = await db.connect()
  166. await conn.beginTransaction()
  167. try {
  168. await conn.execute(`DELETE FROM user_basic_permission_denials WHERE user_uuid = ?`, [uuid])
  169. for (const code of deniedCodes) {
  170. await conn.execute(
  171. `INSERT INTO user_basic_permission_denials (user_uuid, permission_code) VALUES (?, ?)`,
  172. [uuid, code]
  173. )
  174. }
  175. await conn.commit()
  176. } catch (error) {
  177. try { await conn.rollback() } catch (_) { }
  178. throw error
  179. }
  180. }
  181. async getResourceRules() {
  182. await this.ensurePermissionSchema()
  183. const rows = await db.query(`
  184. SELECT id, resource_type, resource_key, api_method, api_path, required_codes, enabled, remark
  185. FROM permission_resource_rules
  186. ORDER BY FIELD(resource_type, 'page', 'action', 'api'), id
  187. `)
  188. return (rows || []).map(row => ({
  189. ...row,
  190. required_codes: this.parseArray(row.required_codes)
  191. }))
  192. }
  193. async getResourceRequiredCodes({ resourceType, resourceKey, method, path }) {
  194. await this.ensurePermissionSchema()
  195. let rows = []
  196. if (resourceType && resourceKey) {
  197. rows = await db.query(
  198. `SELECT required_codes
  199. FROM permission_resource_rules
  200. WHERE resource_type = ? AND resource_key = ? AND enabled = 1
  201. LIMIT 1`,
  202. [resourceType, resourceKey]
  203. )
  204. } else if (method && path) {
  205. rows = await db.query(
  206. `SELECT required_codes
  207. FROM permission_resource_rules
  208. WHERE resource_type = 'api' AND api_method = ? AND api_path = ? AND enabled = 1
  209. LIMIT 1`,
  210. [String(method).toUpperCase(), path]
  211. )
  212. }
  213. return this.parseArray(rows?.[0]?.required_codes)
  214. }
  215. async updateResourceRule({ id, required_codes, enabled }) {
  216. await this.ensurePermissionSchema()
  217. const requiredCodes = this.normalizeCodes(required_codes)
  218. const points = await this.getPermissionPoints()
  219. const validCodes = new Set(points.map(point => point.code))
  220. const invalidCodes = requiredCodes.filter(code => !validCodes.has(code))
  221. if (invalidCodes.length > 0)
  222. throw new Error(`存在无效权限点:${invalidCodes.join(', ')}`)
  223. const rows = await db.query(
  224. `UPDATE permission_resource_rules
  225. SET required_codes = ?, enabled = ?
  226. WHERE id = ?`,
  227. [JSON.stringify(requiredCodes), Number(enabled) === 0 ? 0 : 1, id]
  228. )
  229. return rows?.affectedRows === 1
  230. }
  231. async canAccess(uuid, requiredCodes) {
  232. const codes = this.normalizeCodes(requiredCodes)
  233. if (codes.length === 0) return true
  234. if (await this.isSuperAdmin(uuid)) return true
  235. const userCodes = await this.getUserPermissionCodes(uuid)
  236. return codes.some(code => userCodes.includes(code))
  237. }
  238. async checkJwAccount(uuid, username) {
  239. const sql = 'SELECT password FROM jw_account WHERE create_user = ? AND state = 1 AND username = ?'
  240. const rows = await db.query(sql, [uuid, username]);
  241. if (!rows || rows.length !== 1 || !rows[0].password)
  242. return false
  243. return rows[0]?.password
  244. }
  245. }
  246. module.exports = new AccessControl();