Skip to content

Commit

Permalink
Merge pull request #23 from keratin/redis-optional
Browse files Browse the repository at this point in the history
Redis is optional
  • Loading branch information
cainlevy authored Nov 17, 2017
2 parents bff840b + 5c7e286 commit 27917e8
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 59 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ server: vendor generate
docker-compose up -d redis
DATABASE_URL=sqlite3://localhost/dev \
REDIS_URL=redis://127.0.0.1:8701/11 \
go run *.go
go run main.go routing.go

# Run tests
.PHONY: test
Expand Down
47 changes: 28 additions & 19 deletions api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

raven "github.com/getsentry/raven-go"
"github.com/go-redis/redis"
"github.com/keratin/authn-server/config"
"github.com/keratin/authn-server/data"
"github.com/keratin/authn-server/ops"
Expand Down Expand Up @@ -49,28 +50,33 @@ func NewApp() (*App, error) {
return nil, errors.Wrap(err, "data.NewDB")
}

redis, err := dataRedis.New(cfg.RedisURL)
if err != nil {
return nil, errors.Wrap(err, "redis.New")
var redis *redis.Client
if cfg.RedisURL != nil {
redis, err = dataRedis.New(cfg.RedisURL)
if err != nil {
return nil, errors.Wrap(err, "redis.New")
}
}

accountStore := data.NewAccountStore(db)
if accountStore == nil {
accountStore, err := data.NewAccountStore(db)
if err != nil {
return nil, errors.Wrap(err, "NewAccountStore")
}

tokenStore := data.NewRefreshTokenStore(db, redis, reporter, cfg.RefreshTokenTTL)
if tokenStore == nil {
tokenStore, err := data.NewRefreshTokenStore(db, redis, reporter, cfg.RefreshTokenTTL)
if err != nil {
return nil, errors.Wrap(err, "NewRefreshTokenStore")
}

blobStore, err := data.NewBlobStore(cfg.AccessTokenTTL, redis, db, reporter)
if err != nil {
return nil, errors.Wrap(err, "NewBlobStore")
}

keyStore := data.NewRotatingKeyStore()
if cfg.IdentitySigningKey == nil {
m := data.NewKeyStoreRotater(
data.NewEncryptedBlobStore(
data.NewBlobStore(cfg.AccessTokenTTL, redis, db, reporter),
cfg.DBEncryptionKey,
),
data.NewEncryptedBlobStore(blobStore, cfg.DBEncryptionKey),
cfg.AccessTokenTTL,
)
err := m.Maintain(keyStore, reporter)
Expand All @@ -81,17 +87,20 @@ func NewApp() (*App, error) {
keyStore.Rotate(cfg.IdentitySigningKey)
}

actives := dataRedis.NewActives(
redis,
cfg.StatisticsTimeZone,
cfg.DailyActivesRetention,
cfg.WeeklyActivesRetention,
5*12,
)
var actives data.Actives
if redis != nil {
actives = dataRedis.NewActives(
redis,
cfg.StatisticsTimeZone,
cfg.DailyActivesRetention,
cfg.WeeklyActivesRetention,
5*12,
)
}

return &App{
DbCheck: func() bool { return db.Ping() == nil },
RedisCheck: func() bool { return redis.Ping().Err() == nil },
RedisCheck: func() bool { return redis != nil && redis.Ping().Err() == nil },
Config: cfg,
AccountStore: accountStore,
RefreshTokenStore: tokenStore,
Expand Down
13 changes: 13 additions & 0 deletions api/meta/get_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ func TestGetStats(t *testing.T) {
assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
assert.NotEmpty(t, body)
}

func TestGetStatsWithoutRedis(t *testing.T) {
app := test.App()
app.Actives = nil
server := test.Server(app, meta.Routes(app))
defer server.Close()

client := route.NewClient(server.URL).Authenticated(app.Config.AuthUsername, app.Config.AuthPassword)

res, err := client.Get("/stats")
require.NoError(t, err)
assert.Equal(t, http.StatusNotFound, res.StatusCode)
}
19 changes: 14 additions & 5 deletions api/meta/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ import (
func Routes(app *api.App) []*route.HandledRoute {
authentication := route.BasicAuthSecurity(app.Config.AuthUsername, app.Config.AuthPassword, "Private AuthN Realm")

return []*route.HandledRoute{
routes := []*route.HandledRoute{}

if app.Actives != nil {
routes = append(routes,
route.Get("/stats").
SecuredWith(authentication).
Handle(getStats(app)),
)
}

routes = append(routes,
route.Get("/").
SecuredWith(route.Unsecured()).
Handle(getRoot(app)),
Expand All @@ -22,11 +32,10 @@ func Routes(app *api.App) []*route.HandledRoute {
route.Get("/configuration").
SecuredWith(route.Unsecured()).
Handle(getConfiguration(app)),
route.Get("/stats").
SecuredWith(authentication).
Handle(getStats(app)),
route.Get("/metrics").
SecuredWith(authentication).
Handle(promhttp.Handler()),
}
)

return routes
}
4 changes: 3 additions & 1 deletion api/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ func SetSession(cfg *config.Config, w http.ResponseWriter, val string) {
}

func IdentityForSession(keyStore data.KeyStore, actives data.Actives, cfg *config.Config, session *sessions.Claims, accountID int) (string, error) {
actives.Track(accountID)
if actives != nil {
actives.Track(accountID)
}
identity := identities.New(cfg, session, accountID)
identityToken, err := identity.Sign(keyStore.Key())
if err != nil {
Expand Down
3 changes: 0 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,6 @@ var configurers = []configurer{
func(c *Config) error {
val, err := lookupURL("REDIS_URL")
if err == nil {
if val == nil {
return ErrMissingEnvVar("REDIS_URL")
}
c.RedisURL = val
}
return err
Expand Down
10 changes: 6 additions & 4 deletions data/account_store.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package data

import (
"fmt"

"github.com/jmoiron/sqlx"
"github.com/keratin/authn-server/data/mysql"
"github.com/keratin/authn-server/data/sqlite3"
Expand All @@ -19,13 +21,13 @@ type AccountStore interface {
UpdateUsername(id int, u string) error
}

func NewAccountStore(db *sqlx.DB) AccountStore {
func NewAccountStore(db *sqlx.DB) (AccountStore, error) {
switch db.DriverName() {
case "sqlite3":
return &sqlite3.AccountStore{DB: db}
return &sqlite3.AccountStore{DB: db}, nil
case "mysql":
return &mysql.AccountStore{DB: db}
return &mysql.AccountStore{DB: db}, nil
default:
return nil
return nil, fmt.Errorf("unsupported driver: %v", db.DriverName())
}
}
9 changes: 5 additions & 4 deletions data/blob_store.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package data

import (
"fmt"
"time"

"github.com/go-redis/redis"
Expand All @@ -22,7 +23,7 @@ type BlobStore interface {
Write(name string, blob []byte) error
}

func NewBlobStore(interval time.Duration, redis *redis.Client, db *sqlx.DB, reporter ops.ErrorReporter) BlobStore {
func NewBlobStore(interval time.Duration, redis *redis.Client, db *sqlx.DB, reporter ops.ErrorReporter) (BlobStore, error) {
// the lifetime of a key should be slightly more than two intervals
ttl := interval*2 + 10*time.Second

Expand All @@ -35,7 +36,7 @@ func NewBlobStore(interval time.Duration, redis *redis.Client, db *sqlx.DB, repo
TTL: ttl,
LockTime: lockTime,
Client: redis,
}
}, nil
}

switch db.DriverName() {
Expand All @@ -46,8 +47,8 @@ func NewBlobStore(interval time.Duration, redis *redis.Client, db *sqlx.DB, repo
DB: db,
}
store.Clean(reporter)
return store
return store, nil
default:
return nil
return nil, fmt.Errorf("unsupported driver: %v", db.DriverName())
}
}
9 changes: 5 additions & 4 deletions data/refresh_token_store.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package data

import (
"fmt"
"time"

"github.com/keratin/authn-server/ops"
Expand Down Expand Up @@ -35,12 +36,12 @@ type RefreshTokenStore interface {
Revoke(t models.RefreshToken) error
}

func NewRefreshTokenStore(db *sqlx.DB, redis *redis.Client, reporter ops.ErrorReporter, ttl time.Duration) RefreshTokenStore {
func NewRefreshTokenStore(db *sqlx.DB, redis *redis.Client, reporter ops.ErrorReporter, ttl time.Duration) (RefreshTokenStore, error) {
if redis != nil {
return &dataRedis.RefreshTokenStore{
Client: redis,
TTL: ttl,
}
}, nil
}

switch db.DriverName() {
Expand All @@ -50,8 +51,8 @@ func NewRefreshTokenStore(db *sqlx.DB, redis *redis.Client, reporter ops.ErrorRe
TTL: ttl,
}
store.Clean(reporter)
return store
return store, nil
default:
return nil
return nil, fmt.Errorf("unsupported driver: %v", db.DriverName())
}
}
11 changes: 7 additions & 4 deletions data/sqlite3/blob_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sqlite3

import (
"database/sql"
"math/rand"
"time"

"github.com/jmoiron/sqlx"
Expand All @@ -20,11 +21,13 @@ type BlobStore struct {

func (s *BlobStore) Clean(reporter ops.ErrorReporter) {
go func() {
_, err := s.DB.Exec("DELETE FROM blobs WHERE expires_at < ?", time.Now())
if err != nil {
reporter.ReportError(err)
for range time.Tick(time.Minute + time.Duration(rand.Intn(5))*time.Second) {
_, err := s.DB.Exec("DELETE FROM blobs WHERE expires_at < ?", time.Now())
if err != nil {
reporter.ReportError(errors.Wrap(err, "BlobStore Clean"))
}
time.Sleep(time.Minute)
}
time.Sleep(time.Minute)
}()
}

Expand Down
12 changes: 8 additions & 4 deletions data/sqlite3/refresh_token_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"database/sql"
"encoding/hex"
"fmt"
"math/rand"
"time"

"github.com/keratin/authn-server/ops"
"github.com/pkg/errors"

"github.com/jmoiron/sqlx"
"github.com/keratin/authn-server/lib"
Expand All @@ -20,11 +22,13 @@ type RefreshTokenStore struct {

func (s *RefreshTokenStore) Clean(reporter ops.ErrorReporter) {
go func() {
_, err := s.Exec("DELETE FROM refresh_tokens WHERE expires_at < ?", time.Now())
if err != nil {
reporter.ReportError(err)
for range time.Tick(time.Minute + time.Duration(rand.Intn(5))*time.Second) {
_, err := s.Exec("DELETE FROM refresh_tokens WHERE expires_at < ?", time.Now())
if err != nil {
reporter.ReportError(errors.Wrap(err, "RefreshTokenStore Clean"))
}
time.Sleep(time.Minute)
}
time.Sleep(time.Minute)
}()
}

Expand Down
14 changes: 4 additions & 10 deletions docs/guide-deploying_with_docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,21 @@ rely on the `:latest` tag, as it is not guaranteed to reflect the newest stable

### Daemon

You can run the AuthN Docker image as a daemon. Here's the quickest way to get it running:
You can run the AuthN Docker image as a daemon. Here's the quickest way to get it running with
minimal dependencies:

```sh
# start a Redis server in the background
docker run --detach --name authn_redis redis

# then, configure and start an AuthN server on localhost:8080
docker run \
docker run -it --rm \
--publish 8080:3000 \
--link authn_redis:rd \
-e AUTHN_URL=localhost:8080 \
-e APP_DOMAINS=localhost \
-e DATABASE_URL=sqlite3:db/demo.sqlite3 \
-e REDIS_URL=redis://rd:6379/1 \
-e SECRET_KEY_BASE=changeme \
-e HTTP_AUTH_USERNAME=hello \
-e HTTP_AUTH_PASSWORD=world \
--detach \
--name authn_app \
keratin/authn-server:latest \
sh -c "./authn migrate && ./authn 3000 server"
sh -c "./authn migrate && ./authn server"
```

### Compose
Expand Down

0 comments on commit 27917e8

Please sign in to comment.