Files
notify-gateway/internal/auth/auth.go
T
2026-09-16 06:26:16 +02:00

102 lines
2.5 KiB
Go

package auth
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"golang.org/x/crypto/bcrypt"
"strconv"
"strings"
"time"
)
const passwordRounds = 150000
func HashPassword(password string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(b), err
}
func RandomSecret() string {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(b)
}
func CheckPassword(encoded, password string) bool {
if strings.HasPrefix(encoded, "$2") {
return bcrypt.CompareHashAndPassword([]byte(encoded), []byte(password)) == nil
}
parts := strings.Split(encoded, "$")
if len(parts) != 4 || parts[0] != "sha256" {
return false
}
rounds, err := strconv.Atoi(parts[1])
if err != nil || rounds < 10000 || rounds > 1000000 {
return false
}
salt, err := base64.RawStdEncoding.DecodeString(parts[2])
if err != nil {
return false
}
want, err := base64.RawStdEncoding.DecodeString(parts[3])
if err != nil {
return false
}
got := stretch([]byte(password), salt, rounds)
return len(got) == len(want) && subtle.ConstantTimeCompare(got, want) == 1
}
func stretch(password, salt []byte, rounds int) []byte {
buf := append(append([]byte{}, salt...), password...)
s := sha256.Sum256(buf)
out := s[:]
for i := 1; i < rounds; i++ {
h := sha256.New()
h.Write(out)
h.Write(salt)
h.Write(password)
out = h.Sum(nil)
}
return out
}
func SignSession(secret, username string, expires time.Time) string {
payload := fmt.Sprintf("%s|%d", username, expires.Unix())
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." + sig
}
func VerifySession(secret, token string, now time.Time) (string, bool) {
parts := strings.Split(token, ".")
if len(parts) != 2 {
return "", false
}
payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return "", false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payloadBytes)
want := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
if subtle.ConstantTimeCompare([]byte(want), []byte(parts[1])) != 1 {
return "", false
}
fields := strings.Split(string(payloadBytes), "|")
if len(fields) != 2 {
return "", false
}
exp, err := strconv.ParseInt(fields[1], 10, 64)
if err != nil || now.Unix() > exp {
return "", false
}
return fields[0], true
}