server.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * АРХИВ ПУБЛИКАЦИЙ ОБУЧАЮЩИХСЯ И СОТРУДНИКОВ СУНЦ УрФУ
  3. * Copyright © 2021, А.М.Гольдин. ISC license
  4. */
  5. "use strict";
  6. /* ПОДКЛЮЧЕНИЕ МОДУЛЕЙ И ОПРЕДЕЛЕНИЕ ГЛОБАЛЬНЫХ КОНСТАНТ
  7. * ----------------------------------------------------------------------- */
  8. const http = require("http"),
  9. nedb = require("@yetzt/nedb"),
  10. fetch = require("node-fetch"),
  11. api = require("./api"),
  12. sertGen = require("./api/sertGen"),
  13. putFile = require("./api/putFile"),
  14. {
  15. ADMIN, admEml, SALT, authServ, director, glred,
  16. smtpSrv, smtpPort, smtpUs, smtpPwd
  17. } = require("./config");
  18. global.fs = require("fs");
  19. global.sendEml = require("./api/sendEml");
  20. global.ADMIN = ADMIN;
  21. global.ADMEML = admEml;
  22. global.DIRECTOR = director;
  23. global.GLRED = glred;
  24. global.SMTPSRV = smtpSrv;
  25. global.SMTPPORT = smtpPort;
  26. global.SMTPUS = smtpUs;
  27. global.SMTPPWD = smtpPwd;
  28. const mmmagic = require("mmmagic"),
  29. magic = mmmagic.Magic;
  30. global.mType = new magic(mmmagic.MAGIC_MIME_TYPE);
  31. /* ИНИЦИАЛИЗАЦИЯ КОЛЛЕКЦИЙ БАЗЫ ДАННЫХ
  32. * ----------------------------------------------------------------------- */
  33. global.db = {};
  34. db.articles = new nedb({filename:`${__dirname}/db/articles.db`, autoload:true});
  35. db.staff = new nedb({filename:`${__dirname}/db/staff.db`, autoload:true});
  36. /* ОПРЕДЕЛЕНИЯ ФУНКЦИЙ
  37. * ----------------------------------------------------------------------- */
  38. // Промисификатор метода find() работы с базой db
  39. // Пример вызова: let res = await dbFind("curric", {type: "class"})
  40. global.dbFind = (collectionName, objFind) => {
  41. return new Promise((resolve, reject) => {
  42. db[collectionName].find(objFind, (err, docs) => {
  43. if (err) reject(err);
  44. else resolve(docs);
  45. })
  46. })
  47. };
  48. // Изготавление хэша длины 24 из строки str
  49. const hash = str => {
  50. let
  51. alph = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz",
  52. char, strNew, h = 0, pass = '';
  53. for (let j = 0; j < 24; j++) {
  54. strNew = j + str;
  55. for (let i = 0; i < strNew.length; i++) {
  56. char = strNew.charCodeAt(i);
  57. h = ((h << 5) - h) + char;
  58. }
  59. pass += alph[Math.abs(h) % alph.length];
  60. }
  61. return pass;
  62. }
  63. // Отправка ответа (otvet - объект ответа, content - тело)
  64. const sendOtvet = (otvet, content) => {
  65. otvet.writeHead(200, {"Content-Type": "text/plain"});
  66. otvet.end(content);
  67. }
  68. /* СОБСТВЕННО ЦИКЛ ОБРАБОТКИ ЗАПРОСА
  69. * ----------------------------------------------------------------------- */
  70. http.createServer(async (zapros, otvet) => {
  71. try {
  72. // Количество дней, прошедших с начала Юникс-эры
  73. let DT = ~~(Date.now()/(1000 * 3600 * 24));
  74. // Получаем параметры запроса
  75. let ADDR = zapros.socket.remoteAddress || "unknown";
  76. ADDR = ADDR.replace("::1", "127.0.0.1").replace(/\:.*\:/, '');
  77. let url = new URL("http://" + zapros.headers.host + zapros.url);
  78. let qstring = (url.search || '').trim(); // вместе с ?;
  79. let cookies = {}, cookiesStr = zapros.headers.cookie || '';
  80. let cookiesArr = cookiesStr.split(';').map(x => {
  81. let keyValueArr = x.trim().split('=');
  82. cookies[keyValueArr[0]] = decodeURIComponent(keyValueArr[1]);
  83. });
  84. // Если пришел запрос на поиск (без авторизации)
  85. if (qstring == "?find") {
  86. let postData = '';
  87. zapros.on("data", dann => postData += dann.toString());
  88. zapros.on("end", async () => {
  89. let cont = await api(postData, [0, 'a', 'a', 'a', '']);
  90. sendOtvet(otvet, cont);
  91. });
  92. }
  93. // Первичная авторизация (логин-пароль передается в query string)
  94. else if (qstring.includes('-')) {
  95. // От сервера авторизации приходит либо none, либо
  96. // "pupkin,Пупкин,Василий,Петрович,"
  97. let resp = "none";
  98. try {
  99. resp = await (await fetch(`${authServ + qstring}`)).text();
  100. } catch (e) {;}
  101. if (resp == "none") sendOtvet(otvet, "noauth");
  102. else {
  103. // Определяем роли этого клиента (Editor, Corrector, Reviewer)
  104. // присобачиваем в качестве первой цифры куки (двоичная маска)
  105. let login = resp.split(',')[0], roles = 0;
  106. if (login == ADMIN) roles = 8;
  107. else {
  108. let res = await dbFind("staff", {login: login});
  109. if (res.length) roles = res[0].roles;
  110. }
  111. // Готовим куку u=4pupkin,Пупкин,Василий,Петрович,jgkjhs6
  112. let token = hash(SALT + ADDR + DT + roles + resp),
  113. cook = `u=${encodeURIComponent(roles+resp+token)}; path=/`;
  114. otvet.writeHead(200, {
  115. "Content-Type": "text/plain", "Set-Cookie": cook});
  116. otvet.end("auth");
  117. }
  118. }
  119. // Обработка запроса к API (кроме первичной авторизации)
  120. else {
  121. // Объект с данными юзера (роли, логин, фам, имя, отч (возм., пустое))
  122. let user = [];
  123. // Проверяем, авторизован ли клиент
  124. let auth = false;
  125. if (cookies['u']) {
  126. let [lgRl, uFam, uName, uOtch, uToken] = cookies['u'].split(','),
  127. tokTrue = hash(SALT + ADDR + DT
  128. + [lgRl, uFam, uName, uOtch].join(',') + ',');
  129. if (uToken == tokTrue) {
  130. auth = true;
  131. let uRoles = Number(lgRl[0]), uLgn = lgRl.slice(1);
  132. user = [uRoles, uLgn, uFam, uName, uOtch];
  133. }
  134. }
  135. if (!auth) sendOtvet(otvet, "forbidden");
  136. // Если пришел запрос на добавление файла <host>/api/?put_<id>
  137. else if (/^\?put_/.test(qstring)) {
  138. let id = qstring.replace("?put_", ''),
  139. fl = Buffer.alloc(0);
  140. zapros.on("data", dann => fl = Buffer.concat([fl, dann]));
  141. zapros.on("end", async () => {
  142. let cont = await putFile(user, id, fl);
  143. sendOtvet(otvet, cont);
  144. });
  145. }
  146. // Если пришел запрос изображения <host>/api/?draft_<id>/<flName>
  147. else if (/^\?draft_\w+\/\d+\.(jpg|png|svg)$/.test(qstring)) {
  148. const TYPES = {
  149. "jpg":"image/jpeg", "png":"image/png", "svg":"image/svg+xml"
  150. };
  151. let fullName = qstring.replace("?draft_", ''),
  152. ext = fullName.split('.')[1]; ;
  153. fs.readFile(__dirname + `/draft/${fullName}`, (e, data) => {
  154. if(e) {
  155. otvet.writeHead(404, {"Content-Type": "text/plain"});
  156. otvet.end("404 Not found");
  157. }
  158. else {
  159. otvet.writeHead(200, {"Content-Type": TYPES[ext]});
  160. otvet.end(data);
  161. }
  162. });
  163. }
  164. // Если пришел запрос просмотра черновика <host>/api/?view_<id>
  165. else if (/^\?view_\w+$/.test(qstring)) {
  166. let id = qstring.replace("?view_", ''); ;
  167. let cont = await api(
  168. JSON.stringify({f:"loadArticle", dt:id}),
  169. user
  170. );
  171. otvet.writeHead(200, {"Content-Type": "text/html"});
  172. otvet.end(cont);
  173. }
  174. // Если пришел запрос сертификата статьи вида <host>/api/?fjhgdj
  175. else if (/^\?\w{3,20}$/.test(qstring))
  176. sertGen(otvet, qstring.slice(1), user);
  177. // Если пришел запрос к api
  178. else {
  179. let postData = '';
  180. zapros.on("data", dann => postData += dann.toString());
  181. zapros.on("end", async () => {
  182. let cont = await api(postData, user);
  183. sendOtvet(otvet, cont);
  184. });
  185. }
  186. }
  187. }
  188. catch(e) {
  189. otvet.writeHead(500, {"Content-Type": "text/plain"});
  190. otvet.end("Internal Server Error");
  191. }
  192. }).listen(8080);
  193. let now = (new Date()).toString().replace(/ \(.*\)/, '');
  194. console.info(`${now} lycArch api server стартовал на порту 8080`);