2021-10-23 15:22:58 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-07-14 14:15:58 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
2021-10-23 15:22:58 +00:00
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/gofrs/uuid/v3"
|
|
|
|
"github.com/pkg/errors"
|
2023-07-14 14:15:58 +00:00
|
|
|
"golang.org/x/crypto/argon2"
|
2021-11-25 22:48:16 +00:00
|
|
|
|
|
|
|
"github.com/Luzifer/go_helpers/v2/str"
|
2023-07-14 14:15:58 +00:00
|
|
|
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// OWASP recommendations - 2023-07-07
|
|
|
|
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
|
|
argonFmt = "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s"
|
|
|
|
argonHashLen = 16
|
|
|
|
argonMemory = 46 * 1024
|
|
|
|
argonSaltLength = 8
|
|
|
|
argonThreads = 1
|
|
|
|
argonTime = 1
|
2021-10-23 15:22:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func fillAuthToken(token *configAuthToken) error {
|
|
|
|
token.Token = uuid.Must(uuid.NewV4()).String()
|
|
|
|
|
2023-07-14 14:15:58 +00:00
|
|
|
salt := make([]byte, argonSaltLength)
|
|
|
|
if _, err := rand.Read(salt); err != nil {
|
|
|
|
return errors.Wrap(err, "reading salt")
|
2021-10-23 15:22:58 +00:00
|
|
|
}
|
|
|
|
|
2023-07-14 14:15:58 +00:00
|
|
|
token.Hash = fmt.Sprintf(
|
|
|
|
argonFmt,
|
|
|
|
argon2.Version,
|
|
|
|
argonMemory, argonTime, argonThreads,
|
|
|
|
base64.RawStdEncoding.EncodeToString(salt),
|
|
|
|
base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(token.Token), salt, argonTime, argonMemory, argonThreads, argonHashLen)),
|
|
|
|
)
|
2021-10-23 15:22:58 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeAuthMiddleware(h http.Handler, module string) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
token := r.Header.Get("Authorization")
|
|
|
|
if token == "" {
|
|
|
|
http.Error(w, "auth not successful", http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-07-14 14:15:58 +00:00
|
|
|
for _, fn := range []func() error{
|
|
|
|
// First try to validate against internal token management
|
|
|
|
func() error { return validateAuthToken(token, module) },
|
|
|
|
// If not successful validate against Twitch and check for bot-editors
|
|
|
|
func() error { return validateTwitchBotEditorAuthToken(token) },
|
|
|
|
} {
|
|
|
|
if err := fn(); err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
h.ServeHTTP(w, r)
|
2022-02-08 18:58:19 +00:00
|
|
|
return
|
|
|
|
}
|
2021-10-23 15:22:58 +00:00
|
|
|
|
2023-07-14 14:15:58 +00:00
|
|
|
http.Error(w, "auth not successful", http.StatusForbidden)
|
2022-02-08 18:58:19 +00:00
|
|
|
})
|
|
|
|
}
|
2021-10-23 15:22:58 +00:00
|
|
|
|
2022-02-08 18:58:19 +00:00
|
|
|
func validateAuthToken(token string, modules ...string) error {
|
|
|
|
for _, auth := range config.AuthTokens {
|
2023-07-14 14:15:58 +00:00
|
|
|
if auth.validate(token) != nil {
|
2022-02-08 18:58:19 +00:00
|
|
|
continue
|
2021-10-23 15:22:58 +00:00
|
|
|
}
|
|
|
|
|
2022-02-08 18:58:19 +00:00
|
|
|
for _, reqMod := range modules {
|
|
|
|
if !str.StringInSlice(reqMod, auth.Modules) && !str.StringInSlice("*", auth.Modules) {
|
|
|
|
return errors.New("missing module in auth")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil // We found a matching token and it has all required tokens
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New("no matching token")
|
2021-10-23 15:22:58 +00:00
|
|
|
}
|
2023-07-14 14:15:58 +00:00
|
|
|
|
|
|
|
func validateTwitchBotEditorAuthToken(token string) error {
|
|
|
|
tc := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, token, "")
|
|
|
|
|
|
|
|
id, user, err := tc.GetAuthorizedUser()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting authorized user")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !str.StringInSlice(user, config.BotEditors) && !str.StringInSlice(id, config.BotEditors) {
|
|
|
|
return errors.New("user is not an bot-edtior")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|