From 14b951ac444e30bb879790673787b22eea2c54e7 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Fri, 28 Jun 2024 06:59:12 -0700 Subject: [PATCH] feat(maildev): auto-configure instances with maildev settings --- .changeset/giant-knives-dance.md | 5 + packages/plugin-maildev/package.json | 3 +- packages/plugin-maildev/src/constants.ts | 10 +- packages/plugin-maildev/src/index.ts | 14 +- .../hooks/plugin-maildev-smtp-settings.pb.js | 139 ++++++++ .../src/instance-app/settings.json | 329 ++++++++++++++++++ 6 files changed, 497 insertions(+), 3 deletions(-) create mode 100644 .changeset/giant-knives-dance.md create mode 100644 packages/plugin-maildev/src/instance-app/hooks/plugin-maildev-smtp-settings.pb.js create mode 100644 packages/plugin-maildev/src/instance-app/settings.json diff --git a/.changeset/giant-knives-dance.md b/.changeset/giant-knives-dance.md new file mode 100644 index 00000000..ea369839 --- /dev/null +++ b/.changeset/giant-knives-dance.md @@ -0,0 +1,5 @@ +--- +'@pockethost/plugin-maildev': patch +--- + +At launch, instances are configured to use maildev settings diff --git a/packages/plugin-maildev/package.json b/packages/plugin-maildev/package.json index 94c87fcf..7fb3ef1f 100644 --- a/packages/plugin-maildev/package.json +++ b/packages/plugin-maildev/package.json @@ -1,6 +1,6 @@ { "name": "@pockethost/plugin-maildev", - "version": "1.0.0", + "version": "0.0.0", "repository": { "type": "git", "url": "http://github.com/pockethost/pockethost/packages/plugin-maildev" @@ -23,6 +23,7 @@ "license": "MIT", "dependencies": { "commander": "^11.1.0", + "immer": "^10.1.1", "maildev": "^2.1.0" }, "peerDependencies": { diff --git a/packages/plugin-maildev/src/constants.ts b/packages/plugin-maildev/src/constants.ts index 8ef2064f..60ccef78 100644 --- a/packages/plugin-maildev/src/constants.ts +++ b/packages/plugin-maildev/src/constants.ts @@ -1,22 +1,30 @@ -import { join } from 'path' +import { dirname, join } from 'path' import { DEBUG } from 'pockethost' import { PH_HOME, Settings, logSettings, + mkBoolean, mkNumber, mkPath, } from 'pockethost/core' +import { fileURLToPath } from 'url' export const PLUGIN_NAME = `plugin-maildev` export const HOME_DIR = process.env.PH_MAILDEV_HOME || join(PH_HOME(), PLUGIN_NAME) +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export const PROJECT_DIR = (...paths: string[]) => + join(__dirname, '..', ...paths) + const settings = Settings({ PH_MAILDEV_HOME: mkPath(HOME_DIR, { create: true }), PH_MAILDEV_SMTP_PORT: mkNumber(1025), PH_MAILDEV_WEB_ADMIN_PORT: mkNumber(1080), + PH_MAILDEV_ALWAYS_USE_LOCAL: mkBoolean(true), }) export const PORT = () => settings.PH_MAILDEV_SMTP_PORT diff --git a/packages/plugin-maildev/src/index.ts b/packages/plugin-maildev/src/index.ts index 9e8c14fd..e7b78e63 100644 --- a/packages/plugin-maildev/src/index.ts +++ b/packages/plugin-maildev/src/index.ts @@ -1,13 +1,15 @@ import { Command } from 'commander' +import { produce } from 'immer' import MailDev from 'maildev' import { IS_DEV, PocketHostPlugin, onCliCommandsFilter, + onInstanceConfigFilter, onServeAction, onServeSlugsFilter, } from 'pockethost' -import { PLUGIN_NAME, PORT, WEB_ADMIN_PORT } from './constants' +import { PLUGIN_NAME, PORT, PROJECT_DIR, WEB_ADMIN_PORT } from './constants' import { dbg } from './log' const serve = () => { @@ -44,6 +46,16 @@ const plugin: PocketHostPlugin = async ({}) => { // } // }) + onInstanceConfigFilter(async (config) => { + return produce(config, (draft) => { + draft.env.PH_MAILDEV_PORT = PORT().toString() + draft.binds.hooks.push({ + src: PROJECT_DIR(`src/instance-app/hooks/**/*`), + base: PROJECT_DIR(`src/instance-app/hooks`), + }) + }) + }) + onCliCommandsFilter(async (commands) => { return [...commands, MailDevCommand()] }) diff --git a/packages/plugin-maildev/src/instance-app/hooks/plugin-maildev-smtp-settings.pb.js b/packages/plugin-maildev/src/instance-app/hooks/plugin-maildev-smtp-settings.pb.js new file mode 100644 index 00000000..08420166 --- /dev/null +++ b/packages/plugin-maildev/src/instance-app/hooks/plugin-maildev-smtp-settings.pb.js @@ -0,0 +1,139 @@ +/// + +$app.onBeforeServe().add((e) => { + return + const dao = $app.dao() + const { mkLog } = /** @type {Lib} */ (require(`${__hooks}/_ph_lib.js`)) + + const log = mkLog(`plugin-maildev`) + log(`Starting plugin-maildev`) + + const APP_DEFAULTS = { + appName: 'Acme', + appUrl: 'http://localhost:8090', + senderName: 'Support', + senderAddress: 'support@example.com', + } + + try { + const settings = dao.findSettings() + if (!settings) { + throw new Error(`Expected settings here`) + } + + const fix = (field, newValue) => { + if (!newValue || settings.meta[field] !== APP_DEFAULTS[field]) return + settings.meta[field] = newValue + } + fix(`appName`, PH_APP_NAME) + fix(`appUrl`, PH_INSTANCE_URL) + fix(`senderName`, PH_APP_NAME) + fix(`senderAddress`, `${PH_APP_NAME}@app.pockethost.io`) + + dao.saveSettings(settings) + + log(`***defaults successfully applied`) + } catch (e) { + log(`***error applying defaults: ${e}`) + } +}) + +$app.onBeforeServe().add((e) => { + return + const dao = $app.dao() + const { mkLog } = /** @type {Lib} */ (require(`${__hooks}/_ph_lib.js`)) + + const log = mkLog(`plugin-maildev`) + log(`Starting plugin-maildev`) + + const { id, email, tokenKey, passwordHash } = (() => { + try { + return /** @type{{id:string, email:string, tokenKey:string,passwordHash:string}} */ ( + JSON.parse($os.getenv(`ADMIN_SYNC`)) + ) + } catch (e) { + return { id: '', email: '', tokenKey: '', passwordHash: '' } + } + })() + + if (!email) { + log(`Not active - skipped`) + return + } + + const result = new DynamicModel({ + // describe the shape of the data (used also as initial values) + id: '', + }) + + try { + dao + .db() + .newQuery('SELECT * from _admins where email = {:email}') + .bind({ email }) + .one(result) + log( + `Existing admin record matching PocketHost login found - updating with latest credentials`, + ) + try { + dao + .db() + .newQuery( + 'update _admins set tokenKey={:tokenKey}, passwordHash={:passwordHash} where email={:email}', + ) + .bind({ email, tokenKey, passwordHash }) + .execute() + log(`Success`) + } catch (e) { + log(`Failed to update admin credentials: ${e}`) + } + } catch (e) { + log(`No admin record matching PocketHost credentials - creating`) + + try { + dao + .db() + .newQuery( + 'insert into _admins (id,email, tokenKey, passwordHash) VALUES ({:id}, {:email}, {:tokenKey}, {:passwordHash})', + ) + .bind({ + id, + email, + tokenKey, + passwordHash, + created: new Date().toISOString(), + updated: new Date().toISOString(), + }) + .execute() + log(`Success`) + } catch (e) { + log(`Failed to insert admin credentials: ${e}`) + } + } +}) + +$app.onBeforeServe().add((e) => { + const dao = $app.dao() + const { mkLog } = /** @type {Lib} */ (require(`${__hooks}/_ph_lib.js`)) + + const log = mkLog(`plugin-maildev`) + log(`Starting plugin-maildev`) + + try { + const settings = dao.findSettings() + if (!settings) { + throw new Error(`Expected settings here`) + } + + settings.smtp[`enabled`] = true + settings.smtp[`host`] = `localhost` + settings.smtp[`port`] = $os.getenv(`PH_MAILDEV_PORT`) + settings.smtp[`tls`] = false + + dao.saveSettings(settings) + + log(`***defaults successfully applied`) + } catch (e) { + log(`***error applying defaults: ${e}`) + } +}) diff --git a/packages/plugin-maildev/src/instance-app/settings.json b/packages/plugin-maildev/src/instance-app/settings.json new file mode 100644 index 00000000..7fe09481 --- /dev/null +++ b/packages/plugin-maildev/src/instance-app/settings.json @@ -0,0 +1,329 @@ +{ + "adminAuthToken": { + "duration": 1209600, + "secret": "TxmGmMf91xixFDppollsByz7gc0kEgNPGR4MD4lMBCBxmYLKh7" + }, + "adminFileToken": { + "duration": 120, + "secret": "BrYUNzmQ6819CY9LqfcTFFZ3v6bMs34aZxAmjym1BxqiaUmMNJ" + }, + "adminPasswordResetToken": { + "duration": 1800, + "secret": "ODnVe9kQaR0PByFTtpILOTFJNzooaMyFTQh2jJRbUxD31N280T" + }, + "appleAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "backups": { + "cron": "0 0 * * *", + "cronMaxKeep": 3, + "s3": { + "accessKey": "", + "bucket": "", + "enabled": false, + "endpoint": "", + "forcePathStyle": false, + "region": "", + "secret": "" + } + }, + "discordAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "emailAuth": { + "enabled": true, + "exceptDomains": null, + "minPasswordLength": 8, + "onlyDomains": null + }, + "facebookAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "giteaAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "giteeAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "githubAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "gitlabAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "googleAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "instagramAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "kakaoAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "livechatAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "logs": { + "logIp": true, + "maxDays": 7, + "minLevel": 0 + }, + "mailcowAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "meta": { + "appName": "PocketHost", + "appUrl": "https://pockethost-central.pockethost.io", + "confirmEmailChangeTemplate": { + "actionUrl": "{APP_URL}/_/#/users/confirm-email-change/{TOKEN}", + "body": "

Hello,

\n

Click on the button below to confirm your new email address.

\n

\n Confirm new email\n

\n

If you didn't ask to change your email address, you can ignore this email.

\n

\n Thanks,
\n {APP_NAME} team\n

", + "subject": "Confirm your {APP_NAME} new email address" + }, + "hideControls": false, + "resetPasswordTemplate": { + "actionUrl": "https://app.pockethost.io/login/password-reset/confirm?token={TOKEN}", + "body": "

Hello,

\n

Click on the button below to reset your password.

\n

\n Reset password\n

\n

If you didn't ask to reset your password, you can ignore this email.

\n

\n Thanks,
\n {APP_NAME} team\n

", + "subject": "Reset your {APP_NAME} password" + }, + "senderAddress": "ben@pockethost.io", + "senderName": "Ben", + "verificationTemplate": { + "actionUrl": "https://app.pockethost.io/login/confirm-account?token={TOKEN}", + "body": "

Hello,

\n

Thank you for joining us at {APP_NAME}.

\n

Click on the button below to verify your email address.

\n

\n Verify\n

\n

\n Thanks,
\n {APP_NAME} team\n

", + "subject": "Verify your {APP_NAME} email" + } + }, + "microsoftAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "oidc2Auth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "oidc3Auth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "oidcAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "patreonAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "recordAuthToken": { + "duration": 1209600, + "secret": "7DgE4l39rrhFKF97V9yadQdolXGRHg0jSVA7j97CSHZ9QoK6Bn" + }, + "recordEmailChangeToken": { + "duration": 1800, + "secret": "XbDSjPSPBISzRJbVj02mP2xHLpGrwegpAnqnUqrHbJWoMmhCid" + }, + "recordFileToken": { + "duration": 120, + "secret": "rjZuZ3vofE30E7q4l3XjgADmwBo4gSCVSUS66aEB4gZBOs6kMV" + }, + "recordPasswordResetToken": { + "duration": 1800, + "secret": "s3jkwjDqtboL9JCju73IPslQrflLngBIFO1JY2FH3Vf9oX4gkl" + }, + "recordVerificationToken": { + "duration": 604800, + "secret": "8qNJXE3z90UwCzxafChqEPuNyeaEzAgSmvGKBPAOmM23yQzhv5" + }, + "s3": { + "accessKey": "", + "bucket": "", + "enabled": false, + "endpoint": "", + "forcePathStyle": false, + "region": "", + "secret": "" + }, + "smtp": { + "authMethod": "PLAIN", + "enabled": true, + "host": "host.docker.internal", + "localName": "", + "password": "", + "port": 1025, + "tls": false, + "username": "" + }, + "spotifyAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "stravaAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "twitchAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "twitterAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "vkAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + }, + "yandexAuth": { + "authUrl": "", + "clientId": "", + "clientSecret": "", + "displayName": "", + "enabled": false, + "pkce": null, + "tokenUrl": "", + "userApiUrl": "" + } +}