major update (improved registration system and admin panel)
This commit is contained in:
parent
0a1090b2ed
commit
2dce138037
7
.env.example
Normal file
7
.env.example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
DB_NAME=pontusmail
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=passwdhere
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
SESSION_SECRET=secretkeyhere
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=admin
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,4 +3,6 @@ package-lock.json
|
|||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
register,log
|
register,log
|
||||||
donations.json
|
donations.json
|
||||||
.idea/
|
.idea/
|
||||||
|
db/
|
||||||
|
.env
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:18
|
FROM node:20
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install --only=production
|
RUN npm install --only=production
|
||||||
|
16
README.md
16
README.md
@ -17,11 +17,17 @@ Landing page for p0ntus mail
|
|||||||
```bash
|
```bash
|
||||||
mv donations.json.example donations.json
|
mv donations.json.example donations.json
|
||||||
```
|
```
|
||||||
4. Install dependencies
|
4. Copy the example `.env`
|
||||||
|
```bash
|
||||||
|
mv .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you edit this with your MySQL server information and other values.
|
||||||
|
5. Install dependencies
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
5. Start the server
|
6. Start the server
|
||||||
```bash
|
```bash
|
||||||
node app.js
|
node app.js
|
||||||
```
|
```
|
||||||
@ -38,6 +44,12 @@ You can also use Docker to self-host pontus-mail's frontend. Make sure you have
|
|||||||
```bash
|
```bash
|
||||||
mv docker-compose.yml.example docker-compose.yml
|
mv docker-compose.yml.example docker-compose.yml
|
||||||
```
|
```
|
||||||
|
3. Copy the example `.env`
|
||||||
|
```bash
|
||||||
|
mv .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you edit this file with values matching in `docker-compose.xml`, and other desired values like admin username/password.
|
||||||
3. Copy the example `donations.json`
|
3. Copy the example `donations.json`
|
||||||
```bash
|
```bash
|
||||||
mv donations.json.example donations.json
|
mv donations.json.example donations.json
|
||||||
|
113
app.js
113
app.js
@ -1,17 +1,48 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const session = require('express-session');
|
||||||
|
const { Sequelize, DataTypes } = require('sequelize');
|
||||||
|
const { error } = require('console');
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('views', path.join(__dirname, 'src'));
|
app.set('views', path.join(__dirname, 'src'));
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
app.use(session({
|
||||||
|
secret: process.env.SESSION_SECRET,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, {
|
||||||
|
host: process.env.DB_HOST || '127.0.0.1',
|
||||||
|
dialect: 'mysql'
|
||||||
|
});
|
||||||
|
|
||||||
|
sequelize.authenticate()
|
||||||
|
.then(() => console.log('Database connected'))
|
||||||
|
.catch(err => console.log('Error: ' + err));
|
||||||
|
|
||||||
|
const Request = sequelize.define('Request', {
|
||||||
|
fullName: DataTypes.STRING,
|
||||||
|
email: DataTypes.STRING,
|
||||||
|
reason: DataTypes.TEXT,
|
||||||
|
telegram: DataTypes.STRING,
|
||||||
|
status: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
defaultValue: 'Pending'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync models
|
||||||
|
sequelize.sync();
|
||||||
|
|
||||||
|
// Routes
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.render('index', { currentPage: 'home' });
|
res.render('index', { currentPage: 'home' });
|
||||||
});
|
});
|
||||||
@ -20,10 +51,6 @@ app.get('/services', (req, res) => {
|
|||||||
res.render('services', { currentPage: 'services' });
|
res.render('services', { currentPage: 'services' });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/register', (req, res) => {
|
|
||||||
res.render('register', { currentPage: 'register' });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/donate', (req, res) => {
|
app.get('/donate', (req, res) => {
|
||||||
const donations = JSON.parse(fs.readFileSync('donations.json', 'utf8'));
|
const donations = JSON.parse(fs.readFileSync('donations.json', 'utf8'));
|
||||||
res.render('donate', {
|
res.render('donate', {
|
||||||
@ -64,7 +91,75 @@ app.get('/guides/vaultwarden/firefox', (req, res) => {
|
|||||||
res.render('guides/vaultwarden/firefox', { currentPage: 'guides' });
|
res.render('guides/vaultwarden/firefox', { currentPage: 'guides' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
app.get('/register', (req, res) => {
|
||||||
app.listen(PORT, () => {
|
res.render('register', { currentPage: 'register' });
|
||||||
console.log(`Server is running on port ${PORT}`);
|
});
|
||||||
|
|
||||||
|
app.post('/register', async (req, res) => {
|
||||||
|
const { fullName, email, reason, telegram } = req.body;
|
||||||
|
await Request.create({ fullName, email, reason, telegram });
|
||||||
|
res.render('reg-success', { currentPage: 'register' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/request', async (req, res) => {
|
||||||
|
console.log("Got /request");
|
||||||
|
const { email } = req.query;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
return res.status(400).render('error/email');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = await Request.findOne({ where: { email } });
|
||||||
|
if (!request) {
|
||||||
|
return res.status(404).render('error/404');
|
||||||
|
}
|
||||||
|
res.render('req-status', { request });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).render('error/500');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkAdminAuth(req, res, next) {
|
||||||
|
if (req.session.admin) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect('/admin', { error: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/admin', (req, res) => {
|
||||||
|
res.render('admin-login', { currentPage: 'admin', error: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/admin', (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
if (username === process.env.ADMIN_USERNAME && password === process.env.ADMIN_PASSWORD) {
|
||||||
|
req.session.admin = true;
|
||||||
|
res.redirect('/admin/dashboard');
|
||||||
|
} else {
|
||||||
|
res.render('admin-login', { error: 'An error occurred.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/admin/dashboard', checkAdminAuth, async (req, res) => {
|
||||||
|
const requests = await Request.findAll();
|
||||||
|
res.render('admin-dash', { requests, currentPage: 'admin' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/admin/update-status', checkAdminAuth, async (req, res) => {
|
||||||
|
const { id, status } = req.body;
|
||||||
|
await Request.update({ status }, { where: { id } });
|
||||||
|
res.redireHot('/admin/dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/admin/delete-request', checkAdminAuth, async (req, res) => {
|
||||||
|
const { id } = req.body;
|
||||||
|
await Request.destroy({ where: { id } });
|
||||||
|
res.redirect('/admin/dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => {
|
||||||
|
console.log('Server started on port 3000');
|
||||||
});
|
});
|
@ -4,10 +4,23 @@ services:
|
|||||||
container_name: pontus-mail
|
container_name: pontus-mail
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "80:3000"
|
- "2424:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./register.log:/usr/src/app/register.log
|
- ./register.log:/usr/src/app/register.log
|
||||||
- ./exclusions.json:/usr/src/app/exclusions.json
|
- ./exclusions.json:/usr/src/app/exclusions.json
|
||||||
- ./donations.json:/usr/src/app/donations.json
|
- ./donations.json:/usr/src/app/donations.json
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
|
- DB_HOST=pontus-mail-db
|
||||||
|
- DB_USER=root
|
||||||
|
- DB_PASSWORD=mysqlrootpasswdhere
|
||||||
|
- DB_NAME=pontusmail
|
||||||
|
pontus-mail-db:
|
||||||
|
image: mysql:latest
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=mysqlrootpasswdhere
|
||||||
|
- MYSQL_DATABASE=pontusmail
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- ./db:/var/lib/mysql
|
@ -11,11 +11,15 @@
|
|||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
|
"express-session": "^1.18.1",
|
||||||
|
"mariadb": "^3.4.0",
|
||||||
|
"mysql2": "^3.11.5",
|
||||||
|
"sequelize": "^6.37.5",
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/ihatenodejs/pontus-mail.git"
|
"url": "git+https://github.com/ihatenodejs/pontus-mail.git"
|
||||||
|
50
src/admin-dash.ejs
Normal file
50
src/admin-dash.ejs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<%- include('shards/header', { title: 'Admin Dashboard - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<%- include('shards/nav', { currentPage: 'admin' }) %>
|
||||||
|
<i class="il mt-5">Dashboard</i>
|
||||||
|
<hr>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Full Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Reason</th>
|
||||||
|
<th>Telegram</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Update Status</th>
|
||||||
|
<th>Delete</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% requests.forEach(request => { %>
|
||||||
|
<tr>
|
||||||
|
<td><%= request.id %></td>
|
||||||
|
<td><%= request.fullName %></td>
|
||||||
|
<td><%= request.email %></td>
|
||||||
|
<td><%= request.reason %></td>
|
||||||
|
<td><%= request.telegram %></td>
|
||||||
|
<td><%= request.status %></td>
|
||||||
|
<td>
|
||||||
|
<form action="/admin/update-status" method="POST">
|
||||||
|
<input type="hidden" name="id" value="<%= request.id %>">
|
||||||
|
<select name="status" class="form-control">
|
||||||
|
<option value="Pending" <% if (request.status === 'Pending') { %>selected<% } %>>Pending</option>
|
||||||
|
<option value="Approved" <% if (request.status === 'Approved') { %>selected<% } %>>Approved</option>
|
||||||
|
<option value="Denied" <% if (request.status === 'Denied') { %>selected<% } %>>Denied</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-primary mt-2">Update</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form action="/admin/delete-request" method="POST">
|
||||||
|
<input type="hidden" name="id" value="<%= request.id %>">
|
||||||
|
<button type="submit" class="btn btn-danger mt-2">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% }); %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<%- include('shards/footer') %>
|
21
src/admin-login.ejs
Normal file
21
src/admin-login.ejs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<%- include('shards/header', { title: 'Admin - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<%- include('shards/nav', { currentPage: 'admin' }) %>
|
||||||
|
<i class="il mt-5">Login to administration panel</i>
|
||||||
|
<hr>
|
||||||
|
<% if (error) { %>
|
||||||
|
<div class="alert alert-danger"><%= error %></div>
|
||||||
|
<% } %>
|
||||||
|
<form action="/admin" method="POST">
|
||||||
|
<div class="form-group mt-2">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" class="form-control mt-1" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-2">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" class="form-control mt-1" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-dark mt-3">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<%- include('shards/footer') %>
|
7
src/error/404.ejs
Normal file
7
src/error/404.ejs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<%- include('../shards/header', { title: '404 Not Found - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<i class="il mt-2">404 Not Found</i>
|
||||||
|
<hr>
|
||||||
|
<p>The requested resource could not be found.</p>
|
||||||
|
</div>
|
||||||
|
<%- include('../shards/footer') %>
|
7
src/error/500.ejs
Normal file
7
src/error/500.ejs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<%- include('../shards/header', { title: '500 Internal Server Error - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<i class="il mt-2">500 Internal Server Error</i>
|
||||||
|
<hr>
|
||||||
|
<p>An unexpected error occurred. Please try again later.</p>
|
||||||
|
</div>
|
||||||
|
<%- include('../shards/footer') %>
|
14
src/error/email.ejs
Normal file
14
src/error/email.ejs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<%- include('../shards/header', { title: 'Request Status - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<i class="il mt-2">We need your email</i>
|
||||||
|
<hr>
|
||||||
|
<p class="mt-4 mb-4">We need an email to check your request. Please enter your p0ntus mail email address below:</p>
|
||||||
|
<form action="/request" method="get">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="mb-2" for="email">p0ntus mail email address:</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<%- include('../shards/footer') %>
|
8
src/reg-success.ejs
Normal file
8
src/reg-success.ejs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<%- include('shards/header', { title: 'Registration Successful - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<%- include('shards/nav', { currentPage: 'register' }) %>
|
||||||
|
<i class="il mt-5">Registration Submitted</i>
|
||||||
|
<hr>
|
||||||
|
<p>Your request has been submitted successfully. You can check the status of your request <a href="/request">here</a>.</p>
|
||||||
|
</div>
|
||||||
|
<%- include('shards/footer') %>
|
@ -1,14 +1,26 @@
|
|||||||
<%- include('shards/header', { title: 'Register - p0ntus mail' }) %>
|
<%- include('shards/header', { title: 'Register - p0ntus mail' }) %>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<%- include('shards/nav', { currentPage: 'register' }) %>
|
<%- include('shards/nav', { currentPage: 'register' }) %>
|
||||||
<i class="il mt-5">Register</i>
|
<h1 class="mt-5">Register</h1>
|
||||||
<hr>
|
<hr>
|
||||||
<i>Please contact <a href="mailto:signup@p0ntus.com">signup<i class="fa-solid fa-at"></i>p0ntus.com</a> with the following info:</i>
|
<form action="/register" method="POST">
|
||||||
<ul class="list-group mt-3">
|
<div class="form-group">
|
||||||
<li class="list-group-item">Name that goes in email header (full name/username/alias)</li>
|
<label for="fullName">Name that goes in email header (full name/username/alias)</label>
|
||||||
<li class="list-group-item">Desired email address (e.g., yourname@p0ntus.com)</li>
|
<input type="text" class="form-control" id="fullName" name="fullName" required>
|
||||||
<li class="list-group-item">Reason for wanting an email</li>
|
</div>
|
||||||
<li class="list-group-item">Telegram username</li>
|
<div class="form-group">
|
||||||
</ul>
|
<label for="email">Desired email address (e.g., yourname@p0ntus.com)</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="reason">Reason for wanting an email</label>
|
||||||
|
<textarea class="form-control" id="reason" name="reason" rows="3" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="telegram">Telegram username</label>
|
||||||
|
<input type="text" class="form-control" id="telegram" name="telegram" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Register</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<%- include('shards/footer') %>
|
<%- include('shards/footer') %>
|
21
src/req-status.ejs
Normal file
21
src/req-status.ejs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<%- include('shards/header', { title: 'Request Status - p0ntus mail' }) %>
|
||||||
|
<div class="container">
|
||||||
|
<%- include('shards/nav', { currentPage: 'request' }) %>
|
||||||
|
<i class="il mt-5">Request Status</i>
|
||||||
|
<hr>
|
||||||
|
<% if (request) { %>
|
||||||
|
<p>Request Status: <strong><%= request.status %></strong></p>
|
||||||
|
<% if (request.status === 'Approved') { %>
|
||||||
|
<div class="text-start">
|
||||||
|
<p>Your request has been approved. You can access your p0ntus mail account <a href="https://user.p0ntus.com/">here</a> with your email as the username and abcabc for the password. <b>FOR SECURITY, CHANGE YOUR PASSWORD ASAP.</b></p>
|
||||||
|
<p>I also suggest you check out our guides at <a href="https://mail.p0ntus.com/guides">mail.p0ntus.com/guides</a>.</p>
|
||||||
|
<p>You can also access Vaultwarden at <a href="https://vaultwarden.p0ntus.com/">vaultwarden.p0ntus.com</a>.</p>
|
||||||
|
<p>If you need any help or support, contact <a href="mailto:admin@p0ntus.com">admin@p0ntus.com</a>.</p>
|
||||||
|
<p>Your webmail can be accessed <a href="https://mail.p0ntus.com/SOGo">here</a>.</p>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<% } else { %>
|
||||||
|
<p>No request found for the provided email.</p>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<%- include('shards/footer') %>
|
@ -32,4 +32,10 @@
|
|||||||
<% } else { %>
|
<% } else { %>
|
||||||
<a href="/guides" class="nav-link text-dark">Guides</a>
|
<a href="/guides" class="nav-link text-dark">Guides</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% if (currentPage === 'request') { %>
|
||||||
|
<span class="nav-link text-dark"><b>Request</b></span>
|
||||||
|
<% } %>
|
||||||
|
<% if (currentPage === 'admin') { %>
|
||||||
|
<span class="nav-link text-dark"><b>Admin</b></span>
|
||||||
|
<% } %>
|
||||||
</nav>
|
</nav>
|
Reference in New Issue
Block a user