settings.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { TAPi18n } from '/imports/i18n';
  3. import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
  4. //var nodemailer = require('nodemailer');
  5. // Sandstorm context is detected using the METEOR_SETTINGS environment variable
  6. // in the package definition.
  7. const isSandstorm =
  8. Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm;
  9. Settings = new Mongo.Collection('settings');
  10. Settings.attachSchema(
  11. new SimpleSchema({
  12. disableRegistration: {
  13. type: Boolean,
  14. optional: true,
  15. defaultValue: false,
  16. },
  17. disableForgotPassword: {
  18. type: Boolean,
  19. optional: true,
  20. defaultValue: false,
  21. },
  22. 'mailServer.username': {
  23. type: String,
  24. optional: true,
  25. },
  26. 'mailServer.password': {
  27. type: String,
  28. optional: true,
  29. },
  30. 'mailServer.host': {
  31. type: String,
  32. optional: true,
  33. },
  34. 'mailServer.port': {
  35. type: String,
  36. optional: true,
  37. },
  38. 'mailServer.enableTLS': {
  39. type: Boolean,
  40. optional: true,
  41. },
  42. 'mailServer.from': {
  43. type: String,
  44. optional: true,
  45. },
  46. productName: {
  47. type: String,
  48. optional: true,
  49. },
  50. displayAuthenticationMethod: {
  51. type: Boolean,
  52. optional: true,
  53. },
  54. defaultAuthenticationMethod: {
  55. type: String,
  56. optional: false,
  57. },
  58. spinnerName: {
  59. type: String,
  60. optional: true,
  61. },
  62. hideLogo: {
  63. type: Boolean,
  64. optional: true,
  65. },
  66. hideCardCounterList: {
  67. type: Boolean,
  68. optional: true,
  69. },
  70. hideBoardMemberList: {
  71. type: Boolean,
  72. optional: true,
  73. },
  74. customLoginLogoImageUrl: {
  75. type: String,
  76. optional: true,
  77. },
  78. customLoginLogoLinkUrl: {
  79. type: String,
  80. optional: true,
  81. },
  82. customHelpLinkUrl: {
  83. type: String,
  84. optional: true,
  85. },
  86. textBelowCustomLoginLogo: {
  87. type: String,
  88. optional: true,
  89. },
  90. automaticLinkedUrlSchemes: {
  91. type: String,
  92. optional: true,
  93. },
  94. customTopLeftCornerLogoImageUrl: {
  95. type: String,
  96. optional: true,
  97. },
  98. customTopLeftCornerLogoLinkUrl: {
  99. type: String,
  100. optional: true,
  101. },
  102. customTopLeftCornerLogoHeight: {
  103. type: String,
  104. optional: true,
  105. },
  106. oidcBtnText: {
  107. type: String,
  108. optional: true,
  109. },
  110. mailDomainName: {
  111. type: String,
  112. optional: true,
  113. },
  114. legalNotice: {
  115. type: String,
  116. optional: true,
  117. },
  118. createdAt: {
  119. type: Date,
  120. denyUpdate: true,
  121. // eslint-disable-next-line consistent-return
  122. autoValue() {
  123. if (this.isInsert) {
  124. return new Date();
  125. } else if (this.isUpsert) {
  126. return { $setOnInsert: new Date() };
  127. } else {
  128. this.unset();
  129. }
  130. },
  131. },
  132. modifiedAt: {
  133. type: Date,
  134. // eslint-disable-next-line consistent-return
  135. autoValue() {
  136. if (this.isInsert || this.isUpsert || this.isUpdate) {
  137. return new Date();
  138. } else {
  139. this.unset();
  140. }
  141. },
  142. },
  143. }),
  144. );
  145. Settings.helpers({
  146. mailUrl() {
  147. if (!this.mailServer.host) {
  148. return null;
  149. }
  150. const protocol = this.mailServer.enableTLS ? 'smtps://' : 'smtp://';
  151. if (!this.mailServer.username && !this.mailServer.password) {
  152. return `${protocol}${this.mailServer.host}:${this.mailServer.port}/`;
  153. }
  154. return `${protocol}${this.mailServer.username}:${encodeURIComponent(
  155. this.mailServer.password,
  156. )}@${this.mailServer.host}:${this.mailServer.port}/`;
  157. },
  158. });
  159. Settings.allow({
  160. update(userId) {
  161. const user = ReactiveCache.getUser(userId);
  162. return user && user.isAdmin;
  163. },
  164. });
  165. if (Meteor.isServer) {
  166. Meteor.startup(() => {
  167. Settings._collection.createIndex({ modifiedAt: -1 });
  168. const setting = ReactiveCache.getCurrentSetting();
  169. if (!setting) {
  170. const now = new Date();
  171. const domain = process.env.ROOT_URL.match(
  172. /\/\/(?:www\.)?(.*)?(?:\/)?/,
  173. )[1];
  174. const from = `Boards Support <support@${domain}>`;
  175. const defaultSetting = {
  176. disableRegistration: false,
  177. mailServer: {
  178. username: '',
  179. password: '',
  180. host: '',
  181. port: '',
  182. enableTLS: false,
  183. from,
  184. },
  185. createdAt: now,
  186. modifiedAt: now,
  187. displayAuthenticationMethod: true,
  188. defaultAuthenticationMethod: 'password',
  189. };
  190. Settings.insert(defaultSetting);
  191. }
  192. if (isSandstorm) {
  193. // At Sandstorm, Admin Panel has SMTP settings
  194. const newSetting = ReactiveCache.getCurrentSetting();
  195. if (!process.env.MAIL_URL && newSetting.mailUrl())
  196. process.env.MAIL_URL = newSetting.mailUrl();
  197. Accounts.emailTemplates.from = process.env.MAIL_FROM
  198. ? process.env.MAIL_FROM
  199. : newSetting.mailServer.from;
  200. } else {
  201. // Not running on Sandstorm, so using environment variables
  202. Accounts.emailTemplates.from = process.env.MAIL_FROM;
  203. }
  204. });
  205. if (isSandstorm) {
  206. // At Sandstorm Wekan Admin Panel, save SMTP settings.
  207. Settings.after.update((userId, doc, fieldNames) => {
  208. // assign new values to mail-from & MAIL_URL in environment
  209. if (_.contains(fieldNames, 'mailServer') && doc.mailServer.host) {
  210. const protocol = doc.mailServer.enableTLS ? 'smtps://' : 'smtp://';
  211. if (!doc.mailServer.username && !doc.mailServer.password) {
  212. process.env.MAIL_URL = `${protocol}${doc.mailServer.host}:${doc.mailServer.port}/`;
  213. } else {
  214. process.env.MAIL_URL = `${protocol}${
  215. doc.mailServer.username
  216. }:${encodeURIComponent(doc.mailServer.password)}@${
  217. doc.mailServer.host
  218. }:${doc.mailServer.port}/`;
  219. }
  220. Accounts.emailTemplates.from = doc.mailServer.from;
  221. }
  222. });
  223. }
  224. function getRandomNum(min, max) {
  225. const range = max - min;
  226. const rand = Math.random();
  227. return min + Math.round(rand * range);
  228. }
  229. function getEnvVar(name) {
  230. const value = process.env[name];
  231. if (value) {
  232. return value;
  233. }
  234. throw new Meteor.Error([
  235. 'var-not-exist',
  236. `The environment variable ${name} does not exist`,
  237. ]);
  238. }
  239. function loadOidcConfig(service){
  240. check(service, String);
  241. var config = ServiceConfiguration.configurations.findOne({service: service});
  242. return config;
  243. }
  244. function sendInvitationEmail(_id) {
  245. const icode = ReactiveCache.getInvitationCode(_id);
  246. const author = ReactiveCache.getCurrentUser();
  247. try {
  248. const fullName = ReactiveCache.getUser(icode.authorId)?.profile?.fullname || "";
  249. const params = {
  250. email: icode.email,
  251. inviter: fullName != "" ? fullName + " (" + ReactiveCache.getUser(icode.authorId).username + " )" : ReactiveCache.getUser(icode.authorId).username,
  252. user: icode.email.split('@')[0],
  253. icode: icode.code,
  254. url: FlowRouter.url('sign-up'),
  255. };
  256. const lang = author.getLanguage();
  257. /*
  258. if (process.env.MAIL_SERVICE !== '') {
  259. let transporter = nodemailer.createTransport({
  260. service: process.env.MAIL_SERVICE,
  261. auth: {
  262. user: process.env.MAIL_SERVICE_USER,
  263. pass: process.env.MAIL_SERVICE_PASSWORD
  264. },
  265. })
  266. let info = transporter.sendMail({
  267. to: icode.email,
  268. from: Accounts.emailTemplates.from,
  269. subject: TAPi18n.__('email-invite-register-subject', params, lang),
  270. text: TAPi18n.__('email-invite-register-text', params, lang),
  271. })
  272. } else {
  273. Email.send({
  274. to: icode.email,
  275. from: Accounts.emailTemplates.from,
  276. subject: TAPi18n.__('email-invite-register-subject', params, lang),
  277. text: TAPi18n.__('email-invite-register-text', params, lang),
  278. });
  279. }
  280. */
  281. Email.send({
  282. to: icode.email,
  283. from: Accounts.emailTemplates.from,
  284. subject: TAPi18n.__('email-invite-register-subject', params, lang),
  285. text: TAPi18n.__('email-invite-register-text', params, lang),
  286. });
  287. } catch (e) {
  288. InvitationCodes.remove(_id);
  289. throw new Meteor.Error('email-fail', e.message);
  290. }
  291. }
  292. function isNonAdminAllowedToSendMail(currentUser){
  293. const currSett = ReactiveCache.getCurrentSetting();
  294. let isAllowed = false;
  295. if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){
  296. for(let i = 0; i < currentUser.emails.length; i++) {
  297. if(currentUser.emails[i].address.endsWith(currSett.mailDomainName)){
  298. isAllowed = true;
  299. break;
  300. }
  301. }
  302. }
  303. return isAllowed;
  304. }
  305. function isLdapEnabled() {
  306. return (
  307. process.env.LDAP_ENABLE === 'true' || process.env.LDAP_ENABLE === true
  308. );
  309. }
  310. function isOauth2Enabled() {
  311. return (
  312. process.env.OAUTH2_ENABLED === 'true' ||
  313. process.env.OAUTH2_ENABLED === true
  314. );
  315. }
  316. function isCasEnabled() {
  317. return (
  318. process.env.CAS_ENABLED === 'true' || process.env.CAS_ENABLED === true
  319. );
  320. }
  321. function isApiEnabled() {
  322. return process.env.WITH_API === 'true' || process.env.WITH_API === true;
  323. }
  324. Meteor.methods({
  325. sendInvitation(emails, boards) {
  326. let rc = 0;
  327. check(emails, [String]);
  328. check(boards, [String]);
  329. const user = ReactiveCache.getCurrentUser();
  330. if (!user.isAdmin && !isNonAdminAllowedToSendMail(user)) {
  331. rc = -1;
  332. throw new Meteor.Error('not-allowed');
  333. }
  334. emails.forEach(email => {
  335. if (email && SimpleSchema.RegEx.Email.test(email)) {
  336. // Checks if the email is already link to an account.
  337. const userExist = ReactiveCache.getUser({ email });
  338. if (userExist) {
  339. rc = -1;
  340. throw new Meteor.Error(
  341. 'user-exist',
  342. `The user with the email ${email} has already an account.`,
  343. );
  344. }
  345. // Checks if the email is already link to an invitation.
  346. const invitation = ReactiveCache.getInvitationCode({ email });
  347. if (invitation) {
  348. InvitationCodes.update(invitation, {
  349. $set: { boardsToBeInvited: boards },
  350. });
  351. sendInvitationEmail(invitation._id);
  352. } else {
  353. const code = getRandomNum(100000, 999999);
  354. InvitationCodes.insert(
  355. {
  356. code,
  357. email,
  358. boardsToBeInvited: boards,
  359. createdAt: new Date(),
  360. authorId: Meteor.userId(),
  361. },
  362. function(err, _id) {
  363. if (!err && _id) {
  364. sendInvitationEmail(_id);
  365. } else {
  366. rc = -1;
  367. throw new Meteor.Error(
  368. 'invitation-generated-fail',
  369. err.message,
  370. );
  371. }
  372. },
  373. );
  374. }
  375. }
  376. });
  377. return rc;
  378. },
  379. sendSMTPTestEmail() {
  380. if (!Meteor.userId()) {
  381. throw new Meteor.Error('invalid-user');
  382. }
  383. const user = ReactiveCache.getCurrentUser();
  384. if (!user.emails || !user.emails[0] || !user.emails[0].address) {
  385. throw new Meteor.Error('email-invalid');
  386. }
  387. this.unblock();
  388. const lang = user.getLanguage();
  389. try {
  390. /*
  391. if (process.env.MAIL_SERVICE !== '') {
  392. let transporter = nodemailer.createTransport({
  393. service: process.env.MAIL_SERVICE,
  394. auth: {
  395. user: process.env.MAIL_SERVICE_USER,
  396. pass: process.env.MAIL_SERVICE_PASSWORD
  397. },
  398. })
  399. let info = transporter.sendMail({
  400. to: user.emails[0].address,
  401. from: Accounts.emailTemplates.from,
  402. subject: TAPi18n.__('email-smtp-test-subject', { lng: lang }),
  403. text: TAPi18n.__('email-smtp-test-text', { lng: lang }),
  404. })
  405. } else {
  406. Email.send({
  407. to: user.emails[0].address,
  408. from: Accounts.emailTemplates.from,
  409. subject: TAPi18n.__('email-smtp-test-subject', { lng: lang }),
  410. text: TAPi18n.__('email-smtp-test-text', { lng: lang }),
  411. });
  412. }
  413. */
  414. Email.send({
  415. to: user.emails[0].address,
  416. from: Accounts.emailTemplates.from,
  417. subject: TAPi18n.__('email-smtp-test-subject', { lng: lang }),
  418. text: TAPi18n.__('email-smtp-test-text', { lng: lang }),
  419. });
  420. } catch ({ message }) {
  421. throw new Meteor.Error(
  422. 'email-fail',
  423. `${TAPi18n.__('email-fail-text', { lng: lang })}: ${message}`,
  424. message,
  425. );
  426. }
  427. return {
  428. message: 'email-sent',
  429. email: user.emails[0].address,
  430. };
  431. },
  432. getCustomUI() {
  433. const setting = ReactiveCache.getCurrentSetting();
  434. if (!setting.productName) {
  435. return {
  436. productName: '',
  437. };
  438. } else {
  439. return {
  440. productName: `${setting.productName}`,
  441. };
  442. }
  443. },
  444. isDisableRegistration() {
  445. const setting = ReactiveCache.getCurrentSetting();
  446. if (setting.disableRegistration === true) {
  447. return true;
  448. } else {
  449. return false;
  450. }
  451. },
  452. isDisableForgotPassword() {
  453. const setting = ReactiveCache.getCurrentSetting();
  454. if (setting.disableForgotPassword === true) {
  455. return true;
  456. } else {
  457. return false;
  458. }
  459. },
  460. getMatomoConf() {
  461. return {
  462. address: getEnvVar('MATOMO_ADDRESS'),
  463. siteId: getEnvVar('MATOMO_SITE_ID'),
  464. doNotTrack: process.env.MATOMO_DO_NOT_TRACK || false,
  465. withUserName: process.env.MATOMO_WITH_USERNAME || false,
  466. };
  467. },
  468. _isLdapEnabled() {
  469. return isLdapEnabled();
  470. },
  471. _isOauth2Enabled() {
  472. return isOauth2Enabled();
  473. },
  474. _isCasEnabled() {
  475. return isCasEnabled();
  476. },
  477. _isApiEnabled() {
  478. return isApiEnabled();
  479. },
  480. // Gets all connection methods to use it in the Template
  481. getAuthenticationsEnabled() {
  482. return {
  483. ldap: isLdapEnabled(),
  484. oauth2: isOauth2Enabled(),
  485. cas: isCasEnabled(),
  486. };
  487. },
  488. getOauthServerUrl(){
  489. return process.env.OAUTH2_SERVER_URL;
  490. },
  491. getOauthDashboardUrl(){
  492. return process.env.DASHBOARD_URL;
  493. },
  494. getDefaultAuthenticationMethod() {
  495. return process.env.DEFAULT_AUTHENTICATION_METHOD;
  496. },
  497. isPasswordLoginEnabled() {
  498. return !(process.env.PASSWORD_LOGIN_ENABLED === 'false');
  499. },
  500. isOidcRedirectionEnabled(){
  501. return process.env.OIDC_REDIRECTION_ENABLED === 'true' && Object.keys(loadOidcConfig("oidc")).length > 0;
  502. },
  503. getServiceConfiguration(service){
  504. return loadOidcConfig(service);
  505. }
  506. });
  507. }
  508. export default Settings;