Compare commits
3 Commits
2fbc7186c0
...
1029cc8f0f
Author | SHA1 | Date |
---|---|---|
wpetit | 1029cc8f0f | |
wpetit | 0f673671b8 | |
wpetit | 6aec6da078 |
|
@ -39,6 +39,7 @@ import (
|
|||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/wlynxg/anet"
|
||||
|
||||
_ "embed"
|
||||
|
||||
|
@ -251,7 +252,7 @@ func runApp(ctx context.Context, path, address, documentStoreDSN, blobStoreDSN,
|
|||
fetchModule.Mount(),
|
||||
),
|
||||
appHTTP.WithHTTPMiddlewares(
|
||||
authModuleMiddleware.AnonymousUser(key, jwa.HS256),
|
||||
authModuleMiddleware.DefaultUser(key, jwa.HS256, authModuleMiddleware.WithAnonymousUser()),
|
||||
),
|
||||
)
|
||||
if err := handler.Load(ctx, bundle); err != nil {
|
||||
|
@ -360,13 +361,13 @@ func findMatchingDeviceAddress(ctx context.Context, from string, defaultAddr str
|
|||
return defaultAddr, nil
|
||||
}
|
||||
|
||||
ifaces, err := net.Interfaces()
|
||||
ifaces, err := anet.Interfaces()
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, ifa := range ifaces {
|
||||
addrs, err := ifa.Addrs()
|
||||
addrs, err := anet.InterfaceAddrsByInterface(&ifa)
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
ctx, "could not retrieve iface adresses",
|
||||
|
|
2
go.mod
2
go.mod
|
@ -16,6 +16,7 @@ require (
|
|||
github.com/lestrrat-go/jwx/v2 v2.0.8
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/wlynxg/anet v0.0.1
|
||||
go.uber.org/goleak v1.3.0
|
||||
modernc.org/sqlite v1.20.4
|
||||
)
|
||||
|
@ -43,7 +44,6 @@ require (
|
|||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/wlynxg/anet v0.0.1 // indirect
|
||||
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
|
|
|
@ -16,7 +16,7 @@ func TestBundle(t *testing.T) {
|
|||
bundles := []Bundle{
|
||||
NewDirectoryBundle("testdata/bundle"),
|
||||
NewTarBundle("testdata/bundle.tar.gz"),
|
||||
NewZipBundle("testdata/bundle.zip"),
|
||||
Must(NewZipBundleFromPath("testdata/bundle.zip")),
|
||||
}
|
||||
|
||||
for _, b := range bundles {
|
||||
|
|
|
@ -54,7 +54,7 @@ func matchArchivePattern(archivePath string) (Bundle, error) {
|
|||
}
|
||||
|
||||
if matches {
|
||||
return NewZipBundle(archivePath), nil
|
||||
return NewZipBundleFromPath(archivePath)
|
||||
}
|
||||
|
||||
matches, err = filepath.Match(fmt.Sprintf("*.%s", ExtZim), base)
|
||||
|
|
|
@ -13,15 +13,10 @@ import (
|
|||
)
|
||||
|
||||
type ZipBundle struct {
|
||||
archivePath string
|
||||
reader *zip.Reader
|
||||
}
|
||||
|
||||
func (b *ZipBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) {
|
||||
reader, err := b.openArchive()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ctx := logger.With(
|
||||
context.Background(),
|
||||
logger.F("filename", filename),
|
||||
|
@ -29,7 +24,7 @@ func (b *ZipBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) {
|
|||
|
||||
logger.Debug(ctx, "opening file")
|
||||
|
||||
f, err := reader.Open(filename)
|
||||
f, err := b.reader.Open(filename)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
|
@ -43,21 +38,10 @@ func (b *ZipBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) {
|
|||
}
|
||||
|
||||
func (b *ZipBundle) Dir(dirname string) ([]os.FileInfo, error) {
|
||||
reader, err := b.openArchive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := reader.Close(); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
files := make([]os.FileInfo, 0)
|
||||
ctx := context.Background()
|
||||
|
||||
for _, f := range reader.File {
|
||||
for _, f := range b.reader.File {
|
||||
if !strings.HasPrefix(f.Name, dirname) {
|
||||
continue
|
||||
}
|
||||
|
@ -82,17 +66,35 @@ func (b *ZipBundle) Dir(dirname string) ([]os.FileInfo, error) {
|
|||
return files, nil
|
||||
}
|
||||
|
||||
func (b *ZipBundle) openArchive() (*zip.ReadCloser, error) {
|
||||
zr, err := zip.OpenReader(b.archivePath)
|
||||
func NewZipBundleFromReader(reader io.ReaderAt, size int64) (*ZipBundle, error) {
|
||||
zipReader, err := zip.NewReader(reader, size)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not decompress '%v'", b.archivePath)
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return zr, nil
|
||||
}
|
||||
|
||||
func NewZipBundle(archivePath string) *ZipBundle {
|
||||
return &ZipBundle{
|
||||
archivePath: archivePath,
|
||||
}
|
||||
reader: zipReader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewZipBundleFromPath(filename string) (*ZipBundle, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return NewZipBundleFromReader(file, stat.Size())
|
||||
}
|
||||
|
||||
func Must(bundle Bundle, err error) Bundle {
|
||||
if err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
|
|
@ -5,101 +5,45 @@ import (
|
|||
"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)
|
||||
}
|
||||
func WithAnonymousUser(funcs ...DefaultUserOptionFunc) DefaultUserOptionFunc {
|
||||
return func(opts *DefaultUserOptions) {
|
||||
opts.GetSubject = getAnonymousSubject
|
||||
opts.GetPreferredUsername = getAnonymousPreferredUsername
|
||||
opts.Issuer = AnonIssuer
|
||||
|
||||
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)
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(handler)
|
||||
}
|
||||
}
|
||||
|
||||
func getAnonymousSubject(r *http.Request) (string, error) {
|
||||
uuid, err := uuid.NewUUID()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not generate uuid for anonymous user")
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s-%s", AnonIssuer, uuid.String())
|
||||
|
||||
return subject, nil
|
||||
}
|
||||
|
||||
func getAnonymousPreferredUsername(r *http.Request) (string, error) {
|
||||
preferredUsername, err := generateRandomPreferredUsername(8)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not generate preferred username for anonymous user")
|
||||
}
|
||||
|
||||
return preferredUsername, nil
|
||||
}
|
||||
|
||||
func generateRandomPreferredUsername(size int) (string, error) {
|
||||
var letters = []rune("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
max := big.NewInt(int64(len(letters)))
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func DefaultUser(key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm, funcs ...DefaultUserOptionFunc) func(next http.Handler) http.Handler {
|
||||
opts := defaultUserOptions()
|
||||
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()
|
||||
|
||||
subject, err := opts.GetSubject(r)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve user subject", logger.CapturedE(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
preferredUsername, err := opts.GetPreferredUsername(r)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve user preferred username", logger.CapturedE(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
claims := map[string]any{
|
||||
auth.ClaimSubject: subject,
|
||||
auth.ClaimIssuer: opts.Issuer,
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -11,47 +11,52 @@ func defaultGetCookieDomain(r *http.Request) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
type AnonymousUserOptions struct {
|
||||
GetCookieDomain GetCookieDomainFunc
|
||||
CookieDuration time.Duration
|
||||
Tenant string
|
||||
Entrypoint string
|
||||
Role string
|
||||
type DefaultUserOptions struct {
|
||||
GetCookieDomain GetCookieDomainFunc
|
||||
CookieDuration time.Duration
|
||||
Tenant string
|
||||
Entrypoint string
|
||||
Role string
|
||||
Issuer string
|
||||
GetPreferredUsername func(r *http.Request) (string, error)
|
||||
GetSubject func(r *http.Request) (string, error)
|
||||
}
|
||||
|
||||
type AnonymousUserOptionFunc func(*AnonymousUserOptions)
|
||||
type DefaultUserOptionFunc func(opts *DefaultUserOptions)
|
||||
|
||||
func defaultAnonymousUserOptions() *AnonymousUserOptions {
|
||||
return &AnonymousUserOptions{
|
||||
GetCookieDomain: defaultGetCookieDomain,
|
||||
CookieDuration: 24 * time.Hour,
|
||||
Tenant: "",
|
||||
Entrypoint: "",
|
||||
Role: "",
|
||||
func defaultUserOptions() *DefaultUserOptions {
|
||||
return &DefaultUserOptions{
|
||||
GetCookieDomain: defaultGetCookieDomain,
|
||||
CookieDuration: 24 * time.Hour,
|
||||
Tenant: "",
|
||||
Entrypoint: "",
|
||||
Role: "",
|
||||
GetSubject: getAnonymousSubject,
|
||||
GetPreferredUsername: getAnonymousPreferredUsername,
|
||||
}
|
||||
}
|
||||
|
||||
func WithCookieOptions(getCookieDomain GetCookieDomainFunc, duration time.Duration) AnonymousUserOptionFunc {
|
||||
return func(opts *AnonymousUserOptions) {
|
||||
func WithCookieOptions(getCookieDomain GetCookieDomainFunc, duration time.Duration) DefaultUserOptionFunc {
|
||||
return func(opts *DefaultUserOptions) {
|
||||
opts.GetCookieDomain = getCookieDomain
|
||||
opts.CookieDuration = duration
|
||||
}
|
||||
}
|
||||
|
||||
func WithTenant(tenant string) AnonymousUserOptionFunc {
|
||||
return func(opts *AnonymousUserOptions) {
|
||||
func WithTenant(tenant string) DefaultUserOptionFunc {
|
||||
return func(opts *DefaultUserOptions) {
|
||||
opts.Tenant = tenant
|
||||
}
|
||||
}
|
||||
|
||||
func WithEntrypoint(entrypoint string) AnonymousUserOptionFunc {
|
||||
return func(opts *AnonymousUserOptions) {
|
||||
func WithEntrypoint(entrypoint string) DefaultUserOptionFunc {
|
||||
return func(opts *DefaultUserOptions) {
|
||||
opts.Entrypoint = entrypoint
|
||||
}
|
||||
}
|
||||
|
||||
func WithRole(role string) AnonymousUserOptionFunc {
|
||||
return func(opts *AnonymousUserOptions) {
|
||||
func WithRole(role string) DefaultUserOptionFunc {
|
||||
return func(opts *DefaultUserOptions) {
|
||||
opts.Role = role
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package chromecast
|
|||
import (
|
||||
"context"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -15,6 +13,7 @@ import (
|
|||
"github.com/barnybug/go-cast/log"
|
||||
"github.com/hashicorp/mdns"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wlynxg/anet"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -151,28 +150,11 @@ func (d *Discovery) listener(ctx context.Context) {
|
|||
case d.found <- client:
|
||||
case <-time.After(time.Second):
|
||||
case <-ctx.Done():
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeDnsEntry(text string) string {
|
||||
text = strings.Replace(text, `\.`, ".", -1)
|
||||
text = strings.Replace(text, `\ `, " ", -1)
|
||||
|
||||
re := regexp.MustCompile(`([\\][0-9][0-9][0-9])`)
|
||||
text = re.ReplaceAllStringFunc(text, func(source string) string {
|
||||
i, err := strconv.Atoi(source[1:])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string([]byte{byte(i)})
|
||||
})
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
func decodeTxtRecord(txt string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
|
@ -196,7 +178,7 @@ func isIPv6(ip net.IP) bool {
|
|||
}
|
||||
|
||||
func findMulticastInterfaces(ctx context.Context) ([]net.Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
ifaces, err := anet.Interfaces()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -223,7 +205,7 @@ func findMulticastInterfaces(ctx context.Context) ([]net.Interface, error) {
|
|||
}
|
||||
|
||||
func retrieveSupportedProtocols(iface net.Interface) (bool, bool, error) {
|
||||
adresses, err := iface.Addrs()
|
||||
adresses, err := anet.InterfaceAddrsByInterface(&iface)
|
||||
if err != nil {
|
||||
return false, false, errors.WithStack(err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue