From cfeafebd68018238f20faea5b002cc2508b91af8 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 18 Dec 2023 22:02:09 +0000 Subject: [PATCH] Add more API and config stuff and add basic FETokens --- .gitignore | 1 + cmd/addUser.go | 60 ++++ cmd/generateKeys.go | 63 ++++ cmd/root.go | 10 +- go.mod | 9 + go.sum | 30 ++ gqlgen.yml | 2 +- graph/generated.go | 270 +++++++++++++++--- graph/model/fesession.go | 18 ++ graph/model/models_gen.go | 8 - graph/schema.graphqls | 22 +- graph/schema.resolvers.go | 232 ++++++++++++++- internal/entities/user/user.go | 6 + internal/helpers/fetoken/fetoken.go | 93 ++++++ internal/keystore/keystore.go | 76 +++++ internal/machines/fesession/db.go | 70 +++++ .../{fe_session => fesession}/fe_session.go | 52 ++-- internal/server/api.go | 26 +- 18 files changed, 959 insertions(+), 89 deletions(-) create mode 100644 cmd/addUser.go create mode 100644 cmd/generateKeys.go create mode 100644 graph/model/fesession.go create mode 100644 internal/helpers/fetoken/fetoken.go create mode 100644 internal/keystore/keystore.go create mode 100644 internal/machines/fesession/db.go rename internal/machines/{fe_session => fesession}/fe_session.go (73%) diff --git a/.gitignore b/.gitignore index 60048fc..8ae8fbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /config.yaml +/.WROOF_KEYS.json ### VisualStudioCode ### .vscode/* diff --git a/cmd/addUser.go b/cmd/addUser.go new file mode 100644 index 0000000..2ccb536 --- /dev/null +++ b/cmd/addUser.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "bufio" + "context" + "fmt" + "os" + + "git.1in9.net/raider/wroofauth/internal/database" + "git.1in9.net/raider/wroofauth/internal/entities" + "git.1in9.net/raider/wroofauth/internal/entities/user" + "github.com/spf13/cobra" +) + +// addUserCmd represents the generateKeys command +var addUserCmd = &cobra.Command{ + Use: "add-user [username]", + Short: "Creates a user", + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.Usage() + return + } + ctx := context.Background() + + database.MongoConnect(ctx) + database.RedisConnect(ctx) + + entities.SetupEntityDatabases() + + newUser := user.New() + + newUser.Username = &args[0] + + reader := bufio.NewReader(os.Stdin) + + fmt.Printf("Password for %s: ", args[0]) + + password, _, err := reader.ReadLine() + if err != nil { + fmt.Println(err) + return + } + + passwordString := string(password) + + newUser.SetPassword(passwordString) + + newUser.Persist(context.Background()) + + fmt.Printf("User \"%s\" created. ID: %s\n", args[0], newUser.ID) + + database.MongoDisconnect(ctx) + database.RedisDisconnect() + }, +} + +func init() { + rootCmd.AddCommand(addUserCmd) +} diff --git a/cmd/generateKeys.go b/cmd/generateKeys.go new file mode 100644 index 0000000..bd97f22 --- /dev/null +++ b/cmd/generateKeys.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "crypto/ed25519" + "crypto/rand" + "encoding/json" + "fmt" + + "git.1in9.net/raider/wroofauth/internal/keystore" + "github.com/lestrrat-go/jwx/jwk" + "github.com/spf13/cobra" +) + +// generateKeysCmd represents the generateKeys command +var generateKeysCmd = &cobra.Command{ + Use: "generate-keys", + Short: "Generates a new keys", + Run: func(cmd *cobra.Command, args []string) { + public, private, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + cmd.PrintErrln(err) + return + } + + key, err := jwk.New(private) + if err != nil { + cmd.PrintErrln(err) + return + } + + keyPublic, err := jwk.New(public) + if err != nil { + cmd.PrintErrln(err) + return + } + + kid, err := keystore.GenerateKeyID() + if err != nil { + cmd.PrintErrln(err) + return + } + + key.Set(jwk.KeyIDKey, kid) + keyPublic.Set(jwk.KeyIDKey, kid) + key.Set(jwk.AlgorithmKey, "EdDSA") + keyPublic.Set(jwk.AlgorithmKey, "EdDSA") + + keystore.Global.Add(key) + keystore.Global.Add(keyPublic) + + buf, err := json.MarshalIndent(keystore.Global, "", " ") + if err != nil { + fmt.Printf("failed to marshal key into JSON: %s\n", err) + return + } + fmt.Printf("%s\n", buf) + + }, +} + +func init() { + rootCmd.AddCommand(generateKeysCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 5bded10..4c5962c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,7 +4,9 @@ import ( "os" "time" + "git.1in9.net/raider/wroofauth/internal/keystore" "git.1in9.net/raider/wroofauth/internal/logger" + "github.com/lestrrat-go/jwx/jwk" "github.com/spf13/cobra" "github.com/spf13/viper" "go.uber.org/zap" @@ -68,10 +70,10 @@ func init() { viper.BindEnv("http.wyrd_url", "WYRD_URL") viper.BindEnv("http.frontend_url", "WYRD_FRONTEND_URL")*/ - /*viper.SetDefault("crypto.keys", jwk.NewSet()) + viper.SetDefault("crypto.keys", jwk.NewSet()) viper.SetDefault("crypto.keyfile", nil) - viper.SetDefault("crypto.use_key", "") - viper.BindEnv("crypto.keyfile", "WYRD_KEYFILE")*/ + viper.SetDefault("crypto.use_key.frontend", "") + viper.BindEnv("crypto.keyfile", "WROOF_KEYS") viper.SetDefault("totp.issuer", "WroofAuth") // Used for 2fa issuer value @@ -122,4 +124,6 @@ func loadConfig() { logger.StartLogger() // Restart Logger, as we may have changed our loglevel logger.Sugar.Info("Using config file:", viper.ConfigFileUsed()) + + keystore.LoadKeystore() } diff --git a/go.mod b/go.mod index bc6778b..3fc94de 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/bsm/redislock v0.9.4 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + github.com/lestrrat-go/jwx v1.2.26 github.com/pquerna/otp v1.4.0 github.com/prometheus/client_golang v1.4.0 github.com/redis/go-redis/v9 v9.2.1 @@ -26,9 +27,11 @@ require ( github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-chi/chi v1.5.5 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/google/uuid v1.3.1 // indirect @@ -37,11 +40,17 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.17.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.1 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.9.1 // indirect github.com/prometheus/procfs v0.0.8 // indirect diff --git a/go.sum b/go.sum index 8721540..1443c68 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= @@ -151,6 +154,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -298,6 +303,19 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= +github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.26 h1:4iFo8FPRZGDYe1t19mQP0zTRqA7n8HnJ5lkIiDvJcB0= +github.com/lestrrat-go/jwx v1.2.26/go.mod h1:MaiCdGbn3/cckbOFSCluJlJMmp9dmZm5hDuIkx8ftpQ= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -341,6 +359,7 @@ github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6 github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -481,6 +500,7 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -522,6 +542,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -568,6 +589,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -597,6 +620,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -670,10 +694,14 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -685,6 +713,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -746,6 +775,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gqlgen.yml b/gqlgen.yml index 1fdf3c7..36ff98d 100644 --- a/gqlgen.yml +++ b/gqlgen.yml @@ -66,7 +66,7 @@ resolver: # gqlgen will search for any type names in the schema in these go packages # if they match it will use them, otherwise it will generate them. autobind: -# - "git.1in9.net/raider/wroofauth/graph/model" + - "git.1in9.net/raider/wroofauth/graph/model" # This section declares type mapping between the GraphQL and go type systems # diff --git a/graph/generated.go b/graph/generated.go index 4057d70..598ad57 100644 --- a/graph/generated.go +++ b/graph/generated.go @@ -44,6 +44,7 @@ type ResolverRoot interface { } type DirectiveRoot struct { + FeToken func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) Internal func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) Self func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) } @@ -56,11 +57,12 @@ type ComplexityRoot struct { Mutation struct { AuthFeSessionCreate func(childComplexity int) int - AuthFeSessionIdentify func(childComplexity int, identification string) int - AuthFeSessionLock func(childComplexity int) int - AuthFeSessionLogout func(childComplexity int) int - AuthFeSessionPassword func(childComplexity int, password string) int - AuthFeSessionTotp func(childComplexity int, totp string) int + AuthFeSessionIdentify func(childComplexity int, session string, identification string) int + AuthFeSessionLock func(childComplexity int, session string) int + AuthFeSessionLogout func(childComplexity int, session string) int + AuthFeSessionPassword func(childComplexity int, session string, password string) int + AuthFeSessionTotp func(childComplexity int, session string, totp string) int + AuthFeTokenCreate func(childComplexity int) int } Query struct { @@ -86,11 +88,12 @@ type ComplexityRoot struct { type MutationResolver interface { AuthFeSessionCreate(ctx context.Context) (*model.FeSession, error) - AuthFeSessionIdentify(ctx context.Context, identification string) (*model.FeSession, error) - AuthFeSessionPassword(ctx context.Context, password string) (*model.FeSession, error) - AuthFeSessionTotp(ctx context.Context, totp string) (*model.FeSession, error) - AuthFeSessionLock(ctx context.Context) (*model.FeSession, error) - AuthFeSessionLogout(ctx context.Context) (*model.FeSession, error) + AuthFeSessionIdentify(ctx context.Context, session string, identification string) (*model.FeSession, error) + AuthFeSessionPassword(ctx context.Context, session string, password string) (*model.FeSession, error) + AuthFeSessionTotp(ctx context.Context, session string, totp string) (*model.FeSession, error) + AuthFeSessionLock(ctx context.Context, session string) (*model.FeSession, error) + AuthFeSessionLogout(ctx context.Context, session string) (*model.FeSession, error) + AuthFeTokenCreate(ctx context.Context) (string, error) } type QueryResolver interface { Self(ctx context.Context) (model.Actor, error) @@ -149,21 +152,31 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.AuthFeSessionIdentify(childComplexity, args["identification"].(string)), true + return e.complexity.Mutation.AuthFeSessionIdentify(childComplexity, args["session"].(string), args["identification"].(string)), true case "Mutation.authFeSessionLock": if e.complexity.Mutation.AuthFeSessionLock == nil { break } - return e.complexity.Mutation.AuthFeSessionLock(childComplexity), true + args, err := ec.field_Mutation_authFeSessionLock_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AuthFeSessionLock(childComplexity, args["session"].(string)), true case "Mutation.authFeSessionLogout": if e.complexity.Mutation.AuthFeSessionLogout == nil { break } - return e.complexity.Mutation.AuthFeSessionLogout(childComplexity), true + args, err := ec.field_Mutation_authFeSessionLogout_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AuthFeSessionLogout(childComplexity, args["session"].(string)), true case "Mutation.authFeSessionPassword": if e.complexity.Mutation.AuthFeSessionPassword == nil { @@ -175,7 +188,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.AuthFeSessionPassword(childComplexity, args["password"].(string)), true + return e.complexity.Mutation.AuthFeSessionPassword(childComplexity, args["session"].(string), args["password"].(string)), true case "Mutation.authFeSessionTOTP": if e.complexity.Mutation.AuthFeSessionTotp == nil { @@ -187,7 +200,14 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.AuthFeSessionTotp(childComplexity, args["totp"].(string)), true + return e.complexity.Mutation.AuthFeSessionTotp(childComplexity, args["session"].(string), args["totp"].(string)), true + + case "Mutation.authFeTokenCreate": + if e.complexity.Mutation.AuthFeTokenCreate == nil { + break + } + + return e.complexity.Mutation.AuthFeTokenCreate(childComplexity), true case "Query.authFeSession": if e.complexity.Query.AuthFeSession == nil { @@ -408,14 +428,53 @@ func (ec *executionContext) field_Mutation_authFeSessionIdentify_args(ctx contex var err error args := map[string]interface{}{} var arg0 string - if tmp, ok := rawArgs["identification"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("identification")) - arg0, err = ec.unmarshalNString2string(ctx, tmp) + if tmp, ok := rawArgs["session"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("session")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) if err != nil { return nil, err } } - args["identification"] = arg0 + args["session"] = arg0 + var arg1 string + if tmp, ok := rawArgs["identification"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("identification")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["identification"] = arg1 + return args, nil +} + +func (ec *executionContext) field_Mutation_authFeSessionLock_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["session"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("session")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["session"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Mutation_authFeSessionLogout_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["session"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("session")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["session"] = arg0 return args, nil } @@ -423,14 +482,23 @@ func (ec *executionContext) field_Mutation_authFeSessionPassword_args(ctx contex var err error args := map[string]interface{}{} var arg0 string - if tmp, ok := rawArgs["password"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password")) - arg0, err = ec.unmarshalNString2string(ctx, tmp) + if tmp, ok := rawArgs["session"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("session")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) if err != nil { return nil, err } } - args["password"] = arg0 + args["session"] = arg0 + var arg1 string + if tmp, ok := rawArgs["password"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["password"] = arg1 return args, nil } @@ -438,14 +506,23 @@ func (ec *executionContext) field_Mutation_authFeSessionTOTP_args(ctx context.Co var err error args := map[string]interface{}{} var arg0 string - if tmp, ok := rawArgs["totp"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("totp")) - arg0, err = ec.unmarshalNString2string(ctx, tmp) + if tmp, ok := rawArgs["session"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("session")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) if err != nil { return nil, err } } - args["totp"] = arg0 + args["session"] = arg0 + var arg1 string + if tmp, ok := rawArgs["totp"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("totp")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["totp"] = arg1 return args, nil } @@ -658,8 +735,14 @@ func (ec *executionContext) _Mutation_authFeSessionCreate(ctx context.Context, f } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -720,7 +803,7 @@ func (ec *executionContext) _Mutation_authFeSessionIdentify(ctx context.Context, resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().AuthFeSessionIdentify(rctx, fc.Args["identification"].(string)) + return ec.resolvers.Mutation().AuthFeSessionIdentify(rctx, fc.Args["session"].(string), fc.Args["identification"].(string)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.Internal == nil { @@ -728,8 +811,14 @@ func (ec *executionContext) _Mutation_authFeSessionIdentify(ctx context.Context, } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -801,7 +890,7 @@ func (ec *executionContext) _Mutation_authFeSessionPassword(ctx context.Context, resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().AuthFeSessionPassword(rctx, fc.Args["password"].(string)) + return ec.resolvers.Mutation().AuthFeSessionPassword(rctx, fc.Args["session"].(string), fc.Args["password"].(string)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.Internal == nil { @@ -809,8 +898,14 @@ func (ec *executionContext) _Mutation_authFeSessionPassword(ctx context.Context, } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -882,7 +977,7 @@ func (ec *executionContext) _Mutation_authFeSessionTOTP(ctx context.Context, fie resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().AuthFeSessionTotp(rctx, fc.Args["totp"].(string)) + return ec.resolvers.Mutation().AuthFeSessionTotp(rctx, fc.Args["session"].(string), fc.Args["totp"].(string)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.Internal == nil { @@ -890,8 +985,14 @@ func (ec *executionContext) _Mutation_authFeSessionTOTP(ctx context.Context, fie } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -963,7 +1064,7 @@ func (ec *executionContext) _Mutation_authFeSessionLock(ctx context.Context, fie resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().AuthFeSessionLock(rctx) + return ec.resolvers.Mutation().AuthFeSessionLock(rctx, fc.Args["session"].(string)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.Internal == nil { @@ -971,8 +1072,14 @@ func (ec *executionContext) _Mutation_authFeSessionLock(ctx context.Context, fie } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -1015,6 +1122,17 @@ func (ec *executionContext) fieldContext_Mutation_authFeSessionLock(ctx context. return nil, fmt.Errorf("no field named %q was found under type FeSession", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_authFeSessionLock_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } return fc, nil } @@ -1033,7 +1151,7 @@ func (ec *executionContext) _Mutation_authFeSessionLogout(ctx context.Context, f resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().AuthFeSessionLogout(rctx) + return ec.resolvers.Mutation().AuthFeSessionLogout(rctx, fc.Args["session"].(string)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.Internal == nil { @@ -1041,8 +1159,14 @@ func (ec *executionContext) _Mutation_authFeSessionLogout(ctx context.Context, f } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -1085,6 +1209,61 @@ func (ec *executionContext) fieldContext_Mutation_authFeSessionLogout(ctx contex return nil, fmt.Errorf("no field named %q was found under type FeSession", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_authFeSessionLogout_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_authFeTokenCreate(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_authFeTokenCreate(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AuthFeTokenCreate(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_authFeTokenCreate(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } return fc, nil } @@ -1220,8 +1399,14 @@ func (ec *executionContext) _Query_authFeSession(ctx context.Context, field grap } return ec.directives.Internal(ctx, nil, directive0) } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.FeToken == nil { + return nil, errors.New("directive feToken is not implemented") + } + return ec.directives.FeToken(ctx, nil, directive1) + } - tmp, err := directive1(rctx) + tmp, err := directive2(rctx) if err != nil { return nil, graphql.ErrorOnPath(ctx, err) } @@ -3723,6 +3908,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "authFeTokenCreate": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_authFeTokenCreate(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/graph/model/fesession.go b/graph/model/fesession.go new file mode 100644 index 0000000..61d9cb6 --- /dev/null +++ b/graph/model/fesession.go @@ -0,0 +1,18 @@ +package model + +import "git.1in9.net/raider/wroofauth/internal/machines/fesession" + +type FeSession struct { + ID string `json:"id"` + State SessionState `json:"state"` +} + +func (FeSession) IsNode() {} +func (this FeSession) GetID() string { return this.ID } + +func FeSessionFromDb(fesession fesession.FeSession) FeSession { + return FeSession{ + ID: fesession.ID.Hex(), + State: SessionState(fesession.State), + } +} diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index f27a41c..976555f 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -17,14 +17,6 @@ type Node interface { GetID() string } -type FeSession struct { - ID string `json:"id"` - State SessionState `json:"state"` -} - -func (FeSession) IsNode() {} -func (this FeSession) GetID() string { return this.ID } - type SecondFactor struct { Enabled bool `json:"enabled"` Name string `json:"name"` diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 5c16fdb..48f7dbc 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -5,6 +5,12 @@ api clients. """ directive @internal on FIELD_DEFINITION +""" +Fields with @feToken require an frontend token +to be submitted with the request. +""" +directive @feToken on FIELD_DEFINITION + """ Fields with @self may only be queried when queried directly by the actor represented by the object. @@ -52,18 +58,20 @@ type Query { self: Actor! user(id: ID!): User! - authFeSession(id: ID!): FeSession! @internal + authFeSession(id: ID!): FeSession! @internal @feToken node(id: ID!): Node! } type Mutation { - authFeSessionCreate: FeSession! @internal - authFeSessionIdentify(identification: String!): FeSession! @internal - authFeSessionPassword(password: String!): FeSession! @internal - authFeSessionTOTP(totp: String!): FeSession! @internal - authFeSessionLock: FeSession! @internal - authFeSessionLogout: FeSession! @internal + authFeSessionCreate: FeSession! @internal @feToken + authFeSessionIdentify(session: ID!, identification: String!): FeSession! @internal @feToken + authFeSessionPassword(session: ID!, password: String!): FeSession! @internal @feToken + authFeSessionTOTP(session: ID!, totp: String!): FeSession! @internal @feToken + authFeSessionLock(session: ID!): FeSession! @internal @feToken + authFeSessionLogout(session: ID!): FeSession! @internal @feToken + + authFeTokenCreate: String! } interface Node { diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index 9908185..925c0eb 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -6,39 +6,235 @@ package graph import ( "context" + "errors" "fmt" + "time" "git.1in9.net/raider/wroofauth/graph/model" + "git.1in9.net/raider/wroofauth/internal/keystore" + "git.1in9.net/raider/wroofauth/internal/logger" + "git.1in9.net/raider/wroofauth/internal/machines/fesession" + "github.com/go-chi/chi/v5/middleware" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwt" + redis "github.com/redis/go-redis/v9" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/zap" ) // AuthFeSessionCreate is the resolver for the authFeSessionCreate field. func (r *mutationResolver) AuthFeSessionCreate(ctx context.Context) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSessionCreate - authFeSessionCreate")) + // TODO: Check auth + + feSession := fesession.NewSession(ctx, primitive.NilObjectID) // TODO: Put actual object here + err := feSession.Persist(ctx) + if err != nil { + logger.Logger.Warn("failed to create fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + modSession := model.FeSessionFromDb(*feSession) + return &modSession, nil } // AuthFeSessionIdentify is the resolver for the authFeSessionIdentify field. -func (r *mutationResolver) AuthFeSessionIdentify(ctx context.Context, identification string) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSessionIdentify - authFeSessionIdentify")) +func (r *mutationResolver) AuthFeSessionIdentify(ctx context.Context, session string, identification string) (*model.FeSession, error) { + // TODO: Check auth + + sessionId, err := primitive.ObjectIDFromHex(session) + if err != nil { + return nil, err + } + + feSession, err := fesession.GetById(ctx, sessionId) + if err != nil { + if err == redis.Nil { + return nil, errors.New("session not found") + } + logger.Logger.Warn("failed to load fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + err = feSession.HandleIdentification(ctx, identification) + if err != nil { + return nil, err + } + + err = feSession.Persist(ctx) + if err != nil { + logger.Logger.Warn("failed to persist fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + modSession := model.FeSessionFromDb(*feSession) + return &modSession, nil } // AuthFeSessionPassword is the resolver for the authFeSessionPassword field. -func (r *mutationResolver) AuthFeSessionPassword(ctx context.Context, password string) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSessionPassword - authFeSessionPassword")) +func (r *mutationResolver) AuthFeSessionPassword(ctx context.Context, session string, password string) (*model.FeSession, error) { + // TODO: Check auth + + sessionId, err := primitive.ObjectIDFromHex(session) + if err != nil { + return nil, err + } + + feSession, err := fesession.GetById(ctx, sessionId) + if err != nil { + if err == redis.Nil { + return nil, errors.New("session not found") + } + logger.Logger.Warn("failed to load fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + err = feSession.HandlePassword(ctx, password) + if err != nil { + return nil, err + } + + err = feSession.Persist(ctx) + if err != nil { + logger.Logger.Warn("failed to persist fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + modSession := model.FeSessionFromDb(*feSession) + return &modSession, nil } // AuthFeSessionTotp is the resolver for the authFeSessionTOTP field. -func (r *mutationResolver) AuthFeSessionTotp(ctx context.Context, totp string) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSessionTotp - authFeSessionTOTP")) +func (r *mutationResolver) AuthFeSessionTotp(ctx context.Context, session string, totp string) (*model.FeSession, error) { + // TODO: Check auth + + sessionId, err := primitive.ObjectIDFromHex(session) + if err != nil { + return nil, err + } + + feSession, err := fesession.GetById(ctx, sessionId) + if err != nil { + if err == redis.Nil { + return nil, errors.New("session not found") + } + logger.Logger.Warn("failed to load fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + err = feSession.HandleTOTP(ctx, totp) + if err != nil { + return nil, err + } + + err = feSession.Persist(ctx) + if err != nil { + logger.Logger.Warn("failed to persist fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + modSession := model.FeSessionFromDb(*feSession) + return &modSession, nil } // AuthFeSessionLock is the resolver for the authFeSessionLock field. -func (r *mutationResolver) AuthFeSessionLock(ctx context.Context) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSessionLock - authFeSessionLock")) +func (r *mutationResolver) AuthFeSessionLock(ctx context.Context, session string) (*model.FeSession, error) { + // TODO: Check auth + + sessionId, err := primitive.ObjectIDFromHex(session) + if err != nil { + return nil, err + } + + feSession, err := fesession.GetById(ctx, sessionId) + if err != nil { + if err == redis.Nil { + return nil, errors.New("session not found") + } + logger.Logger.Warn("failed to load fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + err = feSession.HandleLock(ctx) + if err != nil { + return nil, err + } + + err = feSession.Persist(ctx) + if err != nil { + logger.Logger.Warn("failed to persist fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + modSession := model.FeSessionFromDb(*feSession) + return &modSession, nil } // AuthFeSessionLogout is the resolver for the authFeSessionLogout field. -func (r *mutationResolver) AuthFeSessionLogout(ctx context.Context) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSessionLogout - authFeSessionLogout")) +func (r *mutationResolver) AuthFeSessionLogout(ctx context.Context, session string) (*model.FeSession, error) { + // TODO: Check auth + + sessionId, err := primitive.ObjectIDFromHex(session) + if err != nil { + return nil, err + } + + feSession, err := fesession.GetById(ctx, sessionId) + if err != nil { + if err == redis.Nil { + return nil, errors.New("session not found") + } + logger.Logger.Warn("failed to load fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + err = feSession.HandleLogout(ctx) + if err != nil { + return nil, err + } + + err = feSession.Persist(ctx) + if err != nil { + logger.Logger.Warn("failed to persist fesession", zap.Error(err), zap.String("request-id", middleware.GetReqID(ctx))) + return nil, errors.New("internal server error") + } + + modSession := model.FeSessionFromDb(*feSession) + return &modSession, nil +} + +// AuthFeTokenCreate is the resolver for the authFeTokenCreate field. +func (r *mutationResolver) AuthFeTokenCreate(ctx context.Context) (string, error) { + id := primitive.NewObjectID() + + t := jwt.NewBuilder() + t.Issuer(viper.GetString("wroofauth.url")) + t.IssuedAt(time.Now()) + t.Audience([]string{ + viper.GetString("wroofauth.url"), + }) + t.Claim("is_frontend_token", true) + t.JwtID(id.Hex()) + + token, err := t.Build() + if err != nil { + return "", err + } + + kid := viper.GetString("crypto.use_key.frontend") + + key, found := keystore.Global.LookupKeyID(kid) + if !found { + logger.Logger.Error("Keystore doesn't contain key to use for frontend!") + return "", errors.New("misconfigured server") + } + + signed, err := jwt.Sign(token, jwa.EdDSA, key) + if err != nil { + return "", err + } + + return string(signed), nil } // Self is the resolver for the self field. @@ -53,7 +249,19 @@ func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error // AuthFeSession is the resolver for the authFeSession field. func (r *queryResolver) AuthFeSession(ctx context.Context, id string) (*model.FeSession, error) { - panic(fmt.Errorf("not implemented: AuthFeSession - authFeSession")) + sessionId, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + + session, err := fesession.GetById(ctx, sessionId) + if err != nil { + return nil, err + } + + obj := model.FeSessionFromDb(*session) + + return &obj, nil } // Node is the resolver for the node field. diff --git a/internal/entities/user/user.go b/internal/entities/user/user.go index e19a205..cc0b4b0 100644 --- a/internal/entities/user/user.go +++ b/internal/entities/user/user.go @@ -45,6 +45,12 @@ type User struct { SecondFactorOverride bool `bson:"secondFactorOverride,omitempty"` // Can be used to temporarily disable 2fa } +func New() User { + return User{ + ID: primitive.NewObjectID(), + } +} + func (u *User) GetType() string { return "user" } diff --git a/internal/helpers/fetoken/fetoken.go b/internal/helpers/fetoken/fetoken.go new file mode 100644 index 0000000..c4db785 --- /dev/null +++ b/internal/helpers/fetoken/fetoken.go @@ -0,0 +1,93 @@ +package fetoken + +import ( + "context" + "net/http" + "strings" + + "git.1in9.net/raider/wroofauth/internal/keystore" + "git.1in9.net/raider/wroofauth/internal/logger" + "github.com/go-chi/chi/v5/middleware" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwt" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/zap" +) + +type FeToken struct { + ID primitive.ObjectID +} + +type feTokenCtxKeyType = string + +const feTokenCtxKey = feTokenCtxKeyType("feToken") + +func getFeToken(r *http.Request) *FeToken { + header := r.Header.Get("Authorization") + if !strings.HasPrefix(header, "Bearer ") { + logger.Logger.Info("FeToken - Authorization is not Bearer", zap.String("requestId", middleware.GetReqID(r.Context()))) + return nil + } + + tokenString := strings.TrimPrefix(header, "Bearer ") + + kid := viper.GetString("crypto.use_key.frontend") + + key, found := keystore.Global.LookupKeyID(kid) + if !found { + logger.Logger.Error("Keystore doesn't contain key to use for frontend!") + return nil + } + + public, err := key.PublicKey() + if err != nil { + logger.Logger.Error("Failed to make key into public key!", zap.Error(err)) + return nil + } + + token, err := jwt.Parse([]byte(tokenString), jwt.WithVerify(jwa.EdDSA, public)) + if err != nil { + logger.Logger.Info("FeToken - Could not parse token", zap.Error(err), zap.String("requestId", middleware.GetReqID(r.Context()))) + return nil + } + + err = jwt.Validate(token) + if err != nil { + logger.Logger.Info("FeToken - Could not validate token", zap.Error(err), zap.String("requestId", middleware.GetReqID(r.Context()))) + return nil + } + + tokenId := token.JwtID() + + id, err := primitive.ObjectIDFromHex(tokenId) + if err != nil { + logger.Logger.Info("FeToken - Could not parse token ID", zap.Error(err), zap.String("requestId", middleware.GetReqID(r.Context()))) + return nil + } + + feToken := FeToken{ + ID: id, + } + + return &feToken +} + +func ForContext(ctx context.Context) *FeToken { + raw, _ := ctx.Value(feTokenCtxKey).(*FeToken) + return raw +} + +func Middleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + feToken := getFeToken(r) + // feToken is a pointer. + + ctx := context.WithValue(r.Context(), feTokenCtxKey, feToken) + + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) + } +} diff --git a/internal/keystore/keystore.go b/internal/keystore/keystore.go new file mode 100644 index 0000000..c00c78d --- /dev/null +++ b/internal/keystore/keystore.go @@ -0,0 +1,76 @@ +package keystore + +import ( + "crypto/rand" + "encoding/base64" + "os" + + "git.1in9.net/raider/wroofauth/internal/logger" + "github.com/lestrrat-go/jwx/jwk" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +var ( + Global jwk.Set +) + +func GenerateRandomBytes(n uint32) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + return nil, err + } + + return b, nil +} + +func GenerateKeyID() (string, error) { + random, err := GenerateRandomBytes(64) + if err != nil { + return "", err + } + + kid := base64.StdEncoding.EncodeToString(random) + + return kid, nil +} + +func LoadKeystore() { + if viper.GetString("crypto.keyfile") != "" { + keystoreContent, err := os.ReadFile(viper.GetString("crypto.keyfile")) + if err != nil { + logger.Logger.Fatal("Unable to load keyfile", zap.Error(err)) + return + } + + fileKeyStore, err := jwk.Parse(keystoreContent) + if err != nil { + logger.Logger.Fatal("Unable to load keyfile", zap.Error(err)) + return + } + + Global = fileKeyStore + + if key, found := Global.Get(0); found { + viper.SetDefault("crypto.use_key", key.KeyID()) + } + return + } + + configKeyStore := jwk.NewSet() + err := viper.UnmarshalKey("crypto.keys", &configKeyStore) + + if err != nil { + logger.Logger.Fatal("Unable to load keys", zap.Error(err)) + return + } + + Global = configKeyStore + + if key, found := Global.Get(0); found { + viper.SetDefault("crypto.use_key", key.KeyID()) + } + + return +} diff --git a/internal/machines/fesession/db.go b/internal/machines/fesession/db.go new file mode 100644 index 0000000..82ba6e3 --- /dev/null +++ b/internal/machines/fesession/db.go @@ -0,0 +1,70 @@ +package fesession + +import ( + "context" + "encoding/base64" + + "git.1in9.net/raider/wroofauth/internal/database" + "git.1in9.net/raider/wroofauth/internal/logger" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +const ( + PREFIX = "fe_session_" +) + +func (s *FeSession) Persist(ctx context.Context) error { + return Persist(ctx, s) +} + +func Persist(ctx context.Context, session *FeSession) error { + if session == nil { + logger.Logger.Panic("trying to persist null-pointer") + } + + err := session.Validate(ctx) + if err != nil { + return err + } + + marshaled, err := bson.Marshal(session) + if err != nil { + return err + } + + encoded := base64.StdEncoding.EncodeToString(marshaled) + + status := database.Redis.Set(ctx, PREFIX+session.ID.Hex(), encoded, 0) + if status.Err() != nil { + return status.Err() + } + + return nil +} + +func GetById(ctx context.Context, id primitive.ObjectID) (*FeSession, error) { + encoded, err := database.Redis.Get(ctx, PREFIX+id.Hex()).Result() + if err != nil { + return nil, err + } + + marshaled, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + + var session FeSession + + err = bson.Unmarshal(marshaled, &session) + if err != nil { + return nil, err + } + + err = session.Hydrate(ctx) + if err != nil { + return nil, err + } + + return &session, nil +} diff --git a/internal/machines/fe_session/fe_session.go b/internal/machines/fesession/fe_session.go similarity index 73% rename from internal/machines/fe_session/fe_session.go rename to internal/machines/fesession/fe_session.go index ffbb609..d9e8949 100644 --- a/internal/machines/fe_session/fe_session.go +++ b/internal/machines/fesession/fe_session.go @@ -1,4 +1,4 @@ -package machines +package fesession import ( "context" @@ -42,19 +42,22 @@ const ( SecondFactor_TOTP = "TOTP" ) -type Session struct { - ID primitive.ObjectID +type FeSession struct { + ID primitive.ObjectID `bson:"_id"` + LinkedToken primitive.ObjectID `bson:"token"` - State SessionState + State SessionState `bson:"state"` - AuthenticationMethod AuthenticationMethod - SecondFactor SecondFactor + AuthenticationMethod AuthenticationMethod `bson:"auth_method"` + SecondFactor SecondFactor `bson:"second_factor"` - User *user.User + User *user.User `bson:"-"` + UserId *primitive.ObjectID `bson:"user"` } -func NewSession(ctx context.Context) *Session { - return &Session{ +func NewSession(ctx context.Context, token primitive.ObjectID) *FeSession { + return &FeSession{ + ID: primitive.NewObjectID(), State: SessionState_EMPTY, AuthenticationMethod: AuthenticationMethod_NONE, SecondFactor: SecondFactor_NONE, @@ -62,7 +65,7 @@ func NewSession(ctx context.Context) *Session { } // s.Validate checks if the session is in a valid state -func (s *Session) Validate(ctx context.Context) error { +func (s *FeSession) Validate(ctx context.Context) error { if s.IsAnyAuthenticated() { if s.User == nil { // We can only be here if a user is set @@ -84,11 +87,11 @@ func (s *Session) Validate(ctx context.Context) error { return nil } -func (s *Session) IsAnyAuthenticated() bool { +func (s *FeSession) IsAnyAuthenticated() bool { return strings.HasPrefix(string(s.State), "AUTHENTICATED_") } -func (s *Session) performPreflight(ctx context.Context) error { +func (s *FeSession) performPreflight(ctx context.Context) error { // TODO: Do Preflight Checks. // TODO: Do PASSWORD_EXPIRED check @@ -107,7 +110,19 @@ func (s *Session) performPreflight(ctx context.Context) error { return nil } -func (s *Session) HandleIdentification(ctx context.Context, identification string) error { +func (s *FeSession) Hydrate(ctx context.Context) error { + if s.UserId != nil { + var err error + s.User, err = user.GetById(ctx, *s.UserId) + if err != nil { + return err + } + } + + return nil +} + +func (s *FeSession) HandleIdentification(ctx context.Context, identification string) error { if s.State != SessionState_EMPTY { return ErrIllegalStateAction // This step may only run on EMPTY sessions } @@ -122,6 +137,7 @@ func (s *Session) HandleIdentification(ctx context.Context, identification strin } s.User = user + s.UserId = &user.ID //TODO: Check for SAML @@ -130,7 +146,7 @@ func (s *Session) HandleIdentification(ctx context.Context, identification strin return nil } -func (s *Session) HandlePassword(ctx context.Context, password string) error { +func (s *FeSession) HandlePassword(ctx context.Context, password string) error { if s.State != SessionState_UNAUTHENTICATED { return ErrIllegalStateAction // This step may only run on UNAUTHENTICATED sessions } @@ -155,7 +171,7 @@ func (s *Session) HandlePassword(ctx context.Context, password string) error { // TODO: Passkey action -func (s *Session) HandleTOTP(ctx context.Context, otp string) error { +func (s *FeSession) HandleTOTP(ctx context.Context, otp string) error { if s.State != SessionState_AWAITING_FACTOR { return ErrIllegalStateAction } @@ -173,7 +189,7 @@ func (s *Session) HandleTOTP(ctx context.Context, otp string) error { return s.performPreflight(ctx) } -func (s *Session) HandleLock(ctx context.Context) error { +func (s *FeSession) HandleLock(ctx context.Context) error { if !s.IsAnyAuthenticated() { return ErrIllegalStateAction } @@ -181,7 +197,7 @@ func (s *Session) HandleLock(ctx context.Context) error { return nil } -func (s *Session) HandleLogout(ctx context.Context) error { +func (s *FeSession) HandleLogout(ctx context.Context) error { if !s.IsAnyAuthenticated() { return ErrIllegalStateAction } @@ -189,7 +205,7 @@ func (s *Session) HandleLogout(ctx context.Context) error { return nil } -func (s *Session) Destroy(ctx context.Context) error { +func (s *FeSession) Destroy(ctx context.Context) error { // TODO: Destroy Session return nil } diff --git a/internal/server/api.go b/internal/server/api.go index d36fa85..de3d8f9 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -1,10 +1,14 @@ package server import ( + "context" + "errors" "net/http" "git.1in9.net/raider/wroofauth/graph" + "git.1in9.net/raider/wroofauth/internal/helpers/fetoken" chiprometheus "github.com/766b/chi-prometheus" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/playground" "github.com/go-chi/chi/v5" @@ -25,7 +29,8 @@ func SetupAPI() chi.Router { m := chiprometheus.NewMiddleware("api") router.Use(m) - //router.Use(decodeAuthMiddleware) + + router.Use(fetoken.Middleware()) router.NotFound(notFoundHandler) router.MethodNotAllowed(methodNotAllowedHandler) @@ -34,6 +39,25 @@ func SetupAPI() chi.Router { c := graph.Config{Resolvers: &graph.Resolver{}} + c.Directives.FeToken = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { + feToken := fetoken.ForContext(ctx) + if feToken == nil { + return nil, errors.New("FeToken is invalid") + } + + return next(ctx) + } + + c.Directives.Internal = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { + // TODO + return next(ctx) + } + + c.Directives.Self = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { + // TODO + return next(ctx) + } + srv := handler.NewDefaultServer(graph.NewExecutableSchema(c)) router.Handle("/query", srv)