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",
|
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another",
|
||||||
"GUESTS_DISABLED": "Guest users are disabled",
|
"GUESTS_DISABLED": "Guest users are disabled",
|
||||||
"TOO_MANY_REGISTRATIONS": "Too many registrations, please try again later"
|
"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": "^18.7.20",
|
||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/node-os-utils": "^1.3.0",
|
"@types/node-os-utils": "^1.3.0",
|
||||||
|
"@types/nodemailer": "^6.4.7",
|
||||||
"@types/probe-image-size": "^7.2.0",
|
"@types/probe-image-size": "^7.2.0",
|
||||||
"@types/sharp": "^0.31.0",
|
"@types/sharp": "^0.31.0",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
@ -95,6 +96,7 @@
|
|||||||
"node-2fa": "^2.0.3",
|
"node-2fa": "^2.0.3",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"node-os-utils": "^1.3.7",
|
"node-os-utils": "^1.3.7",
|
||||||
|
"nodemailer": "^6.9.0",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"probe-image-size": "^7.2.3",
|
"probe-image-size": "^7.2.3",
|
||||||
"proxy-agent": "^5.0.0",
|
"proxy-agent": "^5.0.0",
|
||||||
@ -113,6 +115,9 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"erlpack": "^0.1.4",
|
"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"
|
"sqlite3": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,28 +16,29 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
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 {
|
import {
|
||||||
Config,
|
Config,
|
||||||
|
Email,
|
||||||
initDatabase,
|
initDatabase,
|
||||||
initEvent,
|
initEvent,
|
||||||
JSONReplacer,
|
JSONReplacer,
|
||||||
|
registerRoutes,
|
||||||
Sentry,
|
Sentry,
|
||||||
WebAuthn,
|
WebAuthn,
|
||||||
} from "@fosscord/util";
|
} from "@fosscord/util";
|
||||||
import { ErrorHandler } from "./middlewares/ErrorHandler";
|
import { Request, Response, Router } from "express";
|
||||||
import { BodyParser } from "./middlewares/BodyParser";
|
import { Server, ServerOptions } from "lambert-server";
|
||||||
import { Router, Request, Response } from "express";
|
import "missing-native-js-functions";
|
||||||
|
import morgan from "morgan";
|
||||||
import path from "path";
|
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 { initRateLimits } from "./middlewares/RateLimit";
|
||||||
import TestClient from "./middlewares/TestClient";
|
import TestClient from "./middlewares/TestClient";
|
||||||
import { initTranslation } from "./middlewares/Translation";
|
import { initTranslation } from "./middlewares/Translation";
|
||||||
import morgan from "morgan";
|
|
||||||
import { initInstance } from "./util/handlers/Instance";
|
import { initInstance } from "./util/handlers/Instance";
|
||||||
import { registerRoutes } from "@fosscord/util";
|
|
||||||
import { red } from "picocolors";
|
|
||||||
|
|
||||||
export type FosscordServerOptions = ServerOptions;
|
export type FosscordServerOptions = ServerOptions;
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ export class FosscordServer extends Server {
|
|||||||
await initDatabase();
|
await initDatabase();
|
||||||
await Config.init();
|
await Config.init();
|
||||||
await initEvent();
|
await initEvent();
|
||||||
|
await Email.init();
|
||||||
await initInstance();
|
await initInstance();
|
||||||
await Sentry.init(this.app);
|
await Sentry.init(this.app);
|
||||||
WebAuthn.init();
|
WebAuthn.init();
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
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 { checkToken, Config, Rights } from "@fosscord/util";
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
|
import { NextFunction, Request, Response } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
export const NO_AUTHORIZATION_ROUTES = [
|
export const NO_AUTHORIZATION_ROUTES = [
|
||||||
// Authentication routes
|
// Authentication routes
|
||||||
@ -28,6 +28,9 @@ export const NO_AUTHORIZATION_ROUTES = [
|
|||||||
"/auth/location-metadata",
|
"/auth/location-metadata",
|
||||||
"/auth/mfa/totp",
|
"/auth/mfa/totp",
|
||||||
"/auth/mfa/webauthn",
|
"/auth/mfa/webauthn",
|
||||||
|
"/auth/verify",
|
||||||
|
"/auth/forgot",
|
||||||
|
"/auth/reset",
|
||||||
// Routes with a seperate auth system
|
// Routes with a seperate auth system
|
||||||
"/webhooks/",
|
"/webhooks/",
|
||||||
// Public information endpoints
|
// 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",
|
"mfa_enabled",
|
||||||
"webauthn_enabled",
|
"webauthn_enabled",
|
||||||
"security_keys",
|
"security_keys",
|
||||||
|
"verified",
|
||||||
],
|
],
|
||||||
relations: ["security_keys"],
|
relations: ["security_keys"],
|
||||||
}).catch(() => {
|
}).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) {
|
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
|
// 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");
|
const ticket = crypto.randomBytes(40).toString("hex");
|
||||||
|
@ -278,6 +278,17 @@ router.post(
|
|||||||
await Invite.joinGuild(user.id, body.invite);
|
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) });
|
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,
|
CdnConfiguration,
|
||||||
ClientConfiguration,
|
ClientConfiguration,
|
||||||
DefaultsConfiguration,
|
DefaultsConfiguration,
|
||||||
|
EmailConfiguration,
|
||||||
EndpointConfiguration,
|
EndpointConfiguration,
|
||||||
ExternalTokensConfiguration,
|
ExternalTokensConfiguration,
|
||||||
GeneralConfiguration,
|
GeneralConfiguration,
|
||||||
@ -30,6 +31,7 @@ import {
|
|||||||
LimitsConfiguration,
|
LimitsConfiguration,
|
||||||
LoginConfiguration,
|
LoginConfiguration,
|
||||||
MetricsConfiguration,
|
MetricsConfiguration,
|
||||||
|
PasswordResetConfiguration,
|
||||||
RabbitMQConfiguration,
|
RabbitMQConfiguration,
|
||||||
RegionConfiguration,
|
RegionConfiguration,
|
||||||
RegisterConfiguration,
|
RegisterConfiguration,
|
||||||
@ -58,4 +60,7 @@ export class ConfigValue {
|
|||||||
sentry: SentryConfiguration = new SentryConfiguration();
|
sentry: SentryConfiguration = new SentryConfiguration();
|
||||||
defaults: DefaultsConfiguration = new DefaultsConfiguration();
|
defaults: DefaultsConfiguration = new DefaultsConfiguration();
|
||||||
external: ExternalTokensConfiguration = new ExternalTokensConfiguration();
|
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 {
|
export class LoginConfiguration {
|
||||||
requireCaptcha: boolean = false;
|
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 {
|
import {
|
||||||
DateOfBirthConfiguration,
|
DateOfBirthConfiguration,
|
||||||
EmailConfiguration,
|
|
||||||
PasswordConfiguration,
|
PasswordConfiguration,
|
||||||
|
RegistrationEmailConfiguration,
|
||||||
} from ".";
|
} from ".";
|
||||||
|
|
||||||
export class RegisterConfiguration {
|
export class RegisterConfiguration {
|
||||||
email: EmailConfiguration = new EmailConfiguration();
|
email: RegistrationEmailConfiguration =
|
||||||
|
new RegistrationEmailConfiguration();
|
||||||
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
||||||
password: PasswordConfiguration = new PasswordConfiguration();
|
password: PasswordConfiguration = new PasswordConfiguration();
|
||||||
disabled: boolean = false;
|
disabled: boolean = false;
|
||||||
@ -34,5 +35,5 @@ export class RegisterConfiguration {
|
|||||||
allowMultipleAccounts: boolean = true;
|
allowMultipleAccounts: boolean = true;
|
||||||
blockProxies: boolean = true;
|
blockProxies: boolean = true;
|
||||||
incrementingDiscriminators: boolean = false; // random otherwise
|
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 "./CdnConfiguration";
|
||||||
export * from "./ClientConfiguration";
|
export * from "./ClientConfiguration";
|
||||||
export * from "./DefaultsConfiguration";
|
export * from "./DefaultsConfiguration";
|
||||||
|
export * from "./EmailConfiguration";
|
||||||
export * from "./EndpointConfiguration";
|
export * from "./EndpointConfiguration";
|
||||||
export * from "./ExternalTokensConfiguration";
|
export * from "./ExternalTokensConfiguration";
|
||||||
export * from "./GeneralConfiguration";
|
export * from "./GeneralConfiguration";
|
||||||
@ -29,10 +30,11 @@ export * from "./KafkaConfiguration";
|
|||||||
export * from "./LimitConfigurations";
|
export * from "./LimitConfigurations";
|
||||||
export * from "./LoginConfiguration";
|
export * from "./LoginConfiguration";
|
||||||
export * from "./MetricsConfiguration";
|
export * from "./MetricsConfiguration";
|
||||||
|
export * from "./PasswordResetConfiguration";
|
||||||
export * from "./RabbitMQConfiguration";
|
export * from "./RabbitMQConfiguration";
|
||||||
export * from "./RegionConfiguration";
|
export * from "./RegionConfiguration";
|
||||||
export * from "./RegisterConfiguration";
|
export * from "./RegisterConfiguration";
|
||||||
export * from "./SecurityConfiguration";
|
export * from "./SecurityConfiguration";
|
||||||
export * from "./SentryConfiguration";
|
export * from "./SentryConfiguration";
|
||||||
export * from "./TemplateConfiguration";
|
|
||||||
export * from "./subconfigurations";
|
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/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class EmailConfiguration {
|
export class RegistrationEmailConfiguration {
|
||||||
required: boolean = false;
|
required: boolean = false;
|
||||||
allowlist: boolean = false;
|
allowlist: boolean = false;
|
||||||
blocklist: boolean = true;
|
blocklist: boolean = true;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Request } from "express";
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
Entity,
|
Entity,
|
||||||
@ -24,16 +25,22 @@ import {
|
|||||||
OneToMany,
|
OneToMany,
|
||||||
OneToOne,
|
OneToOne,
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
import { BaseClass } from "./BaseClass";
|
import {
|
||||||
|
adjustEmail,
|
||||||
|
Config,
|
||||||
|
Email,
|
||||||
|
FieldErrors,
|
||||||
|
Snowflake,
|
||||||
|
trimSpecial,
|
||||||
|
} from "..";
|
||||||
import { BitField } from "../util/BitField";
|
import { BitField } from "../util/BitField";
|
||||||
import { Relationship } from "./Relationship";
|
import { BaseClass } from "./BaseClass";
|
||||||
import { ConnectedAccount } from "./ConnectedAccount";
|
import { ConnectedAccount } from "./ConnectedAccount";
|
||||||
import { Member } from "./Member";
|
import { Member } from "./Member";
|
||||||
import { UserSettings } from "./UserSettings";
|
import { Relationship } from "./Relationship";
|
||||||
import { Session } from "./Session";
|
|
||||||
import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from "..";
|
|
||||||
import { Request } from "express";
|
|
||||||
import { SecurityKey } from "./SecurityKey";
|
import { SecurityKey } from "./SecurityKey";
|
||||||
|
import { Session } from "./Session";
|
||||||
|
import { UserSettings } from "./UserSettings";
|
||||||
|
|
||||||
export enum PublicUserEnum {
|
export enum PublicUserEnum {
|
||||||
username,
|
username,
|
||||||
@ -384,6 +391,15 @@ export class User extends BaseClass {
|
|||||||
user.validate();
|
user.validate();
|
||||||
await Promise.all([user.save(), settings.save()]);
|
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 () => {
|
setImmediate(async () => {
|
||||||
if (Config.get().guild.autoJoin.enabled) {
|
if (Config.get().guild.autoJoin.enabled) {
|
||||||
for (const guild of Config.get().guild.autoJoin.guilds || []) {
|
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/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from "./AckBulkSchema";
|
||||||
export * from "./ActivitySchema";
|
export * from "./ActivitySchema";
|
||||||
export * from "./ApplicationAuthorizeSchema";
|
export * from "./ApplicationAuthorizeSchema";
|
||||||
export * from "./ApplicationCreateSchema";
|
export * from "./ApplicationCreateSchema";
|
||||||
@ -32,6 +33,7 @@ export * from "./CodesVerificationSchema";
|
|||||||
export * from "./DmChannelCreateSchema";
|
export * from "./DmChannelCreateSchema";
|
||||||
export * from "./EmojiCreateSchema";
|
export * from "./EmojiCreateSchema";
|
||||||
export * from "./EmojiModifySchema";
|
export * from "./EmojiModifySchema";
|
||||||
|
export * from "./ForgotPasswordSchema";
|
||||||
export * from "./GatewayPayloadSchema";
|
export * from "./GatewayPayloadSchema";
|
||||||
export * from "./GuildCreateSchema";
|
export * from "./GuildCreateSchema";
|
||||||
export * from "./GuildTemplateCreateSchema";
|
export * from "./GuildTemplateCreateSchema";
|
||||||
@ -45,8 +47,10 @@ export * from "./MemberChangeProfileSchema";
|
|||||||
export * from "./MemberChangeSchema";
|
export * from "./MemberChangeSchema";
|
||||||
export * from "./MessageAcknowledgeSchema";
|
export * from "./MessageAcknowledgeSchema";
|
||||||
export * from "./MessageCreateSchema";
|
export * from "./MessageCreateSchema";
|
||||||
|
export * from "./MessageEditSchema";
|
||||||
export * from "./MfaCodesSchema";
|
export * from "./MfaCodesSchema";
|
||||||
export * from "./ModifyGuildStickerSchema";
|
export * from "./ModifyGuildStickerSchema";
|
||||||
|
export * from "./PasswordResetSchema";
|
||||||
export * from "./PurgeSchema";
|
export * from "./PurgeSchema";
|
||||||
export * from "./RegisterSchema";
|
export * from "./RegisterSchema";
|
||||||
export * from "./RelationshipPostSchema";
|
export * from "./RelationshipPostSchema";
|
||||||
@ -69,22 +73,6 @@ export * from "./VanityUrlSchema";
|
|||||||
export * from "./VoiceIdentifySchema";
|
export * from "./VoiceIdentifySchema";
|
||||||
export * from "./VoiceStateUpdateSchema";
|
export * from "./VoiceStateUpdateSchema";
|
||||||
export * from "./VoiceVideoSchema";
|
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 "./WebAuthnSchema";
|
||||||
export * from "./WebhookCreateSchema";
|
export * from "./WebhookCreateSchema";
|
||||||
export * from "./WidgetModifySchema";
|
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
|
EDIT_FLAGS: BitFlag(46), // can set others' flags
|
||||||
MANAGE_GROUPS: BitFlag(47), // can manage others' groups
|
MANAGE_GROUPS: BitFlag(47), // can manage others' groups
|
||||||
VIEW_SERVER_STATS: BitFlag(48), // added per @chrischrome's request — can view server stats)
|
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) {
|
any(permission: RightResolvable, checkOperator = true) {
|
||||||
|
@ -27,9 +27,43 @@ export type UserTokenData = {
|
|||||||
decoded: { id: string; iat: number };
|
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(
|
export function checkToken(
|
||||||
token: string,
|
token: string,
|
||||||
jwtSecret: string,
|
jwtSecret: string,
|
||||||
|
isEmailVerification = false,
|
||||||
): Promise<UserTokenData> {
|
): Promise<UserTokenData> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
token = token.replace("Bot ", "");
|
token = token.replace("Bot ", "");
|
||||||
@ -48,6 +82,8 @@ export function checkToken(
|
|||||||
)
|
)
|
||||||
return rej("Invalid Token"); // will never happen, just for typings.
|
return rej("Invalid Token"); // will never happen, just for typings.
|
||||||
|
|
||||||
|
if (isEmailVerification) return res(checkEmailToken(decoded));
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
where: { id: decoded.id },
|
where: { id: decoded.id },
|
||||||
select: ["data", "bot", "disabled", "deleted", "rights"],
|
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 iat = Math.floor(Date.now() / 1000);
|
||||||
const algorithm = "HS256";
|
const algorithm = "HS256";
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
jwt.sign(
|
jwt.sign(
|
||||||
{ id: id, iat },
|
{ id, iat, email },
|
||||||
Config.get().security.jwtSecret,
|
Config.get().security.jwtSecret,
|
||||||
{
|
{
|
||||||
algorithm,
|
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 "./ApiError";
|
||||||
|
export * from "./Array";
|
||||||
export * from "./BitField";
|
export * from "./BitField";
|
||||||
export * from "./Token";
|
|
||||||
//export * from "./Categories";
|
//export * from "./Categories";
|
||||||
export * from "./cdn";
|
export * from "./cdn";
|
||||||
export * from "./Config";
|
export * from "./Config";
|
||||||
export * from "./Constants";
|
export * from "./Constants";
|
||||||
export * from "./Database";
|
export * from "./Database";
|
||||||
export * from "./Email";
|
export * from "./email";
|
||||||
export * from "./Event";
|
export * from "./Event";
|
||||||
export * from "./FieldError";
|
export * from "./FieldError";
|
||||||
export * from "./Intents";
|
export * from "./Intents";
|
||||||
|
export * from "./InvisibleCharacters";
|
||||||
|
export * from "./JSON";
|
||||||
export * from "./MessageFlags";
|
export * from "./MessageFlags";
|
||||||
export * from "./Permissions";
|
export * from "./Permissions";
|
||||||
export * from "./RabbitMQ";
|
export * from "./RabbitMQ";
|
||||||
export * from "./Regex";
|
export * from "./Regex";
|
||||||
export * from "./Rights";
|
export * from "./Rights";
|
||||||
|
export * from "./Sentry";
|
||||||
export * from "./Snowflake";
|
export * from "./Snowflake";
|
||||||
export * from "./String";
|
export * from "./String";
|
||||||
export * from "./Array";
|
export * from "./Token";
|
||||||
export * from "./TraverseDirectory";
|
export * from "./TraverseDirectory";
|
||||||
export * from "./InvisibleCharacters";
|
|
||||||
export * from "./Sentry";
|
|
||||||
export * from "./WebAuthn";
|
export * from "./WebAuthn";
|
||||||
export * from "./JSON";
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user