feat(module,auth): authentication module with arbitrary claims support
This commit is contained in:
@ -2,19 +2,28 @@ package app
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||
appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
@ -53,14 +62,35 @@ func RunCommand() *cli.Command {
|
||||
Usage: "use `FILE` for SQLite storage database",
|
||||
Value: "data.sqlite",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "auth-subject",
|
||||
Usage: "set the `SUBJECT` associated with the simulated connected user",
|
||||
Value: "jdoe",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "auth-role",
|
||||
Usage: "set the `ROLE` associated with the simulated connected user",
|
||||
Value: "user",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "auth-preferred-username",
|
||||
Usage: "set the `PREFERRED_USERNAME` associated with the simulated connected user",
|
||||
Value: "Jane Doe",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
address := ctx.String("address")
|
||||
path := ctx.String("path")
|
||||
|
||||
logFormat := ctx.String("log-format")
|
||||
logLevel := ctx.Int("log-level")
|
||||
|
||||
storageFile := ctx.String("storage-file")
|
||||
|
||||
authSubject := ctx.String("auth-subject")
|
||||
authRole := ctx.String("auth-role")
|
||||
authPreferredUsername := ctx.String("auth-preferred-username")
|
||||
|
||||
logger.SetFormat(logger.Format(logFormat))
|
||||
logger.SetLevel(logger.Level(logLevel))
|
||||
|
||||
@ -81,6 +111,7 @@ func RunCommand() *cli.Command {
|
||||
mux := chi.NewMux()
|
||||
|
||||
mux.Use(middleware.Logger)
|
||||
mux.Use(dummyAuthMiddleware(authSubject, authRole, authPreferredUsername))
|
||||
|
||||
bus := memory.NewBus()
|
||||
|
||||
@ -89,21 +120,12 @@ func RunCommand() *cli.Command {
|
||||
return errors.Wrapf(err, "could not open database with path '%s'", storageFile)
|
||||
}
|
||||
|
||||
documentStore := sqlite.NewDocumentStoreWithDB(db)
|
||||
blobStore := sqlite.NewBlobStoreWithDB(db)
|
||||
ds := sqlite.NewDocumentStoreWithDB(db)
|
||||
bs := sqlite.NewBlobStoreWithDB(db)
|
||||
|
||||
handler := appHTTP.NewHandler(
|
||||
appHTTP.WithBus(bus),
|
||||
appHTTP.WithServerModules(
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
cast.CastModuleFactory(),
|
||||
module.LifecycleModuleFactory(bus),
|
||||
module.NetModuleFactory(bus),
|
||||
module.RPCModuleFactory(bus),
|
||||
module.StoreModuleFactory(documentStore),
|
||||
module.BlobModuleFactory(bus, blobStore),
|
||||
),
|
||||
appHTTP.WithServerModules(getServerModules(bus, ds, bs)...),
|
||||
)
|
||||
if err := handler.Load(bundle); err != nil {
|
||||
return errors.Wrap(err, "could not load app bundle")
|
||||
@ -121,3 +143,89 @@ func RunCommand() *cli.Command {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getServerModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore) []app.ServerModuleFactory {
|
||||
return []app.ServerModuleFactory{
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
cast.CastModuleFactory(),
|
||||
module.LifecycleModuleFactory(),
|
||||
net.ModuleFactory(bus),
|
||||
module.RPCModuleFactory(bus),
|
||||
module.StoreModuleFactory(ds),
|
||||
module.BlobModuleFactory(bus, bs),
|
||||
module.Extends(
|
||||
auth.ModuleFactory(
|
||||
auth.WithJWT(dummyKeyFunc),
|
||||
),
|
||||
func(o *goja.Object) {
|
||||
if err := o.Set("CLAIM_ROLE", "role"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_ROLE' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property"))
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
var dummySecret = []byte("not_so_secret")
|
||||
|
||||
func dummyKeyFunc(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
|
||||
return dummySecret, nil
|
||||
}
|
||||
|
||||
func dummyAuthMiddleware(subject, role, username string) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
unauthenticated := subject == "" && role == "" && username == ""
|
||||
|
||||
if unauthenticated {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
claims := jwt.MapClaims{
|
||||
"nbf": time.Now().UTC().Unix(),
|
||||
}
|
||||
|
||||
if subject != "" {
|
||||
claims["sub"] = subject
|
||||
}
|
||||
|
||||
if role != "" {
|
||||
claims["role"] = role
|
||||
}
|
||||
|
||||
if username != "" {
|
||||
claims["preferred_username"] = username
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
rawToken, err := token.SignedString(dummySecret)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not sign token", logger.E(errors.WithStack(err)))
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Add("Authorization", "Bearer "+rawToken)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user