smc-hub
Version:
CoCalc: Backend webserver component
757 lines • 38.1 kB
JavaScript
"use strict";
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.email_verification_problem = exports.email_verified_successfully = exports.welcome_email = exports.mass_email = exports.send_email = exports.is_banned = exports.send_invite_email = exports.escape_email_body = void 0;
//########################################
// Sending emails
//########################################
var BANNED_DOMAINS = { "qq.com": true };
var util_1 = require("util");
var fs = __importStar(require("fs"));
var os_path = __importStar(require("path"));
var lodash_1 = require("lodash");
var fs_readFile_prom = util_1.promisify(fs.readFile);
var async = require("async");
var winston = require("./logger").getLogger("email");
var lodash_2 = require("lodash");
var site_defaults_1 = require("smc-util/db-schema/site-defaults");
var base_path_1 = __importDefault(require("smc-util-node/base-path"));
var data_1 = require("smc-util-node/data");
// sendgrid API v3: https://sendgrid.com/docs/API_Reference/Web_API/mail.html
var sendgrid = __importStar(require("@sendgrid/client"));
var nodemailer = __importStar(require("nodemailer"));
var misc = require("smc-util/misc");
var defaults = misc.defaults, required = misc.required;
var site_defaults_2 = require("smc-util/db-schema/site-defaults");
var sanitize_html_1 = __importDefault(require("sanitize-html"));
var misc_1 = require("smc-util-node/misc");
var _a = require("smc-util/theme"), SENDGRID_TEMPLATE_ID = _a.SENDGRID_TEMPLATE_ID, SENDGRID_ASM_NEWSLETTER = _a.SENDGRID_ASM_NEWSLETTER, SENDGRID_ASM_INVITES = _a.SENDGRID_ASM_INVITES, COMPANY_NAME = _a.COMPANY_NAME, COMPANY_EMAIL = _a.COMPANY_EMAIL, SITE_NAME = _a.SITE_NAME, DNS = _a.DNS, HELP_EMAIL = _a.HELP_EMAIL, LIVE_DEMO_REQUEST = _a.LIVE_DEMO_REQUEST;
function escape_email_body(body, allow_urls) {
// in particular, no img and no anchor a
var allowedTags = [
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"blockquote",
"p",
"ul",
"ol",
"nl",
"li",
"b",
"i",
"strong",
"em",
"strike",
"code",
"hr",
"br",
"div",
"table",
"thead",
"caption",
"tbody",
"tr",
"th",
"td",
"pre",
];
if (allow_urls) {
allowedTags.push("a");
}
return sanitize_html_1.default(body, { allowedTags: allowedTags });
}
exports.escape_email_body = escape_email_body;
function fallback(val, alt) {
if (typeof val == "string" && val.length > 0) {
return val;
}
else {
return alt;
}
}
// global state
var sendgrid_server = undefined;
var sendgrid_server_disabled = false;
var smtp_server = undefined;
var smtp_server_created = undefined; // timestamp
var smtp_server_conf = undefined;
var smtp_pw_reset_server = undefined;
function init_sendgrid(opts, dbg) {
return __awaiter(this, void 0, void 0, function () {
var api_key, ssgk, filename, err_1, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (sendgrid_server != null) {
return [2 /*return*/];
}
dbg("sendgrid not configured, starting...");
_a.label = 1;
case 1:
_a.trys.push([1, 7, , 8]);
api_key = "";
ssgk = opts.settings.sendgrid_key;
if (!(typeof ssgk == "string" && ssgk.trim().length > 0)) return [3 /*break*/, 2];
dbg("... using site settings/sendgrid_key");
api_key = ssgk.trim();
return [3 /*break*/, 6];
case 2:
filename = os_path.join(data_1.secrets, "sendgrid");
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, fs_readFile_prom(filename, "utf8")];
case 4:
api_key = _a.sent();
api_key = api_key.toString().trim();
dbg("... using sendgrid_key stored in " + filename);
return [3 /*break*/, 6];
case 5:
err_1 = _a.sent();
throw new Error("unable to read the file '" + filename + "', which is needed to send emails -- " + err_1);
case 6:
if (api_key.length === 0) {
dbg("sendgrid_server: explicitly disabled -- so pretend to always succeed for testing purposes");
sendgrid_server_disabled = true;
}
else {
sendgrid.setApiKey(api_key);
sendgrid_server = sendgrid;
dbg("started sendgrid client");
}
return [3 /*break*/, 8];
case 7:
err_2 = _a.sent();
dbg("Problem initializing Sendgrid -- " + err_2);
return [3 /*break*/, 8];
case 8: return [2 /*return*/];
}
});
});
}
function init_smtp_server(opts, dbg) {
return __awaiter(this, void 0, void 0, function () {
var s, conf;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
s = opts.settings;
conf = {
host: s.email_smtp_server,
port: s.email_smtp_port,
secure: s.email_smtp_secure,
auth: {
user: s.email_smtp_login,
pass: s.email_smtp_password,
},
};
// we check, if we can keep the smtp server instance
if (smtp_server != null &&
smtp_server_conf != null &&
s._timestamp != null &&
smtp_server_created != null) {
if (smtp_server_created < s._timestamp) {
if (!lodash_1.isEqual(smtp_server_conf, conf)) {
dbg("SMTP server instance outdated, recreating");
}
else {
// settings changed, but the server config is the same
smtp_server_created = Date.now();
return [2 /*return*/];
}
}
else {
return [2 /*return*/];
}
}
dbg("SMTP server not configured. setting up ...");
return [4 /*yield*/, nodemailer.createTransport(conf)];
case 1:
smtp_server = _a.sent();
smtp_server_created = Date.now();
smtp_server_conf = conf;
dbg("SMTP server configured");
return [2 /*return*/];
}
});
});
}
function send_via_smtp(opts, dbg) {
return __awaiter(this, void 0, void 0, function () {
var msg, info;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
dbg("sending email via SMTP backend");
msg = {
from: opts.from,
to: opts.to,
subject: opts.subject,
html: smtp_email_body(opts),
};
if (opts.replyto) {
msg.replyTo = opts.replyto;
}
if (opts.cc != null && opts.cc.length > 0) {
msg.cc = opts.cc;
}
if (opts.bcc != null && opts.bcc.length > 0) {
msg.bcc = opts.bcc;
}
return [4 /*yield*/, smtp_server.sendMail(msg)];
case 1:
info = _a.sent();
dbg("sending email via SMTP succeeded -- message id='" + info.messageId + "'");
return [2 /*return*/, info.messageId];
}
});
});
}
function send_via_sendgrid(opts, dbg) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var msg, req;
return __generator(this, function (_b) {
dbg("sending email to " + opts.to + " starting...");
msg = {
from: { email: opts.from, name: opts.fromname },
subject: opts.subject,
content: [
{
type: "text/html",
value: opts.body,
},
],
// plain template with a header (cocalc logo), a h1 title, and a footer
template_id: SENDGRID_TEMPLATE_ID,
personalizations: [
{
subject: opts.subject,
to: [
{
email: opts.to,
},
],
},
],
// This #title# will end up below the header in an <h1> according to the template
substitutions: {
"#title#": opts.subject,
},
};
if (opts.replyto) {
msg.reply_to = {
name: (_a = opts.replyto_name) !== null && _a !== void 0 ? _a : opts.replyto,
email: opts.replyto,
};
}
if (opts.cc != null && opts.cc.length > 0) {
msg.cc = [{ email: opts.cc }];
}
if (opts.bcc != null && opts.bcc.length > 0) {
msg.bcc = [{ email: opts.bcc }];
}
// one or more strings to categorize the sent emails on sendgrid
if (opts.category != null) {
if (typeof opts.category == "string") {
msg.categories = [opts.category];
}
else if (Array.isArray(opts.category)) {
msg.categories = opts.category;
}
}
// to unsubscribe only from a specific type of email, not everything!
// https://app.sendgrid.com/suppressions/advanced_suppression_manager
if (opts.asm_group != null) {
msg.asm = { group_id: opts.asm_group };
}
dbg("sending email to " + opts.to + " -- data -- " + misc.to_json(msg));
req = {
body: msg,
method: "POST",
url: "/v3/mail/send",
};
return [2 /*return*/, new Promise(function (done, fail) {
sendgrid_server
.request(req)
.then(function (_a) {
var _b = __read(_a, 2), _ = _b[0], body = _b[1];
dbg("sending email to " + opts.to + " -- success -- " + misc.to_json(body));
done();
})
.catch(function (err) {
dbg("sending email to " + opts.to + " -- error = " + misc.to_json(err));
fail(err);
});
})];
});
});
}
// constructs the email body for INVITES! (collaborator and student course)
// this includes sign up instructions pointing to the given project
// it might throw an error!
function create_email_body(subject, body, email_address, project_title, link2proj, allow_urls_in_emails) {
var direct_link;
var base_url;
if (link2proj != null) {
var base_url_segments = link2proj.split("/");
base_url = base_url_segments[0] + "//" + base_url_segments[2];
direct_link = "Open <a href='" + link2proj + "'>the project '" + project_title + "'</a>.";
}
else {
// no link2proj provided -- show something useful:
direct_link = "";
base_url = "https://cocalc.com";
}
var email_body = "";
if (body) {
email_body = escape_email_body(body, allow_urls_in_emails);
// we check if there are plain URLs, which can be used to send SPAM
if (!allow_urls_in_emails && misc_1.contains_url(email_body)) {
throw new Error("Sorry, links to specific websites are not allowed!");
}
}
else {
email_body = subject;
}
email_body += "\n<br/><br/>\n<b>To accept the invitation:\n<ol>\n<li>Open <a href=\"" + base_url + "/app\">CoCalc</a></li>\n<li>Sign up/in using <i>exactly</i> your email address <code>" + email_address + "</code></li>\n<li>" + direct_link + "</li>\n</ol></b>\n<br/><br />\n(If you're already signed in via <i>another</i> email address,\n you have to sign out and sign up/in using the mentioned email address.)\n";
return email_body;
}
function send_invite_email(opts) {
try {
var email_body = create_email_body(opts.subject, opts.email, opts.email_address, opts.title, opts.link2proj, opts.allow_urls);
send_email({
to: opts.to,
bcc: opts.settings.kucalc === site_defaults_1.KUCALC_COCALC_COM ? "invites@cocalc.com" : "",
fromname: fallback(opts.settings.organization_name, COMPANY_NAME),
from: fallback(opts.settings.organization_email, COMPANY_EMAIL),
category: "invite",
asm_group: SENDGRID_ASM_INVITES,
settings: opts.settings,
subject: opts.subject,
body: email_body,
replyto: opts.replyto,
replyto_name: opts.replyto_name,
cb: opts.cb,
});
}
catch (err) {
opts.cb(err);
}
}
exports.send_invite_email = send_invite_email;
function is_banned(address) {
var i = address.indexOf("@");
if (i === -1) {
return false;
}
var x = address.slice(i + 1).toLowerCase();
return !!BANNED_DOMAINS[x];
}
exports.is_banned = is_banned;
function make_dbg(opts) {
if (opts.verbose) {
return function (m) { return winston.debug("send_email(to:" + opts.to + ") -- " + m); };
}
else {
return function (_) { };
}
}
function init_pw_reset_smtp_server(opts) {
return __awaiter(this, void 0, void 0, function () {
var s;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
s = opts.settings;
if (smtp_pw_reset_server != null) {
return [2 /*return*/];
}
return [4 /*yield*/, nodemailer.createTransport({
host: s.password_reset_smtp_server,
port: s.password_reset_smtp_port,
secure: s.password_reset_smtp_secure,
auth: {
user: s.password_reset_smtp_login,
pass: s.password_reset_smtp_password,
},
})];
case 1:
// s.password_reset_smtp_from;
smtp_pw_reset_server = _a.sent();
return [2 /*return*/];
}
});
});
}
var smtp_footer = "\n<p style=\"margin-top:150px; border-top: 1px solid gray; color: gray; font-size:85%; text-align:center\">\nThis email was sent by <a href=\"<%= url %>\"><%= settings.site_name %></a> by <%= company_name %>.\nContact <a href=\"mailto:<%= settings.help_email %>\"><%= settings.help_email %></a> if you have any questions.\n</p>";
// construct the actual HTML body of a password reset email sent via SMTP
// in particular, all emails must have a body explaining who sent it!
var pw_reset_body_tmpl = lodash_2.template("\n<h2><%= subject %></h2>\n\n<%= body %>\n\n" + smtp_footer + "\n");
function password_reset_body(opts) {
return pw_reset_body_tmpl(opts);
}
var smtp_email_body_tmpl = lodash_2.template("\n<%= body %>\n\n" + smtp_footer + "\n");
// construct the email body for mails sent via smtp
function smtp_email_body(opts) {
return smtp_email_body_tmpl(opts);
}
var opts_default = {
subject: required,
body: required,
fromname: undefined,
from: undefined,
to: required,
replyto: undefined,
replyto_name: undefined,
cc: "",
bcc: "",
verbose: true,
cb: undefined,
category: undefined,
asm_group: undefined,
settings: required,
};
// here's how I test this function:
// require('email').send_email(subject:'TEST MESSAGE', body:'body', to:'wstein@sagemath.com', cb:console.log)
function send_email(opts) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var settings, company_name, dns, dbg, message, x, pw_reset_smtp, email_verify_smtp, email_backend, html, info, _b, err_3;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
settings = opts.settings;
company_name = fallback(settings.organization_name, COMPANY_NAME);
opts_default.fromname = opts_default.fromname || company_name;
opts_default.from = opts_default.from || settings.organization_email;
opts = defaults(opts, opts_default);
opts.company_name = company_name;
dns = fallback(settings.dns, DNS);
opts.url = "https://" + dns;
dbg = make_dbg(opts);
dbg(opts.body.slice(0, 201) + "...");
if (is_banned(opts.to) || is_banned(opts.from)) {
dbg("WARNING: attempt to send banned email");
if (typeof opts.cb === "function") {
opts.cb("banned domain");
}
return [2 /*return*/];
}
message = undefined;
if (opts.settings.email_enabled == false) {
x = site_defaults_2.site_settings_conf.email_enabled.name;
message = "sending any emails is disabled -- see 'Admin/Site Settings/" + x + "'";
dbg(message);
}
pw_reset_smtp = opts.category == "password_reset" &&
opts.settings.password_reset_override == "smtp";
email_verify_smtp = opts.category == "verify" &&
opts.settings.password_reset_override == "smtp";
email_backend = (_a = opts.settings.email_backend) !== null && _a !== void 0 ? _a : "sendgrid";
_c.label = 1;
case 1:
_c.trys.push([1, 15, , 16]);
if (!(pw_reset_smtp || email_verify_smtp)) return [3 /*break*/, 4];
dbg("initializing PW SMTP server...");
return [4 /*yield*/, init_pw_reset_smtp_server(opts)];
case 2:
_c.sent();
html = opts.category == "verify" ? opts.body : password_reset_body(opts);
dbg("sending email category=" + opts.category + " via SMTP server ...");
return [4 /*yield*/, smtp_pw_reset_server.sendMail({
from: opts.settings.password_reset_smtp_from,
replyTo: opts.settings.password_reset_smtp_from,
to: opts.to,
subject: opts.subject,
html: html,
})];
case 3:
info = _c.sent();
message = "password reset email sent via SMTP: " + info.messageId;
dbg(message);
return [3 /*break*/, 14];
case 4:
// INIT phase
return [4 /*yield*/, init_sendgrid(opts, dbg)];
case 5:
// INIT phase
_c.sent();
return [4 /*yield*/, init_smtp_server(opts, dbg)];
case 6:
_c.sent();
_b = email_backend;
switch (_b) {
case "sendgrid": return [3 /*break*/, 7];
case "smtp": return [3 /*break*/, 11];
case "none": return [3 /*break*/, 13];
}
return [3 /*break*/, 14];
case 7:
if (!(sendgrid_server == null || sendgrid_server_disabled)) return [3 /*break*/, 8];
message = "sendgrid email is disabled -- no actual message sent";
dbg(message);
return [3 /*break*/, 10];
case 8: return [4 /*yield*/, send_via_sendgrid(opts, dbg)];
case 9:
_c.sent();
_c.label = 10;
case 10: return [3 /*break*/, 14];
case 11: return [4 /*yield*/, send_via_smtp(opts, dbg)];
case 12:
_c.sent();
return [3 /*break*/, 14];
case 13:
message =
"no email sent, because email_backend is 'none' -- configure it in 'Admin/Site Settings'";
dbg(message);
return [3 /*break*/, 14];
case 14:
// all fine, no errors
typeof opts.cb === "function" ? opts.cb(undefined, message) : undefined;
return [3 /*break*/, 16];
case 15:
err_3 = _c.sent();
if (err_3) {
// so next time it will try fresh to connect to email server, rather than being wrecked forever.
sendgrid_server = undefined;
err_3 = "error sending email -- " + misc.to_json(err_3);
dbg(err_3);
}
else {
dbg("successfully sent email");
}
typeof opts.cb === "function" ? opts.cb(err_3, message) : undefined;
return [3 /*break*/, 16];
case 16: return [2 /*return*/];
}
});
});
}
exports.send_email = send_email;
// Send a mass email to every address in a file.
// E.g., put the email addresses in a file named 'a' and
// require('email').mass_email(subject:'TEST MESSAGE', body:'body', to:'a', cb:console.log)
function mass_email(opts) {
opts = defaults(opts, {
subject: required,
body: required,
from: COMPANY_EMAIL,
fromname: COMPANY_NAME,
to: required,
cc: "",
limit: 10,
cb: undefined,
}); // cb(err, list of recipients that we succeeded in sending email to)
var dbg = function (m) { return winston.debug("mass_email: " + m); };
dbg(opts.filename);
dbg(opts.subject);
dbg(opts.body);
var success = [];
var recipients = [];
return async.series([
function (cb) {
if (typeof opts.to !== "string") {
recipients.push(opts.to);
cb();
}
else {
fs.readFile(opts.to, function (err, data) {
if (err) {
cb(err);
}
else {
recipients.push.apply(recipients, __spreadArray([], __read(misc.split(data.toString()))));
cb();
}
});
}
},
function (cb) {
var n = 0;
var f = function (to, cb) {
if (n % 100 === 0) {
dbg(n + "/" + (recipients.length - 1));
}
n += 1;
send_email({
subject: opts.subject,
body: opts.body,
from: opts.from,
fromname: opts.fromname,
to: to,
cc: opts.cc,
asm_group: SENDGRID_ASM_NEWSLETTER,
category: "newsletter",
verbose: false,
settings: {},
cb: function (err) {
if (!err) {
success.push(to);
cb();
}
else {
cb("error sending email to " + to + " -- " + err);
}
},
});
};
async.mapLimit(recipients, opts.limit, f, cb);
},
], function (err) { return (typeof opts.cb === "function" ? opts.cb(err, success) : undefined); });
}
exports.mass_email = mass_email;
function verify_email_html(token_url) {
return "\n<p style=\"margin-top:0;margin-bottom:20px;\">\n<strong>\nPlease <a href=\"" + token_url + "\">click here</a> to verify your email address!\n</strong>\n</p>\n\n<p style=\"margin-top:0;margin-bottom:20px;\">\nIf this link does not work, please copy/paste this URL into a new browser tab and open the link:\n</p>\n\n<pre style=\"margin-top:10px;margin-bottom:10px;font-size:11px;\">\n" + token_url + "\n</pre>\n";
}
// beware, this needs to be HTML which is compatible with email-clients!
function welcome_email_html(_a) {
var token_url = _a.token_url, verify_emails = _a.verify_emails, site_name = _a.site_name, url = _a.url;
return "<h1>Welcome to " + site_name + "</h1>\n\n<p style=\"margin-top:0;margin-bottom:10px;\">\n<a href=\"" + url + "\">" + site_name + "</a> helps you to work with open-source scientific software in your web browser.\n</p>\n\n<p style=\"margin-top:0;margin-bottom:20px;\">\nYou received this email because an account with your email address was created.\nThis was either initiated by you, a friend or colleague invited you, or you're\na student as part of a course.\n</p>\n\n" + (verify_emails ? verify_email_html(token_url) : "") + "\n\n<hr size=\"1\"/>\n\n<h3>Exploring " + site_name + "</h3>\n<p style=\"margin-top:0;margin-bottom:10px;\">\nIn " + site_name + " your work happens inside <strong>private projects</strong>.\nThese are personal workspaces which contain your files, computational worksheets, and data.\nYou can run your computations through the web interface, via interactive worksheets and notebooks, or by executing a program in a terminal.\n" + site_name + " supports online editing of\n <a href=\"https://jupyter.org/\">Jupyter Notebooks</a>,\n <a href=\"https://www.sagemath.org/\">Sage Worksheets</a>,\n <a href=\"https://en.wikibooks.org/wiki/LaTeX\">Latex files</a>, etc.\n</p>\n\n<p style=\"margin-top:0;margin-bottom:10px;\">\n<strong>How to get from 0 to 100:</strong>\n</p>\n\n<ul>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <strong><a href=\"https://doc.cocalc.com/\">CoCalc Manual:</a></strong> learn more about CoCalc's features.\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <a href=\"https://doc.cocalc.com/jupyter.html\">Working with Jupyter Notebooks</a>\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <a href=\"https://doc.cocalc.com/sagews.html\">Working with SageMath Worksheets</a>\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <strong><a href=\"https://cocalc.com/policies/pricing.html\">Subscriptions:</a></strong> make hosting more robust and increase project quotas\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <a href=\"https://doc.cocalc.com/teaching-instructors.html\">Teaching a course on CoCalc</a>.\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <a href=\"https://doc.cocalc.com/howto/connectivity-issues.html\">Troubleshooting connectivity issues</a>\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">\n <a href=\"https://github.com/sagemathinc/cocalc/wiki/MathematicalSyntaxErrors\">Common mathematical syntax errors:</a> look into this if you are new to working with a programming language!\n</li>\n</ul>\n\n\n<p style=\"margin-top:0;margin-bottom:20px;\">\n<strong>Collaboration:</strong>\nYou can invite collaborators to work with you inside a project.\nLike you, they can edit the files in that project.\nEdits are visible in <strong>real time</strong> for everyone online.\nYou can share your thoughts in a <strong>side chat</strong> next to each document.\n</p>\n\n\n<p><strong>Software:</strong>\n<ul>\n<li style=\"margin-top:0;margin-bottom:10px;\">Mathematical calculation:\n <a href=\"https://www.sagemath.org/\">SageMath</a>,\n <a href=\"https://www.sympy.org/\">SymPy</a>, etc.\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">Statistics and Data Science:\n <a href=\"https://www.r-project.org/\">R project</a>,\n <a href=\"http://pandas.pydata.org/\">Pandas</a>,\n <a href=\"http://www.statsmodels.org/\">statsmodels</a>,\n <a href=\"http://scikit-learn.org/\">scikit-learn</a>,\n <a href=\"http://www.nltk.org/\">NLTK</a>, etc.\n</li>\n<li style=\"margin-top:0;margin-bottom:10px;\">Various other computation:\n <a href=\"https://www.tensorflow.org/\">Tensorflow</a>,\n <a href=\"https://www.gnu.org/software/octave/\">Octave</a>,\n <a href=\"https://julialang.org/\">Julia</a>, etc.\n</li>\n</ul>\n\n<p style=\"margin-top:0;margin-bottom:20px;\">\nVisit our <a href=\"https://cocalc.com/doc/software.html\">Software overview page</a> for more details!\n</p>\n\n\n<p style=\"margin-top:20px;margin-bottom:10px;\">\n<strong>Questions?</strong>\n</p>\n<p style=\"margin-top:0;margin-bottom:10px;\">\nSchedule a Live Demo with a specialist from CoCalc: <a href=\"" + LIVE_DEMO_REQUEST + "\">request form</a>.\n</p>\n<p style=\"margin-top:0;margin-bottom:20px;\">\nIn case of problems, concerns why you received this email, or other questions please contact:\n<a href=\"mailto:" + HELP_EMAIL + "\">" + HELP_EMAIL + "</a>.\n</p>\n";
}
function welcome_email(opts) {
var _a;
var body, category, subject;
opts = defaults(opts, {
to: required,
token: required,
only_verify: false,
settings: required,
cb: undefined,
});
if (opts.to == null) {
// users can sign up without an email address. ignore this.
typeof opts.cb === "function" ? opts.cb(undefined) : undefined;
return;
}
var settings = opts.settings;
var site_name = fallback(settings.site_name, SITE_NAME);
var dns = fallback(settings.dns, DNS);
var url = "https://" + dns;
var token_query = encodeURI("email=" + encodeURIComponent(opts.to) + "&token=" + opts.token);
var endpoint = os_path.join(base_path_1.default, "auth", "verify");
var token_url = "" + url + endpoint + "?" + token_query;
var verify_emails = (_a = opts.settings.verify_emails) !== null && _a !== void 0 ? _a : true;
if (opts.only_verify) {
// only send the verification email, if settings.verify_emails is true
if (!verify_emails)
return;
subject = "Verify your email address on " + site_name + " (" + dns + ")";
body = verify_email_html(token_url);
category = "verify";
}
else {
subject = "Welcome to " + site_name + " - " + dns;
body = welcome_email_html({ token_url: token_url, verify_emails: verify_emails, site_name: site_name, url: url });
category = "welcome";
}
send_email({
subject: subject,
body: body,
fromname: fallback(settings.organization_name, COMPANY_NAME),
from: fallback(settings.organization_email, COMPANY_EMAIL),
to: opts.to,
cb: opts.cb,
category: category,
settings: opts.settings,
asm_group: 147985,
}); // https://app.sendgrid.com/suppressions/advanced_suppression_manager
}
exports.welcome_email = welcome_email;
function email_verified_successfully(url) {
var title = SITE_NAME + ": Email verification successful";
return "<DOCTYPE html>\n<html>\n<head>\n<meta http-equiv=\"refresh\" content=\"5;url=" + url + "\" />\n<style>\n* {font-family: sans-serif;}\n</style>\n <title>" + title + "</title>\n</head>\n<body>\n<h1>Email verification successful!</h1>\n<div>\nClick <a href=\"" + url + "\">here</a> if you aren't automatically redirected to <a href=\"" + url + "\">" + SITE_NAME + "</a> within 30 seconds.\n</div>\n</body>\n</html>\n";
}
exports.email_verified_successfully = email_verified_successfully;
function email_verification_problem(url, problem) {
var title = SITE_NAME + ": Email verification problem";
return "<DOCTYPE html>\n<html>\n<head>\n<style>\ndiv, p, h1, h2 {font-family: sans-serif;}\ndiv {margin-top: 1rem;}\n</style>\n <title>" + title + "</title>\n</head>\n<body>\n <h1>" + title + "</h1>\n <div>There was a problem verifying your email address.</div>\n <div>Reason: <code>" + problem + "</code></div>\n <div>\n Continue to <a href=\"" + url + "\">" + SITE_NAME + "</a> or\n contact support: <a href=\"mailto:" + HELP_EMAIL + "\">" + HELP_EMAIL + "</a>.\n </div>\n</body>\n</html>\n ";
}
exports.email_verification_problem = email_verification_problem;
//# sourceMappingURL=email.js.map