Compare commits
1 Commits
09da1c6ce9
...
d2472623f2
Author | SHA1 | Date | |
---|---|---|---|
d2472623f2 |
@ -34,6 +34,7 @@ import (
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -203,6 +204,7 @@ func runApp(ctx context.Context, path, address, documentStoreDSN, blobStoreDSN,
|
||||
|
||||
deps := &moduleDeps{}
|
||||
funcs := []ModuleDepFunc{
|
||||
initAppID(manifest),
|
||||
initMemoryBus,
|
||||
initDatastores(documentStoreDSN, blobStoreDSN, shareStoreDSN, manifest.ID),
|
||||
initAccounts(accountsFile, manifest.ID),
|
||||
@ -223,6 +225,7 @@ func runApp(ctx context.Context, path, address, documentStoreDSN, blobStoreDSN,
|
||||
authModule.Mount(
|
||||
authHTTP.NewLocalHandler(
|
||||
key,
|
||||
jwa.HS256,
|
||||
authHTTP.WithRoutePrefix("/auth"),
|
||||
authHTTP.WithAccounts(deps.Accounts...),
|
||||
),
|
||||
@ -232,7 +235,7 @@ func runApp(ctx context.Context, path, address, documentStoreDSN, blobStoreDSN,
|
||||
),
|
||||
),
|
||||
appHTTP.WithHTTPMiddlewares(
|
||||
authModuleMiddleware.AnonymousUser(key),
|
||||
authModuleMiddleware.AnonymousUser(key, jwa.HS256),
|
||||
),
|
||||
)
|
||||
if err := handler.Load(bundle); err != nil {
|
||||
@ -409,6 +412,13 @@ func newAppRepository(host string, basePort uint64, manifests ...*app.Manifest)
|
||||
)
|
||||
}
|
||||
|
||||
func initAppID(manifest *app.Manifest) ModuleDepFunc {
|
||||
return func(deps *moduleDeps) error {
|
||||
deps.AppID = manifest.ID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func initAppRepository(repo appModule.Repository) ModuleDepFunc {
|
||||
return func(deps *moduleDeps) error {
|
||||
deps.AppRepository = repo
|
||||
|
53
cmd/storage-server/command/auth/new_token.go
Normal file
53
cmd/storage-server/command/auth/new_token.go
Normal file
@ -0,0 +1,53 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/cmd/storage-server/command/flag"
|
||||
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func NewToken() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "new-token",
|
||||
Usage: "Generate new authentication token",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "tenant",
|
||||
},
|
||||
flag.PrivateKey,
|
||||
flag.PrivateKeySigningAlgorithm,
|
||||
flag.PrivateKeyDefaultSize,
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
privateKeyFile := flag.GetPrivateKey(ctx)
|
||||
signingAlgorithm := flag.GetSigningAlgorithm(ctx)
|
||||
privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx)
|
||||
tenant := ctx.String("tenant")
|
||||
|
||||
privateKey, err := jwtutil.LoadOrGenerateKey(
|
||||
privateKeyFile,
|
||||
privateKeyDefaultSize,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
claims := map[string]any{
|
||||
"tenant": tenant,
|
||||
}
|
||||
|
||||
token, err := jwtutil.SignedToken(privateKey, jwa.SignatureAlgorithm(signingAlgorithm), claims)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not generate signed token")
|
||||
}
|
||||
|
||||
fmt.Println(string(token))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
@ -6,8 +6,10 @@ import (
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "auth",
|
||||
Usage: "Auth related command",
|
||||
Subcommands: []*cli.Command{},
|
||||
Name: "auth",
|
||||
Usage: "Auth related command",
|
||||
Subcommands: []*cli.Command{
|
||||
NewToken(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
43
cmd/storage-server/command/flag/flag.go
Normal file
43
cmd/storage-server/command/flag/flag.go
Normal file
@ -0,0 +1,43 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const PrivateKeyFlagName = "private-key"
|
||||
|
||||
var PrivateKey = &cli.StringFlag{
|
||||
Name: PrivateKeyFlagName,
|
||||
EnvVars: []string{"STORAGE_SERVER_PRIVATE_KEY"},
|
||||
Value: "storage-server.key",
|
||||
TakesFile: true,
|
||||
}
|
||||
|
||||
func GetPrivateKey(ctx *cli.Context) string {
|
||||
return ctx.String(PrivateKeyFlagName)
|
||||
}
|
||||
|
||||
const SigningAlgorithmFlagName = "signing-algorithm"
|
||||
|
||||
var PrivateKeySigningAlgorithm = &cli.StringFlag{
|
||||
Name: SigningAlgorithmFlagName,
|
||||
EnvVars: []string{"STORAGE_SERVER_PRIVATE_KEY_SIGNING_ALGORITHM"},
|
||||
Value: jwa.RS256.String(),
|
||||
}
|
||||
|
||||
func GetSigningAlgorithm(ctx *cli.Context) string {
|
||||
return ctx.String(SigningAlgorithmFlagName)
|
||||
}
|
||||
|
||||
const PrivateKeyDefaultSizeFlagName = "private-key-default-size"
|
||||
|
||||
var PrivateKeyDefaultSize = &cli.IntFlag{
|
||||
Name: PrivateKeyDefaultSizeFlagName,
|
||||
EnvVars: []string{"STORAGE_SERVER_PRIVATE_KEY_DEFAULT_SIZE"},
|
||||
Value: 2048,
|
||||
}
|
||||
|
||||
func GetPrivateKeyDefaultSize(ctx *cli.Context) int {
|
||||
return ctx.Int(PrivateKeyDefaultSizeFlagName)
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
"github.com/keegancsmith/rpc"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
@ -19,6 +20,7 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
// Register storage drivers
|
||||
"forge.cadoles.com/arcad/edge/cmd/storage-server/command/flag"
|
||||
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/driver"
|
||||
@ -53,12 +55,9 @@ func Run() *cli.Command {
|
||||
EnvVars: []string{"STORAGE_SERVER_SHARESTORE_DSN_PATTERN"},
|
||||
Value: fmt.Sprintf("sqlite://data/%%TENANT%%/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d", (60 * time.Second).Milliseconds()),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "private-key",
|
||||
EnvVars: []string{"STORAGE_SERVER_PRIVATE_KEY"},
|
||||
Value: "storage-server.key",
|
||||
TakesFile: true,
|
||||
},
|
||||
flag.PrivateKey,
|
||||
flag.PrivateKeySigningAlgorithm,
|
||||
flag.PrivateKeyDefaultSize,
|
||||
&cli.DurationFlag{
|
||||
Name: "cache-ttl",
|
||||
EnvVars: []string{"STORAGE_SERVER_CACHE_TTL"},
|
||||
@ -77,16 +76,21 @@ func Run() *cli.Command {
|
||||
shareStoreDSNPattern := ctx.String("sharestore-dsn-pattern")
|
||||
cacheSize := ctx.Int("cache-size")
|
||||
cacheTTL := ctx.Duration("cache-ttl")
|
||||
privateKeyFile := ctx.String("private-key")
|
||||
privateKeyFile := flag.GetPrivateKey(ctx)
|
||||
signingAlgorithm := flag.GetSigningAlgorithm(ctx)
|
||||
privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx)
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
rsaKey, err := jwtutil.LoadOrGenerateRSAKey(privateKeyFile, 2048)
|
||||
privateKey, err := jwtutil.LoadOrGenerateKey(
|
||||
privateKeyFile,
|
||||
privateKeyDefaultSize,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
privateKey, err := jwtutil.FromRSA(rsaKey)
|
||||
publicKey, err := privateKey.PublicKey()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@ -120,11 +124,11 @@ func Run() *cli.Command {
|
||||
|
||||
router.Use(middleware.RealIP)
|
||||
router.Use(middleware.Logger)
|
||||
router.Use(authenticate(privateKey))
|
||||
router.Use(authenticate(publicKey, jwa.SignatureAlgorithm(signingAlgorithm)))
|
||||
|
||||
router.Handle("/blobstore", createStoreHandler(getBlobStoreServer, blobStoreDSNPattern, cacheSize, cacheTTL))
|
||||
router.Handle("/documentstore", createStoreHandler(getDocumentStoreServer, documentStoreDSNPattern, cacheSize, cacheTTL))
|
||||
router.Handle("/sharestore", createStoreHandler(getShareStoreServer, shareStoreDSNPattern, cacheSize, cacheTTL))
|
||||
router.Handle("/blobstore", createStoreHandler(getBlobStoreServer, blobStoreDSNPattern, true, cacheSize, cacheTTL))
|
||||
router.Handle("/documentstore", createStoreHandler(getDocumentStoreServer, documentStoreDSNPattern, true, cacheSize, cacheTTL))
|
||||
router.Handle("/sharestore", createStoreHandler(getShareStoreServer, shareStoreDSNPattern, false, cacheSize, cacheTTL))
|
||||
|
||||
if err := http.ListenAndServe(addr, router); err != nil {
|
||||
return errors.WithStack(err)
|
||||
@ -171,17 +175,19 @@ func createGetCachedStoreServer[T any](storeFactory func(dsn string) (T, error),
|
||||
}
|
||||
}
|
||||
|
||||
func createStoreHandler(getStoreServer getRPCServerFunc, dsnPattern string, cacheSize int, cacheTTL time.Duration) http.Handler {
|
||||
func createStoreHandler(getStoreServer getRPCServerFunc, dsnPattern string, appIDRequired bool, cacheSize int, cacheTTL time.Duration) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tenant := r.URL.Query().Get("tenant")
|
||||
if tenant == "" {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
ctx := r.Context()
|
||||
|
||||
tenant, ok := ctx.Value("tenant").(string)
|
||||
if !ok || tenant == "" {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
appID := r.URL.Query().Get("appId")
|
||||
if tenant == "" {
|
||||
if appIDRequired && appID == "" {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
@ -199,20 +205,41 @@ func createStoreHandler(getStoreServer getRPCServerFunc, dsnPattern string, cach
|
||||
})
|
||||
}
|
||||
|
||||
func authenticate(privateKey jwk.Key) func(http.Handler) http.Handler {
|
||||
keySet, err := jwtutil.NewKeySet(privateKey)
|
||||
getKeySet := func() (jwk.Set, error) {
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return keySet, nil
|
||||
}
|
||||
func authenticate(privateKey jwk.Key, signingAlgorithm jwa.SignatureAlgorithm) func(http.Handler) http.Handler {
|
||||
var (
|
||||
createKeySet sync.Once
|
||||
err error
|
||||
getKeySet jwtutil.GetKeySetFunc
|
||||
)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
createKeySet.Do(func() {
|
||||
err = privateKey.Set(jwk.AlgorithmKey, signingAlgorithm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var keySet jwk.Set
|
||||
|
||||
keySet, err = jwtutil.NewKeySet(privateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
getKeySet = func() (jwk.Set, error) {
|
||||
return keySet, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not create keyset accessor", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwtutil.FindToken(r, getKeySet, jwtutil.WithFinders(
|
||||
jwtutil.FindTokenFromQueryString("token"),
|
||||
))
|
||||
@ -249,24 +276,6 @@ func authenticate(privateKey jwk.Key) func(http.Handler) http.Handler {
|
||||
|
||||
r = r.WithContext(context.WithValue(ctx, "tenant", tenant))
|
||||
|
||||
rawAppId, exists := tokenMap["appId"]
|
||||
if !exists {
|
||||
logger.Warn(ctx, "could not find appId claim", logger.F("token", token))
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
appId, ok := rawAppId.(string)
|
||||
if !ok {
|
||||
logger.Warn(ctx, "unexpected appId claim value", logger.F("token", token))
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r = r.WithContext(context.WithValue(ctx, "appId", appId))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
mocha.checkLeaks();
|
||||
</script>
|
||||
<script src="/edge/sdk/client.js"></script>
|
||||
<script src="/test/util.js"></script>
|
||||
<script src="/test/client-sdk.js"></script>
|
||||
<script src="/test/auth-module.js"></script>
|
||||
<script src="/test/net-module.js"></script>
|
||||
@ -31,6 +32,7 @@
|
||||
<script src="/test/file-module.js"></script>
|
||||
<script src="/test/app-module.js"></script>
|
||||
<script src="/test/fetch-module.js"></script>
|
||||
<script src="/test/share-module.js"></script>
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
|
||||
@ -44,6 +46,7 @@
|
||||
.setItem('file-module', 'File Module', { linkUrl: '/?grep=File%20Module', order: 6})
|
||||
.setItem('app-module', 'App Module', { linkUrl: '/?grep=App%20Module' , order: 7})
|
||||
.setItem('fetch-module', 'Fetch Module', { linkUrl: '/?grep=Fetch%20Module' , order: 8})
|
||||
.setItem('share-module', 'Share Module', { linkUrl: '/?grep=Share%20Module' , order: 9})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
29
misc/client-sdk-testsuite/src/public/test/share-module.js
Normal file
29
misc/client-sdk-testsuite/src/public/test/share-module.js
Normal file
@ -0,0 +1,29 @@
|
||||
describe('Share Module', function() {
|
||||
|
||||
before(() => {
|
||||
return Edge.Client.connect();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
Edge.Client.disconnect();
|
||||
});
|
||||
|
||||
it('should create a new resource and find it', async () => {
|
||||
const resource = await TestUtil.serverSideCall('share', 'upsertResource', 'my-resource', { name: "color", type: "text", value: "red" });
|
||||
chai.assert.isNotNull(resource);
|
||||
chai.assert.equal(resource.origin, 'edge.sdk.client.test')
|
||||
|
||||
|
||||
const results = await TestUtil.serverSideCall('share', 'findResources', 'color', 'text');
|
||||
chai.assert.isAbove(results.length, 0);
|
||||
|
||||
const createdResource = results.find(res => {
|
||||
return res.origin === 'edge.sdk.client.test' &&
|
||||
res.attributes.find(attr => attr.name === 'color' && attr.type === 'text')
|
||||
})
|
||||
|
||||
chai.assert.isNotNull(createdResource)
|
||||
|
||||
console.log(createdResource)
|
||||
});
|
||||
});
|
7
misc/client-sdk-testsuite/src/public/test/util.js
Normal file
7
misc/client-sdk-testsuite/src/public/test/util.js
Normal file
@ -0,0 +1,7 @@
|
||||
(function(TestUtil) {
|
||||
TestUtil.serverSideCall = (module, func, ...args) => {
|
||||
return Edge.Client.rpc('serverSideCall', { module, func, args })
|
||||
}
|
||||
console.log(TestUtil)
|
||||
|
||||
}(globalThis.TestUtil = globalThis.TestUtil || {}));
|
@ -15,6 +15,8 @@ function onInit() {
|
||||
rpc.register("listApps");
|
||||
rpc.register("getApp");
|
||||
rpc.register("getAppUrl");
|
||||
|
||||
rpc.register("serverSideCall", serverSideCall)
|
||||
}
|
||||
|
||||
// Called for each client message
|
||||
@ -103,4 +105,9 @@ function getAppUrl(ctx, params) {
|
||||
|
||||
function onClientFetch(ctx, url, remoteAddr) {
|
||||
return { allow: url === 'http://example.com' };
|
||||
}
|
||||
|
||||
function serverSideCall(ctx, params) {
|
||||
console.log("Calling %s.%s(args...)", params.module, params.func)
|
||||
return globalThis[params.module][params.func].call(null, ctx, ...params.args);
|
||||
}
|
11
modd.conf
11
modd.conf
@ -2,16 +2,19 @@
|
||||
**/*.tmpl
|
||||
pkg/sdk/client/src/**/*.js
|
||||
pkg/sdk/client/src/**/*.ts
|
||||
misc/client-sdk-testsuite/src/**/*
|
||||
misc/client-sdk-testsuite/dist/server/*.js
|
||||
modd.conf
|
||||
{
|
||||
prep: make build-sdk
|
||||
prep: make build-client-sdk-test-app
|
||||
prep: make build
|
||||
prep: make build-sdk build-cli build-storage-server
|
||||
daemon: make run-app
|
||||
daemon: make run-storage-server
|
||||
}
|
||||
|
||||
misc/client-sdk-testsuite/src/**/*
|
||||
{
|
||||
prep: make build-client-sdk-test-app
|
||||
}
|
||||
|
||||
**/*.go {
|
||||
prep: make GOTEST_ARGS="-short" test
|
||||
}
|
71
pkg/jwtutil/io.go
Normal file
71
pkg/jwtutil/io.go
Normal file
@ -0,0 +1,71 @@
|
||||
package jwtutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"os"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func LoadOrGenerateKey(path string, defaultKeySize int) (jwk.Key, error) {
|
||||
key, err := LoadKey(path)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
key, err = GenerateKey(defaultKeySize)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := SaveKey(path, key); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func LoadKey(path string) (jwk.Key, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
key, err := jwk.ParseKey(data, jwk.WithPEM(true))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func SaveKey(path string, key jwk.Key) error {
|
||||
data, err := jwk.Pem(key)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, os.FileMode(0600)); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateKey(keySize int) (jwk.Key, error) {
|
||||
rsaKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
key, err := jwk.FromRaw(rsaKey)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package jwtutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func FromRSA(privateKey *rsa.PrivateKey) (jwk.Key, error) {
|
||||
key, err := jwk.FromRaw(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := key.Set(jwk.AlgorithmKey, jwa.RS256); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func LoadOrGenerateRSAKey(path string, size int) (*rsa.PrivateKey, error) {
|
||||
privateKey, err := LoadRSAKey(path)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
privateKey, err = GenerateRSAKey(size)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := SaveRSAKey(path, privateKey); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func LoadRSAKey(path string) (*rsa.PrivateKey, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to parse pem block")
|
||||
}
|
||||
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func SaveRSAKey(path string, privateKey *rsa.PrivateKey) error {
|
||||
data := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
pem := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: data,
|
||||
},
|
||||
)
|
||||
|
||||
if err := os.WriteFile(path, pem, os.FileMode(0600)); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateRSAKey(size int) (*rsa.PrivateKey, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, size)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
@ -3,12 +3,13 @@ package jwtutil
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GenerateSignedToken(key jwk.Key, claims map[string]any) ([]byte, error) {
|
||||
func SignedToken(key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm, claims map[string]any) ([]byte, error) {
|
||||
token := jwt.New()
|
||||
|
||||
if err := token.Set(jwt.NotBeforeKey, time.Now()); err != nil {
|
||||
@ -21,11 +22,11 @@ func GenerateSignedToken(key jwk.Key, claims map[string]any) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := token.Set(jwk.AlgorithmKey, key.Algorithm()); err != nil {
|
||||
if err := token.Set(jwk.AlgorithmKey, signingAlgorithm); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
rawToken, err := jwt.Sign(token, jwt.WithKey(key.Algorithm(), key))
|
||||
rawToken, err := jwt.Sign(token, jwt.WithKey(signingAlgorithm, key))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
@ -30,11 +31,12 @@ func init() {
|
||||
}
|
||||
|
||||
type LocalHandler struct {
|
||||
router chi.Router
|
||||
key jwk.Key
|
||||
getCookieDomain GetCookieDomainFunc
|
||||
cookieDuration time.Duration
|
||||
accounts map[string]LocalAccount
|
||||
router chi.Router
|
||||
key jwk.Key
|
||||
signingAlgorithm jwa.SignatureAlgorithm
|
||||
getCookieDomain GetCookieDomainFunc
|
||||
cookieDuration time.Duration
|
||||
accounts map[string]LocalAccount
|
||||
}
|
||||
|
||||
func (h *LocalHandler) initRouter(prefix string) {
|
||||
@ -111,7 +113,7 @@ func (h *LocalHandler) handleForm(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
account.Claims[auth.ClaimIssuer] = "local"
|
||||
|
||||
token, err := jwtutil.GenerateSignedToken(h.key, account.Claims)
|
||||
token, err := jwtutil.SignedToken(h.key, h.signingAlgorithm, account.Claims)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not generate signed token", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
@ -180,17 +182,18 @@ func (h *LocalHandler) authenticate(username, password string) (*LocalAccount, e
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func NewLocalHandler(key jwk.Key, funcs ...LocalHandlerOptionFunc) *LocalHandler {
|
||||
func NewLocalHandler(key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm, funcs ...LocalHandlerOptionFunc) *LocalHandler {
|
||||
opts := defaultLocalHandlerOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
handler := &LocalHandler{
|
||||
key: key,
|
||||
accounts: toAccountsMap(opts.Accounts),
|
||||
getCookieDomain: opts.GetCookieDomain,
|
||||
cookieDuration: opts.CookieDuration,
|
||||
key: key,
|
||||
signingAlgorithm: signingAlgorithm,
|
||||
accounts: toAccountsMap(opts.Accounts),
|
||||
getCookieDomain: opts.GetCookieDomain,
|
||||
cookieDuration: opts.CookieDuration,
|
||||
}
|
||||
|
||||
handler.initRouter(opts.RoutePrefix)
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"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"
|
||||
@ -17,7 +18,7 @@ import (
|
||||
|
||||
const AnonIssuer = "anon"
|
||||
|
||||
func AnonymousUser(key jwk.Key, funcs ...AnonymousUserOptionFunc) func(next http.Handler) http.Handler {
|
||||
func AnonymousUser(key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm, funcs ...AnonymousUserOptionFunc) func(next http.Handler) http.Handler {
|
||||
opts := defaultAnonymousUserOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
@ -65,7 +66,7 @@ func AnonymousUser(key jwk.Key, funcs ...AnonymousUserOptionFunc) func(next http
|
||||
auth.ClaimEdgeTenant: opts.Tenant,
|
||||
}
|
||||
|
||||
token, err := jwtutil.GenerateSignedToken(key, claims)
|
||||
token, err := jwtutil.SignedToken(key, signingAlgorithm, claims)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not generate signed token", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
Loading…
x
Reference in New Issue
Block a user