remove environment
This commit is contained in:
parent
42a3181797
commit
5e805b5b4d
@ -7,3 +7,8 @@ API_PORT= # defaul to 8585
|
|||||||
API_SECURE= # default to "false"
|
API_SECURE= # default to "false"
|
||||||
|
|
||||||
API_STORE_DIR= # default to "./store"
|
API_STORE_DIR= # default to "./store"
|
||||||
|
|
||||||
|
# use a master key if you run on production
|
||||||
|
# MEILI_MASTER_KEY=
|
||||||
|
BASEURL_MEILISEARCH=http://meilisearch:7700
|
||||||
|
MEILI_ENV=development
|
||||||
4
Makefile
4
Makefile
@ -8,5 +8,9 @@ build: lint
|
|||||||
lint:
|
lint:
|
||||||
golangci-lint run --fix
|
golangci-lint run --fix
|
||||||
|
|
||||||
|
# .run-meilisearch:
|
||||||
|
# docker-compose up -d meilisearch
|
||||||
|
|
||||||
run: lint
|
run: lint
|
||||||
|
# while [ "`curl --insecure -s -o /dev/null -w ''%{http_code}'' http://localhost:7700/health`" != "200" ]; do sleep 2; echo "waiting..."; done
|
||||||
go run main.go
|
go run main.go
|
||||||
6
go.mod
6
go.mod
@ -2,11 +2,13 @@ module librapi
|
|||||||
|
|
||||||
go 1.22.4
|
go 1.22.4
|
||||||
|
|
||||||
require github.com/rs/zerolog v1.33.0
|
require (
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
github.com/rs/zerolog v1.33.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
|
||||||
golang.org/x/sys v0.12.0 // indirect
|
golang.org/x/sys v0.12.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@ -135,7 +135,7 @@ func postLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := session.GenerateCookie()
|
cookie := session.GenerateCookie(a.IsSecure())
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
buf := bytes.NewBufferString("")
|
buf := bytes.NewBufferString("")
|
||||||
|
|||||||
35
main.go
35
main.go
@ -6,6 +6,8 @@ import (
|
|||||||
"librapi/services"
|
"librapi/services"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -15,6 +17,33 @@ import (
|
|||||||
"librapi/handlers/upload"
|
"librapi/handlers/upload"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPort = 8585
|
||||||
|
defaulStoreDir = "./store"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
isSecure = os.Getenv("API_SECURE") == "true"
|
||||||
|
|
||||||
|
port = sync.OnceValue[int](func() int {
|
||||||
|
port, err := strconv.Atoi(os.Getenv("API_PORT"))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Int("default", defaultPort).Msg("unable to load API_PORT, set to default")
|
||||||
|
return defaultPort
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
})
|
||||||
|
|
||||||
|
storeDir = sync.OnceValue[string](func() string {
|
||||||
|
storeDir := os.Getenv("API_STORE_DIR")
|
||||||
|
if storeDir == "" {
|
||||||
|
log.Warn().Str("default", defaulStoreDir).Msg("API_STORE_DIR env var empty, set to default")
|
||||||
|
return defaulStoreDir
|
||||||
|
}
|
||||||
|
return storeDir
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
func initLogger() {
|
func initLogger() {
|
||||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||||
log.Logger = log.With().Caller().Logger().Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
log.Logger = log.With().Caller().Logger().Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
@ -26,12 +55,12 @@ func main() {
|
|||||||
ctx, fnCancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
|
ctx, fnCancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
|
||||||
defer fnCancel()
|
defer fnCancel()
|
||||||
|
|
||||||
auth := services.NewAuthentication(ctx)
|
auth := services.NewAuthentication(ctx, isSecure)
|
||||||
bs := services.NewBookStore(services.GetEnv().GetDir())
|
bs := services.NewBookStore(storeDir())
|
||||||
|
|
||||||
srv := server.NewServer(
|
srv := server.NewServer(
|
||||||
ctx,
|
ctx,
|
||||||
services.GetEnv().GetPort(),
|
port(),
|
||||||
server.NewHandler(home.URL, home.Handler(bs)),
|
server.NewHandler(home.URL, home.Handler(bs)),
|
||||||
server.NewHandler(upload.URL, upload.Handler(auth, bs)),
|
server.NewHandler(upload.URL, upload.Handler(auth, bs)),
|
||||||
server.NewHandler(login.URL, login.Handler(auth)),
|
server.NewHandler(login.URL, login.Handler(auth)),
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"librapi/services"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@ -34,11 +33,6 @@ type Server struct {
|
|||||||
type ServerOption func()
|
type ServerOption func()
|
||||||
|
|
||||||
func NewServer(ctx context.Context, port int, handlers ...Handler) Server {
|
func NewServer(ctx context.Context, port int, handlers ...Handler) Server {
|
||||||
if port == 0 {
|
|
||||||
log.Warn().Int("port", services.GetEnv().GetPort()).Msg("no port detected, set to default")
|
|
||||||
port = services.GetEnv().GetPort()
|
|
||||||
}
|
|
||||||
|
|
||||||
srvmux := http.NewServeMux()
|
srvmux := http.NewServeMux()
|
||||||
for _, h := range handlers {
|
for _, h := range handlers {
|
||||||
srvmux.HandleFunc(h.url, h.fnHandle)
|
srvmux.HandleFunc(h.url, h.fnHandle)
|
||||||
|
|||||||
@ -6,17 +6,52 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAPISessionExpirationDuration = 5 * 60 * time.Second
|
||||||
|
defaultAdminPassword = "admin"
|
||||||
|
defaultAdminUsername = "admin"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrSessionIDCollision = errors.New("sessionId collision")
|
ErrSessionIDCollision = errors.New("sessionId collision")
|
||||||
ErrUnauthorized = errors.New("unauthorized")
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var adminPassword = sync.OnceValue[string](func() string {
|
||||||
|
adminPassword := os.Getenv("API_ADMIN_PASSWORD")
|
||||||
|
if adminPassword == "" {
|
||||||
|
log.Error().Msg("API_ADMIN_PASSWORD env var is empty, set to default")
|
||||||
|
return defaultAdminPassword
|
||||||
|
}
|
||||||
|
return adminPassword
|
||||||
|
})
|
||||||
|
|
||||||
|
var adminUsername = sync.OnceValue[string](func() string {
|
||||||
|
adminUsername := os.Getenv("API_ADMIN_USERNAME")
|
||||||
|
if adminUsername == "" {
|
||||||
|
log.Error().Msg("API_ADMIN_USERNAME env var is empty, set to default")
|
||||||
|
return defaultAdminUsername
|
||||||
|
}
|
||||||
|
return adminUsername
|
||||||
|
})
|
||||||
|
|
||||||
|
var sessionExpirationTime = sync.OnceValue[time.Duration](func() time.Duration {
|
||||||
|
sessionExpirationDuration, err := strconv.Atoi(os.Getenv("API_SESSION_EXPIRATION_DURATION"))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Dur("default", defaultAPISessionExpirationDuration).Msg("unable to load API_SESSION_EXPIRATION_DURATION, set to default")
|
||||||
|
return defaultAPISessionExpirationDuration
|
||||||
|
}
|
||||||
|
return time.Duration(sessionExpirationDuration)
|
||||||
|
})
|
||||||
|
|
||||||
func generateSessionID() (string, error) {
|
func generateSessionID() (string, error) {
|
||||||
sessionID := make([]byte, 32) //nolint
|
sessionID := make([]byte, 32) //nolint
|
||||||
if _, err := rand.Read(sessionID); err != nil {
|
if _, err := rand.Read(sessionID); err != nil {
|
||||||
@ -32,7 +67,7 @@ type Session struct {
|
|||||||
expirationTime time.Time
|
expirationTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) GenerateCookie() *http.Cookie {
|
func (s *Session) GenerateCookie(isSecure bool) *http.Cookie {
|
||||||
s.l.RLock()
|
s.l.RLock()
|
||||||
defer s.l.RUnlock()
|
defer s.l.RUnlock()
|
||||||
|
|
||||||
@ -40,7 +75,7 @@ func (s *Session) GenerateCookie() *http.Cookie {
|
|||||||
Name: "session_id",
|
Name: "session_id",
|
||||||
Value: s.sessionID,
|
Value: s.sessionID,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: GetEnv().isSecure,
|
Secure: isSecure,
|
||||||
Expires: s.expirationTime,
|
Expires: s.expirationTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +83,7 @@ func (s *Session) GenerateCookie() *http.Cookie {
|
|||||||
type IAuthenticate interface {
|
type IAuthenticate interface {
|
||||||
IsLogged(r *http.Request) bool
|
IsLogged(r *http.Request) bool
|
||||||
Authenticate(username, password string) (*Session, error)
|
Authenticate(username, password string) (*Session, error)
|
||||||
|
IsSecure() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IAuthenticate = (*Authentication)(nil)
|
var _ IAuthenticate = (*Authentication)(nil)
|
||||||
@ -59,15 +95,17 @@ type Authentication struct {
|
|||||||
fnCancel context.CancelFunc
|
fnCancel context.CancelFunc
|
||||||
|
|
||||||
sessions map[string]*Session
|
sessions map[string]*Session
|
||||||
|
isSecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthentication(ctx context.Context) *Authentication {
|
func NewAuthentication(ctx context.Context, isSecure bool) *Authentication {
|
||||||
ctxChild, fnCancel := context.WithCancel(ctx)
|
ctxChild, fnCancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
s := &Authentication{
|
s := &Authentication{
|
||||||
ctx: ctxChild,
|
ctx: ctxChild,
|
||||||
fnCancel: fnCancel,
|
fnCancel: fnCancel,
|
||||||
sessions: map[string]*Session{},
|
sessions: map[string]*Session{},
|
||||||
|
isSecure: isSecure,
|
||||||
}
|
}
|
||||||
s.purgeWorker()
|
s.purgeWorker()
|
||||||
|
|
||||||
@ -108,6 +146,10 @@ func (a *Authentication) purgeWorker() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Authentication) IsSecure() bool {
|
||||||
|
return a.isSecure
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Authentication) Stop() {
|
func (a *Authentication) Stop() {
|
||||||
a.fnCancel()
|
a.fnCancel()
|
||||||
}
|
}
|
||||||
@ -117,8 +159,7 @@ func (a *Authentication) Done() <-chan struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authentication) Authenticate(username, password string) (*Session, error) {
|
func (a *Authentication) Authenticate(username, password string) (*Session, error) {
|
||||||
adminUsername, adminPassword := GetEnv().GetCredentials()
|
if username != adminUsername() || password != adminPassword() {
|
||||||
if username != adminUsername || password != adminPassword {
|
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +177,7 @@ func (a *Authentication) Authenticate(username, password string) (*Session, erro
|
|||||||
return nil, ErrSessionIDCollision
|
return nil, ErrSessionIDCollision
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Add(GetEnv().GetSessionExpirationDuration())
|
now := time.Now().Add(sessionExpirationTime())
|
||||||
session := Session{expirationTime: now, sessionID: sessionID}
|
session := Session{expirationTime: now, sessionID: sessionID}
|
||||||
a.sessions[sessionID] = &session
|
a.sessions[sessionID] = &session
|
||||||
|
|
||||||
|
|||||||
@ -174,7 +174,7 @@ func (bs *BookStore) Save(bm *BookMetadata, content io.ReadCloser) error {
|
|||||||
|
|
||||||
defer content.Close()
|
defer content.Close()
|
||||||
|
|
||||||
bm.Path = filepath.Join(GetEnv().GetDir(), bm.getFormattedName())
|
bm.Path = filepath.Join(bs.dir, bm.getFormattedName())
|
||||||
|
|
||||||
dst, err := os.Create(bm.Path)
|
dst, err := os.Create(bm.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultAPISessionExpirationDuration = 5 * 60 * time.Second
|
|
||||||
defaultPort = 8585
|
|
||||||
defaultMainDir = "./store"
|
|
||||||
)
|
|
||||||
|
|
||||||
var env = sync.OnceValue[environment](newEnv)
|
|
||||||
|
|
||||||
type environment struct {
|
|
||||||
adminUsername string
|
|
||||||
adminPassword string
|
|
||||||
sessionExpirationDuration time.Duration
|
|
||||||
port int
|
|
||||||
isSecure bool
|
|
||||||
storeDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e environment) GetCredentials() (username, password string) {
|
|
||||||
return e.adminUsername, e.adminPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e environment) GetSessionExpirationDuration() time.Duration {
|
|
||||||
return e.sessionExpirationDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e environment) GetPort() int {
|
|
||||||
return e.port
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e environment) IsSecure() bool {
|
|
||||||
return e.isSecure
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e environment) GetDir() string {
|
|
||||||
return e.storeDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEnv() environment {
|
|
||||||
env := environment{
|
|
||||||
adminUsername: "test",
|
|
||||||
adminPassword: "test",
|
|
||||||
isSecure: os.Getenv("API_SECURE") == "true",
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionExpirationDuration, err := strconv.Atoi(os.Getenv("API_SESSION_EXPIRATION_DURATION"))
|
|
||||||
env.sessionExpirationDuration = time.Duration(sessionExpirationDuration)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Dur("default", defaultAPISessionExpirationDuration).Msg("unable to load API_SESSION_EXPIRATION_DURATION, set to default")
|
|
||||||
env.sessionExpirationDuration = defaultAPISessionExpirationDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := strconv.Atoi(os.Getenv("API_PORT"))
|
|
||||||
env.port = port
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Int("default", defaultPort).Msg("unable to load API_PORT, set to default")
|
|
||||||
env.port = defaultPort
|
|
||||||
}
|
|
||||||
|
|
||||||
storeDir := os.Getenv("API_STORE_DIR")
|
|
||||||
if storeDir == "" {
|
|
||||||
storeDir = defaultMainDir
|
|
||||||
}
|
|
||||||
env.storeDir = storeDir
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEnv() environment {
|
|
||||||
return env()
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user