Add more API and config stuff and add basic FETokens

This commit is contained in:
Kevin Kandlbinder 2023-12-18 22:02:09 +00:00
parent f682d44244
commit cfeafebd68
18 changed files with 959 additions and 89 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
/config.yaml
/.WROOF_KEYS.json
### VisualStudioCode ###
.vscode/*

60
cmd/addUser.go Normal file
View file

@ -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)
}

63
cmd/generateKeys.go Normal file
View file

@ -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)
}

View file

@ -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()
}

9
go.mod
View file

@ -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

30
go.sum
View file

@ -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=

View file

@ -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
#

View file

@ -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))
}

18
graph/model/fesession.go Normal file
View file

@ -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),
}
}

View file

@ -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"`

View file

@ -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 {

View file

@ -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.

View file

@ -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"
}

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)