DC-053: Public share links + Tailscale-mediated share (Pro-gated)
- Share-store: HMAC-signed tokens bound to serviceId+kind, persistent signing secret in dataDir/.share-secret, atomic writes, auto-prune - Routes: admin endpoints gated on licenseManager.isPro() (402 Free); public endpoints CSRF-exempt (token IS proof) - Tailscale path: mints single-use ephemeral pre-auth key, emails join link, rolls back share record if createAuthKey throws - Email-failure path: exposes urlPath for manual delivery fallback - 53 new tests (24 store + 29 routes), full suite 1372/1372 - Drift-test parser hardened against quoted-word comments - share-store dataDir resolver handles Proxy/function values CHANGELOG + BACKLOG updated.
This commit is contained in:
@@ -22,6 +22,7 @@ const platformPaths = require('../platform-paths');
|
||||
const { LicenseManager } = require('./managers/license-manager');
|
||||
const credentialManager = require('./managers/credential-manager');
|
||||
const authManager = require('./managers/auth-manager');
|
||||
const { createShareStore } = require('./security/share-store');
|
||||
const dockerSecurity = require('./security/docker-security');
|
||||
const auditLogger = require('./security/audit-logger');
|
||||
const portLockManager = require('./managers/port-lock-manager');
|
||||
@@ -58,6 +59,7 @@ const healthRoutes = require('../routes/health');
|
||||
const monitoringRoutes = require('../routes/monitoring');
|
||||
const updatesRoutes = require('../routes/updates');
|
||||
const authRoutes = require('../routes/auth');
|
||||
const shareRoutes = require('../routes/share');
|
||||
const configRoutes = require('../routes/config');
|
||||
const dnsRoutes = require('../routes/dns');
|
||||
const notificationRoutes = require('../routes/notifications');
|
||||
@@ -125,6 +127,15 @@ async function createApp() {
|
||||
const servicesStateManager = new StateManager(config.SERVICES_FILE);
|
||||
const configStateManager = new StateManager(config.CONFIG_FILE);
|
||||
|
||||
// DC-053: share-store. Single shared instance, lazy file creation on first
|
||||
// write. Lives alongside user-store/invite-store semantics (defensive
|
||||
// dataDir resolver, atomic JSON writes). Always available — Free tier
|
||||
// simply blocks creation via the route-level _requirePro gate.
|
||||
const shareStore = createShareStore({
|
||||
dataDir: platformPaths.dataDir,
|
||||
log,
|
||||
});
|
||||
|
||||
// Initialize license manager
|
||||
const licenseManager = new LicenseManager(credentialManager, config.CONFIG_FILE, console);
|
||||
licenseManager.loadSecret(config.LICENSE_SECRET_FILE);
|
||||
@@ -334,6 +345,9 @@ async function createApp() {
|
||||
servicesStateManager,
|
||||
configStateManager,
|
||||
|
||||
// DC-053: share store + signing secret
|
||||
shareStore,
|
||||
|
||||
// Managers
|
||||
credentialManager,
|
||||
authManager,
|
||||
@@ -491,6 +505,20 @@ async function createApp() {
|
||||
// Mount route modules
|
||||
apiRouter.use(authRoutes(ctx));
|
||||
apiRouter.use(configRoutes(ctx));
|
||||
// DC-053: share routes (public share links + Tailscale-mediated share).
|
||||
// Always mounted — Free tier enforcement is at the route level, not the
|
||||
// mount level, so the API surface is uniform across tiers (operators can
|
||||
// upgrade without restarting route registration).
|
||||
apiRouter.use(shareRoutes({
|
||||
shareStore: ctx.shareStore,
|
||||
licenseManager: ctx.licenseManager,
|
||||
tailscaleCoord: ctx.tailscaleCoord,
|
||||
notificationManager: ctx.notification,
|
||||
servicesStateManager: ctx.servicesStateManager,
|
||||
servicesFile: platformPaths.servicesFile,
|
||||
asyncHandler: ctx.asyncHandler,
|
||||
log: ctx.log,
|
||||
}));
|
||||
apiRouter.use('/dns', dnsRoutes({
|
||||
dns: ctx.dns,
|
||||
siteConfig: ctx.siteConfig,
|
||||
|
||||
Reference in New Issue
Block a user