Compare commits

...

23 Commits

Author SHA1 Message Date
wpetit 888ff2ec47 chore: generate tag before building docker image 2024-03-21 09:20:58 +01:00
wpetit 040a9d0e46 feat: migrate project to cadoles org 2024-03-21 09:11:50 +01:00
wpetit 4d2ca6bd3a chore: docker image release with new tagging scheme 2024-02-27 17:38:04 +01:00
wpetit 805c873695 chore: update go dependencies 2024-02-27 17:26:32 +01:00
wpetit 13d3991a10 Merge pull request 'fix(docker): image now builds' (#3) from fix/dockerfile into develop
Reviewed-on: wpetit/fake-smtp#3
2024-02-27 17:19:38 +01:00
wpetit 4871876bf0 chore: use go run to exec modd 2024-02-27 17:19:16 +01:00
wpetit d47b819e1d feat(docker): use go 1.21 2024-02-27 17:14:25 +01:00
Philippe Caseiro f7bff1b697 fix(docker): image now builds
adding non root user execution capabilities
2024-02-27 17:09:30 +01:00
wpetit 1ce43d2c76 Merge pull request 'build(deps): bump nodejs to 20.x & update node modules' (#4) from upgrade_nodejs into develop
Reviewed-on: wpetit/fake-smtp#4
Reviewed-by: wpetit <wpetit@cadoles.com>
2024-02-27 17:06:16 +01:00
Benjamin Gaudé 75213ebe6e build(deps): update node version 2024-02-27 15:50:00 +01:00
Benjamin Gaudé 6a9c2fd13f build(deps): bump nodejs to 20.x & update node modules 2024-02-27 14:26:12 +01:00
wpetit 19e15d3fe7 Fix docker build recipe 2022-11-22 15:18:28 -06:00
wpetit db0a3ac98f Trigger Nuonet mirror sync 2022-03-16 16:58:15 +01:00
wpetit b335a825a3 Refactor inbox query filtering 2022-02-18 15:16:42 +01:00
wpetit f959fdb93f Update Dockerfile 2022-02-18 10:24:46 +01:00
wpetit 6a52595fa7 Allow filtering emails via headers 2022-02-18 10:14:16 +01:00
wpetit 450a9de82d Add missing base url in clear inbox endpoint url 2021-07-30 16:25:00 +02:00
wpetit 3afd252c71 Upgrade node-sass 2021-07-26 14:48:24 +02:00
wpetit 8027ba7bcc Add BaseURL config parameter 2021-07-26 14:47:58 +02:00
wpetit 61c3cd33be Update README 2020-11-06 09:44:30 +01:00
wpetit 62344993f5 Allow email relaying to a real MTA 2020-11-05 19:48:18 +01:00
wpetit 9035280818 Tag Docker image with release date 2020-11-03 12:43:16 +01:00
wpetit 82f21d0298 Upgrade to Go 1.15 2020-11-03 12:32:06 +01:00
39 changed files with 6008 additions and 6452 deletions

View File

@ -5,4 +5,5 @@
/bin
/misc/docker/Dockerfile
/.env
/.env.dist
/.env.dist
/tools

4
.gitignore vendored
View File

@ -3,4 +3,6 @@
/vendor
/bin
/node_modules
/.env
/.env
/tools
.mktools/

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v20.11.1

View File

@ -1,7 +1,20 @@
DOCKER_REPOSITORY ?= reg.cadoles.com/cadoles
YQ_VERSION ?= v4.30.4
YQ_BINARY ?= yq_linux_amd64
tools: tools/yq/bin/yq
tools/yq/bin/yq:
mkdir -p tools/yq/bin
wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY} -O tools/yq/bin/yq &&\
chmod +x tools/yq/bin/yq
build:
CGO_ENABLED=0 go build -v -o bin/fake-smtp ./cmd/fake-smtp
docker-image:
git tag -a $(MKT_PROJECT_VERSION) -m "v$(MKT_PROJECT_VERSION)" || exit 0
docker build \
--build-arg HTTP_PROXY=$(HTTP_PROXY) \
--build-arg HTTPS_PROXY=$(HTTPS_PROXY) \
@ -19,15 +32,20 @@ docker-run:
--tmpfs /app/data \
fake-smtp:latest
docker-release:
docker tag fake-smtp:latest bornholm/fake-smtp:latest
docker login
docker push bornholm/fake-smtp:latest
docker-release: .mktools
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION)
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION)
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION_CHANNEL)-latest
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION_CHANNEL)-latest
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION)
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION)
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION_CHANNEL)-latest
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION_CHANNEL)-latest
test:
go test -v -race ./...
release: dist
release: dist tools
@./misc/script/release.sh
dist:
@ -37,7 +55,7 @@ tidy:
go mod tidy
watch:
modd
go run github.com/cortesi/modd/cmd/modd@v0.8.1
lint:
golangci-lint run --enable-all
@ -51,4 +69,14 @@ clean:
rm -rf vendor
rm -rf bin
.PHONY: mktools
mktools:
rm -rf .mktools
curl -q https://forge.cadoles.com/Cadoles/mktools/raw/branch/master/install.sh | $(SHELL)
.mktools:
$(MAKE) mktools
-include .mktools/*.mk
.PHONY: lint watch build vendor tidy release

View File

@ -11,7 +11,7 @@ Serveur SMTP factice pour le développement avec interface web.
### Avec Docker
```bash
docker run -it --rm -p 8080:8080 -p 2525:2525 bornholm/fake-smtp
docker run -it --rm -p 8080:8080 -p 2525:2525 reg.cadoles.com/cadoles/fake-smtp
```
L'interface Web sera accessible à l'adresse http://localhost:8080/.
@ -55,6 +55,18 @@ smtp:
# Configuration du stockage
data:
path: fakesmtp.db
# Configuration du relais SMTP
relay:
enabled: false # Activer/désactiver le relais SMTP
address: "" # Adresse du serveur au format "host:port"
identity: "" # Identité du compte utilisateur, peut être laissé vide
username: "" # Identifiant du compte SMTP, non utilisé sur anonymous = true
password: "" # Mot de passe du compte SMTP, non utilisé sur anonymous = true
anonymous: false # Utiliser le mode d'authentification "anonyme"
useTLS: false # Utiliser TLS pour se connecter au serveur SMTP
insecureSkipVerify: true # Ne pas vérifier le certificat du serveur pour les connexions TLS/STARTTLS
fromOverride: "" # Surcharger l'adresse émetteur des courriels transmis
```
### Variables d'environnement
@ -63,28 +75,36 @@ La configuration de FakeSMTP peut être personnalisée via des variables d'envir
Les valeurs des variables d'environnement surchargent les valeurs présentes dans le fichier de configuration.
|Variable|Correspondance dans le fichier de configuration|
|--------|-----------------------------------------------|
|`FAKESMTP_HTTP_ADDRESS`|`http.address`|
|`FAKESMTP_HTTP_TEMPLATEDIR`|`http.templateDir`|
|`FAKESMTP_HTTP_PUBLICDIR`|`http.publicDir`|
|`FAKESMTP_SMTP_ADDRESS`|`smtp.address`|
|`FAKESMTP_SMTP_USERNAME`|`smtp.username`|
|`FAKESMTP_SMTP_PASSWORD`|`smtp.password`|
|`FAKESMTP_SMTP_DOMAIN`|`smtp.domain`|
|`FAKESMTP_SMTP_READTIMEOUT`|`smtp.readTimeout`|
|`FAKESMTP_SMTP_WRITETIMEOUT`|`smtp.writeTimeout`|
|`FAKESMTP_SMTP_MAXMESSAGEBYTES`|`smtp.maxMessageBytes`|
|`FAKESMTP_SMTP_MAXRECIPIENTS`|`smtp.maxRecipients`|
|`FAKESMTP_SMTP_ALLOWINSECUREAUTH`|`smtp.allowInsecureAuth`|
|`FAKESMTP_SMTP_DEBUG`|`smtp.debug`|
|`FAKESMTP_DATA_PATH`|`data.path`|
| Variable | Correspondance dans le fichier de configuration |
| ------------------------------------- | ----------------------------------------------- |
| `FAKESMTP_HTTP_ADDRESS` | `http.address` |
| `FAKESMTP_HTTP_TEMPLATEDIR` | `http.templateDir` |
| `FAKESMTP_HTTP_PUBLICDIR` | `http.publicDir` |
| `FAKESMTP_SMTP_ADDRESS` | `smtp.address` |
| `FAKESMTP_SMTP_USERNAME` | `smtp.username` |
| `FAKESMTP_SMTP_PASSWORD` | `smtp.password` |
| `FAKESMTP_SMTP_DOMAIN` | `smtp.domain` |
| `FAKESMTP_SMTP_READTIMEOUT` | `smtp.readTimeout` |
| `FAKESMTP_SMTP_WRITETIMEOUT` | `smtp.writeTimeout` |
| `FAKESMTP_SMTP_MAXMESSAGEBYTES` | `smtp.maxMessageBytes` |
| `FAKESMTP_SMTP_MAXRECIPIENTS` | `smtp.maxRecipients` |
| `FAKESMTP_SMTP_ALLOWINSECUREAUTH` | `smtp.allowInsecureAuth` |
| `FAKESMTP_SMTP_DEBUG` | `smtp.debug` |
| `FAKESMTP_DATA_PATH` | `data.path` |
| `FAKESMTP_RELAY_ENABLED` | `relay.enabled` |
| `FAKESMTP_RELAY_ADDRESS` | `relay.address` |
| `FAKESMTP_RELAY_IDENTITY` | `relay.identity` |
| `FAKESMTP_RELAY_USERNAME` | `relay.username` |
| `FAKESMTP_RELAY_PASSWORD` | `relay.password` |
| `FAKESMTP_RELAY_ANONYMOUS` | `relay.anonymous` |
| `FAKESMTP_RELAY_INSECURE_SKIP_VERIFY` | `relay.insecureSkipVerify` |
| `FAKESMTP_RELAY_FROM_OVERRIDE` | `relay.fromOverride` |
## Démarrer avec les sources
### Dépendances
- Go 1.13
- Go 1.21
- modd
- make
- NodeJS/npm
@ -108,4 +128,4 @@ make release
## Licence
AGPL-3.0
AGPL-3.0

View File

@ -3,10 +3,10 @@ package main
import (
"gitlab.com/wpetit/goweb/template/html"
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/service"
"gitlab.com/wpetit/goweb/service/build"
@ -53,6 +53,11 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
cqrs.CommandHandlerFunc(command.HandleDeleteEmail),
)
bus.RegisterCommand(
cqrs.MatchCommandRequest(&command.RelayEmailRequest{}),
cqrs.CommandHandlerFunc(command.HandleRelayEmail),
)
bus.RegisterQuery(
cqrs.MatchQueryRequest(&query.GetInboxRequest{}),
cqrs.QueryHandlerFunc(query.HandleGetInbox),

View File

@ -3,9 +3,9 @@ package main
import (
"net/http"
"forge.cadoles.com/wpetit/fake-smtp/internal/route"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"forge.cadoles.com/Cadoles/fake-smtp/internal/route"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"gitlab.com/wpetit/goweb/middleware/container"
"flag"
@ -14,11 +14,11 @@ import (
"os"
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
"github.com/pkg/errors"
)
//nolint: gochecknoglobals
// nolint: gochecknoglobals
var (
configFile = ""
workdir = ""
@ -33,7 +33,7 @@ var (
BuildDate = "unknown"
)
//nolint: gochecknoinits
// nolint: gochecknoinits
func init() {
flag.StringVar(&configFile, "config", configFile, "configuration file")
flag.StringVar(&workdir, "workdir", workdir, "working directory")

View File

@ -6,12 +6,13 @@ import (
"log"
"os"
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
"github.com/emersion/go-smtp"
"github.com/jhillyerd/enmime"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/logger"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service"
)
@ -74,12 +75,31 @@ func (s *Session) Data(r io.Reader) error {
return errors.Wrap(err, "could not retrieve cqrs service")
}
conf, err := config.From(s.ctn)
if err != nil {
return errors.Wrap(err, "could not retrieve config service")
}
if conf.Relay.Enabled {
cmd := &command.RelayEmailRequest{
Envelope: env,
}
if _, err := bus.Exec(s.ctx, cmd); err != nil {
logger.Error(s.ctx, "could not exec command", logger.E(err))
return errors.Wrapf(err, "could not exec '%T' command", cmd)
}
}
cmd := &command.StoreEmailRequest{
Envelope: env,
}
if _, err := bus.Exec(s.ctx, cmd); err != nil {
return errors.Wrap(err, "could not exec command")
logger.Error(s.ctx, "could not exec command", logger.E(err))
return errors.Wrapf(err, "could not exec '%T' command", cmd)
}
return nil

View File

@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{block "title" . -}}{{- end}}</title>
{{- block "head_style" . -}}
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="{{ .BaseURL }}/css/main.css" />
{{end}}
{{- block "head_script" . -}}
<script type="text/javascript" src="/main.js"></script>
<script type="text/javascript" src="{{ .BaseURL }}/main.js"></script>
{{end}}
</head>
<body>

View File

@ -5,6 +5,6 @@
Date de construction: {{ .BuildInfo.BuildDate }}
</p>
<p class="has-text-right is-size-7 has-text-grey">
Propulsé par <a target="_blank" href="https://forge.cadoles.com/wpetit/fake-smtp">FakeSMTP</a> et publié sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL-3.0</a>.
Propulsé par <a target="_blank" href="https://forge.cadoles.com/Cadoles/fake-smtp">FakeSMTP</a> et publié sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL-3.0</a>.
</p>
{{end}}

View File

@ -2,7 +2,7 @@
<div class="columns is-mobile">
<div class="column is-narrow">
<h1 class="is-size-3 title">
<a href="/" rel="Inbox" class="has-text-grey-dark">
<a href="{{ .BaseURL }}/" rel="Inbox" class="has-text-grey-dark">
{{if or .Emails .Email}}
📬
{{else}}

View File

@ -2,9 +2,9 @@
{{define "header_buttons"}}
<button class="button is-danger"
data-controller="restful"
data-restful-endpoint="./{{ .Email.ID }}"
data-restful-endpoint="{{ .BaseURL }}/emails/{{ .Email.ID }}"
data-restful-method="DELETE"
data-restful-redirect="../">
data-restful-redirect="{{ .BaseURL }}/">
🗑️ Delete
</button>
{{end}}
@ -24,8 +24,9 @@
<h4 class="title is-size-4">Attachments ({{len .Email.Attachments}})</h4>
<ul>
{{ $email := .Email }}
{{ $baseURL := .BaseURL }}
{{range $i, $a := .Email.Attachments}}
<li><a href="{{ $email.ID }}/attachments/{{ $i }}" download="{{ $a.Name }}">{{ $a.Name }}</a></li>
<li><a href="{{ $baseURL }}/emails/{{ $email.ID }}/attachments/{{ $i }}" download="{{ $a.Name }}">{{ $a.Name }}</a></li>
{{end}}
</ul>
</div>
@ -44,7 +45,7 @@
data-controller="iframe"
data-action="load->iframe#onLoad"
style="width:100%;{{if not .Email.HTML}}display:none;{{end}}"
src="{{ .Email.ID }}/html">
src="{{ .BaseURL }}/emails/{{ .Email.ID }}/html">
</iframe>
<div data-target="tabs.tabContent" data-tabs-for="text" style="{{if .Email.HTML}}display:none;{{end}}width:100%;overflow:hidden;">
<pre style="white-space:pre-line;">{{ .Email.Text }}</pre>

View File

@ -2,7 +2,7 @@
{{define "header_buttons"}}
<button
data-controller="restful"
data-restful-endpoint="/emails"
data-restful-endpoint="{{ .BaseURL }}/emails"
data-restful-method="DELETE"
class="button is-danger">
🗑️ Clear
@ -24,10 +24,11 @@
</tr>
</thead>
<tbody>
{{ $baseURL := .BaseURL }}
{{range .Emails}}
<tr data-controller="inbox-entry"
data-action="click->inbox-entry#onClick"
data-inbox-entry-link="./emails/{{ .ID }}">
data-inbox-entry-link="{{ $baseURL }}/emails/{{ .ID }}">
<td class="email-subject"><div>{{ .Subject }}</div></td>
<td class="email-from">
{{range .From}}
@ -44,7 +45,7 @@
</td>
<td class="email-actions">
<div class="buttons is-right">
<a href="./emails/{{ .ID }}" class="button is-small is-link">👁️ See</a>
<a href="{{ $baseURL }}/emails/{{ .ID }}" class="button is-small is-link">👁️ See</a>
</div>
</td>
</tr>

45
go.mod
View File

@ -1,19 +1,52 @@
module forge.cadoles.com/wpetit/fake-smtp
module forge.cadoles.com/Cadoles/fake-smtp
go 1.14
go 1.21
require (
github.com/asdine/storm/v3 v3.1.1
github.com/caarlos0/env/v6 v6.2.1
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
github.com/emersion/go-smtp v0.12.1
github.com/go-chi/chi v4.1.1+incompatible
github.com/go-chi/chi/v5 v5.0.12
github.com/jhillyerd/enmime v0.8.0
github.com/microcosm-cc/bluemonday v1.0.2
github.com/microcosm-cc/bluemonday v1.0.26
github.com/pkg/errors v0.9.1
gitlab.com/wpetit/goweb v0.0.0-20200707070104-985ce3eba3c2
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
gopkg.in/yaml.v2 v2.2.8
)
require (
cdr.dev/slog v1.3.0 // indirect
github.com/alecthomas/chroma v0.7.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/go-chi/chi v4.1.1+incompatible // indirect
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 // indirect
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 // indirect
github.com/leodido/go-urn v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.11 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/olekukonko/tablewriter v0.0.1 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
go.etcd.io/bbolt v1.3.4 // indirect
go.opencensus.io v0.22.2 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
)
// replace gitlab.com/wpetit/goweb => ../goweb

39
go.sum
View File

@ -36,6 +36,8 @@ github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkx
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/asdine/storm/v3 v3.1.1 h1:5ESJvmcNhQQOFcvpxkIHcZs7mp8Z6XGdBqEoAgf+11g=
github.com/asdine/storm/v3 v3.1.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/caarlos0/env/v6 v6.2.1 h1:/bFpX1dg4TNioJjg7mrQaSrBoQvRfLUHNfXivdFbbEo=
github.com/caarlos0/env/v6 v6.2.1/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -51,10 +53,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5sNA=
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -64,6 +65,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
@ -73,7 +76,6 @@ github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
@ -102,11 +104,11 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -134,8 +136,8 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
@ -159,7 +161,6 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -178,8 +179,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -200,7 +202,6 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -211,10 +212,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -235,14 +235,16 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -293,7 +295,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -5,8 +5,8 @@ import (
"github.com/pkg/errors"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/middleware/container"
)

View File

@ -5,8 +5,8 @@ import (
"github.com/pkg/errors"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/middleware/container"
)

View File

@ -0,0 +1,164 @@
package command
import (
"context"
"crypto/tls"
"io"
"sync"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
"github.com/jhillyerd/enmime"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/middleware/container"
)
type RelayEmailRequest struct {
Envelope *enmime.Envelope
}
func HandleRelayEmail(ctx context.Context, cmd cqrs.Command) error {
req, ok := cmd.Request().(*RelayEmailRequest)
if !ok {
return cqrs.ErrUnexpectedRequest
}
ctn, err := container.From(ctx)
if err != nil {
return errors.Wrap(err, "could not retrieve service container")
}
conf, err := config.From(ctn)
if err != nil {
return errors.Wrap(err, "could not retrieve config service")
}
relay := conf.Relay
if err := forwardMail(req.Envelope, relay); err != nil {
return errors.Wrap(err, "could not forward mail")
}
return nil
}
func forwardMail(env *enmime.Envelope, conf config.RelayConfig) error {
var tlsConfig *tls.Config
if conf.InsecureSkipVerify {
tlsConfig = &tls.Config{
// nolint: gosec
InsecureSkipVerify: true,
}
}
addr := conf.Address
var (
client *smtp.Client
err error
)
if conf.UseTLS {
client, err = smtp.DialTLS(addr, tlsConfig)
if err != nil {
return errors.WithStack(err)
}
} else {
client, err = smtp.Dial(addr)
if err != nil {
return errors.WithStack(err)
}
}
defer client.Close()
if err = client.Hello("localhost"); err != nil {
return errors.WithStack(err)
}
if ok, _ := client.Extension("STARTTLS"); ok {
if err = client.StartTLS(tlsConfig); err != nil {
return errors.WithStack(err)
}
}
if conf.Username != "" || conf.Password != "" {
if ok, _ := client.Extension("AUTH"); ok {
var auth sasl.Client
if conf.Anonymous {
auth = sasl.NewAnonymousClient("fakesmtp")
} else {
auth = sasl.NewPlainClient(conf.Identity, conf.Username, conf.Password)
}
if err := client.Auth(auth); err != nil {
return errors.WithStack(err)
}
}
}
var from string
if conf.FromOverride != "" {
from = conf.FromOverride
} else {
from = env.GetHeader("From")
}
if err = client.Mail(from, nil); err != nil {
return errors.WithStack(err)
}
to := env.GetHeaderValues("To")
for _, addr := range to {
if err = client.Rcpt(addr); err != nil {
return errors.WithStack(err)
}
}
w, err := client.Data()
if err != nil {
return errors.WithStack(err)
}
pr, pw := io.Pipe()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
defer pw.Close()
if err = env.Root.Encode(pw); err != nil {
err = errors.WithStack(err)
}
}()
_, err = io.Copy(w, pr)
if err != nil {
return err
}
wg.Wait()
if err != nil {
return errors.WithStack(err)
}
if err = w.Close(); err != nil {
return err
}
if err := client.Quit(); err != nil {
return errors.WithStack(err)
}
return nil
}

View File

@ -6,8 +6,8 @@ import (
"github.com/pkg/errors"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"github.com/jhillyerd/enmime"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/middleware/container"

View File

@ -12,15 +12,17 @@ import (
)
type Config struct {
HTTP HTTPConfig `yaml:"http"`
SMTP SMTPConfig `yaml:"smtp"`
Data DataConfig `ymal:"data"`
HTTP HTTPConfig `yaml:"http"`
SMTP SMTPConfig `yaml:"smtp"`
Data DataConfig `yaml:"data"`
Relay RelayConfig `yaml:"relay"`
}
type HTTPConfig struct {
Address string `yaml:"address" env:"FAKESMTP_HTTP_ADDRESS"`
TemplateDir string `yaml:"templateDir" env:"FAKESMTP_HTTP_TEMPLATEDIR"`
PublicDir string `yaml:"publicDir" env:"FAKESMTP_HTTP_PUBLICDIR"`
BaseURL string `yaml:"baseUrl" env:"FAKESMTP_HTTP_BASEURL"`
}
type SMTPConfig struct {
@ -36,6 +38,18 @@ type SMTPConfig struct {
Debug bool `yaml:"debug" env:"FAKESMTP_SMTP_DEBUG"`
}
type RelayConfig struct {
Enabled bool `yaml:"enabled" env:"FAKESMTP_RELAY_ENABLED"`
Address string `yaml:"address" env:"FAKESMTP_RELAY_ADDRESS"`
Identity string `yaml:"identity" env:"FAKESMTP_RELAY_IDENTITY"`
Username string `yaml:"username" env:"FAKESMTP_RELAY_USERNAME"`
Password string `yaml:"password" env:"FAKESMTP_RELAY_PASSWORD"`
Anonymous bool `yaml:"anonymous" env:"FAKESMTP_RELAY_ANONYMOUS"`
UseTLS bool `yaml:"useTLS" env:"FAKESMTP_RELAY_USE_TLS"`
InsecureSkipVerify bool `yaml:"insecureSkipVerify" env:"FAKESMTP_RELAY_INSECURE_SKIP_VERIFY"`
FromOverride string `yaml:"fromOverride" env:"FAKESMTP_RELAY_FROM_OVERRIDE"`
}
type DataConfig struct {
Path string `yaml:"path" env:"FAKESMTP_DATA_PATH"`
}
@ -91,6 +105,9 @@ func NewDefault() *Config {
Data: DataConfig{
Path: "fakesmtp.db",
},
Relay: RelayConfig{
Enabled: false,
},
}
}

View File

@ -0,0 +1,195 @@
package query
import (
"net/mail"
"testing"
"time"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"github.com/pkg/errors"
)
type emailMatcherTestCase struct {
Name string
Search *InboxSearch
Email *model.Email
Matcher emailMatcherFunc
Expect bool
}
func TestEmailMatcher(t *testing.T) {
t.Parallel()
johnDoeAddr, err := mail.ParseAddress("john.doe@test.local")
if err != nil {
t.Fatal(errors.WithStack(err))
}
adaLovelaceAddr, err := mail.ParseAddress("ada.lovelace@test.local")
if err != nil {
t.Fatal(errors.WithStack(err))
}
now := time.Now()
testCases := []emailMatcherTestCase{
{
Name: "Simple matching header",
Email: &model.Email{
Headers: map[string][]string{
"X-Swift-To": {johnDoeAddr.Address},
},
},
Search: &InboxSearch{
Headers: map[string]string{
"X-Swift-To": johnDoeAddr.Address,
},
},
Matcher: matchHeaders,
Expect: true,
},
{
Name: "Multiple matching header",
Email: &model.Email{
Headers: map[string][]string{
"X-Swift-To": {johnDoeAddr.Address},
"Content-Type": {"multipart/alternative; boundary=\"_=_swift_1645181013_7b80ab8ab386ba4fcaff4f6f79593adb_=_\""},
},
},
Search: &InboxSearch{
Headers: map[string]string{
"X-Swift-To": johnDoeAddr.Address,
"Content-Type": "multipart/alternative",
},
},
Matcher: matchHeaders,
Expect: true,
},
{
Name: "Simple non matching header",
Email: &model.Email{
Headers: map[string][]string{
"X-Swift-To": {johnDoeAddr.Address},
},
},
Search: &InboxSearch{
Headers: map[string]string{
"X-Swift-To": adaLovelaceAddr.Address,
},
},
Matcher: matchHeaders,
Expect: false,
},
{
Name: "Multiple non matching headers",
Email: &model.Email{
Headers: map[string][]string{
"X-Swift-To": {johnDoeAddr.Address},
"Content-Type": {"foo/bar"},
},
},
Search: &InboxSearch{
Headers: map[string]string{
"X-Swift-To": johnDoeAddr.Address,
"Content-Type": "multipart/alternative",
},
},
Matcher: matchHeaders,
Expect: false,
},
{
Name: "Simple to",
Email: &model.Email{
To: []*mail.Address{johnDoeAddr},
},
Search: &InboxSearch{
To: johnDoeAddr.Address,
},
Matcher: matchTo,
Expect: true,
},
{
Name: "Simple from",
Email: &model.Email{
From: []*mail.Address{johnDoeAddr},
},
Search: &InboxSearch{
From: johnDoeAddr.Address,
},
Matcher: matchFrom,
Expect: true,
},
{
Name: "Simple after",
Email: &model.Email{
SentAt: now,
},
Search: &InboxSearch{
After: now.Add(-5 * time.Second),
},
Matcher: matchAfter,
Expect: true,
},
{
Name: "Simple before",
Email: &model.Email{
SentAt: now,
},
Search: &InboxSearch{
Before: now.Add(5 * time.Second),
},
Matcher: matchBefore,
Expect: true,
},
{
Name: "Matching composite",
Email: &model.Email{
SentAt: now,
Headers: map[string][]string{
"X-Swift-To": {johnDoeAddr.Address},
"Content-Type": {"multipart/alternative; boundary=\"_=_swift_1645181013_7b80ab8ab386ba4fcaff4f6f79593adb_=_\""},
},
},
Search: &InboxSearch{
Before: now.Add(5 * time.Second),
Headers: map[string]string{
"X-Swift-To": johnDoeAddr.Address,
"Content-Type": "multipart/alternative",
},
},
Matcher: and(matchBefore, matchHeaders),
Expect: true,
},
{
Name: "Non matching composite",
Email: &model.Email{
SentAt: now,
Headers: map[string][]string{
"X-Swift-To": {johnDoeAddr.Address},
"Content-Type": {"multipart/alternative; boundary=\"_=_swift_1645181013_7b80ab8ab386ba4fcaff4f6f79593adb_=_\""},
},
},
Search: &InboxSearch{
Before: now.Add(5 * time.Second),
Headers: map[string]string{
"X-Swift-To": adaLovelaceAddr.Address,
"Content-Type": "multipart/alternative",
},
},
Matcher: and(matchBefore, matchHeaders),
Expect: false,
},
}
for _, tc := range testCases {
func(tc emailMatcherTestCase) {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
if e, g := tc.Expect, tc.Matcher(tc.Email, tc.Search); e != g {
t.Errorf("'%s': expected '%v', got '%v'", tc.Name, e, g)
}
})
}(tc)
}
}

View File

@ -5,8 +5,8 @@ import (
"strings"
"time"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
stormdb "github.com/asdine/storm/v3"
"github.com/asdine/storm/v3/q"
"github.com/pkg/errors"
@ -19,6 +19,7 @@ type InboxSearch struct {
From string
Body string
Subject string
Headers map[string]string
After time.Time
Before time.Time
}
@ -104,55 +105,135 @@ func HandleGetInbox(ctx context.Context, qry cqrs.Query) (interface{}, error) {
return &InboxData{emails}, nil
}
filtered := make([]*model.Email, 0, len(emails))
for _, eml := range emails {
match := true
if req.Search.To != "" {
found := false
for _, addr := range eml.To {
if strings.Contains(addr.Name, req.Search.To) || strings.Contains(addr.Address, req.Search.To) {
found = true
break
}
}
if !found {
match = false
}
}
if req.Search.From != "" {
found := false
for _, addr := range eml.From {
if strings.Contains(addr.Name, req.Search.From) || strings.Contains(addr.Address, req.Search.From) {
found = true
break
}
}
if !found {
match = false
}
}
if !req.Search.After.IsZero() && !eml.SentAt.After(req.Search.After) {
match = false
}
if !req.Search.Before.IsZero() && !eml.SentAt.Before(req.Search.Before) {
match = false
}
if match {
filtered = append(filtered, eml)
}
}
filtered := filterEmails(emails, req.Search)
return &InboxData{filtered}, nil
}
var matchers = []emailMatcherFunc{
matchTo,
matchFrom,
matchBefore,
matchAfter,
matchHeaders,
}
type emailMatcherFunc func(*model.Email, *InboxSearch) bool
func matchTo(eml *model.Email, search *InboxSearch) bool {
if search.To == "" {
return true
}
found := false
for _, addr := range eml.To {
if strings.Contains(addr.Name, search.To) || strings.Contains(addr.Address, search.To) {
found = true
break
}
}
return found
}
func matchFrom(eml *model.Email, search *InboxSearch) bool {
if search.From == "" {
return true
}
found := false
for _, addr := range eml.From {
if strings.Contains(addr.Name, search.From) || strings.Contains(addr.Address, search.From) {
found = true
break
}
}
return found
}
func matchAfter(eml *model.Email, search *InboxSearch) bool {
if search.After.IsZero() {
return true
}
return eml.SentAt.After(search.After)
}
func matchBefore(eml *model.Email, search *InboxSearch) bool {
if search.Before.IsZero() {
return true
}
return eml.SentAt.Before(search.Before)
}
func matchHeaders(eml *model.Email, search *InboxSearch) bool {
if eml.Headers == nil {
return true
}
matches := true
for searchKey, searchValue := range search.Headers {
for headerKey, headerValues := range eml.Headers {
if searchKey != headerKey {
continue
}
matchesHeader := true
for _, hv := range headerValues {
if !strings.Contains(hv, searchValue) {
matchesHeader = false
break
}
}
if !matchesHeader {
matches = false
break
}
}
if !matches {
break
}
}
return matches
}
func and(matchers ...emailMatcherFunc) emailMatcherFunc {
return func(eml *model.Email, search *InboxSearch) bool {
for _, match := range matchers {
if !match(eml, search) {
return false
}
}
return true
}
}
func filterEmails(emails []*model.Email, search *InboxSearch) []*model.Email {
filtered := make([]*model.Email, 0)
match := and(matchers...)
for _, eml := range emails {
if !match(eml, search) {
continue
}
filtered = append(filtered, eml)
}
return filtered
}

View File

@ -3,8 +3,8 @@ package query
import (
"context"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/middleware/container"

View File

@ -3,8 +3,8 @@ package route
import (
"net/http"
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/api"
"gitlab.com/wpetit/goweb/cqrs"

View File

@ -7,11 +7,11 @@ import (
"github.com/microcosm-cc/bluemonday"
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
"github.com/go-chi/chi"
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/middleware/container"

View File

@ -1,13 +1,16 @@
package route
import (
"encoding/json"
"net/http"
"strconv"
"time"
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service"
"gitlab.com/wpetit/goweb/service/template"
)
@ -15,8 +18,8 @@ func extendTemplateData(w http.ResponseWriter, r *http.Request, data template.Da
ctn := container.Must(r.Context())
data, err := template.Extend(data,
template.WithBuildInfo(w, r, ctn),
withBaseURL(ctn),
)
if err != nil {
panic(errors.Wrap(err, "could not extend template data"))
}
@ -73,6 +76,16 @@ func createInboxQueryFromRequest(r *http.Request) (*query.GetInboxRequest, error
}
}
var headers map[string]string
rawHeaders := r.URL.Query().Get("headers")
if rawHeaders != "" {
headers = make(map[string]string)
if err := json.Unmarshal([]byte(rawHeaders), &headers); err != nil {
return nil, errors.WithStack(err)
}
}
search := &query.InboxSearch{}
if to != "" {
search.To = to
@ -94,6 +107,10 @@ func createInboxQueryFromRequest(r *http.Request) (*query.GetInboxRequest, error
search.Before = before
}
if rawHeaders != "" {
search.Headers = headers
}
inboxRequest := &query.GetInboxRequest{
OrderBy: orderBy,
Reverse: reverse == "y",
@ -104,3 +121,16 @@ func createInboxQueryFromRequest(r *http.Request) (*query.GetInboxRequest, error
return inboxRequest, nil
}
func withBaseURL(ctn *service.Container) template.DataExtFunc {
return func(data template.Data) (template.Data, error) {
conf, err := config.From(ctn)
if err != nil {
return nil, errors.WithStack(err)
}
data["BaseURL"] = conf.HTTP.BaseURL
return data, nil
}
}

View File

@ -3,8 +3,8 @@ package route
import (
"net/http"
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/cqrs"
"gitlab.com/wpetit/goweb/logger"

View File

@ -1,9 +1,9 @@
package route
import (
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"gitlab.com/wpetit/goweb/static"
)

5
misc/api.http Normal file
View File

@ -0,0 +1,5 @@
@baseURL = http://localhost:8080
### Filter emails via headers
GET {{ baseURL }}/api/v1/emails?headers={"Mime-Version":"1.0"}

View File

@ -1,4 +1,4 @@
FROM golang:1.13 AS build
FROM reg.cadoles.com/proxy_cache/library/golang:1.21 AS build
ARG HTTP_PROXY=
ARG HTTPS_PROXY=
@ -7,28 +7,26 @@ ARG https_proxy=
RUN apt-get update && apt-get install -y build-essential git bash curl
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs
COPY . /src
WORKDIR /src
RUN cp -f misc/docker/config-patch.yml misc/release/config-patch.yml
RUN go get github.com/krishicks/yaml-patch/cmd/yaml-patch
RUN npm install \
&& make vendor \
&& echo "---" > ./misc/release/config-patch.yml \
RUN cp -f misc/docker/config-patch.txt misc/release/config-patch.txt \
&& npm ci \
&& make ARCH_TARGETS=amd64 release
FROM busybox
FROM reg.cadoles.com/proxy_cache/library/busybox
RUN adduser -D -h /app fsmtp
COPY --from=build /src/release/fake-smtp-linux-amd64 /app
RUN chown -R fsmtp:fsmtp /app
EXPOSE 8080 2525
USER fsmtp
WORKDIR /app
RUN mkdir -p /app
CMD ["bin/fake-smtp", "--config", "config.yml"]

View File

@ -0,0 +1 @@
.smtp.debug = false

View File

@ -1,4 +0,0 @@
---
- op: replace
path: /smtp/debug
value: false

View File

@ -0,0 +1,3 @@
.data.path = "/var/lib/fake-smtp/data.db"
.smtp.address = "127.0.0.1:2525"
.smtp.debug = false

View File

@ -1,10 +0,0 @@
---
- op: replace
path: /data/path
value: /var/lib/fake-smtp/data.db
- op: replace
path: /smtp/address
value: 127.0.0.1:2525
- op: replace
path: /smtp/debug
value: false

View File

@ -73,13 +73,17 @@ function dump_default_conf {
local command=$1
local os=$2
local arch=$3
local tmp_conf=$(mktemp)
local patched_conf=$(mktemp)
go run "$PROJECT_DIR/cmd/$command" -dump-config > "$tmp_conf"
cat "$tmp_conf" | yaml-patch -o misc/release/config-patch.yml > "$patched_conf"
go run "$PROJECT_DIR/cmd/$command" -dump-config > "$patched_conf"
while IFS= read -r yq_cmd; do
echo "patching configuration with '$yq_cmd'..."
tools/yq/bin/yq -i "$yq_cmd" "$patched_conf"
done < misc/release/config-patch.txt
copy "$command" $os $arch "$patched_conf" "config.yml"
rm -f "$tmp_conf" "$patched_conf"
rm -f "$patched_conf"
}
function compress {

View File

@ -6,7 +6,7 @@ for i in {1..10}; do
-s "Test ${i}" \
-a README.md \
-a package.json \
foo_${i}@bar_${i}.com \
foo_${i}@bar${i}.com \
<<EOF
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget metus ornare, placerat mi nec, ornare magna. Vivamus volutpat nisi et aliquet mollis. Phasellus eu malesuada erat, vel molestie ipsum. Etiam tincidunt ligula eget scelerisque blandit. Vivamus nec quam vitae felis rutrum cursus a eget elit. Integer vestibulum ultrices iaculis. Integer varius sapien ac ante accumsan euismod. Quisque fermentum dui nec porttitor pellentesque. Vivamus lobortis mauris eget metus sagittis tempor.

11465
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,21 +11,21 @@
"author": "William Petit <wpetit@cadoles.com>",
"license": "AGPL-3.0",
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/core": "^7.23.9",
"@babel/plugin-proposal-class-properties": "^7.2.1",
"@babel/preset-env": "^7.2.0",
"babel-loader": "^8.0.4",
"bulma": "^0.8.2",
"bulma-switch": "^2.0.0",
"css-loader": "^3.5.2",
"file-loader": "^6.0.0",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.10.0",
"resolve-url-loader": "^3.0.0",
"sass-loader": "^8.0.2",
"stimulus": "^1.1.0",
"style-loader": "^1.1.4",
"webpack": "^4.25.0",
"webpack-cli": "^3.1.2"
"@babel/preset-env": "^7.23.9",
"babel-loader": "^9.1.3",
"bulma": "^0.9.4",
"bulma-switch": "^2.0.4",
"css-loader": "^6.10.0",
"file-loader": "^6.2.0",
"mini-css-extract-plugin": "^2.8.0",
"node-sass": "^9.0.0",
"resolve-url-loader": "^5.0.0",
"sass-loader": "^14.1.1",
"stimulus": "^3.2.2",
"style-loader": "^3.3.4",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4"
}
}

View File

@ -64,8 +64,8 @@ module.exports = {
},
plugins: [
new MiniCssExtractPlugin({
filename: "/css/[name].css",
chunkFilename: "/css/[id].css"
filename: "./css/[name].css",
chunkFilename: "./css/[id].css"
})
]
}