create initial api (add, list endpoints) - NOT SECURE
This commit is contained in:
parent
6f05cfc340
commit
edc18c2c18
4
.gitignore
vendored
4
.gitignore
vendored
@ -130,3 +130,7 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
# extra
|
||||||
|
.idea/
|
||||||
|
bun.lockb
|
||||||
|
|
||||||
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM oven/bun:latest
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json /app/
|
||||||
|
RUN bun install
|
||||||
|
COPY . /app
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD [ "bun", "run", "server.js" ]
|
72
README.md
72
README.md
@ -1,3 +1,73 @@
|
|||||||
# mail-connect
|
# mail-connect
|
||||||
|
|
||||||
API bridge for docker-mailserver
|
API bridge for docker-mailserver
|
||||||
|
|
||||||
|
*mail-connect is still in early beta*
|
||||||
|
|
||||||
|
## What is it
|
||||||
|
|
||||||
|
mail-connect aims to connect your `docker-mailserver` to *anything* you can imagine, through the power of an API. Despite being used as a core component of LibreCloud, you can still implement mail-connect any way you wish!
|
||||||
|
|
||||||
|
We provide an extendable API which interacts with the `setup` utility via a Docker socket. While this offers advantages, mail-connect is still slow on some functions (such as listing accounts), as it's merely executing pre-made commands and parsing the output.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
All features marked with an **E** are extended features, and are not a part of the original `setup` utility.
|
||||||
|
|
||||||
|
### Email
|
||||||
|
|
||||||
|
- [X] Create email
|
||||||
|
- [X] List emails
|
||||||
|
- [ ] **E** Create email from file
|
||||||
|
- [ ] Change password
|
||||||
|
- [ ] Delete email
|
||||||
|
- [ ] Restrict email
|
||||||
|
|
||||||
|
### Alias
|
||||||
|
|
||||||
|
- [ ] Create alias
|
||||||
|
- [ ] List aliases
|
||||||
|
- [ ] Delete alias
|
||||||
|
|
||||||
|
### Quotas
|
||||||
|
|
||||||
|
- [ ] Set quota
|
||||||
|
- [ ] Delete quota
|
||||||
|
|
||||||
|
### dovecot-master
|
||||||
|
|
||||||
|
- [ ] Add
|
||||||
|
- [ ] Update
|
||||||
|
- [ ] Delete
|
||||||
|
- [ ] List
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
- [ ] DKIM
|
||||||
|
|
||||||
|
### Relay
|
||||||
|
|
||||||
|
- [ ] Add auth
|
||||||
|
- [ ] Add domain
|
||||||
|
- [ ] Exclude domain
|
||||||
|
|
||||||
|
### Fail2Ban
|
||||||
|
|
||||||
|
- [ ] Ban IP
|
||||||
|
- [ ] Unban IP
|
||||||
|
- [ ] Ban log
|
||||||
|
- [ ] Fail2Ban status
|
||||||
|
|
||||||
|
### Debug
|
||||||
|
|
||||||
|
- [ ] Fetchmail
|
||||||
|
- [ ] Login
|
||||||
|
- [ ] Show mail logs
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
I plan to implement a *much* more powerful API, when everything else has settled. I will be taking a look at the setup utility itself, and seeing how a more efficient approach can be taken.
|
||||||
|
|
||||||
|
Since `docker-mailserver` is built on Dovecot and Postfix, I am confident we can improve this API to be speedy and efficient as ever.
|
||||||
|
|
||||||
|
## To-Do
|
||||||
|
8
docker-compose.yml
Normal file
8
docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
mail-connect:
|
||||||
|
build: .
|
||||||
|
container_name: mail-connect
|
||||||
|
ports:
|
||||||
|
- "6723:3000"
|
||||||
|
volumes:
|
||||||
|
- '/var/run/docker.sock:/var/run/docker.sock'
|
27
jsconfig.json
Normal file
27
jsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
19
package.json
Normal file
19
package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "mail-connect",
|
||||||
|
"module": "server.js",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"child_process": "^1.0.2",
|
||||||
|
"dockerode": "^4.0.4",
|
||||||
|
"express": "^4.21.2"
|
||||||
|
},
|
||||||
|
"trustedDependencies": [
|
||||||
|
"protobufjs"
|
||||||
|
]
|
||||||
|
}
|
108
server.js
Normal file
108
server.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const Docker = require('dockerode');
|
||||||
|
|
||||||
|
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
function listAccounts() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const container = docker.getContainer('mailserver');
|
||||||
|
container.exec({
|
||||||
|
Cmd: ['setup', 'email', 'list'],
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
}, (err, exec) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
exec.start((err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
output += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
// Remove control characters
|
||||||
|
const cleanOutput = output.replace(/[\u0000-\u001F]+/g, '');
|
||||||
|
const regex = /\*\s*(\S+)\s*\(\s*([^\s\/]+)\s*\/\s*([^)]+)\s*\)\s*\[(\d+)%]/g;
|
||||||
|
const accounts = [];
|
||||||
|
|
||||||
|
for (const match of cleanOutput.matchAll(regex)) {
|
||||||
|
accounts.push({
|
||||||
|
email: match[1],
|
||||||
|
used: match[2].trim() === '~' ? 'Unlimited' : match[2].trim(),
|
||||||
|
capacity: match[3].trim() === '~' ? 'Unlimited' : match[3].trim(),
|
||||||
|
percentage: match[4]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(accounts);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/list', (req, res) => {
|
||||||
|
listAccounts()
|
||||||
|
.then(accounts => res.json({ accounts }))
|
||||||
|
.catch(err => res.status(500).json({ error: err.message }));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/add', (req, res) => {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
if (!email || !password) {
|
||||||
|
return res.status(400).json({ error: "Email and password are required." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = docker.getContainer('mailserver');
|
||||||
|
container.exec({
|
||||||
|
Cmd: ['setup', 'email', 'add', email, password],
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
}, (err, exec) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
exec.start((err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
output += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', async () => {
|
||||||
|
const cleanOutput = output.replace(/[\u0000-\u001F]+/g, '').trim();
|
||||||
|
if (cleanOutput === '') {
|
||||||
|
return res.json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const accounts = await listAccounts();
|
||||||
|
const accountFound = accounts.find(acc => acc.email === email);
|
||||||
|
if (accountFound) {
|
||||||
|
return res.json({ success: true });
|
||||||
|
} else {
|
||||||
|
return res.json({ success: false, message: "Account creation failed" });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => {
|
||||||
|
console.log(`API listening on port 3000`);
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user