mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-10 04:32:35 +01:00
commit
224e2c8374
58
assets/client_test/verify.html
Normal file
58
assets/client_test/verify.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="theme-dark" data-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no" name="viewport" />
|
||||
|
||||
<link rel="stylesheet" href="/assets/40532.bb53efb11e3ed4046082.css" integrity="" />
|
||||
<link rel="icon" href="/assets/847541504914fd33810e70a0ea73177e.ico" />
|
||||
<title>Fosscord Test Client</title>
|
||||
<meta charset="utf-8" data-react-helmet="true" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app-mount"></div>
|
||||
<script>
|
||||
window.__OVERLAY__ = /overlay/.test(location.pathname);
|
||||
window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname);
|
||||
window.GLOBAL_ENV = {
|
||||
API_ENDPOINT: "/api",
|
||||
API_VERSION: 9,
|
||||
GATEWAY_ENDPOINT: `${location.protocol === "https:" ? "wss://" : "ws://"}${location.host}`,
|
||||
WEBAPP_ENDPOINT: "",
|
||||
CDN_HOST: `${location.hostname}`,
|
||||
ASSET_ENDPOINT: "",
|
||||
MEDIA_PROXY_ENDPOINT: "https://media.discordapp.net",
|
||||
WIDGET_ENDPOINT: `//${location.host}/widget`,
|
||||
INVITE_HOST: `${location.hostname}/invite`,
|
||||
GUILD_TEMPLATE_HOST: "${location.host}/template",
|
||||
GIFT_CODE_HOST: "${location.hostname}/gift",
|
||||
RELEASE_CHANNEL: "canary",
|
||||
MARKETING_ENDPOINT: "//discord.com",
|
||||
BRAINTREE_KEY: "production_5st77rrc_49pp2rp4phym7387",
|
||||
STRIPE_KEY: "pk_live_CUQtlpQUF0vufWpnpUmQvcdi",
|
||||
NETWORKING_ENDPOINT: "//router.discordapp.net",
|
||||
RTC_LATENCY_ENDPOINT: "//${location.hostname}/rtc",
|
||||
ACTIVITY_APPLICATION_HOST: "discordsays.com",
|
||||
PROJECT_ENV: "production",
|
||||
REMOTE_AUTH_ENDPOINT: "//localhost:3020",
|
||||
SENTRY_TAGS: { buildId: "d5b97e42230075cb9634c419c0cf4d2f8f9ada53", buildType: "normal" },
|
||||
MIGRATION_SOURCE_ORIGIN: "https://${location.hostname}",
|
||||
MIGRATION_DESTINATION_ORIGIN: "https://${location.hostname}",
|
||||
HTML_TIMESTAMP: Date.now(),
|
||||
ALGOLIA_KEY: "aca0d7082e4e63af5ba5917d5e96bed0"
|
||||
};
|
||||
window.localStorage.setItem("gatewayURL", window.GLOBAL_ENV.GATEWAY_ENDPOINT);
|
||||
window.localStorage.setItem(
|
||||
"DeveloperOptionsStore",
|
||||
`{"trace":false,"canary":true,"logGatewayEvents":true,"logOverlayEvents":true,"logAnalyticsEvents":true,"sourceMapsEnabled":false,"axeEnabled":false}`
|
||||
);
|
||||
</script>
|
||||
<script src="/assets/33329b5e3fc4ba0db663.js"></script>
|
||||
<script src="/assets/c31f5155b71da969ec04.js"></script>
|
||||
<script src="/assets/bba695569f6557775f9c.js"></script>
|
||||
<script src="/assets/867449841939756f0ab0.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
113
assets/email_templates/new_login_location.html
Normal file
113
assets/email_templates/new_login_location.html
Normal file
@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<title>Verify {instanceName} Login from New Location</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color: #202225;">
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
|
||||
alt="Branding"
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
"
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 50px;
|
||||
background-color: #32353b;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
letter-spacing: 0.27px;
|
||||
line-height: 24px;
|
||||
"
|
||||
>
|
||||
Hey {userUsername},
|
||||
</p>
|
||||
<p>
|
||||
It looks like someone tried to log into your {instanceName}
|
||||
account from a new location. If this is you, follow the link
|
||||
below to authorize logging in from this location on your
|
||||
account. If this isn't you, we suggest changing your
|
||||
password as soon as possible.
|
||||
</p>
|
||||
<p>
|
||||
<strong>IP Address:</strong> {ipAddress}
|
||||
<br />
|
||||
<strong>Location:</strong> {locationCity}, {locationRegion},
|
||||
{locationCountryName}
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
style="
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 10px;
|
||||
"
|
||||
>
|
||||
<a
|
||||
href="{verifyUrl}"
|
||||
target="_blank"
|
||||
style="
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 15px 19px;
|
||||
background-color: #ff5f00;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>Verify Login</a
|
||||
>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
style="
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 10px;
|
||||
"
|
||||
>
|
||||
<p>
|
||||
Alternatively, you can directly paste this link into
|
||||
your browser:
|
||||
</p>
|
||||
<a href="{verifyUrl}" target="_blank" style="word-wrap: break-word;">{verifyUrl}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
65
assets/email_templates/password_changed.html
Normal file
65
assets/email_templates/password_changed.html
Normal file
@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<title>{instanceName} Password Changed</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color: #202225;">
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
|
||||
alt="Branding"
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
"
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 50px;
|
||||
background-color: #32353b;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
letter-spacing: 0.27px;
|
||||
line-height: 24px;
|
||||
"
|
||||
>
|
||||
Hey {userUsername},
|
||||
</p>
|
||||
<p>Your {instanceName} password has been changed.</p>
|
||||
<p>
|
||||
If this wasn't done by you, please immediately reset the
|
||||
password to your {instanceName} account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
101
assets/email_templates/password_reset_request.html
Normal file
101
assets/email_templates/password_reset_request.html
Normal file
@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<title>Password Reset Request for {instanceName}</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color: #202225;">
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
|
||||
alt="Branding"
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
"
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 50px;
|
||||
background-color: #32353b;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
letter-spacing: 0.27px;
|
||||
line-height: 24px;
|
||||
"
|
||||
>
|
||||
Hey {userUsername},
|
||||
</p>
|
||||
<p>
|
||||
Your {instanceName} password can be reset by clicking the
|
||||
button below. If you did not request a new password, please
|
||||
ignore this email.
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
style="
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 10px;
|
||||
"
|
||||
>
|
||||
<a
|
||||
href="{passwordResetUrl}"
|
||||
target="_blank"
|
||||
style="
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 15px 19px;
|
||||
background-color: #ff5f00;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>Reset Password</a
|
||||
>
|
||||
</div>
|
||||
<hr />
|
||||
<div style="text-align: center">
|
||||
<p>
|
||||
Alternatively, you can directly paste this link into
|
||||
your browser:
|
||||
</p>
|
||||
<a href="{passwordResetUrl}" target="_blank" style="word-wrap: break-word;"
|
||||
>{passwordResetUrl}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
69
assets/email_templates/phone_removed.html
Normal file
69
assets/email_templates/phone_removed.html
Normal file
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<title>Phone Removed From {instanceName} Account</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color: #202225;">
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
|
||||
alt="Branding"
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
"
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 50px;
|
||||
background-color: #32353b;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
letter-spacing: 0.27px;
|
||||
line-height: 24px;
|
||||
"
|
||||
>
|
||||
Hey {userUsername},
|
||||
</p>
|
||||
<p>
|
||||
Your phone number ********{phoneNumber} was recently removed
|
||||
from this account and added to a different {instanceName}
|
||||
account.
|
||||
</p>
|
||||
<p>
|
||||
Please note that your phone number can only be linked to one
|
||||
{instanceName} account at a time.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
102
assets/email_templates/verify_email.html
Normal file
102
assets/email_templates/verify_email.html
Normal file
@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<title>Verify Email Address for {instanceName}</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color: #202225;">
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
|
||||
alt="Branding"
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
"
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 50px;
|
||||
background-color: #32353b;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
letter-spacing: 0.27px;
|
||||
line-height: 24px;
|
||||
"
|
||||
>
|
||||
Hey {userUsername},
|
||||
</p>
|
||||
<p>
|
||||
Thanks for registering for an account on {instanceName}!
|
||||
Before we get started, we just need to confirm that this is
|
||||
you. Click below to verify your email address:
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
style="
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 10px;
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="btn"
|
||||
href="{emailVerificationUrl}"
|
||||
target="_blank"
|
||||
style="
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 15px 19px;
|
||||
background-color: #ff5f00;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>Verify Email</a
|
||||
>
|
||||
</div>
|
||||
<hr />
|
||||
<div style="text-align: center">
|
||||
<p>
|
||||
Alternatively, you can directly paste this link into
|
||||
your browser:
|
||||
</p>
|
||||
<a href="{emailVerificationUrl}" target="_blank" style="word-wrap: break-word;"
|
||||
>{emailVerificationUrl}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -16,5 +16,9 @@
|
||||
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another",
|
||||
"GUESTS_DISABLED": "Guest users are disabled",
|
||||
"TOO_MANY_REGISTRATIONS": "Too many registrations, please try again later"
|
||||
},
|
||||
"password_reset": {
|
||||
"EMAIL_DOES_NOT_EXIST": "Email does not exist.",
|
||||
"INVALID_TOKEN": "Invalid token."
|
||||
}
|
||||
}
|
||||
|
3207
assets/schemas.json
3207
assets/schemas.json
File diff suppressed because it is too large
Load Diff
6584
package-lock.json
generated
6584
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -51,6 +51,7 @@
|
||||
"@types/node": "^18.7.20",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/node-os-utils": "^1.3.0",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"@types/probe-image-size": "^7.2.0",
|
||||
"@types/sharp": "^0.31.0",
|
||||
"@types/ws": "^8.5.3",
|
||||
@ -95,6 +96,7 @@
|
||||
"node-2fa": "^2.0.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-os-utils": "^1.3.7",
|
||||
"nodemailer": "^6.9.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"probe-image-size": "^7.2.3",
|
||||
"proxy-agent": "^5.0.0",
|
||||
@ -113,6 +115,9 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"erlpack": "^0.1.4",
|
||||
"nodemailer-mailgun-transport": "^2.1.5",
|
||||
"nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport",
|
||||
"nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport",
|
||||
"sqlite3": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
@ -16,28 +16,29 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "missing-native-js-functions";
|
||||
import { Server, ServerOptions } from "lambert-server";
|
||||
import { Authentication, CORS } from "./middlewares/";
|
||||
import {
|
||||
Config,
|
||||
Email,
|
||||
initDatabase,
|
||||
initEvent,
|
||||
JSONReplacer,
|
||||
registerRoutes,
|
||||
Sentry,
|
||||
WebAuthn,
|
||||
} from "@fosscord/util";
|
||||
import { ErrorHandler } from "./middlewares/ErrorHandler";
|
||||
import { BodyParser } from "./middlewares/BodyParser";
|
||||
import { Router, Request, Response } from "express";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { Server, ServerOptions } from "lambert-server";
|
||||
import "missing-native-js-functions";
|
||||
import morgan from "morgan";
|
||||
import path from "path";
|
||||
import { red } from "picocolors";
|
||||
import { Authentication, CORS } from "./middlewares/";
|
||||
import { BodyParser } from "./middlewares/BodyParser";
|
||||
import { ErrorHandler } from "./middlewares/ErrorHandler";
|
||||
import { initRateLimits } from "./middlewares/RateLimit";
|
||||
import TestClient from "./middlewares/TestClient";
|
||||
import { initTranslation } from "./middlewares/Translation";
|
||||
import morgan from "morgan";
|
||||
import { initInstance } from "./util/handlers/Instance";
|
||||
import { registerRoutes } from "@fosscord/util";
|
||||
import { red } from "picocolors";
|
||||
|
||||
export type FosscordServerOptions = ServerOptions;
|
||||
|
||||
@ -63,6 +64,7 @@ export class FosscordServer extends Server {
|
||||
await initDatabase();
|
||||
await Config.init();
|
||||
await initEvent();
|
||||
await Email.init();
|
||||
await initInstance();
|
||||
await Sentry.init(this.app);
|
||||
WebAuthn.init();
|
||||
|
@ -16,10 +16,10 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { checkToken, Config, Rights } from "@fosscord/util";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
||||
export const NO_AUTHORIZATION_ROUTES = [
|
||||
// Authentication routes
|
||||
@ -28,6 +28,9 @@ export const NO_AUTHORIZATION_ROUTES = [
|
||||
"/auth/location-metadata",
|
||||
"/auth/mfa/totp",
|
||||
"/auth/mfa/webauthn",
|
||||
"/auth/verify",
|
||||
"/auth/forgot",
|
||||
"/auth/reset",
|
||||
// Routes with a seperate auth system
|
||||
"/webhooks/",
|
||||
// Public information endpoints
|
||||
|
92
src/api/routes/auth/forgot.ts
Normal file
92
src/api/routes/auth/forgot.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { getIpAdress, route, verifyCaptcha } from "@fosscord/api";
|
||||
import {
|
||||
Config,
|
||||
Email,
|
||||
FieldErrors,
|
||||
ForgotPasswordSchema,
|
||||
User,
|
||||
} from "@fosscord/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({ body: "ForgotPasswordSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const { login, captcha_key } = req.body as ForgotPasswordSchema;
|
||||
|
||||
const config = Config.get();
|
||||
|
||||
if (
|
||||
config.password_reset.requireCaptcha &&
|
||||
config.security.captcha.enabled
|
||||
) {
|
||||
const { sitekey, service } = config.security.captcha;
|
||||
if (!captcha_key) {
|
||||
return res.status(400).json({
|
||||
captcha_key: ["captcha-required"],
|
||||
captcha_sitekey: sitekey,
|
||||
captcha_service: service,
|
||||
});
|
||||
}
|
||||
|
||||
const ip = getIpAdress(req);
|
||||
const verify = await verifyCaptcha(captcha_key, ip);
|
||||
if (!verify.success) {
|
||||
return res.status(400).json({
|
||||
captcha_key: verify["error-codes"],
|
||||
captcha_sitekey: sitekey,
|
||||
captcha_service: service,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const user = await User.findOneOrFail({
|
||||
where: [{ phone: login }, { email: login }],
|
||||
select: ["username", "id", "disabled", "deleted", "email"],
|
||||
relations: ["security_keys"],
|
||||
}).catch(() => {
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
message: req.t("auth:password_reset.EMAIL_DOES_NOT_EXIST"),
|
||||
code: "EMAIL_DOES_NOT_EXIST",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!user.email)
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
message:
|
||||
"This account does not have an email address associated with it.",
|
||||
code: "NO_EMAIL",
|
||||
},
|
||||
});
|
||||
|
||||
if (user.deleted)
|
||||
return res.status(400).json({
|
||||
message: "This account is scheduled for deletion.",
|
||||
code: 20011,
|
||||
});
|
||||
|
||||
if (user.disabled)
|
||||
return res.status(400).json({
|
||||
message: req.t("auth:login.ACCOUNT_DISABLED"),
|
||||
code: 20013,
|
||||
});
|
||||
|
||||
return await Email.sendResetPassword(user, user.email)
|
||||
.then(() => {
|
||||
return res.sendStatus(204);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`,
|
||||
);
|
||||
throw new HTTPError("Failed to send password reset email", 500);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
@ -77,6 +77,7 @@ router.post(
|
||||
"mfa_enabled",
|
||||
"webauthn_enabled",
|
||||
"security_keys",
|
||||
"verified",
|
||||
],
|
||||
relations: ["security_keys"],
|
||||
}).catch(() => {
|
||||
@ -102,6 +103,17 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
// return an error for unverified accounts if verification is required
|
||||
if (config.login.requireVerification && !user.verified) {
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
|
||||
message:
|
||||
"Email verification is required, please check your email.",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (user.mfa_enabled && !user.webauthn_enabled) {
|
||||
// TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
|
||||
const ticket = crypto.randomBytes(40).toString("hex");
|
||||
|
@ -278,6 +278,17 @@ router.post(
|
||||
await Invite.joinGuild(user.id, body.invite);
|
||||
}
|
||||
|
||||
// return an error for unverified accounts if verification is required
|
||||
if (Config.get().login.requireVerification && !user.verified) {
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
|
||||
message:
|
||||
"Email verification is required, please check your email.",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({ token: await generateToken(user.id) });
|
||||
},
|
||||
);
|
||||
|
56
src/api/routes/auth/reset.ts
Normal file
56
src/api/routes/auth/reset.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { route } from "@fosscord/api";
|
||||
import {
|
||||
checkToken,
|
||||
Config,
|
||||
Email,
|
||||
FieldErrors,
|
||||
generateToken,
|
||||
PasswordResetSchema,
|
||||
User,
|
||||
} from "@fosscord/util";
|
||||
import bcrypt from "bcrypt";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({ body: "PasswordResetSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const { password, token } = req.body as PasswordResetSchema;
|
||||
|
||||
const { jwtSecret } = Config.get().security;
|
||||
|
||||
let user;
|
||||
try {
|
||||
const userTokenData = await checkToken(token, jwtSecret, true);
|
||||
user = userTokenData.user;
|
||||
} catch {
|
||||
throw FieldErrors({
|
||||
password: {
|
||||
message: req.t("auth:password_reset.INVALID_TOKEN"),
|
||||
code: "INVALID_TOKEN",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// the salt is saved in the password refer to bcrypt docs
|
||||
const hash = await bcrypt.hash(password, 12);
|
||||
|
||||
const data = {
|
||||
data: {
|
||||
hash,
|
||||
valid_tokens_since: new Date(),
|
||||
},
|
||||
};
|
||||
await User.update({ id: user.id }, data);
|
||||
|
||||
// come on, the user has to have an email to reset their password in the first place
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await Email.sendPasswordChanged(user, user.email!);
|
||||
|
||||
res.json({ token: await generateToken(user.id) });
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
93
src/api/routes/auth/verify/index.ts
Normal file
93
src/api/routes/auth/verify/index.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@fosscord/api";
|
||||
import {
|
||||
checkToken,
|
||||
Config,
|
||||
FieldErrors,
|
||||
generateToken,
|
||||
User,
|
||||
} from "@fosscord/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router();
|
||||
|
||||
async function getToken(user: User) {
|
||||
const token = await generateToken(user.id);
|
||||
|
||||
// Notice this will have a different token structure, than discord
|
||||
// Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
|
||||
// https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
|
||||
|
||||
return { token };
|
||||
}
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({ body: "VerifyEmailSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const { captcha_key, token } = req.body;
|
||||
|
||||
const config = Config.get();
|
||||
|
||||
if (config.register.requireCaptcha) {
|
||||
const { sitekey, service } = config.security.captcha;
|
||||
|
||||
if (!captcha_key) {
|
||||
return res.status(400).json({
|
||||
captcha_key: ["captcha-required"],
|
||||
captcha_sitekey: sitekey,
|
||||
captcha_service: service,
|
||||
});
|
||||
}
|
||||
|
||||
const ip = getIpAdress(req);
|
||||
const verify = await verifyCaptcha(captcha_key, ip);
|
||||
if (!verify.success) {
|
||||
return res.status(400).json({
|
||||
captcha_key: verify["error-codes"],
|
||||
captcha_sitekey: sitekey,
|
||||
captcha_service: service,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { jwtSecret } = Config.get().security;
|
||||
let user;
|
||||
|
||||
try {
|
||||
const userTokenData = await checkToken(token, jwtSecret, true);
|
||||
user = userTokenData.user;
|
||||
} catch {
|
||||
throw FieldErrors({
|
||||
password: {
|
||||
message: req.t("auth:password_reset.INVALID_TOKEN"),
|
||||
code: "INVALID_TOKEN",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (user.verified) return res.json(await getToken(user));
|
||||
|
||||
await User.update({ id: user.id }, { verified: true });
|
||||
|
||||
return res.json(await getToken(user));
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
52
src/api/routes/auth/verify/resend.ts
Normal file
52
src/api/routes/auth/verify/resend.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@fosscord/api";
|
||||
import { Email, User } from "@fosscord/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({ right: "RESEND_VERIFICATION_EMAIL" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const user = await User.findOneOrFail({
|
||||
where: { id: req.user_id },
|
||||
select: ["username", "email"],
|
||||
});
|
||||
|
||||
if (!user.email) {
|
||||
// TODO: whats the proper error response for this?
|
||||
throw new HTTPError("User does not have an email address", 400);
|
||||
}
|
||||
|
||||
await Email.sendVerifyEmail(user, user.email)
|
||||
.then(() => {
|
||||
return res.sendStatus(204);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`,
|
||||
);
|
||||
throw new HTTPError("Failed to send verification email", 500);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
@ -21,6 +21,7 @@ import {
|
||||
CdnConfiguration,
|
||||
ClientConfiguration,
|
||||
DefaultsConfiguration,
|
||||
EmailConfiguration,
|
||||
EndpointConfiguration,
|
||||
ExternalTokensConfiguration,
|
||||
GeneralConfiguration,
|
||||
@ -30,6 +31,7 @@ import {
|
||||
LimitsConfiguration,
|
||||
LoginConfiguration,
|
||||
MetricsConfiguration,
|
||||
PasswordResetConfiguration,
|
||||
RabbitMQConfiguration,
|
||||
RegionConfiguration,
|
||||
RegisterConfiguration,
|
||||
@ -58,4 +60,7 @@ export class ConfigValue {
|
||||
sentry: SentryConfiguration = new SentryConfiguration();
|
||||
defaults: DefaultsConfiguration = new DefaultsConfiguration();
|
||||
external: ExternalTokensConfiguration = new ExternalTokensConfiguration();
|
||||
email: EmailConfiguration = new EmailConfiguration();
|
||||
password_reset: PasswordResetConfiguration =
|
||||
new PasswordResetConfiguration();
|
||||
}
|
||||
|
32
src/util/config/types/EmailConfiguration.ts
Normal file
32
src/util/config/types/EmailConfiguration.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
MailGunConfiguration,
|
||||
MailJetConfiguration,
|
||||
SMTPConfiguration,
|
||||
} from "./subconfigurations/email";
|
||||
import { SendGridConfiguration } from "./subconfigurations/email/SendGrid";
|
||||
|
||||
export class EmailConfiguration {
|
||||
provider: string | null = null;
|
||||
smtp: SMTPConfiguration = new SMTPConfiguration();
|
||||
mailgun: MailGunConfiguration = new MailGunConfiguration();
|
||||
mailjet: MailJetConfiguration = new MailJetConfiguration();
|
||||
sendgrid: SendGridConfiguration = new SendGridConfiguration();
|
||||
}
|
@ -18,4 +18,5 @@
|
||||
|
||||
export class LoginConfiguration {
|
||||
requireCaptcha: boolean = false;
|
||||
requireVerification: boolean = false;
|
||||
}
|
||||
|
21
src/util/config/types/PasswordResetConfiguration.ts
Normal file
21
src/util/config/types/PasswordResetConfiguration.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class PasswordResetConfiguration {
|
||||
requireCaptcha: boolean = false;
|
||||
}
|
@ -18,12 +18,13 @@
|
||||
|
||||
import {
|
||||
DateOfBirthConfiguration,
|
||||
EmailConfiguration,
|
||||
PasswordConfiguration,
|
||||
RegistrationEmailConfiguration,
|
||||
} from ".";
|
||||
|
||||
export class RegisterConfiguration {
|
||||
email: EmailConfiguration = new EmailConfiguration();
|
||||
email: RegistrationEmailConfiguration =
|
||||
new RegistrationEmailConfiguration();
|
||||
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
||||
password: PasswordConfiguration = new PasswordConfiguration();
|
||||
disabled: boolean = false;
|
||||
@ -34,5 +35,5 @@ export class RegisterConfiguration {
|
||||
allowMultipleAccounts: boolean = true;
|
||||
blockProxies: boolean = true;
|
||||
incrementingDiscriminators: boolean = false; // random otherwise
|
||||
defaultRights: string = "312119568366592"; // See `npm run generate:rights`
|
||||
defaultRights: string = "875069521787904"; // See `npm run generate:rights`
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export * from "./ApiConfiguration";
|
||||
export * from "./CdnConfiguration";
|
||||
export * from "./ClientConfiguration";
|
||||
export * from "./DefaultsConfiguration";
|
||||
export * from "./EmailConfiguration";
|
||||
export * from "./EndpointConfiguration";
|
||||
export * from "./ExternalTokensConfiguration";
|
||||
export * from "./GeneralConfiguration";
|
||||
@ -29,10 +30,11 @@ export * from "./KafkaConfiguration";
|
||||
export * from "./LimitConfigurations";
|
||||
export * from "./LoginConfiguration";
|
||||
export * from "./MetricsConfiguration";
|
||||
export * from "./PasswordResetConfiguration";
|
||||
export * from "./RabbitMQConfiguration";
|
||||
export * from "./RegionConfiguration";
|
||||
export * from "./RegisterConfiguration";
|
||||
export * from "./SecurityConfiguration";
|
||||
export * from "./SentryConfiguration";
|
||||
export * from "./TemplateConfiguration";
|
||||
export * from "./subconfigurations";
|
||||
export * from "./TemplateConfiguration";
|
||||
|
22
src/util/config/types/subconfigurations/email/MailGun.ts
Normal file
22
src/util/config/types/subconfigurations/email/MailGun.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class MailGunConfiguration {
|
||||
apiKey: string | null = null;
|
||||
domain: string | null = null;
|
||||
}
|
22
src/util/config/types/subconfigurations/email/MailJet.ts
Normal file
22
src/util/config/types/subconfigurations/email/MailJet.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class MailJetConfiguration {
|
||||
apiKey: string | null = null;
|
||||
apiSecret: string | null = null;
|
||||
}
|
25
src/util/config/types/subconfigurations/email/SMTP.ts
Normal file
25
src/util/config/types/subconfigurations/email/SMTP.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class SMTPConfiguration {
|
||||
host: string | null = null;
|
||||
port: number | null = null;
|
||||
secure: boolean | null = null;
|
||||
username: string | null = null;
|
||||
password: string | null = null;
|
||||
}
|
21
src/util/config/types/subconfigurations/email/SendGrid.ts
Normal file
21
src/util/config/types/subconfigurations/email/SendGrid.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class SendGridConfiguration {
|
||||
apiKey: string | null = null;
|
||||
}
|
21
src/util/config/types/subconfigurations/email/index.ts
Normal file
21
src/util/config/types/subconfigurations/email/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from "./MailGun";
|
||||
export * from "./MailJet";
|
||||
export * from "./SMTP";
|
@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class EmailConfiguration {
|
||||
export class RegistrationEmailConfiguration {
|
||||
required: boolean = false;
|
||||
allowlist: boolean = false;
|
||||
blocklist: boolean = true;
|
||||
|
@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Request } from "express";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
@ -24,16 +25,22 @@ import {
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
} from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import {
|
||||
adjustEmail,
|
||||
Config,
|
||||
Email,
|
||||
FieldErrors,
|
||||
Snowflake,
|
||||
trimSpecial,
|
||||
} from "..";
|
||||
import { BitField } from "../util/BitField";
|
||||
import { Relationship } from "./Relationship";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { ConnectedAccount } from "./ConnectedAccount";
|
||||
import { Member } from "./Member";
|
||||
import { UserSettings } from "./UserSettings";
|
||||
import { Session } from "./Session";
|
||||
import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from "..";
|
||||
import { Request } from "express";
|
||||
import { Relationship } from "./Relationship";
|
||||
import { SecurityKey } from "./SecurityKey";
|
||||
import { Session } from "./Session";
|
||||
import { UserSettings } from "./UserSettings";
|
||||
|
||||
export enum PublicUserEnum {
|
||||
username,
|
||||
@ -384,6 +391,15 @@ export class User extends BaseClass {
|
||||
user.validate();
|
||||
await Promise.all([user.save(), settings.save()]);
|
||||
|
||||
// send verification email if users aren't verified by default and we have an email
|
||||
if (!Config.get().defaults.user.verified && email) {
|
||||
await Email.sendVerifyEmail(user, email).catch((e) => {
|
||||
console.error(
|
||||
`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setImmediate(async () => {
|
||||
if (Config.get().guild.autoJoin.enabled) {
|
||||
for (const guild of Config.get().guild.autoJoin.guilds || []) {
|
||||
|
22
src/util/schemas/ForgotPasswordSchema.ts
Normal file
22
src/util/schemas/ForgotPasswordSchema.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface ForgotPasswordSchema {
|
||||
login: string;
|
||||
captcha_key?: string;
|
||||
}
|
22
src/util/schemas/PasswordResetSchema.ts
Normal file
22
src/util/schemas/PasswordResetSchema.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface PasswordResetSchema {
|
||||
password: string;
|
||||
token: string;
|
||||
}
|
22
src/util/schemas/VerifyEmailSchema.ts
Normal file
22
src/util/schemas/VerifyEmailSchema.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface VerifyEmailSchema {
|
||||
captcha_key?: string | null;
|
||||
token: string;
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from "./AckBulkSchema";
|
||||
export * from "./ActivitySchema";
|
||||
export * from "./ApplicationAuthorizeSchema";
|
||||
export * from "./ApplicationCreateSchema";
|
||||
@ -32,6 +33,7 @@ export * from "./CodesVerificationSchema";
|
||||
export * from "./DmChannelCreateSchema";
|
||||
export * from "./EmojiCreateSchema";
|
||||
export * from "./EmojiModifySchema";
|
||||
export * from "./ForgotPasswordSchema";
|
||||
export * from "./GatewayPayloadSchema";
|
||||
export * from "./GuildCreateSchema";
|
||||
export * from "./GuildTemplateCreateSchema";
|
||||
@ -45,8 +47,10 @@ export * from "./MemberChangeProfileSchema";
|
||||
export * from "./MemberChangeSchema";
|
||||
export * from "./MessageAcknowledgeSchema";
|
||||
export * from "./MessageCreateSchema";
|
||||
export * from "./MessageEditSchema";
|
||||
export * from "./MfaCodesSchema";
|
||||
export * from "./ModifyGuildStickerSchema";
|
||||
export * from "./PasswordResetSchema";
|
||||
export * from "./PurgeSchema";
|
||||
export * from "./RegisterSchema";
|
||||
export * from "./RelationshipPostSchema";
|
||||
@ -69,22 +73,6 @@ export * from "./VanityUrlSchema";
|
||||
export * from "./VoiceIdentifySchema";
|
||||
export * from "./VoiceStateUpdateSchema";
|
||||
export * from "./VoiceVideoSchema";
|
||||
export * from "./IdentifySchema";
|
||||
export * from "./ActivitySchema";
|
||||
export * from "./LazyRequestSchema";
|
||||
export * from "./GuildUpdateSchema";
|
||||
export * from "./ChannelPermissionOverwriteSchema";
|
||||
export * from "./UserGuildSettingsSchema";
|
||||
export * from "./GatewayPayloadSchema";
|
||||
export * from "./RolePositionUpdateSchema";
|
||||
export * from "./ChannelReorderSchema";
|
||||
export * from "./UserSettingsSchema";
|
||||
export * from "./BotModifySchema";
|
||||
export * from "./ApplicationModifySchema";
|
||||
export * from "./ApplicationCreateSchema";
|
||||
export * from "./ApplicationAuthorizeSchema";
|
||||
export * from "./AckBulkSchema";
|
||||
export * from "./WebAuthnSchema";
|
||||
export * from "./WebhookCreateSchema";
|
||||
export * from "./WidgetModifySchema";
|
||||
export * from "./MessageEditSchema";
|
||||
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const EMAIL_REGEX =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
export function adjustEmail(email?: string): string | undefined {
|
||||
if (!email) return email;
|
||||
// body parser already checked if it is a valid email
|
||||
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
|
||||
if (!parts || parts.length < 5) return undefined;
|
||||
|
||||
return email;
|
||||
// // TODO: The below code doesn't actually do anything.
|
||||
// const domain = parts[5];
|
||||
// const user = parts[1];
|
||||
|
||||
// // TODO: check accounts with uncommon email domains
|
||||
// if (domain === "gmail.com" || domain === "googlemail.com") {
|
||||
// // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
|
||||
// const v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
|
||||
// }
|
||||
|
||||
// if (domain === "google.com") {
|
||||
// // replace .dots and +alternatives -> Google Staff GMail Dot Trick
|
||||
// const v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
|
||||
// }
|
||||
|
||||
// return email;
|
||||
}
|
@ -93,6 +93,7 @@ export class Rights extends BitField {
|
||||
EDIT_FLAGS: BitFlag(46), // can set others' flags
|
||||
MANAGE_GROUPS: BitFlag(47), // can manage others' groups
|
||||
VIEW_SERVER_STATS: BitFlag(48), // added per @chrischrome's request — can view server stats)
|
||||
RESEND_VERIFICATION_EMAIL: BitFlag(49), // can resend verification emails (/auth/verify/resend)
|
||||
};
|
||||
|
||||
any(permission: RightResolvable, checkOperator = true) {
|
||||
|
@ -27,9 +27,43 @@ export type UserTokenData = {
|
||||
decoded: { id: string; iat: number };
|
||||
};
|
||||
|
||||
async function checkEmailToken(
|
||||
decoded: jwt.JwtPayload,
|
||||
): Promise<UserTokenData> {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (res, rej) => {
|
||||
if (!decoded.iat) return rej("Invalid Token"); // will never happen, just for typings.
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: decoded.email,
|
||||
},
|
||||
select: [
|
||||
"email",
|
||||
"id",
|
||||
"verified",
|
||||
"deleted",
|
||||
"disabled",
|
||||
"username",
|
||||
"data",
|
||||
],
|
||||
});
|
||||
|
||||
if (!user) return rej("Invalid Token");
|
||||
|
||||
if (new Date().getTime() > decoded.iat * 1000 + 86400 * 1000)
|
||||
return rej("Invalid Token");
|
||||
|
||||
// Using as here because we assert `id` and `iat` are in decoded.
|
||||
// TS just doesn't want to assume its there, though.
|
||||
return res({ decoded, user } as UserTokenData);
|
||||
});
|
||||
}
|
||||
|
||||
export function checkToken(
|
||||
token: string,
|
||||
jwtSecret: string,
|
||||
isEmailVerification = false,
|
||||
): Promise<UserTokenData> {
|
||||
return new Promise((res, rej) => {
|
||||
token = token.replace("Bot ", "");
|
||||
@ -48,6 +82,8 @@ export function checkToken(
|
||||
)
|
||||
return rej("Invalid Token"); // will never happen, just for typings.
|
||||
|
||||
if (isEmailVerification) return res(checkEmailToken(decoded));
|
||||
|
||||
const user = await User.findOne({
|
||||
where: { id: decoded.id },
|
||||
select: ["data", "bot", "disabled", "deleted", "rights"],
|
||||
@ -72,13 +108,13 @@ export function checkToken(
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateToken(id: string) {
|
||||
export async function generateToken(id: string, email?: string) {
|
||||
const iat = Math.floor(Date.now() / 1000);
|
||||
const algorithm = "HS256";
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
jwt.sign(
|
||||
{ id: id, iat },
|
||||
{ id, iat, email },
|
||||
Config.get().security.jwtSecret,
|
||||
{
|
||||
algorithm,
|
||||
|
269
src/util/util/email/index.ts
Normal file
269
src/util/util/email/index.ts
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Fosscord and Fosscord Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { SentMessageInfo, Transporter } from "nodemailer";
|
||||
import { User } from "../../entities";
|
||||
import { Config } from "../Config";
|
||||
import { generateToken } from "../Token";
|
||||
import MailGun from "./transports/MailGun";
|
||||
import MailJet from "./transports/MailJet";
|
||||
import SendGrid from "./transports/SendGrid";
|
||||
import SMTP from "./transports/SMTP";
|
||||
|
||||
const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets");
|
||||
export const EMAIL_REGEX =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
export function adjustEmail(email?: string): string | undefined {
|
||||
if (!email) return email;
|
||||
// body parser already checked if it is a valid email
|
||||
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
|
||||
if (!parts || parts.length < 5) return undefined;
|
||||
|
||||
return email;
|
||||
// // TODO: The below code doesn't actually do anything.
|
||||
// const domain = parts[5];
|
||||
// const user = parts[1];
|
||||
|
||||
// // TODO: check accounts with uncommon email domains
|
||||
// if (domain === "gmail.com" || domain === "googlemail.com") {
|
||||
// // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
|
||||
// const v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
|
||||
// }
|
||||
|
||||
// if (domain === "google.com") {
|
||||
// // replace .dots and +alternatives -> Google Staff GMail Dot Trick
|
||||
// const v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
|
||||
// }
|
||||
|
||||
// return email;
|
||||
}
|
||||
|
||||
const transporters: {
|
||||
[key: string]: () => Promise<Transporter<unknown> | void>;
|
||||
} = {
|
||||
smtp: SMTP,
|
||||
mailgun: MailGun,
|
||||
mailjet: MailJet,
|
||||
sendgrid: SendGrid,
|
||||
};
|
||||
|
||||
export const Email: {
|
||||
transporter: Transporter | null;
|
||||
init: () => Promise<void>;
|
||||
generateLink: (
|
||||
type: "verify" | "reset",
|
||||
id: string,
|
||||
email: string,
|
||||
) => Promise<string>;
|
||||
sendVerifyEmail: (user: User, email: string) => Promise<SentMessageInfo>;
|
||||
sendResetPassword: (user: User, email: string) => Promise<SentMessageInfo>;
|
||||
sendPasswordChanged: (
|
||||
user: User,
|
||||
email: string,
|
||||
) => Promise<SentMessageInfo>;
|
||||
doReplacements: (
|
||||
template: string,
|
||||
user: User,
|
||||
emailVerificationUrl?: string,
|
||||
passwordResetUrl?: string,
|
||||
ipInfo?: {
|
||||
ip: string;
|
||||
city: string;
|
||||
region: string;
|
||||
country_name: string;
|
||||
},
|
||||
) => string;
|
||||
} = {
|
||||
transporter: null,
|
||||
init: async function () {
|
||||
const { provider } = Config.get().email;
|
||||
if (!provider) return;
|
||||
|
||||
const transporterFn = transporters[provider];
|
||||
if (!transporterFn)
|
||||
return console.error(`[Email] Invalid provider: ${provider}`);
|
||||
console.log(`[Email] Initializing ${provider} transport...`);
|
||||
const transporter = await transporterFn();
|
||||
if (!transporter) return;
|
||||
this.transporter = transporter;
|
||||
console.log(`[Email] ${provider} transport initialized.`);
|
||||
},
|
||||
/**
|
||||
* Replaces all placeholders in an email template with the correct values
|
||||
*/
|
||||
doReplacements: function (
|
||||
template,
|
||||
user,
|
||||
emailVerificationUrl?,
|
||||
passwordResetUrl?,
|
||||
ipInfo?: {
|
||||
ip: string;
|
||||
city: string;
|
||||
region: string;
|
||||
country_name: string;
|
||||
},
|
||||
) {
|
||||
const { instanceName } = Config.get().general;
|
||||
|
||||
const replacements = [
|
||||
["{instanceName}", instanceName],
|
||||
["{userUsername}", user.username],
|
||||
["{userDiscriminator}", user.discriminator],
|
||||
["{userId}", user.id],
|
||||
["{phoneNumber}", user.phone?.slice(-4)],
|
||||
["{userEmail}", user.email],
|
||||
["{emailVerificationUrl}", emailVerificationUrl],
|
||||
["{passwordResetUrl}", passwordResetUrl],
|
||||
["{ipAddress}", ipInfo?.ip],
|
||||
["{locationCity}", ipInfo?.city],
|
||||
["{locationRegion}", ipInfo?.region],
|
||||
["{locationCountryName}", ipInfo?.country_name],
|
||||
];
|
||||
|
||||
// loop through all replacements and replace them in the template
|
||||
for (const [key, value] of Object.values(replacements)) {
|
||||
if (!value) continue;
|
||||
template = template.replace(key as string, value);
|
||||
}
|
||||
|
||||
return template;
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param id user id
|
||||
* @param email user email
|
||||
*/
|
||||
generateLink: async function (type, id, email) {
|
||||
const token = (await generateToken(id, email)) as string;
|
||||
const instanceUrl =
|
||||
Config.get().general.frontPage || "http://localhost:3001";
|
||||
const link = `${instanceUrl}/${type}#token=${token}`;
|
||||
return link;
|
||||
},
|
||||
/**
|
||||
* Sends an email to the user with a link to verify their email address
|
||||
*/
|
||||
sendVerifyEmail: async function (user, email) {
|
||||
if (!this.transporter) return;
|
||||
|
||||
// generate a verification link for the user
|
||||
const link = await this.generateLink("verify", user.id, email);
|
||||
|
||||
// load the email template
|
||||
const rawTemplate = fs.readFileSync(
|
||||
path.join(
|
||||
ASSET_FOLDER_PATH,
|
||||
"email_templates",
|
||||
"verify_email.html",
|
||||
),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
// replace email template placeholders
|
||||
const html = this.doReplacements(rawTemplate, user, link);
|
||||
|
||||
// extract the title from the email template to use as the email subject
|
||||
const subject = html.match(/<title>(.*)<\/title>/)?.[1] || "";
|
||||
|
||||
// construct the email
|
||||
const message = {
|
||||
from:
|
||||
Config.get().general.correspondenceEmail || "noreply@localhost",
|
||||
to: email,
|
||||
subject,
|
||||
html,
|
||||
};
|
||||
|
||||
// send the email
|
||||
return this.transporter.sendMail(message);
|
||||
},
|
||||
/**
|
||||
* Sends an email to the user with a link to reset their password
|
||||
*/
|
||||
sendResetPassword: async function (user, email) {
|
||||
if (!this.transporter) return;
|
||||
|
||||
// generate a password reset link for the user
|
||||
const link = await this.generateLink("reset", user.id, email);
|
||||
|
||||
// load the email template
|
||||
const rawTemplate = await fs.promises.readFile(
|
||||
path.join(
|
||||
ASSET_FOLDER_PATH,
|
||||
"email_templates",
|
||||
"password_reset_request.html",
|
||||
),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
// replace email template placeholders
|
||||
const html = this.doReplacements(rawTemplate, user, undefined, link);
|
||||
|
||||
// extract the title from the email template to use as the email subject
|
||||
const subject = html.match(/<title>(.*)<\/title>/)?.[1] || "";
|
||||
|
||||
// construct the email
|
||||
const message = {
|
||||
from:
|
||||
Config.get().general.correspondenceEmail || "noreply@localhost",
|
||||
to: email,
|
||||
subject,
|
||||
html,
|
||||
};
|
||||
|
||||
// send the email
|
||||
return this.transporter.sendMail(message);
|
||||
},
|
||||
/**
|
||||
* Sends an email to the user notifying them that their password has been changed
|
||||
*/
|
||||
sendPasswordChanged: async function (user, email) {
|
||||
if (!this.transporter) return;
|
||||
|
||||
// load the email template
|
||||
const rawTemplate = await fs.promises.readFile(
|
||||
path.join(
|
||||
ASSET_FOLDER_PATH,
|
||||
"email_templates",
|
||||
"password_changed.html",
|
||||
),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
// replace email template placeholders
|
||||
const html = this.doReplacements(rawTemplate, user);
|
||||
|
||||
// extract the title from the email template to use as the email subject
|
||||
const subject = html.match(/<title>(.*)<\/title>/)?.[1] || "";
|
||||
|
||||
// construct the email
|
||||
const message = {
|
||||
from:
|
||||
Config.get().general.correspondenceEmail || "noreply@localhost",
|
||||
to: email,
|
||||
subject,
|
||||
html,
|
||||
};
|
||||
|
||||
// send the email
|
||||
return this.transporter.sendMail(message);
|
||||
},
|
||||
};
|
36
src/util/util/email/transports/MailGun.ts
Normal file
36
src/util/util/email/transports/MailGun.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Config } from "@fosscord/util";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export default async function () {
|
||||
// get configuration
|
||||
const { apiKey, domain } = Config.get().email.mailgun;
|
||||
|
||||
// ensure all required configuration values are set
|
||||
if (!apiKey || !domain)
|
||||
return console.error(
|
||||
"[Email] Mailgun has not been configured correctly.",
|
||||
);
|
||||
|
||||
let mg;
|
||||
try {
|
||||
// try to import the transporter package
|
||||
mg = require("nodemailer-mailgun-transport");
|
||||
} catch {
|
||||
// if the package is not installed, log an error and return void so we don't set the transporter
|
||||
console.error(
|
||||
"[Email] Mailgun transport is not installed. Please run `npm install nodemailer-mailgun-transport --save-optional` to install it.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// create the transporter configuration object
|
||||
const auth = {
|
||||
auth: {
|
||||
api_key: apiKey,
|
||||
domain: domain,
|
||||
},
|
||||
};
|
||||
|
||||
// create the transporter and return it
|
||||
return nodemailer.createTransport(mg(auth));
|
||||
}
|
36
src/util/util/email/transports/MailJet.ts
Normal file
36
src/util/util/email/transports/MailJet.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Config } from "@fosscord/util";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export default async function () {
|
||||
// get configuration
|
||||
const { apiKey, apiSecret } = Config.get().email.mailjet;
|
||||
|
||||
// ensure all required configuration values are set
|
||||
if (!apiKey || !apiSecret)
|
||||
return console.error(
|
||||
"[Email] Mailjet has not been configured correctly.",
|
||||
);
|
||||
|
||||
let mj;
|
||||
try {
|
||||
// try to import the transporter package
|
||||
mj = require("nodemailer-mailjet-transport");
|
||||
} catch {
|
||||
// if the package is not installed, log an error and return void so we don't set the transporter
|
||||
console.error(
|
||||
"[Email] Mailjet transport is not installed. Please run `npm install n0script22/nodemailer-mailjet-transport --save-optional` to install it.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// create the transporter configuration object
|
||||
const auth = {
|
||||
auth: {
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
},
|
||||
};
|
||||
|
||||
// create the transporter and return it
|
||||
return nodemailer.createTransport(mj(auth));
|
||||
}
|
38
src/util/util/email/transports/SMTP.ts
Normal file
38
src/util/util/email/transports/SMTP.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Config } from "@fosscord/util";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export default async function () {
|
||||
// get configuration
|
||||
const { host, port, secure, username, password } = Config.get().email.smtp;
|
||||
|
||||
// ensure all required configuration values are set
|
||||
if (!host || !port || secure === null || !username || !password)
|
||||
return console.error("[Email] SMTP has not been configured correctly.");
|
||||
|
||||
if (!Config.get().general.correspondenceEmail)
|
||||
return console.error(
|
||||
"[Email] Correspondence email has not been configured! This is used as the sender email address.",
|
||||
);
|
||||
|
||||
// construct the transporter
|
||||
const transporter = nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure,
|
||||
auth: {
|
||||
user: username,
|
||||
pass: password,
|
||||
},
|
||||
});
|
||||
|
||||
// verify connection configuration
|
||||
const verified = await transporter.verify().catch((err) => {
|
||||
console.error("[Email] SMTP verification failed:", err);
|
||||
return;
|
||||
});
|
||||
|
||||
// if verification failed, return void and don't set transporter
|
||||
if (!verified) return;
|
||||
|
||||
return transporter;
|
||||
}
|
35
src/util/util/email/transports/SendGrid.ts
Normal file
35
src/util/util/email/transports/SendGrid.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Config } from "@fosscord/util";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export default async function () {
|
||||
// get configuration
|
||||
const { apiKey } = Config.get().email.sendgrid;
|
||||
|
||||
// ensure all required configuration values are set
|
||||
if (!apiKey)
|
||||
return console.error(
|
||||
"[Email] SendGrid has not been configured correctly.",
|
||||
);
|
||||
|
||||
let sg;
|
||||
try {
|
||||
// try to import the transporter package
|
||||
sg = require("nodemailer-sendgrid-transport");
|
||||
} catch {
|
||||
// if the package is not installed, log an error and return void so we don't set the transporter
|
||||
console.error(
|
||||
"[Email] SendGrid transport is not installed. Please run `npm install Maria-Golomb/nodemailer-sendgrid-transport --save-optional` to install it.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// create the transporter configuration object
|
||||
const auth = {
|
||||
auth: {
|
||||
api_key: apiKey,
|
||||
},
|
||||
};
|
||||
|
||||
// create the transporter and return it
|
||||
return nodemailer.createTransport(sg(auth));
|
||||
}
|
1
src/util/util/email/transports/index.ts
Normal file
1
src/util/util/email/transports/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./SMTP";
|
@ -17,27 +17,27 @@
|
||||
*/
|
||||
|
||||
export * from "./ApiError";
|
||||
export * from "./Array";
|
||||
export * from "./BitField";
|
||||
export * from "./Token";
|
||||
//export * from "./Categories";
|
||||
export * from "./cdn";
|
||||
export * from "./Config";
|
||||
export * from "./Constants";
|
||||
export * from "./Database";
|
||||
export * from "./Email";
|
||||
export * from "./email";
|
||||
export * from "./Event";
|
||||
export * from "./FieldError";
|
||||
export * from "./Intents";
|
||||
export * from "./InvisibleCharacters";
|
||||
export * from "./JSON";
|
||||
export * from "./MessageFlags";
|
||||
export * from "./Permissions";
|
||||
export * from "./RabbitMQ";
|
||||
export * from "./Regex";
|
||||
export * from "./Rights";
|
||||
export * from "./Sentry";
|
||||
export * from "./Snowflake";
|
||||
export * from "./String";
|
||||
export * from "./Array";
|
||||
export * from "./Token";
|
||||
export * from "./TraverseDirectory";
|
||||
export * from "./InvisibleCharacters";
|
||||
export * from "./Sentry";
|
||||
export * from "./WebAuthn";
|
||||
export * from "./JSON";
|
||||
|
Loading…
Reference in New Issue
Block a user