Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*~
*.exe
35 changes: 0 additions & 35 deletions auth.go

This file was deleted.

2 changes: 1 addition & 1 deletion db/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var tablesAndIndexes = map[string][]string{
"keys": []string{},
}

func Init() {
func init() {
config.Url = "localhost:28015"
config.AuthKey = ""
config.Db = "dev"
Expand Down
8 changes: 8 additions & 0 deletions env/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package env

type Config struct {
BindAddress string
APIVersion string
LogFormatterType string
SessionDuration int
}
12 changes: 12 additions & 0 deletions env/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package env

import (
"github.com/Sirupsen/logrus"
)

type Environment struct {
Log *logrus.Logger
Config *Config
}

var G *Environment
215 changes: 142 additions & 73 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,96 +1,165 @@
package main

import (
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"time"

"github.com/gorilla/mux"
"github.com/lavab/api/db"
"github.com/lavab/api/utils"
"github.com/stretchr/graceful"

"github.com/Sirupsen/logrus"
"github.com/goji/glogrus"
"github.com/namsral/flag"
"github.com/zenazn/goji/graceful"
"github.com/zenazn/goji/web"
"github.com/zenazn/goji/web/middleware"

"github.com/lavab/api/env"
"github.com/lavab/api/routes"
)

// TODO: "Middleware that implements a few quick security wins"
// https://github.com/unrolled/secure

const (
cTlsFilePub = ".tls/pub"
cTlsFilePriv = ".tls/priv"
cTcpPort = 5000
cApiVersion = "v0"
var (
bindAddress = flag.String("bind", ":5000", "Network address used to bind")
apiVersion = flag.String("version", "v0", "Shown API version")
logFormatterType = flag.String("log", "text", "Log formatter type. Either \"json\" or \"text\"")
sessionDuration = flag.Int("session_duration", 72, "Session duration expressed in hours")
)

var config struct {
Port int
PortString string
Host string
TlsAvailable bool
RootJSON string
}

func init() {
config.Port = cTcpPort
config.Host = ""
config.TlsAvailable = false
config.RootJSON = rootResponseString() // this avoids an import cycle and also improves perf by caching the response

if tmp := os.Getenv("API_PORT"); tmp != "" {
tmp2, err := strconv.Atoi(tmp)
if err != nil {
config.Port = tmp2
}
log.Println("Running on non-default port", config.Port)
}
config.PortString = fmt.Sprintf(":%d", config.Port)

if utils.FileExists(cTlsFilePub) && utils.FileExists(cTlsFilePriv) {
config.TlsAvailable = true
log.Println("Imported TLS cert/key successfully.")
} else {
log.Printf("TLS cert (%s) and key (%s) not found, serving plain HTTP.\n", cTlsFilePub, cTlsFilePriv)
}

// Set up RethinkDB
go db.Init()
}

func main() {
setupAndRun()
}
// Parse the flags
flag.Parse()

func setupAndRun() {
r := mux.NewRouter()
// Set up a new logger
log := logrus.New()

if config.TlsAvailable {
r = r.Schemes("https").Subrouter()
}
if tmp := os.Getenv("API_HOST"); tmp != "" {
r = r.Host(tmp).Subrouter()
// Set the formatter depending on the passed flag's value
if *logFormatterType == "text" {
log.Formatter = &logrus.TextFormatter{}
} else if *logFormatterType == "json" {
log.Formatter = &logrus.JSONFormatter{}
}

for _, rt := range publicRoutes {
r.HandleFunc(rt.Path, rt.HandleFunc).Methods(rt.Method)
// Create a new goji mux
mux := web.New()

// Include the most basic middlewares:
// - RequestID assigns an unique ID for each request in order to identify errors.
// - Glogrus logs each request
// - Recoverer prevents panics from crashing the API
// - AutomaticOptions automatically responds to OPTIONS requests
mux.Use(middleware.RequestID)
mux.Use(glogrus.NewGlogrus(log, "api"))
mux.Use(middleware.Recoverer)
mux.Use(middleware.AutomaticOptions)

// Set up an auth'd mux
auth := web.New()
mux.Use(routes.AuthMiddleware)

// Index route
mux.Get("/", routes.Hello)

// Accounts
auth.Get("/accounts", routes.AccountsList)
mux.Post("/accounts", routes.AccountsCreate)
auth.Get("/accounts/:id", routes.AccountsGet)
auth.Put("/accounts/:id", routes.AccountsUpdate)
auth.Delete("/accounts/:id", routes.AccountsDelete)
auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeData)
auth.Get("/accounts/:id/sessions", routes.AccountsSessionsList)

// Tokens
auth.Get("/tokens", routes.TokensGet)
auth.Post("/tokens", routes.TokensCreate)
auth.Delete("/tokens", routes.TokensDelete)

// Threads
auth.Get("/threads", routes.ThreadsList)
auth.Get("/threads/:id", routes.ThreadsGet)
auth.Put("/threads/:id", routes.ThreadsUpdate)

// Emails
auth.Get("/emails", routes.EmailsList)
auth.Post("/emails", routes.EmailsCreate)
auth.Get("/emails/:id", routes.EmailsGet)
auth.Put("/emails/:id", routes.EmailsUpdate)
auth.Delete("/emails/:id", routes.EmailsDelete)

// Labels
auth.Get("/labels", routes.LabelsList)
auth.Post("/labels", routes.LabelsCreate)
auth.Get("/labels/:id", routes.LabelsGet)
auth.Put("/labels/:id", routes.LabelsUpdate)
auth.Delete("/labels/:id", routes.LabelsDelete)

// Contacts
auth.Get("/contacts", routes.ContactsList)
auth.Post("/contacts", routes.ContactsCreate)
auth.Get("/contacts/:id", routes.ContactsGet)
auth.Put("/contacts/:id", routes.ContactsUpdate)
auth.Delete("/contacts/:id", routes.ContactsDelete)

// Keys
auth.Post("/keys", routes.KeysCreate)
mux.Get("/keys/:id", routes.KeysGet)
auth.Post("/keys/:id/vote", routes.KeysVote)

// Merge the muxes
mux.Handle("/", auth)

// Compile the routes
mux.Compile()

// Make the mux handle every request
http.Handle("/", mux)

// Set up a new environment object
env.G = &env.Environment{
Log: log,
Config: &env.Config{
BindAddress: *bindAddress,
APIVersion: *apiVersion,
LogFormatterType: *logFormatterType,
SessionDuration: *sessionDuration,
},
}

for _, rt := range authRoutes {
r.HandleFunc(rt.Path, AuthWrapper(rt.HandleFunc)).Methods(rt.Method)
// Log that we're starting the server
log.WithFields(logrus.Fields{
"address": *bindAddress,
}).Info("Starting the HTTP server")

// Initialize the goroutine listening to signals passed to the app
graceful.HandleSignals()

// Pre-graceful shutdown event
graceful.PreHook(func() {
log.Info("Received a singnal, stopping the application")
})

// Post-shutdown event
graceful.PostHook(func() {
log.Info("Stopped the application")
})

// Listen to the passed address
listener, err := net.Listen("tcp", *bindAddress)
if err != nil {
log.WithFields(logrus.Fields{
"error": err,
"address": *bindAddress,
}).Fatal("Cannot set up a TCP listener")
}

srv := &graceful.Server{
Timeout: 10 * time.Second,
Server: &http.Server{
Addr: config.PortString,
Handler: r,
},
// Start the listening
err = graceful.Serve(listener, http.DefaultServeMux)
if err != nil {
// Don't use .Fatal! We need the code to shut down properly.
log.Error(err)
}

if config.TlsAvailable {
log.Fatal(srv.ListenAndServeTLS(cTlsFilePub, cTlsFilePriv))
} else {
log.Fatal(srv.ListenAndServe())
}
// If code reaches this place, it means that it was forcefully closed.

// Wait until open connections close.
graceful.Wait()
}
8 changes: 4 additions & 4 deletions models/base/expiring.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (

// Expiring is a base struct for resources that expires e.g. sessions.
type Expiring struct {
// ExpDate is the RFC3339-encoded time when the resource will expire.
ExpDate string `json:"exp_date" gorethink:"exp_date"`
// ExpirationDate is the RFC3339-encoded time when the resource will expire.
ExpirationDate string `json:"exp_date" gorethink:"exp_date"`
}

// HasExpired returns true if the resource has expired (or if the ExpDate string is badly formatted)
// HasExpired returns true if the resource has expired (or if the ExpirationDate string is badly formatted)
func (e *Expiring) HasExpired() bool {
t, err := time.Parse(time.RFC3339, e.ExpDate)
t, err := time.Parse(time.RFC3339, e.ExpirationDate)
if err != nil {
log.Println("Bad format! The expiry date is not RFC3339-formatted.", err)
return true
Expand Down
Loading