Merge branch 'develop'
This commit is contained in:
commit
911008af8b
@ -88,6 +88,7 @@
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"jest.config.js",
|
||||
"scripts/**/*",
|
||||
"webpack.config.js",
|
||||
"dist/**/*",
|
||||
"public/**/*",
|
||||
|
@ -30,4 +30,4 @@ $errorColor: desaturate($errorText, 50%);
|
||||
|
||||
// Responsivity
|
||||
$mobileThreshold: 632px;
|
||||
$desktopThreshold: 632px;
|
||||
$desktopThreshold: 940px;
|
||||
|
@ -173,6 +173,15 @@ body > header {
|
||||
font-weight: inherit;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity ease-out 100ms;
|
||||
transition-delay: 150ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@ -398,7 +407,7 @@ form {
|
||||
padding: 8px 16px;
|
||||
text-align: center;
|
||||
|
||||
.form-field {
|
||||
.form-field:not(.hidden) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 16px auto;
|
||||
@ -693,6 +702,7 @@ td.actions {
|
||||
|
||||
th {
|
||||
border-bottom: 1px solid #39434a;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
@ -859,37 +869,7 @@ td.actions {
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
|
||||
.feather {
|
||||
--icon-size: 20px;
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copyable-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 8px;
|
||||
|
||||
background-color: darken($backgroundColor, 2%);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
@ -939,3 +919,34 @@ td.actions {
|
||||
background: $secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.table-col-grow {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
list-style: none;
|
||||
padding: 8px;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
a, &.active, &.ellipsis {
|
||||
display: block;
|
||||
min-width: 40px;
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
|
||||
line-height: 32px;
|
||||
text-align:center;
|
||||
|
||||
&:hover:not(.active):not(.ellipsis) {
|
||||
background-color: #fff5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +1,73 @@
|
||||
{
|
||||
app: {
|
||||
name: 'Rainbox Email',
|
||||
contact_email: 'contact@rainbox.email',
|
||||
webmail_url: 'https://rc.rainbox.email',
|
||||
},
|
||||
log_level: "DEV",
|
||||
db_log_level: "ERROR",
|
||||
public_url: "http://localhost:4899",
|
||||
public_websocket_url: "ws://localhost:4899",
|
||||
port: 4899,
|
||||
mysql: {
|
||||
connectionLimit: 10,
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
password: "",
|
||||
database: "rainbox_email",
|
||||
create_database_automatically: false,
|
||||
},
|
||||
redis: {
|
||||
host: "127.0.0.1",
|
||||
port: 6379,
|
||||
prefix: 'rainbox.email',
|
||||
},
|
||||
session: {
|
||||
cookie: {
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
host: "127.0.0.1",
|
||||
port: "1025",
|
||||
secure: false,
|
||||
username: "",
|
||||
password: "",
|
||||
allow_invalid_tls: true,
|
||||
from: 'contact@rainbox.email',
|
||||
from_name: 'Rainbox Email',
|
||||
},
|
||||
view: {
|
||||
cache: false,
|
||||
},
|
||||
approval_mode: false,
|
||||
magic_link: {
|
||||
validity_period: 20,
|
||||
},
|
||||
mail_autoconfig: {
|
||||
main_domain: 'localhost',
|
||||
display_name: 'Rainbox Email',
|
||||
display_name_short: 'Rainbox',
|
||||
username: '%EMAILADDRESS%',
|
||||
auth_method: 'password-cleartext',
|
||||
imap: {
|
||||
server: 'localhost',
|
||||
port: '143',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
pop3: {
|
||||
server: 'localhost',
|
||||
port: '110',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
smtp: {
|
||||
server: 'localhost',
|
||||
port: '587',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
},
|
||||
app: {
|
||||
name: 'Rainbox Email',
|
||||
contact_email: 'contact@rainbox.email',
|
||||
webmail_url: 'https://rc.rainbox.email',
|
||||
},
|
||||
log_level: "DEV",
|
||||
db_log_level: "ERROR",
|
||||
public_url: "http://localhost:4899",
|
||||
public_websocket_url: "ws://localhost:4899",
|
||||
port: 4899,
|
||||
mysql: {
|
||||
connectionLimit: 10,
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
password: "",
|
||||
database: "rainbox_email",
|
||||
create_database_automatically: false,
|
||||
},
|
||||
redis: {
|
||||
host: "127.0.0.1",
|
||||
port: 6379,
|
||||
prefix: 'rainbox.email',
|
||||
},
|
||||
session: {
|
||||
cookie: {
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
host: "127.0.0.1",
|
||||
port: "1025",
|
||||
secure: false,
|
||||
username: "",
|
||||
password: "",
|
||||
allow_invalid_tls: true,
|
||||
from: 'contact@rainbox.email',
|
||||
from_name: 'Rainbox Email',
|
||||
},
|
||||
view: {
|
||||
cache: false,
|
||||
},
|
||||
auth: {
|
||||
approval_mode: false,
|
||||
// 30 days
|
||||
name_change_wait_period: 2592000000,
|
||||
},
|
||||
magic_link: {
|
||||
validity_period: 20,
|
||||
},
|
||||
mail_autoconfig: {
|
||||
main_domain: 'localhost',
|
||||
display_name: 'Rainbox Email',
|
||||
display_name_short: 'Rainbox',
|
||||
username: '%EMAILADDRESS%',
|
||||
auth_method: 'password-cleartext',
|
||||
imap: {
|
||||
server: 'localhost',
|
||||
port: '143',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
pop3: {
|
||||
server: 'localhost',
|
||||
port: '110',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
smtp: {
|
||||
server: 'localhost',
|
||||
port: '587',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,41 +1,43 @@
|
||||
{
|
||||
log_level: "DEBUG",
|
||||
db_log_level: "ERROR",
|
||||
public_url: "https://rainbox.email",
|
||||
public_websocket_url: "wss://rainbox.email",
|
||||
session: {
|
||||
cookie: {
|
||||
secure: true
|
||||
}
|
||||
},
|
||||
mail: {
|
||||
secure: true,
|
||||
allow_invalid_tls: false
|
||||
},
|
||||
approval_mode: true,
|
||||
magic_link: {
|
||||
validity_period: 900,
|
||||
},
|
||||
mail_autoconfig: {
|
||||
main_domain: 'rainbox.email',
|
||||
display_name: 'Rainbox Email',
|
||||
display_name_short: 'Rainbox',
|
||||
username: '%EMAILADDRESS%',
|
||||
auth_method: 'password-cleartext',
|
||||
imap: {
|
||||
server: 'rainbox.email',
|
||||
port: '143',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
pop3: {
|
||||
server: 'rainbox.email',
|
||||
port: '110',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
smtp: {
|
||||
server: 'rainbox.email',
|
||||
port: '587',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
},
|
||||
log_level: "DEBUG",
|
||||
db_log_level: "ERROR",
|
||||
public_url: "https://rainbox.email",
|
||||
public_websocket_url: "wss://rainbox.email",
|
||||
session: {
|
||||
cookie: {
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
secure: true,
|
||||
allow_invalid_tls: false,
|
||||
},
|
||||
auth: {
|
||||
approval_mode: true,
|
||||
},
|
||||
magic_link: {
|
||||
validity_period: 900,
|
||||
},
|
||||
mail_autoconfig: {
|
||||
main_domain: 'rainbox.email',
|
||||
display_name: 'Rainbox Email',
|
||||
display_name_short: 'Rainbox',
|
||||
username: '%EMAILADDRESS%',
|
||||
auth_method: 'password-cleartext',
|
||||
imap: {
|
||||
server: 'rainbox.email',
|
||||
port: '143',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
pop3: {
|
||||
server: 'rainbox.email',
|
||||
port: '110',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
smtp: {
|
||||
server: 'rainbox.email',
|
||||
port: '587',
|
||||
method: 'STARTTLS',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
14
package.json
14
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rainbox.email",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "ISP mail provider manager with mysql and integrated LDAP server",
|
||||
"repository": "https://gitlab.com/ArisuOngaku/rainbox.email",
|
||||
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||
@ -8,13 +8,13 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"scripts": {
|
||||
"test": "jest --verbose --runInBand",
|
||||
"clean": "(test ! -d dist || rm -r dist)",
|
||||
"prepareSources": "cp package.json src/",
|
||||
"clean": "node scripts/clean.js",
|
||||
"prepare-sources": "node scripts/prepare-sources.js",
|
||||
"compile": "yarn clean && tsc",
|
||||
"build": "yarn prepareSources && yarn compile && webpack --mode production",
|
||||
"dev": "yarn prepareSources && concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"webpack --watch --mode development\" \"maildev\"",
|
||||
"build": "yarn prepare-sources && yarn compile && webpack --mode production",
|
||||
"dev": "yarn prepare-sources && concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"webpack --watch --mode development\" \"maildev\"",
|
||||
"start": "yarn build && node",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
@ -44,7 +44,7 @@
|
||||
"imagemin-gifsicle": "^7.0.0",
|
||||
"imagemin-mozjpeg": "^9.0.0",
|
||||
"imagemin-pngquant": "^9.0.0",
|
||||
"imagemin-svgo": "^8.0.0",
|
||||
"imagemin-svgo": "^9.0.0",
|
||||
"img-loader": "^3.0.1",
|
||||
"jest": "^26.1.0",
|
||||
"maildev": "^1.1.0",
|
||||
|
10
scripts/clean.js
Normal file
10
scripts/clean.js
Normal file
@ -0,0 +1,10 @@
|
||||
const fs = require('fs');
|
||||
|
||||
[
|
||||
'dist',
|
||||
].forEach(file => {
|
||||
if (fs.existsSync(file)) {
|
||||
console.log('Cleaning', file, '...');
|
||||
fs.rmSync(file, {recursive: true});
|
||||
}
|
||||
});
|
4
scripts/prepare-sources.js
Normal file
4
scripts/prepare-sources.js
Normal file
@ -0,0 +1,4 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
fs.copyFileSync('package.json', path.join('src', 'package.json'));
|
@ -42,6 +42,7 @@ import AccountController from "swaf/auth/AccountController";
|
||||
import packageJson = require('./package.json');
|
||||
import AddUsedToMagicLinksMigration from "swaf/auth/magic_link/AddUsedToMagicLinksMigration";
|
||||
import MakeMagicLinksSessionNotUniqueMigration from "swaf/auth/magic_link/MakeMagicLinksSessionNotUniqueMigration";
|
||||
import AddNameChangedAtToUsersMigration from "swaf/auth/migrations/AddNameChangedAtToUsersMigration";
|
||||
|
||||
export default class App extends Application {
|
||||
public constructor(
|
||||
@ -66,6 +67,7 @@ export default class App extends Application {
|
||||
DropLegacyLogsTable,
|
||||
AddUsedToMagicLinksMigration,
|
||||
MakeMagicLinksSessionNotUniqueMigration,
|
||||
AddNameChangedAtToUsersMigration,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -57,9 +57,12 @@ export default class AccountMailboxController extends Controller {
|
||||
});
|
||||
|
||||
// Check whether this identity can be created by this user
|
||||
if (!domain.canCreateAddresses(user)) {
|
||||
throw new ForbiddenHttpError('domain', req.url);
|
||||
}
|
||||
if (domain.isPublic()) {
|
||||
await Validator.validate({
|
||||
name: new Validator<string>().defined().equals(user.as(UserNameComponent).name),
|
||||
name: new Validator<string>().defined().equals(user.as(UserNameComponent).getName()),
|
||||
}, req.body);
|
||||
const actualPublicAddressesCount = await mailIdentityComponent.getPublicAddressesCount();
|
||||
const maxPublicAddressesCount = mailIdentityComponent.getMaxPublicAddressesCount();
|
||||
@ -68,10 +71,6 @@ export default class AccountMailboxController extends Controller {
|
||||
res.redirect(Controller.route('account-mailbox'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!domain.canCreateAddresses(user)) {
|
||||
throw new ForbiddenHttpError('domain', req.url);
|
||||
}
|
||||
}
|
||||
|
||||
// Save identity
|
||||
|
@ -59,7 +59,7 @@ export default class AccountBackendController extends Controller {
|
||||
await user.as(UserPasswordComponent).setPassword(req.body.new_password, 'new_password');
|
||||
await user.save();
|
||||
|
||||
req.flash('success', `New password set for ${user.as(UserNameComponent).name}`);
|
||||
req.flash('success', `New password set for ${user.as(UserNameComponent).getName()}`);
|
||||
res.redirect(Controller.route('backend-list-users'));
|
||||
}
|
||||
}
|
||||
|
@ -52,16 +52,9 @@ export default class MailboxBackendController extends Controller {
|
||||
.get();
|
||||
|
||||
res.render('backend/mailboxes', {
|
||||
users: [{
|
||||
value: 0,
|
||||
display: 'Public',
|
||||
}, ...(await User.select().get()).map(u => ({
|
||||
value: u.id,
|
||||
display: u.name,
|
||||
}))],
|
||||
mailboxes: await Promise.all(users.map(async user => ({
|
||||
id: user.id,
|
||||
username: user.as(UserNameComponent).name,
|
||||
username: user.as(UserNameComponent).getName(),
|
||||
name: await (await user.as(UserMailIdentityComponent).mainMailIdentity.get())?.toEmail(),
|
||||
identity_count: (await user.as(UserMailIdentityComponent).mailIdentities.get()).length,
|
||||
domain_count: (await user.as(UserMailIdentityComponent).mailDomains.get()).length,
|
||||
@ -82,7 +75,7 @@ export default class MailboxBackendController extends Controller {
|
||||
res.render('backend/mailbox', {
|
||||
mailbox: {
|
||||
id: user.id,
|
||||
userName: user.as(UserNameComponent).name,
|
||||
userName: user.as(UserNameComponent).getName(),
|
||||
name: await mainMailIdentity?.toEmail() || 'Not created.',
|
||||
exists: !!mainMailIdentity,
|
||||
},
|
||||
@ -107,9 +100,16 @@ export default class MailboxBackendController extends Controller {
|
||||
domains: await Promise.all(mailDomains.map(async domain => ({
|
||||
id: domain.id,
|
||||
name: domain.name,
|
||||
owner_name: (await domain.owner.get())?.as(UserNameComponent).name,
|
||||
owner_name: (await domain.owner.get())?.as(UserNameComponent).getName(),
|
||||
identity_count: (await domain.identities.get()).length,
|
||||
}))),
|
||||
users: [{
|
||||
value: 0,
|
||||
display: 'Public',
|
||||
}, ...(await User.select().get()).map(u => ({
|
||||
value: u.id,
|
||||
display: u.name,
|
||||
}))],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import Model from "swaf/db/Model";
|
||||
import User from "swaf/auth/models/User";
|
||||
import {ManyModelRelation, OneModelRelation} from "swaf/db/ModelRelation";
|
||||
import MailIdentity from "./MailIdentity";
|
||||
import UserNameComponent from "swaf/auth/models/UserNameComponent";
|
||||
|
||||
export default class MailDomain extends Model {
|
||||
public id?: number = undefined;
|
||||
@ -38,6 +39,6 @@ export default class MailDomain extends Model {
|
||||
}
|
||||
|
||||
public canCreateAddresses(user: User): boolean {
|
||||
return this.user_id === user.id || this.isPublic();
|
||||
return this.user_id === user.id || this.isPublic() && user.as(UserNameComponent).hasName();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user