hasPermission.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import async from "async";
  2. import config from "config";
  3. // eslint-disable-next-line
  4. import moduleManager from "../../index";
  5. const permissions = {};
  6. permissions.dj = {
  7. "stations.autofill": true,
  8. "stations.blacklist": true,
  9. "stations.index": true,
  10. "stations.playback.toggle": true,
  11. "stations.queue.remove": true,
  12. "stations.queue.reposition": true,
  13. "stations.queue.reset": true,
  14. "stations.request": true,
  15. "stations.skip": true,
  16. "stations.view": true,
  17. "stations.view.manage": true
  18. };
  19. permissions.owner = {
  20. ...permissions.dj,
  21. "stations.djs.add": true,
  22. "stations.djs.remove": true,
  23. "stations.remove": true,
  24. "stations.update": true
  25. };
  26. permissions.moderator = {
  27. ...permissions.owner,
  28. "admin.view": true,
  29. "admin.view.import": true,
  30. "admin.view.news": true,
  31. "admin.view.playlists": true,
  32. "admin.view.punishments": true,
  33. "admin.view.reports": true,
  34. "admin.view.artists": true,
  35. "admin.view.albums": true,
  36. "admin.view.songs": true,
  37. "admin.view.stations": true,
  38. "admin.view.users": true,
  39. "admin.view.youtubeVideos": true,
  40. "apis.searchDiscogs": config.get("apis.discogs.enabled"),
  41. "news.create": true,
  42. "news.update": true,
  43. "playlists.create.admin": true,
  44. "playlists.get": true,
  45. "playlists.update.displayName": true,
  46. "playlists.update.featured": true,
  47. "playlists.update.privacy": true,
  48. "playlists.songs.add": true,
  49. "playlists.songs.remove": true,
  50. "playlists.songs.reposition": true,
  51. "playlists.view.others": true,
  52. "punishments.banIP": true,
  53. "punishments.get": true,
  54. "reports.get": true,
  55. "reports.update": true,
  56. "songs.create": true,
  57. "songs.get": true,
  58. "songs.update": true,
  59. "songs.verify": true,
  60. "artists.create": true,
  61. "artists.update": true,
  62. "artist.remove": true,
  63. "albums.create": true,
  64. "albums.update": true,
  65. "stations.create.official": true,
  66. "stations.index": false,
  67. "stations.index.other": true,
  68. "stations.remove": false,
  69. "users.get": true,
  70. "users.ban": true,
  71. "users.requestPasswordReset": config.get("mail.enabled") && !config.get("apis.oidc.enabled"),
  72. "users.resendVerifyEmail": config.get("mail.enabled"),
  73. "users.update": true,
  74. "youtube.requestSetAdmin": true,
  75. ...(config.get("experimental.soundcloud")
  76. ? {
  77. "admin.view.soundcloudTracks": true,
  78. "admin.view.soundcloud": true,
  79. "soundcloud.getArtist": true
  80. }
  81. : {}),
  82. ...(config.get("experimental.spotify")
  83. ? {
  84. "admin.view.spotify": true,
  85. "spotify.getTracksFromMediaSources": true,
  86. "spotify.getAlbumsFromIds": true,
  87. "spotify.getArtistsFromIds": true,
  88. "spotify.getAlternativeArtistSourcesForArtists": true,
  89. "spotify.getAlternativeAlbumSourcesForAlbums": true,
  90. "spotify.getAlternativeMediaSourcesForTracks": true,
  91. "admin.view.youtubeChannels": true,
  92. "youtube.getChannel": true
  93. }
  94. : {})
  95. };
  96. permissions.admin = {
  97. ...permissions.moderator,
  98. "admin.view.dataRequests": true,
  99. "admin.view.statistics": true,
  100. "admin.view.youtube": true,
  101. "dataRequests.resolve": true,
  102. "media.recalculateAllRatings": true,
  103. "media.removeImportJobs": true,
  104. "news.remove": true,
  105. "playlists.clearAndRefill": true,
  106. "playlists.clearAndRefillAll": true,
  107. "playlists.createMissing": true,
  108. "playlists.deleteOrphaned": true,
  109. "playlists.removeAdmin": true,
  110. "playlists.requestOrphanedPlaylistSongs": true,
  111. "punishments.deactivate": true,
  112. "reports.remove": true,
  113. "songs.remove": true,
  114. "songs.updateAll": true,
  115. "stations.clearEveryStationQueue": true,
  116. "stations.remove": true,
  117. "users.remove": true,
  118. "users.remove.sessions": true,
  119. "users.update.restricted": true,
  120. "utils.getModules": true,
  121. "youtube.getApiRequest": true,
  122. "youtube.getMissingVideos": true,
  123. "youtube.resetStoredApiRequests": true,
  124. "youtube.removeStoredApiRequest": true,
  125. "youtube.removeVideos": true,
  126. "youtube.updateVideosV1ToV2": true,
  127. ...(config.get("experimental.soundcloud")
  128. ? {
  129. "soundcloud.fetchNewApiKey": true,
  130. "soundcloud.testApiKey": true
  131. }
  132. : {}),
  133. ...(config.get("experimental.spotify")
  134. ? {
  135. "youtube.getMissingChannels": true
  136. }
  137. : {})
  138. };
  139. export const hasPermission = async (permission, session, stationId) => {
  140. const CacheModule = moduleManager.modules.cache;
  141. const DBModule = moduleManager.modules.db;
  142. const StationsModule = moduleManager.modules.stations;
  143. const UtilsModule = moduleManager.modules.utils;
  144. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  145. return new Promise((resolve, reject) => {
  146. async.waterfall(
  147. [
  148. next => {
  149. let userId;
  150. if (typeof session === "object") {
  151. if (session.userId) userId = session.userId;
  152. else
  153. CacheModule.runJob(
  154. "HGET",
  155. {
  156. table: "sessions",
  157. key: session.sessionId
  158. },
  159. this
  160. )
  161. .then(_session => {
  162. if (_session && _session.userId) userId = _session.userId;
  163. })
  164. .catch(next);
  165. } else userId = session;
  166. if (!userId) return next("User ID required.");
  167. return userModel.findOne({ _id: userId }, next);
  168. },
  169. (user, next) => {
  170. if (!user) return next("Login required.");
  171. if (!stationId) return next(null, [user.role]);
  172. return StationsModule.runJob("GET_STATION", { stationId }, this)
  173. .then(station => {
  174. if (!station) return next("Station not found.");
  175. if (station.type === "community" && station.owner === user._id.toString())
  176. return next(null, [user.role, "owner"]);
  177. if (station.djs.find(dj => dj === user._id.toString()))
  178. return next(null, [user.role, "dj"]);
  179. if (user.role === "admin" || user.role === "moderator") return next(null, [user.role]);
  180. return next("Invalid permissions.");
  181. })
  182. .catch(next);
  183. },
  184. (roles, next) => {
  185. if (!roles) return next("Role required.");
  186. let permissionFound;
  187. roles.forEach(role => {
  188. if (permissions[role] && permissions[role][permission]) permissionFound = true;
  189. });
  190. if (permissionFound) return next();
  191. return next("Insufficient permissions.");
  192. }
  193. ],
  194. async err => {
  195. const userId = typeof session === "object" ? session.userId || session.sessionId : session;
  196. if (err) {
  197. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  198. UtilsModule.log(
  199. "INFO",
  200. "HAS_PERMISSION",
  201. `User "${userId}" does not have required permission "${permission}". "${err}"`
  202. );
  203. return reject(err);
  204. }
  205. UtilsModule.log(
  206. "INFO",
  207. "HAS_PERMISSION",
  208. `User "${userId}" has required permission "${permission}".`,
  209. false
  210. );
  211. return resolve();
  212. }
  213. );
  214. });
  215. };
  216. export const useHasPermission = (options, destination) =>
  217. async function useHasPermission(session, ...args) {
  218. const UtilsModule = moduleManager.modules.utils;
  219. const permission = typeof options === "object" ? options.permission : options;
  220. const cb = args[args.length - 1];
  221. async.waterfall(
  222. [
  223. next => {
  224. if (!session || !session.sessionId) return next("Login required.");
  225. return hasPermission(permission, session)
  226. .then(() => next())
  227. .catch(next);
  228. }
  229. ],
  230. async err => {
  231. if (err) {
  232. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  233. this.log(
  234. "INFO",
  235. "USE_HAS_PERMISSION",
  236. `User "${session.userId}" does not have required permission "${permission}". "${err}"`
  237. );
  238. return cb({ status: "error", message: err });
  239. }
  240. this.log(
  241. "INFO",
  242. "USE_HAS_PERMISSION",
  243. `User "${session.userId}" has required permission "${permission}".`,
  244. false
  245. );
  246. return destination.apply(this, [session].concat(args));
  247. }
  248. );
  249. };
  250. export const getUserPermissions = async (session, stationId) => {
  251. const CacheModule = moduleManager.modules.cache;
  252. const DBModule = moduleManager.modules.db;
  253. const StationsModule = moduleManager.modules.stations;
  254. const UtilsModule = moduleManager.modules.utils;
  255. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  256. return new Promise((resolve, reject) => {
  257. async.waterfall(
  258. [
  259. next => {
  260. let userId;
  261. if (typeof session === "object") {
  262. if (session.userId) userId = session.userId;
  263. else
  264. CacheModule.runJob(
  265. "HGET",
  266. {
  267. table: "sessions",
  268. key: session.sessionId
  269. },
  270. this
  271. )
  272. .then(_session => {
  273. if (_session && _session.userId) userId = _session.userId;
  274. })
  275. .catch(next);
  276. } else userId = session;
  277. if (!userId) return next("User ID required.");
  278. return userModel.findOne({ _id: userId }, next);
  279. },
  280. (user, next) => {
  281. if (!user) return next("Login required.");
  282. if (!stationId) return next(null, [user.role]);
  283. return StationsModule.runJob("GET_STATION", { stationId }, this)
  284. .then(station => {
  285. if (!station) return next("Station not found.");
  286. if (station.type === "community" && station.owner === user._id.toString())
  287. return next(null, [user.role, "owner"]);
  288. if (station.djs.find(dj => dj === user._id.toString()))
  289. return next(null, [user.role, "dj"]);
  290. if (user.role === "admin" || user.role === "moderator") return next(null, [user.role]);
  291. return next("Invalid permissions.");
  292. })
  293. .catch(next);
  294. },
  295. (roles, next) => {
  296. if (!roles) return next("Role required.");
  297. let rolePermissions = {};
  298. roles.forEach(role => {
  299. if (permissions[role]) rolePermissions = { ...rolePermissions, ...permissions[role] };
  300. });
  301. return next(null, rolePermissions);
  302. }
  303. ],
  304. async (err, rolePermissions) => {
  305. const userId = typeof session === "object" ? session.userId || session.sessionId : session;
  306. if (err) {
  307. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  308. UtilsModule.log(
  309. "INFO",
  310. "GET_USER_PERMISSIONS",
  311. `Failed to get permissions for user "${userId}". "${err}"`
  312. );
  313. return reject(err);
  314. }
  315. UtilsModule.log("INFO", "GET_USER_PERMISSIONS", `Fetched permissions for user "${userId}".`, false);
  316. return resolve(rolePermissions);
  317. }
  318. );
  319. });
  320. };