Created InitialSetup method that handles all steps required for starting the server for the first time
This commit is contained in:
parent
398dfd04fc
commit
9e5ffaaff4
|
@ -0,0 +1,14 @@
|
||||||
|
package consts
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
InitialSetupFlagKey = "InitialSetupKey"
|
||||||
|
|
||||||
|
JWTSecretKey = "JWTSecretKey"
|
||||||
|
JWTIssuer = "CloudSonic"
|
||||||
|
JWTTokenExpiration = 30 * time.Minute
|
||||||
|
|
||||||
|
InitialUserName = "admin"
|
||||||
|
InitialName = "Admin"
|
||||||
|
)
|
|
@ -2,18 +2,15 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cloudsonic/sonic-server/log"
|
|
||||||
"github.com/cloudsonic/sonic-server/model"
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
"github.com/cloudsonic/sonic-server/server"
|
"github.com/cloudsonic/sonic-server/server"
|
||||||
"github.com/deluan/rest"
|
"github.com/deluan/rest"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/jwtauth"
|
"github.com/go-chi/jwtauth"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var initialUser = model.User{
|
var initialUser = model.User{
|
||||||
|
@ -31,7 +28,6 @@ type Router struct {
|
||||||
func New(ds model.DataStore, path string) *Router {
|
func New(ds model.DataStore, path string) *Router {
|
||||||
r := &Router{ds: ds, path: path}
|
r := &Router{ds: ds, path: path}
|
||||||
r.mux = r.routes()
|
r.mux = r.routes()
|
||||||
r.createDefaultUser()
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,21 +57,6 @@ func (app *Router) routes() http.Handler {
|
||||||
return r
|
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) {
|
func R(r chi.Router, pathPrefix string, newRepository rest.RepositoryConstructor) {
|
||||||
r.Route(pathPrefix, func(r chi.Router) {
|
r.Route(pathPrefix, func(r chi.Router) {
|
||||||
r.Get("/", rest.GetAll(newRepository))
|
r.Get("/", rest.GetAll(newRepository))
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cloudsonic/sonic-server/consts"
|
||||||
"github.com/cloudsonic/sonic-server/model"
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
"github.com/deluan/rest"
|
"github.com/deluan/rest"
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
@ -16,16 +17,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tokenExpiration = 30 * time.Minute
|
once sync.Once
|
||||||
issuer = "CloudSonic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
jwtSecret []byte
|
jwtSecret []byte
|
||||||
TokenAuth *jwtauth.JWTAuth
|
TokenAuth *jwtauth.JWTAuth
|
||||||
)
|
)
|
||||||
|
|
||||||
func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) {
|
func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
initTokenAuth(ds)
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
data := make(map[string]string)
|
data := make(map[string]string)
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
@ -56,11 +55,22 @@ func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) {
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"message": "User '" + username + "' authenticated successfully",
|
"message": "User '" + username + "' authenticated successfully",
|
||||||
"token": tokenString,
|
"token": tokenString,
|
||||||
"user": strings.Title(user.UserName),
|
"name": strings.Title(user.Name),
|
||||||
"username": username,
|
"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) {
|
func validateLogin(userRepo model.UserRepository, userName, password string) (*model.User, error) {
|
||||||
u, err := userRepo.FindByUsername(userName)
|
u, err := userRepo.FindByUsername(userName)
|
||||||
if err == model.ErrNotFound {
|
if err == model.ErrNotFound {
|
||||||
|
@ -86,14 +96,14 @@ func validateLogin(userRepo model.UserRepository, userName, password string) (*m
|
||||||
func createToken(u *model.User) (string, error) {
|
func createToken(u *model.User) (string, error) {
|
||||||
token := jwt.New(jwt.SigningMethodHS256)
|
token := jwt.New(jwt.SigningMethodHS256)
|
||||||
claims := token.Claims.(jwt.MapClaims)
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
claims["iss"] = issuer
|
claims["iss"] = consts.JWTIssuer
|
||||||
claims["sub"] = u.UserName
|
claims["sub"] = u.UserName
|
||||||
|
|
||||||
return touchToken(token)
|
return touchToken(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func touchToken(token *jwt.Token) (string, error) {
|
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 := token.Claims.(jwt.MapClaims)
|
||||||
claims["exp"] = expireIn
|
claims["exp"] = expireIn
|
||||||
|
|
||||||
|
@ -135,14 +145,3 @@ func Authenticator(next http.Handler) http.Handler {
|
||||||
next.ServeHTTP(w, r.WithContext(newCtx))
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/cloudsonic/sonic-server/conf"
|
"github.com/cloudsonic/sonic-server/conf"
|
||||||
"github.com/cloudsonic/sonic-server/log"
|
"github.com/cloudsonic/sonic-server/log"
|
||||||
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
"github.com/cloudsonic/sonic-server/scanner"
|
"github.com/cloudsonic/sonic-server/scanner"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
|
@ -19,14 +20,16 @@ const Version = "0.2"
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Scanner *scanner.Scanner
|
Scanner *scanner.Scanner
|
||||||
router *chi.Mux
|
router *chi.Mux
|
||||||
|
ds model.DataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(scanner *scanner.Scanner) *Server {
|
func New(scanner *scanner.Scanner, ds model.DataStore) *Server {
|
||||||
a := &Server{Scanner: scanner}
|
a := &Server{Scanner: scanner, ds: ds}
|
||||||
if !conf.Sonic.DevDisableBanner {
|
if !conf.Sonic.DevDisableBanner {
|
||||||
showBanner(Version)
|
showBanner(Version)
|
||||||
}
|
}
|
||||||
initMimeTypes()
|
initMimeTypes()
|
||||||
|
initialSetup(ds)
|
||||||
a.initRoutes()
|
a.initRoutes()
|
||||||
a.initScanner()
|
a.initScanner()
|
||||||
return a
|
return a
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
func CreateServer(musicFolder string) *server.Server {
|
func CreateServer(musicFolder string) *server.Server {
|
||||||
dataStore := persistence.New()
|
dataStore := persistence.New()
|
||||||
scannerScanner := scanner.New(dataStore)
|
scannerScanner := scanner.New(dataStore)
|
||||||
serverServer := server.New(scannerScanner)
|
serverServer := server.New(scannerScanner, dataStore)
|
||||||
return serverServer
|
return serverServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue