feat: initial shared auth module
Signed user-header middleware (UserMiddleware/FromContext/User, ADR-0005) plus the deployed-secrets startup guard (MissingDeployedSecrets, ADR-0005/0006). Replaces the byte-identical auth package + secrets_guard.go copied into every backend service.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Email string `json:"email"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
func (u *User) HasRole(role ...string) bool {
|
||||
for _, r := range role {
|
||||
for _, o := range u.Roles {
|
||||
if r == o {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const (
|
||||
UserKey = ContextKey("user")
|
||||
)
|
||||
|
||||
func UserMiddleware(signingKey []byte) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := &User{}
|
||||
header := r.Header.Get("user")
|
||||
if len(header) == 0 {
|
||||
fmt.Println("User not found in request headers, check api-gateway")
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if len(signingKey) > 0 {
|
||||
signature := r.Header.Get("user-signature")
|
||||
if signature == "" {
|
||||
http.Error(w, "missing user-signature header", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
mac := hmac.New(sha256.New, signingKey)
|
||||
mac.Write([]byte(header))
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
if !hmac.Equal([]byte(signature), []byte(expected)) {
|
||||
http.Error(w, "invalid user-signature", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := json.Unmarshal([]byte(header), &user); err != nil {
|
||||
fmt.Printf("User in header (%s) not parseable, check api-gateway", header)
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
ctx := context.WithValue(r.Context(), UserKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) *User {
|
||||
if user := ctx.Value(UserKey); user != nil {
|
||||
if u, ok := user.(*User); ok {
|
||||
return u
|
||||
}
|
||||
fmt.Println("User in context is not the correct type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user