remove user creation and login endpoints

This commit is contained in:
Denis Arh
2018-09-24 13:20:18 +02:00
parent 42fbfcb898
commit c7a1f94972
19 changed files with 56 additions and 820 deletions
+1 -27
View File
@@ -5,32 +5,6 @@
"path": "/auth",
"entrypoint": "auth",
"authentication": [],
"apis": [
{
"name": "login",
"method": "POST",
"title": "User login",
"parameters": {
"post": [
{ "type": "string", "name": "username", "required": true, "title": "Username or email" },
{ "type": "string", "name": "password", "required": true, "title": "Password for user" }
]
}
},
{
"name": "create",
"path": "/create",
"method": "POST",
"title": "Create new user",
"parameters": {
"post": [
{ "type": "string", "name": "name", "required": true, "title": "Display name" },
{ "type": "string", "name": "email", "required": true, "title": "Email" },
{ "type": "string", "name": "username", "required": true, "title": "Username" },
{ "type": "string", "name": "password", "required": true, "title": "Password" }
]
}
}
]
"apis": []
}
]
-53
View File
@@ -1,53 +0,0 @@
package rest
import (
"context"
"github.com/pkg/errors"
"github.com/crusttech/crust/auth/rest/request"
"github.com/crusttech/crust/auth/service"
"github.com/crusttech/crust/auth/types"
"github.com/crusttech/crust/internal/auth"
)
var _ = errors.Wrap
type (
Auth struct {
user service.UserService
token auth.TokenEncoder
}
)
func (Auth) New(credValidator service.UserService, tknEncoder auth.TokenEncoder) *Auth {
return &Auth{
credValidator,
tknEncoder,
}
}
func (ctrl *Auth) Login(ctx context.Context, r *request.AuthLogin) (interface{}, error) {
return ctrl.tokenize(ctrl.user.With(ctx).ValidateCredentials(r.Username, r.Password))
}
func (ctrl *Auth) Create(ctx context.Context, r *request.AuthCreate) (interface{}, error) {
user := &types.User{Username: r.Username}
user.GeneratePassword(r.Password)
return ctrl.tokenize(ctrl.user.With(ctx).Create(user))
}
// Wraps user return value and appends JWT
func (ctrl *Auth) tokenize(user *types.User, err error) (interface{}, error) {
if err != nil {
return nil, err
}
return struct {
JWT string
User *types.User `json:"user"`
}{
JWT: ctrl.token.Encode(user),
User: user,
}, nil
}
-67
View File
@@ -1,67 +0,0 @@
package handlers
/*
Hello! This file is auto-generated from `docs/src/spec.json`.
For development:
In order to update the generated files, edit this file under the location,
add your struct fields, imports, API definitions and whatever you want, and:
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `auth.go`, `auth.util.go` or `auth_test.go` to
implement your API calls, helper functions and tests. The file `auth.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"context"
"github.com/go-chi/chi"
"net/http"
"github.com/titpetric/factory/resputil"
"github.com/crusttech/crust/auth/rest/request"
)
// Internal API interface
type AuthAPI interface {
Login(context.Context, *request.AuthLogin) (interface{}, error)
Create(context.Context, *request.AuthCreate) (interface{}, error)
}
// HTTP API interface
type Auth struct {
Login func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
}
func NewAuth(ah AuthAPI) *Auth {
return &Auth{
Login: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthLogin()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Login(r.Context(), params)
})
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthCreate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Create(r.Context(), params)
})
},
}
}
func (ah *Auth) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Route("/auth", func(r chi.Router) {
r.Post("/login", ah.Login)
r.Post("/create", ah.Create)
})
})
}
+2 -2
View File
@@ -36,7 +36,7 @@ type (
jwtEncodeCookieSetter interface {
auth.TokenEncoder
SetToCookie(w http.ResponseWriter, r *http.Request, identity auth.Identifiable)
SetCookie(w http.ResponseWriter, r *http.Request, identity auth.Identifiable)
}
)
@@ -162,7 +162,7 @@ func (c *openIdConnect) HandleOAuth2Callback(w http.ResponseWriter, r *http.Requ
resputil.JSON(w, err)
return
} else {
c.jwt.SetToCookie(w, r, user)
c.jwt.SetCookie(w, r, user)
}
http.Redirect(w, r, c.appURL, http.StatusSeeOther)
-141
View File
@@ -1,141 +0,0 @@
package request
/*
Hello! This file is auto-generated from `docs/src/spec.json`.
For development:
In order to update the generated files, edit this file under the location,
add your struct fields, imports, API definitions and whatever you want, and:
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `auth.go`, `auth.util.go` or `auth_test.go` to
implement your API calls, helper functions and tests. The file `auth.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"encoding/json"
"github.com/go-chi/chi"
"github.com/jmoiron/sqlx/types"
"github.com/pkg/errors"
"io"
"mime/multipart"
"net/http"
"strings"
)
var _ = chi.URLParam
var _ = types.JSONText{}
var _ = multipart.FileHeader{}
// Auth login request parameters
type AuthLogin struct {
Username string
Password string
}
func NewAuthLogin() *AuthLogin {
return &AuthLogin{}
}
func (a *AuthLogin) Fill(r *http.Request) error {
var err error
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
r.ParseForm()
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
if val, ok := post["username"]; ok {
a.Username = val
}
if val, ok := post["password"]; ok {
a.Password = val
}
return err
}
var _ RequestFiller = NewAuthLogin()
// Auth create request parameters
type AuthCreate struct {
Name string
Email string
Username string
Password string
}
func NewAuthCreate() *AuthCreate {
return &AuthCreate{}
}
func (a *AuthCreate) Fill(r *http.Request) error {
var err error
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
r.ParseForm()
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
if val, ok := post["name"]; ok {
a.Name = val
}
if val, ok := post["email"]; ok {
a.Email = val
}
if val, ok := post["username"]; ok {
a.Username = val
}
if val, ok := post["password"]; ok {
a.Password = val
}
return err
}
var _ RequestFiller = NewAuthCreate()
-10
View File
@@ -1,10 +0,0 @@
package request
import (
"net/http"
)
// RequestFiller is an interface for typed request parameters
type RequestFiller interface {
Fill(r *http.Request) error
}
+32 -9
View File
@@ -6,17 +6,24 @@ import (
"net/http"
"github.com/crusttech/crust/auth/repository"
"github.com/crusttech/crust/auth/types"
"github.com/crusttech/crust/internal/auth"
"github.com/go-chi/chi"
"github.com/titpetric/factory/resputil"
"github.com/crusttech/crust/auth/rest/handlers"
"github.com/crusttech/crust/auth/service"
"github.com/crusttech/crust/internal/config"
)
type (
checkResponse struct {
JWT string `json:"jwt"`
User *types.User `json:"user"`
}
)
func MountRoutes(oidcConfig *config.OIDC, jwtAuth jwtEncodeCookieSetter) func(chi.Router) {
var userSvc = service.User()
var ctx = context.Background()
oidc, err := OpenIdConnect(ctx, oidcConfig, userSvc, jwtAuth, repository.NewSettings(ctx))
@@ -26,8 +33,6 @@ func MountRoutes(oidcConfig *config.OIDC, jwtAuth jwtEncodeCookieSetter) func(ch
// Initialize handers & controllers.
return func(r chi.Router) {
handlers.NewAuth(Auth{}.New(userSvc, jwtAuth)).MountRoutes(r)
if oidc != nil {
r.Route("/oidc", func(r chi.Router) {
r.Get("/", oidc.HandleRedirect)
@@ -35,12 +40,30 @@ func MountRoutes(oidcConfig *config.OIDC, jwtAuth jwtEncodeCookieSetter) func(ch
})
}
r.Get("/jwt", func(w http.ResponseWriter, r *http.Request) {
if c, err := r.Cookie("jwt"); err != nil {
resputil.JSON(w, "")
} else {
resputil.JSON(w, c.Value)
r.Get("/check", func(w http.ResponseWriter, r *http.Request) {
if c, err := r.Cookie("jwt"); err == nil {
ctx := r.Context()
if identity := auth.GetIdentityFromContext(ctx); identity != nil && identity.Valid() {
if user, err := service.DefaultUser.With(ctx).FindByID(identity.Identity()); err == nil {
resputil.JSON(w, checkResponse{
JWT: c.Value,
User: user,
})
return
}
}
// Did not send response, assuming invalid cookie
jwtAuth.SetCookie(w, r, nil)
}
resputil.JSON(w, "")
})
r.Delete("/check", func(w http.ResponseWriter, r *http.Request) {
jwtAuth.SetCookie(w, r, nil)
})
}
}
+3
View File
@@ -8,6 +8,7 @@ import (
"github.com/SentimensRG/ctx/sigctx"
"github.com/crusttech/crust/auth/rest"
"github.com/crusttech/crust/auth/service"
"github.com/go-chi/chi"
"github.com/go-chi/cors"
"github.com/pkg/errors"
@@ -46,6 +47,8 @@ func Init() error {
},
})
service.Init()
return nil
}
+2 -2
View File
@@ -3,7 +3,7 @@ set -e
function gofmt {
echo "=== fmt all folders ==="
GOPATHS=$(find -name '*.go' | grep -v vendor/ | xargs -n1 dirname | sort | uniq)
GOPATHS=$(find . -name '*.go' | grep -v vendor/ | xargs -n1 dirname | sort | uniq)
for FOLDER in $GOPATHS; do
#echo "== go fmt $FOLDER =="
cd $FOLDER
@@ -26,4 +26,4 @@ for SPEC in $SPECS; do
codegen/codegen.php $(basename $SRC) | tee -a /dev/stderr
done
gofmt
gofmt
+13 -9
View File
@@ -69,16 +69,20 @@ func (t *jwt) Authenticator() func(http.Handler) http.Handler {
}
// Extracts and authenticates JWT from context, validates claims
func (t *jwt) SetToCookie(w http.ResponseWriter, r *http.Request, identity Identifiable) {
// Store state to cookie as well
http.SetCookie(w, &http.Cookie{
Name: "jwt",
Value: t.Encode(identity),
func (t *jwt) SetCookie(w http.ResponseWriter, r *http.Request, identity Identifiable) {
cookie := &http.Cookie{
Name: "jwt",
Expires: time.Now().Add(time.Duration(t.expiry) * time.Minute),
Secure: r.URL.Scheme == "https",
Domain: t.cookieDomain,
Path: "/",
}
Secure: r.URL.Scheme == "https",
Path: "/",
})
if identity == nil {
cookie.Expires = time.Unix(0, 0)
} else {
cookie.Value = t.Encode(identity)
}
http.SetCookie(w, cookie)
}
+1 -38
View File
@@ -614,41 +614,4 @@ The following event types may be sent with a message event:
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| userID | uint64 | PATH | User ID | N/A | YES |
| message | string | POST | Message contents (markdown) | N/A | YES |
# Authentication
## User login
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/auth/login` | HTTP/S | POST | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| username | string | POST | Username or email | N/A | YES |
| password | string | POST | Password for user | N/A | YES |
## Create new user
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/auth/create` | HTTP/S | POST | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| name | string | POST | Display name | N/A | YES |
| email | string | POST | Email | N/A | YES |
| username | string | POST | Username | N/A | YES |
| password | string | POST | Password | N/A | YES |
| message | string | POST | Message contents (markdown) | N/A | YES |
-34
View File
@@ -533,39 +533,5 @@
}
}
]
},
{
"title": "Authentication",
"package": "sam",
"path": "/auth",
"entrypoint": "auth",
"authentication": [],
"apis": [
{
"name": "login",
"method": "POST",
"title": "User login",
"parameters": {
"post": [
{ "type": "string", "name": "username", "required": true, "title": "Username or email" },
{ "type": "string", "name": "password", "required": true, "title": "Password for user" }
]
}
},
{
"name": "create",
"path": "/create",
"method": "POST",
"title": "Create new user",
"parameters": {
"post": [
{ "type": "string", "name": "name", "required": true, "title": "Display name" },
{ "type": "string", "name": "email", "required": true, "title": "Email" },
{ "type": "string", "name": "username", "required": true, "title": "Username" },
{ "type": "string", "name": "password", "required": true, "title": "Password" }
]
}
}
]
}
]
-68
View File
@@ -1,68 +0,0 @@
{
"Title": "Authentication",
"Package": "sam",
"Interface": "Auth",
"Struct": null,
"Parameters": null,
"Protocol": "",
"Authentication": [],
"Path": "/auth",
"APIs": [
{
"Name": "login",
"Method": "POST",
"Title": "User login",
"Path": "/login",
"Parameters": {
"post": [
{
"name": "username",
"required": true,
"title": "Username or email",
"type": "string"
},
{
"name": "password",
"required": true,
"title": "Password for user",
"type": "string"
}
]
}
},
{
"Name": "create",
"Method": "POST",
"Title": "Create new user",
"Path": "/create",
"Parameters": {
"post": [
{
"name": "name",
"required": true,
"title": "Display name",
"type": "string"
},
{
"name": "email",
"required": true,
"title": "Email",
"type": "string"
},
{
"name": "username",
"required": true,
"title": "Username",
"type": "string"
},
{
"name": "password",
"required": true,
"title": "Password",
"type": "string"
}
]
}
}
]
}
+2 -3
View File
@@ -18,8 +18,8 @@ type (
)
var (
_db *factory.DB
_ctx context.Context
_db *factory.DB
_ctx context.Context
)
// DB returns a repository-wide singleton DB handle
@@ -56,7 +56,6 @@ func (r *repository) Context() context.Context {
return r.ctx
}
// db returns context-aware db handle
func (r *repository) db() *factory.DB {
return r.dbh(r.ctx)
-66
View File
@@ -1,66 +0,0 @@
package rest
import (
"context"
"github.com/crusttech/crust/auth/service"
"github.com/crusttech/crust/auth/types"
"github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/sam/rest/request"
"github.com/pkg/errors"
)
var _ = errors.Wrap
type (
Auth struct {
svc struct {
user service.UserService
token auth.TokenEncoder
}
}
authPayload struct {
JWT string
User *types.User `json:"user"`
}
authUserBasics interface {
ValidateCredentials(ctx context.Context, username, password string) (*types.User, error)
Create(ctx context.Context, input *types.User) (user *types.User, err error)
}
)
func (Auth) New(tknEncoder auth.TokenEncoder) *Auth {
ctrl := &Auth{}
ctrl.svc.user = service.DefaultUser
ctrl.svc.token = tknEncoder
return ctrl
}
func (ctrl *Auth) Login(ctx context.Context, r *request.AuthLogin) (interface{}, error) {
return ctrl.tokenize(ctrl.svc.user.ValidateCredentials(r.Username, r.Password))
}
func (ctrl *Auth) Create(ctx context.Context, r *request.AuthCreate) (interface{}, error) {
user := &types.User{Username: r.Username}
user.GeneratePassword(r.Password)
return ctrl.tokenize(ctrl.svc.user.With(ctx).Create(user))
}
// Wraps user return value and appends JWT
func (ctrl *Auth) tokenize(user *types.User, err error) (interface{}, error) {
if err != nil {
return nil, err
}
return &authPayload{
JWT: ctrl.svc.token.Encode(user),
User: user,
}, nil
}
func (ap authPayload) Token() string {
return ap.JWT
}
-67
View File
@@ -1,67 +0,0 @@
package handlers
/*
Hello! This file is auto-generated from `docs/src/spec.json`.
For development:
In order to update the generated files, edit this file under the location,
add your struct fields, imports, API definitions and whatever you want, and:
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `auth.go`, `auth.util.go` or `auth_test.go` to
implement your API calls, helper functions and tests. The file `auth.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"context"
"github.com/go-chi/chi"
"net/http"
"github.com/titpetric/factory/resputil"
"github.com/crusttech/crust/sam/rest/request"
)
// Internal API interface
type AuthAPI interface {
Login(context.Context, *request.AuthLogin) (interface{}, error)
Create(context.Context, *request.AuthCreate) (interface{}, error)
}
// HTTP API interface
type Auth struct {
Login func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
}
func NewAuth(ah AuthAPI) *Auth {
return &Auth{
Login: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthLogin()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Login(r.Context(), params)
})
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthCreate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Create(r.Context(), params)
})
},
}
}
func (ah *Auth) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Route("/auth", func(r chi.Router) {
r.Post("/login", ah.Login)
r.Post("/create", ah.Create)
})
})
}
-76
View File
@@ -1,76 +0,0 @@
package handlers
/*
Hello! This file is auto-generated from `docs/src/spec.json`.
For development:
In order to update the generated files, edit this file under the location,
add your struct fields, imports, API definitions and whatever you want, and:
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `auth.go`, `auth.util.go` or `auth_test.go` to
implement your API calls, helper functions and tests. The file `auth.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"net/http"
"github.com/titpetric/factory/resputil"
"github.com/crusttech/crust/sam/rest/request"
"net/url"
"time"
)
type (
authPayload interface {
Token() string
}
)
// Initializies custom auth handler that attaches cookie info
//
// Cookie with JWT is added on successful login or user creation
//
func NewAuthCustom(ah AuthAPI, cookieExp int) *Auth {
setCookie := func(w http.ResponseWriter, reqUrl *url.URL) func(payload interface{}, err error) (interface{}, error) {
return func(payload interface{}, err error) (interface{}, error) {
if ap, ok := payload.(authPayload); ok && err == nil {
http.SetCookie(w, &http.Cookie{
Name: "jwt",
Value: ap.Token(),
HttpOnly: false, // we need this for attachments & ws!
Secure: reqUrl.Scheme == "https",
Path: "/",
//Domain: "localhost",
// @todo read from the config file.
Expires: time.Now().Add(time.Duration(cookieExp) * time.Minute),
})
}
return payload, err
}
}
return &Auth{
Login: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthLogin()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return setCookie(w, r.URL)(ah.Login(r.Context(), params))
})
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthCreate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return setCookie(w, r.URL)(ah.Create(r.Context(), params))
})
},
}
}
-141
View File
@@ -1,141 +0,0 @@
package request
/*
Hello! This file is auto-generated from `docs/src/spec.json`.
For development:
In order to update the generated files, edit this file under the location,
add your struct fields, imports, API definitions and whatever you want, and:
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `auth.go`, `auth.util.go` or `auth_test.go` to
implement your API calls, helper functions and tests. The file `auth.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"encoding/json"
"github.com/go-chi/chi"
"github.com/jmoiron/sqlx/types"
"github.com/pkg/errors"
"io"
"mime/multipart"
"net/http"
"strings"
)
var _ = chi.URLParam
var _ = types.JSONText{}
var _ = multipart.FileHeader{}
// Auth login request parameters
type AuthLogin struct {
Username string
Password string
}
func NewAuthLogin() *AuthLogin {
return &AuthLogin{}
}
func (a *AuthLogin) Fill(r *http.Request) error {
var err error
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
r.ParseForm()
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
if val, ok := post["username"]; ok {
a.Username = val
}
if val, ok := post["password"]; ok {
a.Password = val
}
return err
}
var _ RequestFiller = NewAuthLogin()
// Auth create request parameters
type AuthCreate struct {
Name string
Email string
Username string
Password string
}
func NewAuthCreate() *AuthCreate {
return &AuthCreate{}
}
func (a *AuthCreate) Fill(r *http.Request) error {
var err error
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
r.ParseForm()
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
if val, ok := post["name"]; ok {
a.Name = val
}
if val, ok := post["email"]; ok {
a.Email = val
}
if val, ok := post["username"]; ok {
a.Username = val
}
if val, ok := post["password"]; ok {
a.Password = val
}
return err
}
var _ RequestFiller = NewAuthCreate()
-7
View File
@@ -9,13 +9,6 @@ import (
func MountRoutes(jwtAuth auth.TokenEncoder) func(chi.Router) {
// Initialize handers & controllers.
return func(r chi.Router) {
// Cookie expiration in minutes
// @todo pull this from auth/jwt config
var cookieExp = 3600
handlers.NewAuthCustom(Auth{}.New(jwtAuth), cookieExp).MountRoutes(r)
// @todo solve cookie issues (
handlers.NewAttachmentDownloadable(Attachment{}.New()).MountRoutes(r)
// Protect all _private_ routes