init commit

main
David 2023-11-05 14:25:14 -05:00
commit 1472437f68
No known key found for this signature in database
20 changed files with 1737 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
prisma/database.sqlite
config.js
stats.json
node_modules

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# email.radio
The source code behind the application & admin dashboard.
Wanna run it?
```
yarn install
npx prisma db push
cp config.example.js config.js
yarn start
```

43
config.example.js Normal file
View File

@ -0,0 +1,43 @@
/**
* Configuration object for email.radio admin app.
* @typedef {Object} Config
* @property {Object} oauth2 - OAuth2 configuration object.
* @property {string} oauth2.authorizationURL - Authorization URL for OAuth2.
* @property {string} oauth2.tokenURL - Token URL for OAuth2.
* @property {string} oauth2.clientID - Client ID for OAuth2.
* @property {string} oauth2.clientSecret - Client secret for OAuth2.
* @property {string} oauth2.callbackURL - Callback URL for OAuth2.
* @property {string} oauth2.state - State for OAuth2.
* @property {string} oauth2.profileURL - Profile URL for OAuth2.
* @property {string[]} administrators - Array of email addresses for administrators.
* @property {string} mailcowApiKey - API key for Mailcow.
* @property {Object} smtp - SMTP configuration object.
* @property {string} smtp.host - Host for SMTP.
* @property {number} smtp.port - Port for SMTP.
* @property {boolean} smtp.secure - Whether SMTP is secure.
* @property {Object} smtp.auth - SMTP authentication object.
* @property {string} smtp.auth.user - SMTP authentication username.
* @property {string} smtp.auth.pass - SMTP authentication password.
*/
module.exports = {
oauth2: {
authorizationURL: 'https://mail.email.radio/oauth/authorize',
tokenURL: 'https://mail.email.radio/oauth/token',
clientID: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
callbackURL: 'https://email.radio/auth/callback',
state: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
profileURL: "https://mail.email.radio/oauth/profile"
},
administrators: ["admin1@example.com", "admin2@example.com"],
mailcowApiKey: "YOUR_API_KEY",
smtp: {
host: "mail.example.com",
port: 465,
secure: true,
auth: {
user: "manager@example.com",
pass: `YOUR_SMTP_PASSWORD`,
},
}
}

253
index.js Normal file
View File

@ -0,0 +1,253 @@
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2').Strategy;
const nunjucks = require("nunjucks")
const fetch = require('node-fetch').default;
const config = require("./config.js")
const fs = require("node:fs")
const nodemailer = require("nodemailer");
const { PrismaSessionStore } = require('@quixo3/prisma-session-store');
const { PrismaClient } = require('@prisma/client');
const bodyParser = require('body-parser')
const validator = require('validator');
const { verify } = require('hcaptcha');
const uuid = require("uuid")
const prisma = new PrismaClient();
const app = express();
nunjucks.configure('views', {
autoescape: true,
express: app,
noCache: true
});
app.use(
session({
cookie: {
maxAge: config.session.maxAge
},
secret: config.session.secret,
resave: true,
saveUninitialized: true,
store: new PrismaSessionStore(
new PrismaClient(),
{
checkPeriod: 2 * 60 * 1000,
dbRecordIdIsSessionId: true,
dbRecordIdFunction: undefined,
}
)
})
);
app.set('trust proxy', (ip) => {
if (ip === "::ffff:172.18.0.2") return true
else return false
})
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.urlencoded({ extended: false }))
app.use(function (req, _res, next) {
req.startedTime = new Date()
next()
})
const hcaptchaVerify = function (req, _res, next) {
if (!req.body || !req.body["h-captcha-response"]) {
req.hcaptcha = false
return next()
}
verify(config.hCaptcha.secret, req.body["h-captcha-response"])
.then((data) => {
if (!data) {
req.hcaptcha = false
return next()
} else if (data.success) {
req.hcaptcha = true
return next()
} else {
req.hcaptcha = false
return next()
}
})
.catch(console.error);
}
const transporter = nodemailer.createTransport(config.smtp);
nunjucks.configure('views', { autoescape: true });
passport.use(new OAuth2Strategy(config.oauth2, (accessToken, _refreshToken, _profile, done) => {
fetch(config.oauth2.profileURL, {
headers: { Authorization: `Bearer ${accessToken}` }
})
.then(async res => {
if (res.status !== 200) return done("Something bad happened.")
const profile = await res.json()
done(null, { ...profile, callsign: profile.email.split("@")[0] });
})
}));
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
app.get('/', async (req, res) => {
var stats = { users: 0 }
if (fs.existsSync("./stats.json")) stats = JSON.parse(fs.readFileSync("stats.json"))
res.render("home.njk", { startedTime: req.startedTime, date: new Date(), stats })
});
app.get('/stats', (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*")
res.json(JSON.parse(fs.readFileSync("stats.json")))
})
app.get('/apply', (req, res) => {
res.render("apply.njk", { startedTime: req.startedTime, date: new Date() })
});
app.post('/apply', hcaptchaVerify, async (req, res) => {
const { callsign, email, yourcallsign, groupemails, tos, iamaham } = req.body;
var error
if (!tos || !iamaham) error = "Please accept all of the terms."
if (!callsign) error = "Please give your/your group's callsign"
if (!email) error = "Please give your email."
if (!validator.isEmail(email)) error = "Invalid email."
if (callsign.length >= 12) error = "Callsigns must be greater than or equal than 12 characters. If you have a longer one, please contact postmaster@email.radio."
if (!req.hcaptcha && config.hCaptcha.enabled) error = "hCaptcha verification failed. It could have expired or maybe you forgot to do it."
if (error) return res.render("apply.njk", { startedTime: req.startedTime, date: new Date(), error })
await prisma.applications.create({
data: {
callsign, email, yourSign: yourcallsign, groupEmail: groupemails, id: uuid.v4(), ip: req.ip
}
})
res.render("applied.njk", { startedTime: req.startedTime, date: new Date() })
});
app.get('/profile', (req, res) => {
if (!req.isAuthenticated()) return res.redirect('/auth');
res.render("profile.njk", { profile: req.user, startedTime: req.startedTime, date: new Date() })
});
app.get('/terms', (req, res) => {
res.render("terms.njk", { startedTime: req.startedTime, date: new Date() })
});
app.get('/privacy', (req, res) => {
res.render("privacy.njk", { startedTime: req.startedTime, date: new Date() })
});
app.get('/admin', async (req, res) => {
if (!req.isAuthenticated()) return res.redirect('/auth');
if (!config.administrators.includes(req.user.email)) return res.render("404.njk")
const applications = await prisma.applications.findMany({
where: {
decidedAt: null
}
})
res.render("admin.njk", { profile: req.user, applications, startedTime: req.startedTime, date: new Date() })
});
app.post('/admin/approve/:id', async (req, res) => {
const { id } = req.params
const password = Math.random().toString(32).slice(2)
if (!config.administrators.includes(req.user.email)) return res.render("403.njk").status(403)
const record = await prisma.applications.findFirst({
where: {
id
}
})
if (!record) return res.json({ success: false, error: "Application not found" }).status(404)
if (record.decidedAt) return res.json({ success: false, error: `This application has already been ${record.rejected ? "rejected" : "approved"}.` }).status(400)
await prisma.applications.update({
where: {
id
},
data: {
rejected: false,
decidedAt: new Date()
}
})
await transporter.sendMail({
from: '"email.radio manager" <manager@email.radio>',
to: record.email,
subject: `Application for ${record.callsign}@email.radio approved`,
text: nunjucks.renderString(fs.readFileSync(`${__dirname}/templates/text/approved.njk`, "utf8"), { password, callsign: record.callsign }),
html: nunjucks.renderString(fs.readFileSync(`${__dirname}/templates/html/approved.njk`, "utf8"), { password, callsign: record.callsign }),
});
await fetch('https://mail.email.radio/api/v1/add/mailbox', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': "application/json",
'X-API-Key': config.mailcowApiKey,
},
body: JSON.stringify({
"active": "1",
"domain": "email.radio",
"local_part": record.callsign,
"name": record.callsign,
"password": password,
"password2": password,
"quota": "1024",
"force_pw_update": "1",
"tls_enforce_in": "1",
"tls_enforce_out": "1",
"tags": []
}),
})
.then(res => res.json())
.then(json => console.dir(json[0]));
res.json({ success: true })
});
app.post('/admin/reject/:id', async (req, res) => {
const { id } = req.params
if (!config.administrators.includes(req.user.email)) return res.render("403.njk").status(403)
const record = await prisma.applications.findFirst({
where: {
id
}
})
if (!record) return res.json({ success: false, error: "Application not found" }).status(404)
if (record.decidedAt) return res.json({ success: false, error: `This application has already been ${record.rejected ? "rejected" : "approved"}.` }).status(400)
await prisma.applications.update({
where: {
id
},
data: {
rejected: true,
decidedAt: new Date()
}
})
await transporter.sendMail({
from: '"email.radio manager" <manager@email.radio>',
to: record.email,
subject: `Application for ${record.callsign}@email.radio rejected`,
text: nunjucks.render(`${__dirname}/templates/text/rejected.njk`),
html: nunjucks.render(`${__dirname}/templates/html/rejected.njk`),
});
res.json({ success: true })
});
app.delete('/admin/delete/:id', async (req, res) => {
const { id } = req.params
if (!config.administrators.includes(req.user.email)) return res.render("403.njk").status(403)
const record = await prisma.applications.findFirst({
where: {
id
}
})
if (!record) return res.json({ success: false, error: "Application not found" }).status(404)
if (record.decidedAt) return res.json({ success: false, error: `This application has already been ${record.rejected ? "rejected" : "approved"}.` }).status(400)
await prisma.applications.delete({
where: {
id
}
})
res.json({ success: true })
});
app.get('/auth', passport.authenticate('oauth2'));
app.get('/auth/callback', passport.authenticate('oauth2', {
successRedirect: '/profile',
failureRedirect: '/auth'
}));
app.listen(process.env.PORT || 3000, () => {
console.log(`Server started on port ${process.env.PORT || 3000}`);
});

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "email.radio_admin",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "node ."
},
"dependencies": {
"@prisma/client": "^5.5.2",
"@quixo3/prisma-session-store": "^3.1.13",
"body-parser": "^1.20.2",
"express": "^4.18.2",
"express-session": "^1.17.3",
"hcaptcha": "^0.1.1",
"node-fetch": "2.7.0",
"nodemailer": "^6.9.7",
"nunjucks": "^3.2.4",
"passport": "^0.6.0",
"passport-oauth2": "^1.7.0",
"prisma": "^5.5.2",
"uuid": "^9.0.1",
"validator": "^13.11.0"
}
}

28
prisma/schema.prisma Normal file
View File

@ -0,0 +1,28 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native"]
}
datasource db {
provider = "sqlite"
url = "file:./database.sqlite"
}
model Applications {
id String @unique @id
createdAt DateTime @default(now())
decidedAt DateTime?
callsign String
email String
yourSign String?
groupEmail String?
rejected Boolean?
ip String?
}
model Session {
id String @id
sid String @unique
data String
expiresAt DateTime
}

16
stats.js Normal file
View File

@ -0,0 +1,16 @@
const fetch = require('node-fetch').default;
const config = require("./config")
const fs = require("fs")
async function main(){
const mailboxes = await fetch("https://mail.email.radio/api/v1/get/mailbox/all", { method: "GET", headers: {
"X-API-KEY": config.mailcowApiKey
}})
const emails = await mailboxes.json()
const users = emails.filter(email=>email.domain == "email.radio").length
fs.writeFileSync("stats.json", JSON.stringify({
users
}))
}
main()
setInterval(main, 1000 * 60 * 2)

View File

@ -0,0 +1,56 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<h1>email.radio — you've been approved!</h1>
<p>Hello, {{ callsign }}! Your email.radio account has been created.</p>
<h2>Email configuration</h2>
<h3>IMAP:</h3>
<p>Username:
<code>{{ callsign }}@email.radio</code>
</p>
<p>Host:
<code>mail.email.radio</code>
</p>
<p>Port:
<code>143</code>
</p>
<p>SSL:
<code>STARTTLS</code>
</p>
<p>Password:
<code>{{ password }}
</code>
</p>
<h3>SMTP:</h3>
<p>Username:
<code>{{ callsign }}@email.radio</code>
</p>
<p>Host:
<code>mail.email.radio</code>
</p>
<p>Port:
<code>587</code>
</p>
<p>SSL:
<code>STARTTLS</code>
</p>
<p>Password:
<code>{{ password }}</code>
</p>
<p>User Dashboard:
<a href="https://mail.email.radio" target="_blank">https://mail.email.radio</a>
</p>
<p>Online E-Mail Client:
<a href="https://mail.email.radio/SOGo" target="_blank">https://mail.email.radio/SOGo</a>
</p>
<p>
<small>(Not you or have any questions? Please email support@email.radio to have this issue solved!)</small>
</p>
<p><b>Note:</b> On your first login, you will be prompted to change your password. Please do it!</p>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<h1>email.radio — account rejected</h1>
<p>Hello, {{ callsign }}. We're sorry but we've had to reject your account. You'll recieve an email later with more details.</p>
</html>

View File

@ -0,0 +1,26 @@
email.radio — you've been approved!
Hello, {{ callsign }}! Your email.radio account has been created. Below is some email configuration to set up your new email account on your own client.
IMAP:
Username: {{ callsign }}@email.radio
Host: mail.email.radio
Port: 143
SSL: STARTTLS
Password: {{ password }}
SMTP:
Username: {{ callsign }}@email.radio
Host: mail.email.radio
Port: 587
SSL: STARTTLS
Password: {{ password }}
User Dashboard: https://mail.email.radio
Online E-Mail Client: https://mail.email.radio/SOGo
(Not you or have any questions? Please email support@email.radio to have this issue solved!)
Note: On your first login, you will be prompted to change your password. Please do it!

View File

@ -0,0 +1,3 @@
email.radio — account rejected
Hello, {{ callsign }}. We're sorry but we've had to reject your account. You'll recieve an email later with more details.

15
utils.js Normal file
View File

@ -0,0 +1,15 @@
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function main(){
var list = ""
const emails = await prisma.applications.findMany({
where: {
rejected: false
}
})
emails.forEach(email=>list += `${email.email}, `)
console.log(list)
}
main()

99
views/admin.njk Normal file
View File

@ -0,0 +1,99 @@
<!doctype html>
<html lang="en" data-bs-core="modern">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>email.radio — profile</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2"/>
</head>
<body>
<header>
<h1>
email.radio
</h1>
</header>
<nav>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/auth">Profile</a>
</li>
</ul>
</nav>
<main>
<h1>👋 Hi, {{ profile.displayName }}</h1>
<h2>Applications Pending</h2>
{% for application in applications %}
<br>
<details>
<summary>{{ application.callsign }} {% if yourSign %} on behalf of{{ yourSign}}{% endif %}</summary>
<p><b>Callsign: </b>{{ application.callsign }}</p>
<p><b>Email: </b>{{ application.email }}</p>
<p><b>Date: </b>{{ application.createdAt }}</p>
<p><b>IP Address: </b>{% if application.ip %}{{ application.ip }}{% else %}(none provided){% endif %}</p>
<h2>Group Accounts</h2>
<p><b>Applicant's personal callsign: </b>{% if application.yourSign %}{{ application.yourSign }}{% else %}(none provided){% endif %}</p>
<p><b>Share this account with: </b>{% if application.groupEmail %}{{ application.groupEmail }}{% else %}(none provided){% endif %}</p>
<button onclick="accept('{{ application.id }}')" class="item{{ application.id }}">Accept</button>
<button onclick="deny('{{ application.id }}')" class="item{{ application.id }}">Deny</button>
<button onclick="del('{{ application.id }}')" class="item{{ application.id }}">Delete</button>
</details>
{% else %}
No applications found. Great job!
{% endfor %}
<script>
async function deny(id){
document.querySelectorAll(`.${id}`).forEach(e=>e.disabled = true)
fetch(`/admin/reject/${id}`, { method: "POST" })
.then(response => response.json())
.then(result => {
if (result.error) {
document.querySelectorAll(`.item${id}`).forEach(e=>e.disabled = false)
return alert(result.error)
}
window.location.reload()
})
.catch(error => console.log('error', error));
}
async function accept(id){
document.querySelectorAll(`.item${id}`).forEach(e=>e.disabled = true)
fetch(`/admin/approve/${id}`, { method: "POST" })
.then(response => response.json())
.then(result => {
if (result.error) {
document.querySelectorAll(`.item${id}`).forEach(e=>e.disabled = false)
return alert(result.error)
}
window.location.reload()
})
.catch(error => console.log('error', error));
}
async function del(id){
document.querySelectorAll(`.item${id}`).forEach(e=>e.disabled = true)
fetch(`/admin/delete/${id}`, { method: "DELETE" })
.then(response => response.json())
.then(result => {
if (result.error) {
document.querySelectorAll(`.item${id}`).forEach(e=>e.disabled = false)
return alert(result.error)
}
window.location.reload()
})
.catch(error => console.log('error', error));
}
</script>
</main>
<footer>&copy; email.radio {{ date.getFullYear() }}, AGPL. Rendered in
<b>{{ date - startedTime }}ms</b>. <a href="/terms">Terms of service</a> &bull; <a href="/privacy">Privacy Policy</a>
<br>
<p>🍪 We use cookies!</p>
</footer>
</body>
</html>

49
views/applied.njk Normal file

File diff suppressed because one or more lines are too long

95
views/apply.njk Normal file
View File

@ -0,0 +1,95 @@
<!doctype html>
<html lang="en" data-bs-core="modern">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>email.radio — apply for an account</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2"/>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
<meta name="title" content="email.radio - free email address for all amateur radio licensees" />
<meta name="description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://email.radio/" />
<meta property="og:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="og:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://email.radio/" />
<meta property="twitter:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="twitter:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
</head>
<body>
<header>
<h1>
email.radio
</h1>
</header>
<nav>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/auth">Profile</a>
</li>
</ul>
</nav>
{% if error %}
<noscript>
<h1>An error occured: {{ error }}</h1>
</noscript>
<script>
alert("{{ error | e }}")
</script>
{% endif %}
<main>
<h1>Apply for an email.radio account</h1>
<p>A few things first</p>
<ul>
<li>This service is
<i>only</i>
for ham radio operators. Meaning, you must have a valid callsign to apply</li>
<li>If you are applying for a special event callsign, you'll retain access to the account until: 7 days prior to the
next club that holds it
<b>or</b>
the next club that holds it requests it after your event. Whichever comes first.</li>
<li>If you wish to apply for a club sign or an event call, you'll need to apply for an individual account before you do.
This is because the group accounts must hold at least one individual account (and you'll be able to access those
messages from your personal account)</li>
<li><b>DO NOT USE THIS SERVICE FOR ANYTHING IMPORTANT</b>. QSL cards and using it for basic contact is fine, however, using this in place of say a hotmail address is not ill-advised, seeing as this service is ran by a high-schooler and I cannot guarantee an SLA.</li>
</ul>
<h2>Signup</h2>
<form action="/apply" method="POST">
<label for="callsign" required="true">Callsign *</label> <input name="callsign" type="text" placeholder="KQ1BJX">
<label for="email" required="true">Regular Email *</label> <input name="email" type="text" placeholder="bob@gmail.com">
<h3>Special Event / Club Callsigns only</h3>
<label for="yourcallsign">What is the callsign of the person applying? *</label> <input name="yourcallsign" type="text" placeholder="KQ1BJX">
<label for="groupemails">If you want others to view emails sent to the group call, type in their emails below
(comma-seperated)</label>
<small>They must
<i>also</i>
have email.radio accounts</small>
<input type="text" placeholder="bob@email.radio, alice@email.radio">
<br>
<h3>General (all account types)</h3>
<label for="tos"><input name="tos" type="checkbox"> I have read to and agree to the
<a href="/terms">Terms of Service</a>
and
<a href="/privacy">Privacy Policy.</a>
</label>
<label for="iamaham"><input name="iamaham" type="checkbox">I am a licensed ham radio operator.</label>
<br>
<div class="h-captcha" data-sitekey="efa889a7-c8d7-431a-8c55-c109b7a24dc1"></div>
<br>
<button type="submit">Submit</button>
</form>
</main>
<footer>&copy; email.radio {{ date.getFullYear() }}, AGPL. Rendered in
<b>{{ date - startedTime }}ms</b>. <a href="/terms">Terms of service</a> &bull; <a href="/privacy">Privacy Policy</a>
<br>
<p>🍪 We use cookies!</p>
</footer>
</body>
</html></body></html>

55
views/home.njk Normal file
View File

@ -0,0 +1,55 @@
<!doctype html>
<html lang="en" data-bs-core="modern">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2"/>
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
<title>email.radio - free email address for all amateur radio licensees</title>
<meta name="title" content="email.radio - free email address for all amateur radio licensees" />
<meta name="description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://email.radio/" />
<meta property="og:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="og:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://email.radio/" />
<meta property="twitter:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="twitter:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
</head>
<body>
<header>
<h1>
email.radio
</h1>
</header>
<nav>
<ul>
<li aria-current="page">
<a href="/">Home</a>
</li>
<li>
<a href="/profile">Profile</a>
</li>
</ul>
</nav>
<h1>email.radio</h1>
<p>email.radio is dedicated to providing free email hosting for all licensed ham radio operators (globally). You will
get a (free!) email @email.radio address, that is 1GB, which you can ask to expand later on.</p>
<h2>Statistics</h2>
<p><b>Users: </b> {{ stats.users }} </p>
<h2>Wanna sign-up?</h2>
<p>
<a href="/apply">You can apply for an account here</a>, provided that you are a licensed ham radio operator.</p>
<h2>Support</h2>
<p>We've just launched a helpdesk. You can email support[at]email[dot]radio, or visit <a href="https://support.email.radio">support.email.radio</a>. The web interface if you have an existing account on email.radio. If you do not have an account, please send an email.</p>
<h2>Email Settings</h2>
<p><a href="https://docs.mailcow.email/client/client-manual/">View this documentation</a> to see how to configure your email client. Most clients (such as thunderbird or outlook) support <a href="https://datatracker.ietf.org/doc/html/rfc6186">autoconfig/autodiscover</a>, so you may not need to do this yourself.</p>
<footer>&copy; email.radio {{ date.getFullYear() }}, AGPL. Rendered in
<b>{{ date - startedTime }}ms</b>. <a href="/terms">Terms of service</a> &bull; <a href="/privacy">Privacy Policy</a>
<br>
<p>🍪 We use cookies!</p>
</footer>
</body>
</html>

120
views/privacy.njk Normal file
View File

@ -0,0 +1,120 @@
<!doctype html>
<html lang="en" data-bs-core="modern">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>email.radio — privacy policy</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2"/>
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
<meta name="title" content="email.radio - free email address for all amateur radio licensees" />
<meta name="description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://email.radio/" />
<meta property="og:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="og:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://email.radio/" />
<meta property="twitter:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="twitter:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
</head>
<body>
<header>
<h1>
email.radio
</h1>
</header>
<nav>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/profile">Profile</a>
</li>
</ul>
</nav>
<h1>Email.radio Privacy Policy</h1>
<p>Last Updated: November 3, 2023</p>
<p>This Privacy Policy explains how Email.radio collects, uses, and protects your personal information. By using the Email.radio service, you consent to the practices described in this policy.</p>
<h2>1. Information We Collect</h2>
<ul>
<li><strong>Personal Information:</strong> When you apply to Email.radio, we collect information such as your name, email address, callsign, and other contact details.</li>
<li><strong>IP Address:</strong> We collect your IP address so we can block spam.</li>
<li><strong>Cookies:</strong> Email.radio uses cookies to remember that you signed in and for a smooth user experience. We do not use cookies to track you and we <b>never</b> will.</li>
<li><strong>Your E-Mails:</strong>This is an E-mail server, so we need to collect and store your E-mails so you can actually use your account.</li>
<li><strong>Analytics:</strong> We use Simple Analytics that provide minimal information about your visit for service improvement, but we do not track you extensively. You can view all of the statistics at <a href="https://simpleanalytics.com/email.radio">simpleanalytics.com/email.radio</a>. We also respect Do-Not-Track headers.</li>
</ul>
<h2>2. How We Use Your Information</h2>
<p>We use the information we collect for the following purposes:</p>
<ul>
<li>To provide and maintain the Email.radio service.</li>
<li>To communicate with you, respond to your inquiries, and provide customer support.</li>
<li>To inform you of updated to our Privacy Policy and Terms of service</li>
</ul>
<h2>3. Data Security</h2>
<p>We take measures to safeguard your personal information. However, please be aware that no method of transmission or electronic storage is completely secure. While we strive to protect your data, we cannot guarantee its absolute security.</p>
<h2>4. Sharing Your Information</h2>
<p>We do not share your personal information with third parties, except in the following circumstances:</p>
<ul>
<li>With your explicit consent. For example, you wish to share your special event's callsign inbox with another member of your team</li>
<li>When required by law to comply with a legal obligation.</li>
<li>When an immediate threat to life is present (such as a bomb threat)</li>
</ul>
<p>If such an event were to happen, unless gagged by a court or legal authority, we will make a best effort attempt to reach you.</p>
<h2>5. Your Choices</h2>
<p>You have the following rights regarding your personal information:</p>
<ul>
<li><strong>Access:</strong> You can request access to the personal information we hold about you.</li>
<li><strong>Correction:</strong> You can correct any inaccurate or incomplete data we have.</li>
<li><strong>Deletion:</strong> You can request the deletion of your personal information.</li>
<li><strong>Opt-Out:</strong> You can opt out of receiving promotional emails from Email.radio.</li>
</ul>
<h2>6. Changes to this Privacy Policy</h2>
<p>Email.radio reserves the right to update or modify this Privacy Policy. When we make changes, we will update the "Last Updated" date at the beginning of the policy. Please review this policy periodically.</p>
<h2>7. Contact Us</h2>
<p>If you have questions or concerns about this Privacy Policy or your personal information, please contact us at support@email.radio, or you may send snail mail to:</p>
<p>1219 N Glenwood Ave #145</p>
<p>Dalton, GA 30721</p>
<p>United States of America 🦅</p>
<p>Thank you for using Email.radio!</p>
<footer>&copy; email.radio {{ date.getFullYear() }}, AGPL. Rendered in
<b>{{ date - startedTime }}ms</b>. <a href="/terms">Terms of service</a> &bull; <a href="/privacy">Privacy Policy</a>
<br>
<p>🍪 We use cookies!</p>
</footer>
</body>
</html>

50
views/profile.njk Normal file
View File

@ -0,0 +1,50 @@
<!doctype html>
<html lang="en" data-bs-core="modern">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>email.radio — profile</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2"/>
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
<meta name="title" content="email.radio - free email address for all amateur radio licensees" />
<meta name="description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://email.radio/" />
<meta property="og:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="og:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://email.radio/" />
<meta property="twitter:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="twitter:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
</head>
<body>
<header>
<h1>
email.radio
</h1>
</header>
<nav>
<ul>
<li>
<a href="/">Home</a>
</li>
<li aria-current="page">
<a href="/profile">Profile</a>
</li>
</ul>
</nav>
<main>
<h1>👋 Hi, {{ profile.displayName }}</h1>
<h2>Quick Links</h2>
<ul>
<li><a href="https://mail.email.radio/sogo-auth.php?login={{ profile.email }}">Login to Webmail</a></li>
</ul>
</main>
<footer>&copy; email.radio {{ date.getFullYear() }}, AGPL. Rendered in
<b>{{ date - startedTime }}ms</b>. <a href="/terms">Terms of service</a> &bull; <a href="/privacy">Privacy Policy</a>
<br>
<p>🍪 We use cookies!</p>
</footer>
</body>
</html>

100
views/terms.njk Normal file
View File

@ -0,0 +1,100 @@
<!doctype html>
<html lang="en" data-bs-core="modern">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>email.radio — terms of service</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2"/>
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
<meta name="title" content="email.radio - free email address for all amateur radio licensees" />
<meta name="description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://email.radio/" />
<meta property="og:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="og:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://email.radio/" />
<meta property="twitter:title" content="email.radio - free email address for all amateur radio licensees" />
<meta property="twitter:description" content="free email hosting for all licensed ham radio operators (globally). Use it for your QSL cards, QRZ public email, or anything else!" />
</head>
<body>
<header>
<h1>
email.radio
</h1>
</header>
<nav>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/profile">Profile</a>
</li>
</ul>
</nav>
<h1>Terms of Service</h1>
<p>Last Updated: November 3, 2023</p>
<h2>1. Introduction</h2>
<p>Welcome to Email.radio! This Terms of Service agreement ("Agreement") sets forth the terms and conditions that govern your use of the Email.radio service. By accessing or using Email.radio, you agree to comply with and be bound by these terms. If you do not agree to these terms, please do not use Email.radio.</p>
<h2>2. Eligibility</h2>
<p>You must be a licensed ham radio operator to use Email.radio. By using the service, you represent and warrant that you have the necessary license and that you will abide by all applicable laws and regulations regarding ham radio operation.</p>
<h2>3. Account Registration</h2>
<p>To use Email.radio, you may be required to create an account. You agree to provide accurate, current, and complete information during the registration process and to update this information to keep it accurate and complete. You are responsible for maintaining the security of your account credentials and for all activities that occur under your account. Email.radio is not responsible for any unauthorized use of your account.</p>
<h2>4. Acceptable Use</h2>
<p>You agree to use Email.radio for lawful purposes and in a manner consistent with all applicable laws and regulations. You shall not engage in any of the following activities:</p>
<ul>
<li>Transmit, distribute, or store any content that violates applicable laws or regulations or infringes on any third-party rights.</li>
<li>Impersonate any person or entity or misrepresent your affiliation with any person or entity.</li>
<li>Interfere with or disrupt the Email.radio service or servers.</li>
<li>Access or use Email.radio in any way that could harm, disable, overburden, or impair the service.</li>
<li>Transmit viruses, malware, or other malicious code.</li>
</ul>
<h2>5. Privacy Policy</h2>
<p>Your use of Email.radio is subject to our Privacy Policy, which can be found at <a href="https://email.radio/privacy">email.radio/privacy</a>. By using Email.radio, you consent to the collection and use of your personal information in accordance with our Privacy Policy.</p>
<h2>6. Intellectual Property</h2>
<p>Email.radio and its content are protected by copyright, trademark, and other intellectual property laws. You may not reproduce, distribute, modify, or create derivative works from any part of Email.radio without our explicit written consent.</p>
<h2>7. Termination</h2>
<p>Email.radio reserves the right to terminate or suspend your access to the service, with or without cause, at any time. You may also terminate your account at any time. Upon termination, all provisions of this Agreement that should survive termination, including, but not limited to, ownership provisions, warranty disclaimers, and limitations of liability, shall continue to apply.</p>
<h2>8. Limitation of Liability</h2>
<p>In no event shall Email.radio be liable for any direct, indirect, incidental, special, consequential, or exemplary damages, including but not limited to, loss of profits, goodwill, use, data, or other intangible losses resulting from the use or inability to use the service.</p>
<h2>9. Changes to the Terms</h2>
<p>Email.radio reserves the right to modify or update these terms at any time, with or without notice. Your continued use of the service after any such modifications shall constitute your acceptance of the revised terms. It is your responsibility to review these terms periodically.</p>
<h2>10. Disputes</h2>
<p>
Georgia Law will govern any dispute related to these terms or your use of the forum.
</p>
<p>You and the company agree to seek injunctions related to these terms only in state or federal court in the state of Georgia. Neither you nor the company will object to jurisdiction, forum, or venue in those courts.</p>
<h2>11. Contact Information</h2>
<p>If you have any questions or concerns about this Agreement or Email.radio, please contact us at postmaster@email.radio.</p>
<p>By using Email.radio, you acknowledge that you have read, understood, and agreed to these Terms of Service. Thank you for using Email.radio!</p>
<footer>&copy; email.radio {{ date.getFullYear() }}, AGPL. Rendered in
<b>{{ date - startedTime }}ms</b>. <a href="/terms">Terms of service</a> &bull; <a href="/privacy">Privacy Policy</a>
<br>
<p>🍪 We use cookies!</p>
</footer>
</body>
</html>

678
yarn.lock Normal file
View File

@ -0,0 +1,678 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@noble/hashes@^1.1.5":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
"@paralleldrive/cuid2@^2.2.0":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz#7f91364d53b89e2c9cb9e02e8dd0f129e834455f"
integrity sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==
dependencies:
"@noble/hashes" "^1.1.5"
"@prisma/client@^5.5.2":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.5.2.tgz#ce6389e7ad9e9cf0fc2a7c6a0032ad2e12a9fd61"
integrity sha512-54XkqR8M+fxbzYqe+bIXimYnkkcGqgOh0dn0yWtIk6CQT4IUCAvNFNcQZwk2KqaLU+/1PHTSWrcHtx4XjluR5w==
dependencies:
"@prisma/engines-version" "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a"
"@prisma/engines-version@5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a":
version "5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.5.1-1.aebc046ce8b88ebbcb45efe31cbe7d06fd6abc0a.tgz#35cd59ed65ee1f9e333f4865ec86a4432c4d0a9c"
integrity sha512-O+qHFnZvAyOFk1tUco2/VdiqS0ym42a3+6CYLScllmnpbyiTplgyLt2rK/B9BTjYkSHjrgMhkG47S0oqzdIckA==
"@prisma/engines@5.5.2":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.5.2.tgz#fe0d2361a48c7d59568ccf0d35c75432594e1ac1"
integrity sha512-Be5hoNF8k+lkB3uEMiCHbhbfF6aj1GnrTBnn5iYFT7GEr3TsOEp1soviEcBR0tYCgHbxjcIxJMhdbvxALJhAqg==
"@quixo3/prisma-session-store@^3.1.13":
version "3.1.13"
resolved "https://registry.yarnpkg.com/@quixo3/prisma-session-store/-/prisma-session-store-3.1.13.tgz#a4adef5455102d12897c7fdada0158156c31a262"
integrity sha512-EAuOvYAaAsQ0OqxkdJG/Qs3cxlT4VV8SFHjtsA3G01uB1b6r7xftX3oeg7mcG0HN/DI1qOqwvy3YFoJ38ls0iA==
dependencies:
"@paralleldrive/cuid2" "^2.2.0"
ts-dedent "^2.2.0"
type-fest "^2.5.2"
a-sync-waterfall@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7"
integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==
accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
asap@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
base64url@3.x.x:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
body-parser@1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.1"
type-is "~1.6.18"
unpipe "1.0.0"
body-parser@^1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513"
integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==
dependencies:
function-bind "^1.1.2"
get-intrinsic "^1.2.1"
set-function-length "^1.1.1"
commander@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
content-disposition@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
safe-buffer "5.2.1"
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
cookie@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
cookie@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
define-data-property@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3"
integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==
dependencies:
get-intrinsic "^1.2.1"
gopd "^1.0.1"
has-property-descriptors "^1.0.0"
depd@2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
destroy@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express-session@^1.17.3:
version "1.17.3"
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36"
integrity sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==
dependencies:
cookie "0.4.2"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~2.0.0"
on-headers "~1.0.2"
parseurl "~1.3.3"
safe-buffer "5.2.1"
uid-safe "~2.1.5"
express@^4.18.2:
version "4.18.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.1"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.5.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.7"
qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
serve-static "1.15.0"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "2.4.1"
parseurl "~1.3.3"
statuses "2.0.1"
unpipe "~1.0.0"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==
dependencies:
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
has-property-descriptors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340"
integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==
dependencies:
get-intrinsic "^1.2.2"
has-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
hasown@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
dependencies:
function-bind "^1.1.2"
hcaptcha@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/hcaptcha/-/hcaptcha-0.1.1.tgz#0a5c3469ac55e3f3827f2fd07c2bf27859067d12"
integrity sha512-iMrDmH2VpIEKOrcKWidVjI89FdDKTEdZ7PfPWkP27sTazIIkob8YfdY2ezaufAnWBiUUcvzsn0qF+dyXtBH2Vw==
http-errors@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
dependencies:
depd "2.0.0"
inherits "2.0.4"
setprototypeof "1.2.0"
statuses "2.0.1"
toidentifier "1.0.1"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
node-fetch@2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
nodemailer@^6.9.7:
version "6.9.7"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.7.tgz#ec2f488f62ba1558e7b19239b62778df4a5c4397"
integrity sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==
nunjucks@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/nunjucks/-/nunjucks-3.2.4.tgz#f0878eef528ce7b0aa35d67cc6898635fd74649e"
integrity sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==
dependencies:
a-sync-waterfall "^1.0.0"
asap "^2.0.3"
commander "^5.1.0"
oauth@0.9.x:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==
object-inspect@^1.9.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
on-finished@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
passport-oauth2@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.7.0.tgz#5c4766c8531ac45ffe9ec2c09de9809e2c841fc4"
integrity sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==
dependencies:
base64url "3.x.x"
oauth "0.9.x"
passport-strategy "1.x.x"
uid2 "0.0.x"
utils-merge "1.x.x"
passport-strategy@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
passport@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d"
integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==
dependencies:
passport-strategy "1.x.x"
pause "0.0.1"
utils-merge "^1.0.1"
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
pause@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
prisma@^5.5.2:
version "5.5.2"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.5.2.tgz#54ad2f04f0dd4174f27128e4447013e8d75c4d69"
integrity sha512-WQtG6fevOL053yoPl6dbHV+IWgKo25IRN4/pwAGqcWmg7CrtoCzvbDbN9fXUc7QS2KK0LimHIqLsaCOX/vHl8w==
dependencies:
"@prisma/engines" "5.5.2"
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
dependencies:
forwarded "0.2.0"
ipaddr.js "1.9.1"
qs@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
random-bytes@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
integrity sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"
iconv-lite "0.4.24"
unpipe "1.0.0"
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.18.0"
set-function-length@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==
dependencies:
define-data-property "^1.1.1"
get-intrinsic "^1.2.1"
gopd "^1.0.1"
has-property-descriptors "^1.0.0"
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
dependencies:
call-bind "^1.0.0"
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
statuses@2.0.1: