feat: add admin authenticator 2FA and configurable login email alerts
This commit is contained in:
1 parent
bee3b990f9
commit
52cc865140
21 files changed
+1961
-20
No files matched your search
@@ -0,0 +1,118 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { SMTPServer } from 'smtp-server';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import postgres from 'postgres';
|
||||
|
||||
test('encrypted SMTP credentials, real TLS delivery and optional login alerts', async ({
|
||||
request,
|
||||
}) => {
|
||||
const messages: string[] = [];
|
||||
const recipients: string[] = [];
|
||||
const server = new SMTPServer({
|
||||
secure: true,
|
||||
key: readFileSync(join(process.env.TEST_TLS_DIR!, 'key.pem')),
|
||||
cert: readFileSync(join(process.env.TEST_TLS_DIR!, 'cert.pem')),
|
||||
onAuth(auth, _session, done) {
|
||||
if (auth.username === 'testmailer' && auth.password === 'smtp-test-password')
|
||||
done(null, { user: 'testmailer' });
|
||||
else done(new Error('Invalid credentials'));
|
||||
},
|
||||
onData(stream, session, done) {
|
||||
let message = '';
|
||||
stream.on('data', (chunk) => {
|
||||
message += chunk.toString();
|
||||
});
|
||||
stream.on('end', () => {
|
||||
messages.push(message);
|
||||
recipients.push(...session.envelope.rcptTo.map((r) => r.address));
|
||||
done();
|
||||
});
|
||||
},
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
server.listen(15440, '127.0.0.1', resolve);
|
||||
});
|
||||
const db = postgres(process.env.TEST_DATABASE_URL!, { max: 1 });
|
||||
const password = process.env.TEST_PASSWORD!;
|
||||
const headers = {
|
||||
origin: 'http://localhost:3100',
|
||||
'x-forwarded-for': '1.2.3.4, 2001:db8::123',
|
||||
'user-agent': 'HamHam-TestBrowser',
|
||||
};
|
||||
const mail = {
|
||||
host: '127.0.0.1',
|
||||
port: 15440,
|
||||
secure: true,
|
||||
username: 'testmailer',
|
||||
password: 'smtp-test-password',
|
||||
from: 'hamham@example.org',
|
||||
};
|
||||
try {
|
||||
expect(
|
||||
(
|
||||
await request.post('/api/auth/login', {
|
||||
headers,
|
||||
data: { username: 'testadmin', password },
|
||||
})
|
||||
).status(),
|
||||
).toBe(200);
|
||||
expect(
|
||||
(
|
||||
await request.put('/api/settings/mail', {
|
||||
headers,
|
||||
data: { password, email: 'admin@example.org', loginNotifications: true, mail },
|
||||
})
|
||||
).status(),
|
||||
).toBe(200);
|
||||
expect((await request.post('/api/settings/mail/test', { headers })).status()).toBe(200);
|
||||
expect(messages).toHaveLength(1);
|
||||
expect(recipients).toEqual(['admin@example.org']);
|
||||
await request.post('/api/auth/logout', { headers });
|
||||
expect(
|
||||
(
|
||||
await request.post('/api/auth/login', {
|
||||
headers,
|
||||
data: { username: 'testadmin', password },
|
||||
})
|
||||
).status(),
|
||||
).toBe(200);
|
||||
await expect
|
||||
.poll(
|
||||
async () => (await (await request.get('/api/auth/security')).json()).events[0].mail_status,
|
||||
)
|
||||
.toBe('sent');
|
||||
expect(messages).toHaveLength(2);
|
||||
expect(messages[1]).toContain('HamHam-TestBrowser');
|
||||
expect(messages[1]).toContain('2001:db8::123');
|
||||
expect(messages[1]).not.toContain('1.2.3.4');
|
||||
expect(messages[1]).not.toContain(password);
|
||||
expect(
|
||||
(
|
||||
await request.put('/api/settings/mail', {
|
||||
headers,
|
||||
data: {
|
||||
password,
|
||||
email: 'admin@example.org',
|
||||
loginNotifications: false,
|
||||
mail: { ...mail, password: '' },
|
||||
},
|
||||
})
|
||||
).status(),
|
||||
).toBe(200);
|
||||
expect((await request.post('/api/settings/mail/test', { headers })).status()).toBe(200);
|
||||
expect(messages).toHaveLength(3); // Blank password preserved the encrypted credential.
|
||||
await request.post('/api/auth/logout', { headers });
|
||||
await request.post('/api/auth/login', { headers, data: { username: 'testadmin', password } });
|
||||
expect((await (await request.get('/api/auth/security')).json()).events[0].mail_status).toBe(
|
||||
'disabled',
|
||||
);
|
||||
expect(messages).toHaveLength(3);
|
||||
} finally {
|
||||
await db`UPDATE admin_security SET login_notifications = false`;
|
||||
await db`DELETE FROM mail_config`;
|
||||
await db.end();
|
||||
await new Promise<void>((resolve) => server.close(resolve));
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user