BREAKING CHANGE(config): convert configuration management to read-only; remove updateConfiguration endpoint and client-side editing

This commit is contained in:
2026-02-03 23:26:51 +00:00
parent 5de3344905
commit 9e0e77737b
25 changed files with 2129 additions and 269 deletions

View File

@@ -0,0 +1,241 @@
import * as plugins from '../../plugins.js';
import { CachedDocument, TTL } from '../classes.cached.document.js';
import { CacheDb } from '../classes.cachedb.js';
/**
* Helper to get the smartdata database instance
*/
const getDb = () => CacheDb.getInstance().getDb();
/**
* CachedDKIMKey - Stores DKIM key pairs for email signing
*
* Caches DKIM private/public key pairs per domain and selector.
* Default TTL is 90 days (typical key rotation interval).
*/
@plugins.smartdata.Collection(() => getDb())
export class CachedDKIMKey extends CachedDocument<CachedDKIMKey> {
/**
* Composite key: domain:selector
*/
@plugins.smartdata.unI()
@plugins.smartdata.svDb()
public domainSelector: string;
/**
* Domain for this DKIM key
*/
@plugins.smartdata.svDb()
public domain: string;
/**
* DKIM selector (e.g., 'mta', 'default', '2024')
*/
@plugins.smartdata.svDb()
public selector: string;
/**
* Private key in PEM format
*/
@plugins.smartdata.svDb()
public privateKey: string;
/**
* Public key in PEM format
*/
@plugins.smartdata.svDb()
public publicKey: string;
/**
* Public key for DNS TXT record (base64, no headers)
*/
@plugins.smartdata.svDb()
public publicKeyDns: string;
/**
* Key size in bits (e.g., 1024, 2048)
*/
@plugins.smartdata.svDb()
public keySize: number = 2048;
/**
* Key algorithm (e.g., 'rsa-sha256')
*/
@plugins.smartdata.svDb()
public algorithm: string = 'rsa-sha256';
/**
* When the key was generated
*/
@plugins.smartdata.svDb()
public generatedAt: Date;
/**
* When the key was last rotated
*/
@plugins.smartdata.svDb()
public rotatedAt: Date;
/**
* Previous selector (for key rotation)
*/
@plugins.smartdata.svDb()
public previousSelector: string;
/**
* Number of emails signed with this key
*/
@plugins.smartdata.svDb()
public signCount: number = 0;
/**
* Whether this key is currently active
*/
@plugins.smartdata.svDb()
public isActive: boolean = true;
constructor() {
super();
this.setTTL(TTL.DAYS_90); // Default 90-day TTL
this.generatedAt = new Date();
}
/**
* Create the composite key from domain and selector
*/
public static createDomainSelector(domain: string, selector: string): string {
return `${domain.toLowerCase()}:${selector.toLowerCase()}`;
}
/**
* Create a new DKIM key entry
*/
public static createNew(domain: string, selector: string): CachedDKIMKey {
const key = new CachedDKIMKey();
key.domain = domain.toLowerCase();
key.selector = selector.toLowerCase();
key.domainSelector = CachedDKIMKey.createDomainSelector(domain, selector);
return key;
}
/**
* Find by domain and selector
*/
public static async findByDomainSelector(
domain: string,
selector: string
): Promise<CachedDKIMKey | null> {
const domainSelector = CachedDKIMKey.createDomainSelector(domain, selector);
return await CachedDKIMKey.getInstance({
domainSelector,
});
}
/**
* Find all keys for a domain
*/
public static async findByDomain(domain: string): Promise<CachedDKIMKey[]> {
return await CachedDKIMKey.getInstances({
domain: domain.toLowerCase(),
});
}
/**
* Find the active key for a domain
*/
public static async findActiveForDomain(domain: string): Promise<CachedDKIMKey | null> {
const keys = await CachedDKIMKey.getInstances({
domain: domain.toLowerCase(),
isActive: true,
});
return keys.length > 0 ? keys[0] : null;
}
/**
* Find all active keys
*/
public static async findAllActive(): Promise<CachedDKIMKey[]> {
return await CachedDKIMKey.getInstances({
isActive: true,
});
}
/**
* Set the key pair
*/
public setKeyPair(privateKey: string, publicKey: string, publicKeyDns?: string): void {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.publicKeyDns = publicKeyDns || this.extractPublicKeyDns(publicKey);
this.generatedAt = new Date();
}
/**
* Extract the base64 public key for DNS from PEM format
*/
private extractPublicKeyDns(publicKeyPem: string): string {
// Remove PEM headers and newlines
return publicKeyPem
.replace(/-----BEGIN PUBLIC KEY-----/g, '')
.replace(/-----END PUBLIC KEY-----/g, '')
.replace(/\s/g, '');
}
/**
* Generate the DNS TXT record value
*/
public getDnsTxtRecord(): string {
return `v=DKIM1; k=rsa; p=${this.publicKeyDns}`;
}
/**
* Get the full DNS record name
*/
public getDnsRecordName(): string {
return `${this.selector}._domainkey.${this.domain}`;
}
/**
* Record that this key was used to sign an email
*/
public recordSign(): void {
this.signCount++;
this.touch();
}
/**
* Deactivate this key (e.g., during rotation)
*/
public deactivate(): void {
this.isActive = false;
}
/**
* Activate this key
*/
public activate(): void {
this.isActive = true;
}
/**
* Rotate to a new selector
*/
public rotate(newSelector: string): void {
this.previousSelector = this.selector;
this.selector = newSelector.toLowerCase();
this.domainSelector = CachedDKIMKey.createDomainSelector(this.domain, this.selector);
this.rotatedAt = new Date();
this.signCount = 0;
// Reset TTL on rotation
this.setTTL(TTL.DAYS_90);
}
/**
* Check if key needs rotation (based on age or sign count)
*/
public needsRotation(maxAgeDays: number = 90, maxSignCount: number = 1000000): boolean {
const ageMs = Date.now() - this.generatedAt.getTime();
const ageDays = ageMs / (24 * 60 * 60 * 1000);
return ageDays > maxAgeDays || this.signCount > maxSignCount;
}
}