Created InitialSetup method that handles all steps required for starting the server for the first time

This commit is contained in:
Deluan 2020-01-20 15:17:43 -05:00
parent 398dfd04fc
commit 9e5ffaaff4
6 changed files with 108 additions and 42 deletions

14
consts/consts.go Normal file
View File

@ -0,0 +1,14 @@
package consts
import "time"
const (
InitialSetupFlagKey = "InitialSetupKey"
JWTSecretKey = "JWTSecretKey"
JWTIssuer = "CloudSonic"
JWTTokenExpiration = 30 * time.Minute
InitialUserName = "admin"
InitialName = "Admin"
)

View File

@ -2,18 +2,15 @@ package app
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
"github.com/cloudsonic/sonic-server/server"
"github.com/deluan/rest"
"github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"github.com/google/uuid"
)
var initialUser = model.User{
@ -31,7 +28,6 @@ type Router struct {
func New(ds model.DataStore, path string) *Router {
r := &Router{ds: ds, path: path}
r.mux = r.routes()
r.createDefaultUser()
return r
}
@ -61,21 +57,6 @@ func (app *Router) routes() http.Handler {
return r
}
func (app *Router) createDefaultUser() {
c, err := app.ds.User().CountAll()
if err != nil {
panic(fmt.Sprintf("Could not access User table: %s", err))
}
if c == 0 {
id, _ := uuid.NewRandom()
initialPassword, _ := uuid.NewRandom()
log.Warn("Creating initial user. Please change the password!", "user", initialUser.UserName, "password", initialPassword)
initialUser.ID = id.String()
initialUser.Password = initialPassword.String()
app.ds.User().Put(&initialUser)
}
}
func R(r chi.Router, pathPrefix string, newRepository rest.RepositoryConstructor) {
r.Route(pathPrefix, func(r chi.Router) {
r.Get("/", rest.GetAll(newRepository))

View File

@ -4,10 +4,11 @@ import (
"context"
"encoding/json"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/cloudsonic/sonic-server/consts"
"github.com/cloudsonic/sonic-server/model"
"github.com/deluan/rest"
"github.com/dgrijalva/jwt-go"
@ -16,16 +17,14 @@ import (
)
var (
tokenExpiration = 30 * time.Minute
issuer = "CloudSonic"
)
var (
once sync.Once
jwtSecret []byte
TokenAuth *jwtauth.JWTAuth
)
func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) {
initTokenAuth(ds)
return func(w http.ResponseWriter, r *http.Request) {
data := make(map[string]string)
decoder := json.NewDecoder(r.Body)
@ -56,11 +55,22 @@ func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) {
map[string]interface{}{
"message": "User '" + username + "' authenticated successfully",
"token": tokenString,
"user": strings.Title(user.UserName),
"name": strings.Title(user.Name),
"username": username,
})
}
}
func initTokenAuth(ds model.DataStore) {
once.Do(func() {
secret, err := ds.Property().DefaultGet(consts.JWTSecretKey, "not so secret")
if err != nil {
log.Error("No JWT secret found in DB. Setting a temp one, but please report this error", err)
}
jwtSecret = []byte(secret)
TokenAuth = jwtauth.New("HS256", jwtSecret, nil)
})
}
func validateLogin(userRepo model.UserRepository, userName, password string) (*model.User, error) {
u, err := userRepo.FindByUsername(userName)
if err == model.ErrNotFound {
@ -86,14 +96,14 @@ func validateLogin(userRepo model.UserRepository, userName, password string) (*m
func createToken(u *model.User) (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["iss"] = issuer
claims["iss"] = consts.JWTIssuer
claims["sub"] = u.UserName
return touchToken(token)
}
func touchToken(token *jwt.Token) (string, error) {
expireIn := time.Now().Add(tokenExpiration).Unix()
expireIn := time.Now().Add(consts.JWTTokenExpiration).Unix()
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = expireIn
@ -135,14 +145,3 @@ func Authenticator(next http.Handler) http.Handler {
next.ServeHTTP(w, r.WithContext(newCtx))
})
}
func init() {
// TODO Store jwtSecret in the DB
secret := os.Getenv("JWT_SECRET")
if secret == "" {
secret = "not so secret"
log.Warn("No JWT_SECRET env var found. Please set one.")
}
jwtSecret = []byte(secret)
TokenAuth = jwtauth.New("HS256", jwtSecret, nil)
}

69
server/initial_setup.go Normal file
View File

@ -0,0 +1,69 @@
package server
import (
"fmt"
"time"
"github.com/cloudsonic/sonic-server/consts"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
"github.com/google/uuid"
)
func initialSetup(ds model.DataStore) {
_ = ds.WithTx(func(tx model.DataStore) error {
_, err := ds.Property().Get(consts.InitialSetupFlagKey)
if err == nil {
return nil
}
log.Warn("Running initial setup")
if err = createDefaultUser(ds); err != nil {
return err
}
if err = createJWTSecret(ds); err != nil {
return err
}
err = ds.Property().Put(consts.InitialSetupFlagKey, time.Now().String())
return err
})
}
func createJWTSecret(ds model.DataStore) error {
_, err := ds.Property().Get(consts.JWTSecretKey)
if err == nil {
return nil
}
jwtSecret, _ := uuid.NewRandom()
log.Warn("Creating JWT secret, used for encrypting UI sessions")
err = ds.Property().Put(consts.JWTSecretKey, jwtSecret.String())
if err != nil {
log.Error("Could not save JWT secret in DB", err)
}
return err
}
func createDefaultUser(ds model.DataStore) error {
c, err := ds.User().CountAll()
if err != nil {
panic(fmt.Sprintf("Could not access User table: %s", err))
}
if c == 0 {
id, _ := uuid.NewRandom()
initialPassword, _ := uuid.NewRandom()
log.Warn("Creating initial user. Please change the password!", "user", consts.InitialUserName, "password", initialPassword)
initialUser := model.User{
ID: id.String(),
UserName: consts.InitialUserName,
Name: consts.InitialName,
Email: "",
Password: initialPassword.String(),
IsAdmin: true,
}
err := ds.User().Put(&initialUser)
if err != nil {
log.Error("Could not create initial user", "user", initialUser, err)
}
}
return err
}

View File

@ -8,6 +8,7 @@ import (
"github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
"github.com/cloudsonic/sonic-server/scanner"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
@ -19,14 +20,16 @@ const Version = "0.2"
type Server struct {
Scanner *scanner.Scanner
router *chi.Mux
ds model.DataStore
}
func New(scanner *scanner.Scanner) *Server {
a := &Server{Scanner: scanner}
func New(scanner *scanner.Scanner, ds model.DataStore) *Server {
a := &Server{Scanner: scanner, ds: ds}
if !conf.Sonic.DevDisableBanner {
showBanner(Version)
}
initMimeTypes()
initialSetup(ds)
a.initRoutes()
a.initScanner()
return a

View File

@ -20,7 +20,7 @@ import (
func CreateServer(musicFolder string) *server.Server {
dataStore := persistence.New()
scannerScanner := scanner.New(dataStore)
serverServer := server.New(scannerScanner)
serverServer := server.New(scannerScanner, dataStore)
return serverServer
}