118 lines
3.3 KiB
Go
118 lines
3.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"time"
|
|
|
|
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
|
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
|
"github.com/google/uuid"
|
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
"github.com/pkg/errors"
|
|
"gitlab.com/wpetit/goweb/logger"
|
|
)
|
|
|
|
const AnonIssuer = "anon"
|
|
|
|
func AnonymousUser(key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm, funcs ...AnonymousUserOptionFunc) func(next http.Handler) http.Handler {
|
|
opts := defaultAnonymousUserOptions()
|
|
for _, fn := range funcs {
|
|
fn(opts)
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
rawToken, err := jwtutil.FindRawToken(r, jwtutil.WithFinders(
|
|
jwtutil.FindTokenFromAuthorizationHeader,
|
|
jwtutil.FindTokenFromQueryString(auth.CookieName),
|
|
jwtutil.FindTokenFromCookie(auth.CookieName),
|
|
))
|
|
|
|
// If request already has a raw token, we do nothing
|
|
if rawToken != "" && err == nil {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
|
|
uuid, err := uuid.NewUUID()
|
|
if err != nil {
|
|
logger.Error(ctx, "could not generate uuid for anonymous user", logger.CapturedE(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
subject := fmt.Sprintf("%s-%s", AnonIssuer, uuid.String())
|
|
preferredUsername, err := generateRandomPreferredUsername(8)
|
|
if err != nil {
|
|
logger.Error(ctx, "could not generate preferred username for anonymous user", logger.CapturedE(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
claims := map[string]any{
|
|
auth.ClaimSubject: subject,
|
|
auth.ClaimIssuer: AnonIssuer,
|
|
auth.ClaimPreferredUsername: preferredUsername,
|
|
auth.ClaimEdgeRole: opts.Role,
|
|
auth.ClaimEdgeEntrypoint: opts.Entrypoint,
|
|
auth.ClaimEdgeTenant: opts.Tenant,
|
|
}
|
|
|
|
token, err := jwtutil.SignedToken(key, signingAlgorithm, claims)
|
|
if err != nil {
|
|
logger.Error(ctx, "could not generate signed token", logger.CapturedE(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
cookieDomain, err := opts.GetCookieDomain(r)
|
|
if err != nil {
|
|
logger.Error(ctx, "could not retrieve cookie domain", logger.CapturedE(errors.WithStack(err)))
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
cookie := http.Cookie{
|
|
Name: auth.CookieName,
|
|
Value: string(token),
|
|
Domain: cookieDomain,
|
|
HttpOnly: false,
|
|
Expires: time.Now().Add(opts.CookieDuration),
|
|
Path: "/",
|
|
}
|
|
|
|
http.SetCookie(w, &cookie)
|
|
|
|
next.ServeHTTP(w, r)
|
|
}
|
|
|
|
return http.HandlerFunc(handler)
|
|
}
|
|
}
|
|
|
|
func generateRandomPreferredUsername(size int) (string, error) {
|
|
var letters = []rune("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
max := big.NewInt(int64(len(letters)))
|
|
|
|
b := make([]rune, size)
|
|
for i := range b {
|
|
idx, err := rand.Int(rand.Reader, max)
|
|
if err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
b[i] = letters[idx.Int64()]
|
|
}
|
|
|
|
return fmt.Sprintf("Anon %s", string(b)), nil
|
|
}
|