diff --git a/.codeclimate.yml b/.codeclimate.yml index f1bedc6..c5f0438 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,9 +1,9 @@ engines: eslint: enabled: true - channel: "eslint-8" + channel: 'eslint-8' config: - config: ".eslintrc.yaml" + config: '.eslintrc.yaml' checks: similar-code: @@ -13,5 +13,5 @@ checks: threshold: 10 ratings: - paths: - - "**.js" + paths: + - '**.js' diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 6731f82..4c1bf2a 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -2,18 +2,9 @@ env: node: true es6: true mocha: true - es2020: true + es2022: true -plugins: [ haraka ] +extends: ['@haraka'] -extends: [ eslint:recommended, plugin:haraka/recommended ] - -root: true - -globals: - OK: true - CONT: true - DENY: true - DENYSOFT: true - DENYDISCONNECT: true - DENYSOFTDISCONNECT: true +rules: + no-unused-vars: 1 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index afafec5..bd8fb43 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,12 @@ Fixes # Changes proposed in this pull request: -- -- + +- +- Checklist: + - [ ] docs updated - [ ] tests updated - [ ] Changes.md updated diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea8ad8e..3d01042 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,11 @@ name: CI -on: [ push ] +on: [push, pull_request] env: CI: true jobs: - lint: uses: haraka/.github/.github/workflows/lint.yml@master @@ -14,24 +13,10 @@ jobs: # uses: haraka/.github/.github/workflows/coverage.yml@master # secrets: inherit - test: - needs: lint - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ ubuntu-latest, windows-latest ] - node-version: [ 14, 16, 18 ] - fail-fast: false - - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-node@v3 - name: Node ${{ matrix.node-version }} on ${{ matrix.os }} - with: - node-version: ${{ matrix.node-version }} - - - run: npm install + ubuntu: + needs: [lint] + uses: haraka/.github/.github/workflows/ubuntu.yml@master - - run: npm test + windows: + needs: [lint] + uses: haraka/.github/.github/workflows/windows.yml@master diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 383aca2..816e8c3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,10 +1,10 @@ -name: "CodeQL" +name: 'CodeQL' on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] schedule: - cron: '18 7 * * 4' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 42a9bb9..e81c15f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,8 @@ on: push: branches: - master + paths: + - package.json env: CI: true @@ -11,4 +13,4 @@ env: jobs: publish: uses: haraka/.github/.github/workflows/publish.yml@master - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a8e94cb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".release"] + path = .release + url = git@github.com:msimerson/.release.git diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8ded5e0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,2 @@ +singleQuote: true +semi: false diff --git a/.release b/.release new file mode 160000 index 0000000..0fa4e69 --- /dev/null +++ b/.release @@ -0,0 +1 @@ +Subproject commit 0fa4e690ffabb0157e46d56f18e4f7cfe49ce291 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..832f202 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/). + +### Unreleased + +### [1.0.2] - 2024-04-29 + +- fix: log when doing operations. Fixes #14 +- index: make separate if blocks in cascading list +- fix: rename config/aliases to config/aliases.json +- fix: wildcard + domain matches before domain. Fixes #15 +- lint: remove duplicate / stale rules from .eslintrc +- dep: eslint-plugin-haraka -> @haraka/eslint-config +- deps: bump to latest versions +- chore: populate [files] in package.json +- doc(CHANGELOG) renamed from Changes +- doc(CONTRIBUTORS): added +- doc(README): fix URL for CI tests +- ci: update to shared GHA workflows +- added the option to alias all emails with \* (#12) + +### 1.0.1 - 2022-05-26 + +- synced from Haraka/plugins/aliases +- dep(eslint): 3 -> 8 +- chore(ci): add github workflows, codeql, ci, publish +- chore(ci): delete travis and appveyor configs +- chore: ignore package-lock.json +- chore(test): replaced nodeunit with mocha +- lint: use shorthand in tests + +### 1.0.0 - 2017-09-01 + +- imported from haraka + +[1.0.2]: https://github.com/haraka/haraka-plugin-aliases/releases/tag/v1.0.2 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..ce7562b --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,8 @@ +# Contributors + +This handcrafted artisinal software is brought to you by: + +|
msimerson (7) |
TimoKoole (1) | +| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | + +this file is maintained by [.release](https://github.com/msimerson/.release) diff --git a/Changes.md b/Changes.md deleted file mode 100644 index 3547b6d..0000000 --- a/Changes.md +++ /dev/null @@ -1,19 +0,0 @@ - -### 1.0.1 - 2022-05-26 - -- synced from Haraka/plugins/aliases -- dep(eslint): 3 -> 8 -- chore(ci): add github workflows, codeql, ci, publish -- chore(ci): delete travis and appveyor configs -- chore: ignore package-lock.json -- chore(test): replaced nodeunit with mocha -- lint: use shorthand in tests - - -### 1.0.0 - 2017-09-01 - -- imported from haraka - -# 1.0.1 - 2020-09-02 - -- added the option to alias all emails with * diff --git a/README.md b/README.md index d2730d3..cd6666a 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,11 @@ # haraka-plugin-aliases -This plugin allows the configuration of aliases that perform an action or -change the RCPT address. Aliases are specified in a JSON formatted config file, -and must have an action. Syntax errors found in the JSON config will stop the server. +This plugin allows the configuration of aliases that perform an action or change the RCPT address. Aliases are specified in a JSON formatted config file, and must have an action. Syntax errors found in the JSON config will stop the server. -IMPORTANT: this plugin must appear in `config/plugins` before other plugins -that run on hook_rcpt +IMPORTANT: this plugin must appear in `config/plugins` before other plugins that run on hook_rcpt -WARNING: DO NOT USE THIS PLUGIN WITH queue/smtp\_proxy. +WARNING: DO NOT USE THIS PLUGIN WITH queue/smtp_proxy. ## Configuration @@ -20,13 +17,13 @@ WARNING: DO NOT USE THIS PLUGIN WITH queue/smtp\_proxy. JSON formatted config file that contains keys to match against RCPT addresses, and values that are objects with an "action" : "" property. Example: ```json -{ "test1" : { "action" : "drop" } } +{ "test1": { "action": "drop" } } ``` -In the above example the "test1" alias will drop any message that matches test1, test1-*, or test1+* (wildcard '-' or '+', see below). Actions may have 0 or more options listed like so: +In the above example the "test1" alias will drop any message that matches test1, test1-_, or test1+_ (wildcard '-' or '+', see below). Actions may have 0 or more options listed like so: ```json -{ "test3" : { "action" : "alias", "to" : "test3-works" } } +{ "test3": { "action": "alias", "to": "test3-works" } } ``` In the above example the "test3" alias has an action of "alias" and a mandatory "to" field. If "to" were missing the alias would fail and an error would be emitted. @@ -44,25 +41,30 @@ Aliases of 'user', '@host' and 'user@host' possible: Aliases may be expanded to multiple recipients: ```json -{ "sales@example.com": { "action": "alias", "to": ["alice@example.com", "bob@example.com"] } } +{ + "sales@example.com": { + "action": "alias", + "to": ["alice@example.com", "bob@example.com"] + } +} ``` ### wildcard notation -This plugin supports wildcard matching of aliases against the right most string of a RCPT address. The characters '-' and '+' are commonly used for subaddressing and this plugin can alias the "user" part of the address. +This plugin supports wildcard matching of aliases against the right most string of a RCPT address. The characters '-' and '+' are commonly used for subaddressing and this plugin can alias the "user" part of the address. If the address were test2-testing@example.com (or test2+testing@example.com), the below alias would match: ```json -{ "test2" : { "action" : "drop" } } +{ "test2": { "action": "drop" } } ``` -Larger and more specific aliases match first when using wildcard '-' notation. If the above RCPT was evaluated with this alias config, it would alias: +Larger and more specific aliases match first when using wildcard '-' notation. If the above RCPT was evaluated with this alias config, it would alias: ```json { - "test2" : { "action" : "drop" }, - "test2-testing" : { "action" : "alias", "to" : "test@foo.com" } + "test2": { "action": "drop" }, + "test2-testing": { "action": "alias", "to": "test@foo.com" } } ``` @@ -70,7 +72,7 @@ It also allows you to route all emails to a certain domain: ```json { - "*" : { "action" : "alias", "to" : "test15-works@success.com" } + "*": { "action": "alias", "to": "test15-works@success.com" } } ``` @@ -78,67 +80,68 @@ It also allows you to route all emails to a certain domain: Alias chaining is not supported. As a side-effect, we enjoy protections against alias circuits. -* optional one line formatting +- optional one line formatting Any valid JSON will due. Please consider keeping each alias on its own line so that others that wish to grep the aliases file have an easier time finding the full configuration for an alias. -* nondeterministic duplicate matches +- nondeterministic duplicate matches -This plugin was written with speed in mind. That means every lookup hashes into the alias file for its match. While the act of doing so is fast, it does mean that any duplicate alias entries will match nondeterministically. That is, we cannot predict what will happen here: +This plugin was written with speed in mind. That means every lookup hashes into the alias file for its match. While the act of doing so is fast, it does mean that any duplicate alias entries will match nondeterministically. That is, we cannot predict what will happen here: ```json { - "coinflip" : { "action" : "alias", "to" : "heads@coin.com" }, - "coinflip" : { "action" : "alias", "to" : "tails@coin.com" } + "coinflip": { "action": "alias", "to": "heads@coin.com" }, + "coinflip": { "action": "alias", "to": "tails@coin.com" } } ``` -Due to node.js implementation, one result will likely always be chosen over the other, so this is not exactly a coinflip. We simply cannot say what the language implementation will do and it could change. +Due to node.js implementation, one result will likely always be chosen over the other, so this is not exactly a coinflip. We simply cannot say what the language implementation will do and it could change. ## action (required) The following is a list of supported actions and their options. -* drop +- drop - Drops a message while pretending everything was okay to the sender. This acts like an alias to /dev/null. + Drops a message while pretending everything was okay to the sender. This acts like an alias to /dev/null. -* alias +- alias - Maps the alias key to the address specified in the "to" option. A note about matching in addition to the note about wildcard '-' above. When we match an alias, we store the hostname of the match for a shortcut substitution syntax later. + Maps the alias key to the address specified in the "to" option. A note about matching in addition to the note about wildcard '-' above. When we match an alias, we store the hostname of the match for a shortcut substitution syntax later. - * to (required) + - to (required) - This option is the full address, or local part at matched hostname that the RCPT address will be re-written to. For an example of an alias to a full address consider the following: + This option is the full address, or local part at matched hostname that the RCPT address will be re-written to. For an example of an alias to a full address consider the following: - ```json - { "test5" : { "action" : "alias", "to" : "test5@foo.com" } } - ``` + ```json + { "test5": { "action": "alias", "to": "test5@foo.com" } } + ``` - This maps RCPT matches for "test5" to "test5-works@foo.com". This would map "test5@somedomain.com" to "test5-works@foo.com" every time. Compare this notation with its shortcut counterpart, best used when the "to" address is at the same domain as the match: + This maps RCPT matches for "test5" to "test5-works@foo.com". This would map "test5@somedomain.com" to "test5-works@foo.com" every time. Compare this notation with its shortcut counterpart, best used when the "to" address is at the same domain as the match: - ```json - { "test4" : { "action" : "alias", "to" : "test4" } } - ``` + ```json + { "test4": { "action": "alias", "to": "test4" } } + ``` - This notation is more compact. Mail to "test4-foo@anydomain.com" will map to "test4@anydomain.com". This notation enables lots of aliases on a single domain to map to other local parts at the same domain. + This notation is more compact. Mail to "test4-foo@anydomain.com" will map to "test4@anydomain.com". This notation enables lots of aliases on a single domain to map to other local parts at the same domain. ## Example Configuration ```json { - "test1" : { "action" : "drop" }, - "test2" : { "action" : "drop" }, - "test3" : { "action" : "alias", "to" : "test3-works" }, - "test4" : { "action" : "alias", "to" : "test4" }, - "test5" : { "action" : "alias", "to" : "test5-works@success.com" }, - "test6" : { "action" : "alias", "to" : "test6-works@success.com" } + "test1": { "action": "drop" }, + "test2": { "action": "drop" }, + "test3": { "action": "alias", "to": "test3-works" }, + "test4": { "action": "alias", "to": "test4" }, + "test5": { "action": "alias", "to": "test5-works@success.com" }, + "test6": { "action": "alias", "to": "test6-works@success.com" } } ``` -[ci-img]: https://travis-ci.org/haraka/haraka-plugin-aliases.svg -[ci-url]: https://travis-ci.org/haraka/haraka-plugin-aliases + +[ci-img]: https://github.com/haraka/haraka-plugin-aliases/actions/workflows/ci.yml/badge.svg +[ci-url]: https://github.com/haraka/haraka-plugin-aliases/actions/workflows/ci.yml [cov-img]: https://codecov.io/github/haraka/haraka-plugin-aliases/coverage.svg [cov-url]: https://codecov.io/github/haraka/haraka-plugin-aliases [clim-img]: https://codeclimate.com/github/haraka/haraka-plugin-aliases/badges/gpa.svg diff --git a/config/aliases b/config/aliases deleted file mode 100644 index 2c63c08..0000000 --- a/config/aliases +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/config/aliases.json b/config/aliases.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/config/aliases.json @@ -0,0 +1 @@ +{} diff --git a/index.js b/index.js index 42f2a46..848d182 100644 --- a/index.js +++ b/index.js @@ -1,109 +1,114 @@ // aliases // Do not run this plugin with the queue/smtp_proxy plugin. -const Address = require('address-rfc2821').Address; +const Address = require('address-rfc2821').Address exports.register = function () { - this.inherits('queue/discard') + this.inherits('queue/discard') - this.load_aliases() + this.load_aliases() - this.register_hook('rcpt', 'aliases') -}; + this.register_hook('rcpt', 'aliases') +} exports.load_aliases = function () { - const plugin = this - plugin.cfg = plugin.config.get('aliases', 'json', function () { - plugin.load_aliases() + this.cfg = this.config.get('aliases.json', 'json', () => { + this.load_aliases() + }) + + if (this.cfg === undefined) { + this.cfg = this.config.get('aliases', 'json', () => { + this.load_aliases() }) - if (plugin.cfg === undefined) plugin.cfg = {} + } + + if (this.cfg === undefined) this.cfg = {} } exports.aliases = function (next, connection, params) { - const plugin = this - const cfg = plugin.cfg - const rcpt = params[0].address() - const user = params[0].user - const host = params[0].host - - let match = user.split(/[+-]/, 1) - let action = '' + const cfg = this.cfg + const rcpt = params[0].address() + const user = params[0].user + const host = params[0].host - function onMatch (alias1, action1, drop1) { - switch (action.toLowerCase()) { - case 'drop': - _drop(plugin, connection, drop1) - break; - case 'alias': - _alias(plugin, connection, alias1, cfg[alias1], host) - break; - default: - connection.loginfo(plugin, `unknown action: ${action1}`) - } - } + let match = user.split(/[+-]/, 1) + let action = '' - if (cfg[rcpt]) { // full email address match - match = rcpt - if (cfg[match].action) action = cfg[match].action - onMatch(match, action, rcpt) + const onMatch = (alias1, action1, drop1) => { + switch (action.toLowerCase()) { + case 'drop': + _drop(this, connection, drop1) + break + case 'alias': + _alias(this, connection, alias1, cfg[alias1], host) + break + default: + connection.loginfo(this, `unknown action: ${action1}`) } + } - if (cfg[`@${host}`]) { // @domain match - match = `@${host}` - if (cfg[match].action) action = cfg[match].action - onMatch(match, action, match) - } + if (cfg[rcpt]) { + // full email address match + match = rcpt + if (cfg[match].action) action = cfg[match].action + onMatch(match, action, rcpt) + } else if (cfg[user]) { + // user only match + match = user + if (cfg[user].action) action = cfg[user].action + onMatch(match, action, rcpt) + } else if (cfg[`${match[0]}@${host}`]) { + // user prefix + domain match + match = `${match[0]}@${host}` + if (cfg[match].action) action = cfg[match].action + onMatch(match, action, rcpt) + } else if (cfg[match[0]]) { + // user prefix + match = match[0] + if (cfg[match].action) action = cfg[match].action + onMatch(match, action, rcpt) + } else if (cfg[`@${host}`]) { + // @domain match + match = `@${host}` + if (cfg[match].action) action = cfg[match].action + onMatch(match, action, match) + } else if (cfg['*']) { + // Match *. When having a * in the alias list it will rewrite all emails that have not been matched by the above rules + if (cfg['*'].action) action = cfg['*'].action + onMatch('*', action) + } - if (cfg[user]) { // user only match - match = user - if (cfg[user].action) action = cfg[user].action - onMatch(match, action, rcpt) - } - else if (cfg[match[0]]) { - match = match[0] - if (cfg[match].action) action = cfg[match].action - onMatch(match, action, rcpt) - } - else if (cfg[`${match[0]}@${host}`]) { // user prefix + domain match - match = `${match[0]}@${host}` - if (cfg[match].action) action = cfg[match].action - onMatch(match, action, rcpt) - } - - // Match *. When having a * in the alias list it will rewrite all emails that have not been matched by the above rules - if (cfg["*"]) { - if (cfg["*"].action) action = cfg["*"].action; - return onMatch("*", action); - } - next() + next() } -function _drop (plugin, connection, rcpt) { - connection.logdebug(plugin, `marking ${rcpt} for drop`) - if (!connection?.transaction?.notes) return - connection.transaction.notes.discard = true +function _drop(plugin, connection, rcpt) { + connection.loginfo(plugin, `marking ${rcpt} for drop`) + if (!connection?.transaction?.notes) return + connection.transaction.notes.discard = true } -function _alias (plugin, connection, key, config, host) { - if (!connection?.transaction) return - if (!config.to) { - connection.loginfo(plugin, `alias failed for ${key}, no "to" field in alias config`) - return - } +function _alias(plugin, connection, key, config, host) { + if (!connection?.transaction) return + if (!config.to) { + connection.loginfo( + plugin, + `alias failed for ${key}, no "to" field in alias config`, + ) + return + } - const txn = connection.transaction - if (Array.isArray(config.to)) { - connection.logdebug(plugin, `aliasing ${txn.rcpt_to} to ${config.to}`) - txn.rcpt_to.pop() - config.to.forEach(addr => { - txn.rcpt_to.push(new Address(`<${addr}>`)) - }) - } - else { - const to = config.to.search('@') === -1 ? `${config.to}@${host}` : config.to - connection.logdebug(plugin, `aliasing ${txn.rcpt_to} to ${to}`) - txn.rcpt_to.pop() - txn.rcpt_to.push(new Address(`<${to}>`)) + const txn = connection.transaction + if (Array.isArray(config.to)) { + connection.loginfo(plugin, `aliasing ${txn.rcpt_to} to ${config.to}`) + txn.rcpt_to.pop() + for (const addr of config.to) { + txn.rcpt_to.push(new Address(`<${addr}>`)) } + } else { + const to = config.to.search('@') === -1 ? `${config.to}@${host}` : config.to + connection.loginfo(plugin, `aliasing ${txn.rcpt_to} to ${to}`) + txn.rcpt_to.pop() + txn.rcpt_to.push(new Address(`<${to}>`)) + } - txn.notes.forward = true + txn.notes.forward = true } diff --git a/package.json b/package.json index de4b568..ba31db0 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,21 @@ { "name": "haraka-plugin-aliases", - "version": "1.0.1", + "version": "1.0.2", "description": "Haraka plugin that add aliases", "main": "index.js", + "files": [ + "CHANGELOG.md", + "config" + ], "scripts": { - "lint": "npx eslint *.js test", - "lintfix": "npx eslint --fix *.js test", - "test": "npx mocha" + "format": "npm run prettier:fix && npm run lint:fix", + "lint": "npx eslint@^8 *.js test", + "lint:fix": "npx eslint@^8 *.js test --fix", + "prettier": "npx prettier . --check", + "prettier:fix": "npx prettier . --write --log-level=warn", + "test": "npx mocha@^10", + "versions": "npx dependency-version-checker check", + "versions:fix": "npx dependency-version-checker update" }, "repository": { "type": "git", @@ -24,12 +33,11 @@ }, "homepage": "https://github.com/haraka/haraka-plugin-aliases#readme", "dependencies": { - "address-rfc2821": "*" + "address-rfc2821": "^2.1.2" }, "devDependencies": { - "eslint": ">=8", - "eslint-plugin-haraka": "*", - "haraka-test-fixtures": "*", - "mocha": "9" + "eslint": "^8.57.0", + "@haraka/eslint-config": "^1.1.5", + "haraka-test-fixtures": "^1.3.7" } } diff --git a/test/config/aliases b/test/config/aliases deleted file mode 100644 index 8e3f4de..0000000 --- a/test/config/aliases +++ /dev/null @@ -1,18 +0,0 @@ -{ - "test1" : { "action" : "drop" }, - "test2" : { "action" : "drop" }, - "test2-specific" : { "action" : "alias", "to" : "test2" }, - "test3" : { "action" : "alias", "to" : "test3-works" }, - "test4" : { "action" : "alias", "to" : "test4" }, - "test5" : { "action" : "alias", "to" : "test5-works@success.com" }, - "test6" : { "action" : "alias", "to" : "test6-works@success.com" }, - "test7" : { "action" : "fail", "to" : "should_fail" }, - "test8" : { "to" : "should_fail" }, - "test9" : { "action" : "alias" }, - "@example.co" : { "action" : "drop" }, - "test11@example.org" : { "action" : "drop" }, - "@demo.com" : { "action" : "alias", "to" : "test12-works@success.com" }, - "test13@example.net" : { "action" : "alias", "to" : "test13-works@success.com" }, - "test14@example.net" : { "action" : "alias", "to" : ["alice@success.com", "bob@success.com"] }, - "*" : { "action" : "alias", "to" : "test15-works@success.com" } -} \ No newline at end of file diff --git a/test/config/aliases.json b/test/config/aliases.json new file mode 100644 index 0000000..6b1b35b --- /dev/null +++ b/test/config/aliases.json @@ -0,0 +1,60 @@ +{ + "test1": { + "action": "drop" + }, + "test2": { + "action": "drop" + }, + "test2-specific": { + "action": "alias", + "to": "test2" + }, + "test3": { + "action": "alias", + "to": "test3-works" + }, + "test4": { + "action": "alias", + "to": "test4" + }, + "test5": { + "action": "alias", + "to": "test5-works@success.com" + }, + "test6": { + "action": "alias", + "to": "test6-works@success.com" + }, + "test7": { + "action": "fail", + "to": "should_fail" + }, + "test8": { + "to": "should_fail" + }, + "test9": { + "action": "alias" + }, + "@example.co": { + "action": "drop" + }, + "test11@example.org": { + "action": "drop" + }, + "@demo.com": { + "action": "alias", + "to": "test12-works@success.com" + }, + "test13@example.net": { + "action": "alias", + "to": "test13-works@success.com" + }, + "test14@example.net": { + "action": "alias", + "to": ["alice@success.com", "bob@success.com"] + }, + "*": { + "action": "alias", + "to": "test15-works@success.com" + } +} diff --git a/test/index.js b/test/index.js index 622b2de..edd32f0 100644 --- a/test/index.js +++ b/test/index.js @@ -1,256 +1,368 @@ -'use strict'; +'use strict' const assert = require('assert') -const path = require('path'); +const path = require('path') -const Address = require('address-rfc2821').Address; -const fixtures = require('haraka-test-fixtures'); - -const stub = fixtures.stub.stub; +const Address = require('address-rfc2821').Address +const fixtures = require('haraka-test-fixtures') const _set_up = function (done) { + this.plugin = new fixtures.plugin('aliases') + this.params = [new Address('')] - // needed for tests - this.plugin = new fixtures.plugin('aliases'); - this.recip = new Address(''); - this.params = [this.recip]; - - this.connection = new fixtures.connection.createConnection(); - this.connection.transaction = new fixtures.transaction.createTransaction(); - this.connection.transaction.rcpt_to = [ this.params ]; - this.connection.loginfo = stub(); + this.connection = new fixtures.connection.createConnection() + this.connection.init_transaction() + this.connection.transaction.rcpt_to = [this.params] + this.connection.loginfo = fixtures.stub.stub() - // some test data - this.plugin.config = this.plugin.config.module_config(path.resolve('test')); - this.plugin.inherits = stub(); + // some test data + this.plugin.config = this.plugin.config.module_config(path.resolve('test')) + this.plugin.inherits = fixtures.stub.stub() - // going to need these in multiple tests - this.plugin.register(); + // going to need these in multiple tests + this.plugin.register() - done(); + done() } describe('aliases', function () { - beforeEach(_set_up) - - it('should have register function', function (done) { - assert.ok(this.plugin) - assert.equal('function', typeof this.plugin.register) + beforeEach(_set_up) + + it('should have register function', function () { + assert.ok(this.plugin) + assert.equal('function', typeof this.plugin.register) + }) + + it('register function should inherit from queue/discard', function () { + assert.ok(this.plugin.inherits.called) + assert.equal(this.plugin.inherits.args[0], 'queue/discard') + }) + + it('register function should call register_hook()', function () { + assert.ok(this.plugin.register_hook.called) + }) + + it('register_hook() should register for proper hook', function () { + assert.equal(this.plugin.register_hook.args[0], 'rcpt') + }) + + it('register_hook() should register available function', function () { + assert.equal(this.plugin.register_hook.args[1], 'aliases') + assert.ok(this.plugin.aliases) + assert.equal(typeof this.plugin.aliases, 'function') + }) + + it('aliases hook always returns next()', function (done) { + this.plugin.aliases( + (action) => { + assert.equal(action, undefined) done() - }) - - it('register function should inherit from queue/discard', function (done) { - assert.ok(this.plugin.inherits.called); - assert.equal(this.plugin.inherits.args[0], 'queue/discard'); + }, + this.connection, + this.params, + ) + }) + + it('should drop test1@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.notes.discard) done() - }) - - it('register function should call register_hook()', function (done) { - assert.ok(this.plugin.register_hook.called); + }, + this.connection, + this.params, + ) + }) + + it('should drop test2-testing@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.notes.discard) done() - }) - - it('register_hook() should register for propper hook', function (done) { - assert.equal(this.plugin.register_hook.args[0], 'rcpt'); + }, + this.connection, + [new Address('')], + ) + }) + + it('should drop test2-specific@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.equal(this.connection.transaction.notes.discard, undefined) + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) done() - }) - - it('register_hook() should register available function', function (done) { - assert.equal(this.plugin.register_hook.args[1], 'aliases'); - assert.ok(this.plugin.aliases); - assert.equal('function', typeof this.plugin.aliases); + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test3@example.com to test3-works@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test4-testing@example.com to test4@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test4+testing@example.com to test4@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test5@example.com to test5-works@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test6-testing@example.com to test6-works@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should drop @example.co', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.notes.discard) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should drop test11@example.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.notes.discard) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map @demo.com to test12-works@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test13@example.net to test13-works@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map test13+subaddress@example.net to test13-works@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should explode test14@example.net to alice@success.com and bob@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual(this.connection.transaction.rcpt_to, [ + new Address(''), + new Address(''), + ]) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should not drop test1@example.com, no config', function (done) { + this.plugin.cfg = {} // empty config data + this.plugin.aliases( + (action) => { + assert.equal(undefined, this.connection.transaction.notes.discard) + done() + }, + this.connection, + this.params, + ) + }) + + it('should fail with loginfo on unknown action', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.loginfo.called) + assert.equal( + this.connection.loginfo.args[1], + `unknown action: ${this.plugin.cfg.test7.action}`, + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should fail with loginfo on missing action', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.loginfo.called) + assert.equal( + this.connection.loginfo.args[1], + 'unknown action: ', + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should map * to test15-works@success.com', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.transaction.rcpt_to) + assert.ok(Array.isArray(this.connection.transaction.rcpt_to)) + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) + done() + }, + this.connection, + [new Address('test15@example.com')], + ) + }) + + it('action alias should fail with loginfo on missing to', function (done) { + this.plugin.aliases( + (action) => { + assert.ok(this.connection.loginfo.called) + assert.equal( + this.connection.loginfo.args[1], + 'alias failed for test9, no "to" field in alias config', + ) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('should prefer more specific rule', function (done) { + this.plugin.cfg = { + '@example.com': { + action: 'alias', + to: 'bar@example.com', + }, + foo: { + action: 'alias', + to: 'foo@example.com', + }, + } + + this.plugin.aliases( + (action) => { + assert.deepEqual( + this.connection.transaction.rcpt_to.pop(), + new Address(''), + ) done() - }) - - it('aliases hook always returns next()', function (done) { - this.plugin.aliases(action => { - assert.equal(undefined, action); - done() - }, this.connection, this.params); - }) - - it('should drop test1@example.com', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.notes.discard); - done() - }, this.connection, this.params); - }) - - it('should drop test2-testing@example.com', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.notes.discard); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should drop test2-specific@example.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.equal(undefined, this.connection.transaction.notes.discard); - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test3@example.com to test3-works@example.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test4-testing@example.com to test4@example.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test4+testing@example.com to test4@example.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test5@example.com to test5-works@success.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test6-testing@example.com to test6-works@success.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should drop @example.co', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.notes.discard); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should drop test11@example.com', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.notes.discard); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map @demo.com to test12-works@success.com', function (done) { - const result = new Address(''); - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test13@example.net to test13-works@success.com', function (done) { - // these will get reset in _set_up everytime - const result = new Address(''); - - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should map test13+subaddress@example.net to test13-works@success.com', function (done) { - // these will get reset in _set_up everytime - const result = new Address(''); - - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to.pop(), result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should explode test14@example.net to alice@success.com and bob@success.com', function (done) { - // these will get reset in _set_up everytime - const result = [new Address(''), new Address('')]; - - this.plugin.aliases(action => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual(this.connection.transaction.rcpt_to, result); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should not drop test1@example.com, no config', function (done) { - this.plugin.cfg = {}; // empty config data - this.plugin.aliases(action => { - assert.equal(undefined, this.connection.transaction.notes.discard); - done() - }, this.connection, this.params); - }) - - it('should fail with loginfo on unknown action', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.loginfo.called); - assert.equal(this.connection.loginfo.args[1], - `unknown action: ${ this.plugin.cfg.test7.action}`); - done() - }, this.connection, [ new Address('') ]); - }) - - it('should fail with loginfo on missing action', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.loginfo.called); - assert.equal(this.connection.loginfo.args[1], - "unknown action: "); - done() - }, this.connection, [ new Address('') ]); - }) - - it('action alias should fail with loginfo on missing to', function (done) { - this.plugin.aliases(action => { - assert.ok(this.connection.loginfo.called); - assert.equal(this.connection.loginfo.args[1], - 'alias failed for test9, no "to" field in alias config'); - done() - }, this.connection, [new Address('')]); - }) - - it('should map * to test15-works@success.com': function (done) { - // these will get reset in _set_up everytime - this.recip = new Address('test15@example.com'); - - this.plugin.aliases((action) => { - assert.ok(this.connection.transaction.rcpt_to); - assert.ok(Array.isArray(this.connection.transaction.rcpt_to)); - assert.deepEqual( - this.connection.transaction.rcpt_to.pop(), - new Address(''), - ); - done(); - }, this.connection, [this.recip]); - }) + }, + this.connection, + [new Address('')], + ) + }) })