Skip to content

Commit

Permalink
first release
Browse files Browse the repository at this point in the history
  • Loading branch information
alash3al committed Oct 22, 2018
0 parents commit 693c397
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MaiLux
=======
Mailux is a microservice designed to recieve mails on temporarily inboxes so the user can send emails from his mail to the temp-email we generated before, so you as a developer can login him without password!

Flow
=====
1- You request the `/mailbox/generate/<email@address.here>`.
2- The user send an email to the mailbox generated by the above step.
3- You hit the `/mailbox/info/<mailboxID>` each X of time (from the browser).
4- if the above step returned that it has been received, just send a request to the backend to hit the above endpoint again just as a confirmation, after that you respond to the browser with a `token/session`.

Help
====
```bash
# have fun with this!
$ mailux --help
```

Credits
=======
Mohamed Al Ashaal
85 changes: 85 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"encoding/base64"
"log"
"strconv"
"strings"
"time"

"github.com/google/uuid"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)

func initHTTP() {
e := echo.New()
e.HideBanner = true

e.Pre(middleware.RemoveTrailingSlash())
e.Use(middleware.Recover())
e.Use(middleware.Logger())
e.Use(middleware.CORS())
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{Level: 9}))
e.Use(middleware.BodyLimit("100K"))

e.GET("/", func(c echo.Context) error {
return c.JSON(200, map[string]interface{}{
"success": true,
"message": "let's reverse the login process",
})
})

/**
* Get the information of an inbox (temp-inbox)
*/
e.GET("/inbox/info/:inboxId", func(c echo.Context) error {
id := c.Param("inboxId")
return c.JSON(200, map[string]interface{}{
"success": true,
"data": map[string]interface{}{
"uuid": id,
"inbox": id + "@" + *flagDomain,
"status": strings.ToLower(redisClient.Get(redisKeyPrefix(id+":status")).Val()) == "done",
"email": redisClient.Get(redisKeyPrefix(id + ":email")).Val(),
"expires": redisClient.Get(redisKeyPrefix(id + ":expires")).Val(),
},
})
})

/**
* Generates a temp-inbox for the specified email
*/
e.GET("/inbox/generate/:email", func(c echo.Context) error {
id := uuid.New().String()
if c.QueryParam("suffix") != "" {
id += "-" + c.QueryParam("suffix")
}
id = base64.RawStdEncoding.EncodeToString([]byte(id))
ttl, _ := strconv.Atoi(c.QueryParam("ttl"))
if ttl < 1 {
ttl = *flagDefaultTTL
}
exp := time.Now().Unix() + int64(ttl)
email := strings.ToLower(c.Param("email"))
redisDur := time.Second * 3600

redisClient.Set(redisKeyPrefix(email+":inbox"), id, redisDur).Val()
redisClient.Set(redisKeyPrefix(id+":status"), "pending", redisDur).Val()
redisClient.Set(redisKeyPrefix(id+":email"), email, redisDur).Val()
redisClient.Set(redisKeyPrefix(id+":expires"), exp, redisDur).Val()

return c.JSON(200, map[string]interface{}{
"success": true,
"data": map[string]interface{}{
"uuid": id,
"inbox": id + "@" + *flagDomain,
"status": false,
"email": email,
"expires": exp,
},
})
})

log.Fatal(e.Start(*flagHTTPAddr))
}
8 changes: 8 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

func main() {
go initHTTP()
go initSMTP()

select {}
}
52 changes: 52 additions & 0 deletions smtp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"errors"
"log"
"net"
"strings"
"time"

"github.com/alash3al/go-mailbox"
"github.com/zaccone/spf"
)

func initSMTP() {
smtp.HandleFunc("*@"+*flagDomain, func(req *smtp.Envelope) error {
ipAddr, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return err
}

fromEmail, toEmail := req.MessageFrom, req.MessageTo
fromEmail = strings.ToLower(fromEmail)
_, fromDomain, err := smtp.SplitAddress(fromEmail)
if err != nil {
return err
}

res, _, err := spf.CheckHost(net.ParseIP(ipAddr), fromDomain, fromEmail)
if (err != nil || res != spf.Pass) && *flagSPFChecker {
return errors.New("ERR_CHEATING_DETECTED")
}

inbox := strings.Split(toEmail, "@")[0]
expires, _ := redisClient.Get(redisKeyPrefix(inbox + ":expires")).Int64()
if time.Now().Unix() >= expires {
return errors.New("ERR_EXPIRED")
}

if strings.ToLower(fromEmail) != redisClient.Get(redisKeyPrefix(inbox+":email")).Val() {
return errors.New("ERR_MISS_MATCH")
}

if strings.ToLower(redisClient.Get(redisKeyPrefix(inbox+":status")).Val()) == "pending" {
ttl := redisClient.TTL(redisKeyPrefix(inbox + ":status")).Val()
redisClient.Set(redisKeyPrefix(inbox+":status"), "done", ttl).Val()
}

return nil
})

log.Fatal(smtp.ListenAndServe(*flagSMTPAddr, nil))
}
39 changes: 39 additions & 0 deletions vars.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"flag"
"log"

"github.com/go-redis/redis"
)

var (
flagSMTPAddr = flag.String("smtp", ":25025", "the smtp (inbox) address to listen on")
flagHTTPAddr = flag.String("http", ":5050", "the http server address to listen on")
flagSPFChecker = flag.Bool("spf", false, "whether to enable SPF checking or not")
flagRedis = flag.String("redis", "redis://localhost:6379/1", "redis url")
flagDefaultTTL = flag.Int("ttl", 30, "the time-to-live of each auth-inbox in seconds")
flagDomain = flag.String("domain", "local.host", "the default domain name of the server")
)

var (
redisClient *redis.Client

redisKeyPrefix = func(k string) string {
return "mailux:" + k
}
)

func init() {
flag.Parse()

opt, err := redis.ParseURL(*flagRedis)
if err != nil {
log.Fatal("[Redis]", " ", err.Error())
}

redisClient = redis.NewClient(opt)
if _, err := redisClient.Ping().Result(); err != nil {
log.Fatal("[Redis]", " ", err.Error())
}
}

0 comments on commit 693c397

Please sign in to comment.