diff --git a/authmiddleware/authmiddleware.go b/authmiddleware/authmiddleware.go index 281bf02..41405cf 100644 --- a/authmiddleware/authmiddleware.go +++ b/authmiddleware/authmiddleware.go @@ -21,6 +21,16 @@ type AuthMiddleware struct { superuserIDs []string } +func New(discordClient discord.IDiscordClient, supportIDs []string, superuserIDs []string) func(fiber.Ctx) error { + am := AuthMiddleware{ + Client: discordClient, + supportIDs: supportIDs, + superuserIDs: superuserIDs, + } + + return am.Handle +} + func (am *AuthMiddleware) Handle(c fiber.Ctx) error { sc := session.FromContext(c) sess := am.Init(sc) @@ -52,16 +62,6 @@ func (am *AuthMiddleware) Commit(sc *session.Middleware, sess *Session) { sc.Set(SessionKey, sess.AsJSON()) } -func SessionFrom(c fiber.Ctx) (s *Session) { - sess := session.FromContext(c) - sessionJSON := sess.Get(SessionKey).([]byte) - s, err := SessionFromJSON(sessionJSON) - if err != nil { - log.Panicln("session failed to parse", err) - } - return s -} - func (am *AuthMiddleware) isSupport(userID string) bool { return slices.Contains(am.supportIDs, userID) } diff --git a/authmiddleware/session.go b/authmiddleware/session.go index c2e9f3d..34325bd 100644 --- a/authmiddleware/session.go +++ b/authmiddleware/session.go @@ -4,10 +4,15 @@ import ( "log" "time" - "git.sapphic.engineer/roleypoly/v4/discord" "git.sapphic.engineer/roleypoly/v4/types" "github.com/goccy/go-json" "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/session" +) + +var ( + SessionKey uint8 + DefaultSession *Session = &Session{Permissions: PermAnonymous} ) type Session struct { @@ -32,16 +37,12 @@ func SessionFromJSON(input []byte) (*Session, error) { return &s, err } -var SessionKey uint8 - -func New(discordClient discord.IDiscordClient, supportIDs []string, superuserIDs []string) func(fiber.Ctx) error { - am := AuthMiddleware{ - Client: discordClient, - supportIDs: supportIDs, - superuserIDs: superuserIDs, +func SessionFrom(c fiber.Ctx) (s *Session) { + sess := session.FromContext(c) + sessionJSON := sess.Get(SessionKey).([]byte) + s, err := SessionFromJSON(sessionJSON) + if err != nil { + log.Panicln("session failed to parse", err) } - - return am.Handle + return s } - -var DefaultSession *Session = &Session{Permissions: PermAnonymous} diff --git a/authmiddleware/util_test.go b/authmiddleware/util_test.go index 681407b..b77099b 100644 --- a/authmiddleware/util_test.go +++ b/authmiddleware/util_test.go @@ -9,6 +9,7 @@ import ( "git.sapphic.engineer/roleypoly/v4/authmiddleware" "git.sapphic.engineer/roleypoly/v4/discord" "git.sapphic.engineer/roleypoly/v4/discord/clientmock" + "git.sapphic.engineer/roleypoly/v4/roleypoly" "git.sapphic.engineer/roleypoly/v4/types" "github.com/goccy/go-json" "github.com/gofiber/fiber/v3" @@ -102,7 +103,7 @@ func ok(c fiber.Ctx) error { } func getApp(dc discord.IDiscordClient) *fiber.App { - app := fiber.New(fiber.Config{}) + app := roleypoly.CreateFiberApp() sessionMiddleware, sessionStore := session.NewWithStore() sessionStore.RegisterType(authmiddleware.Session{}) diff --git a/default.nix b/default.nix index 36ec488..1ff83fa 100644 --- a/default.nix +++ b/default.nix @@ -1,6 +1,6 @@ { pkgs ? import {}, - vendorHash ? "sha256-TvGGu74AWLrWRQvfAesVGJFTp4omPGVmIgyzB3QUKnc=", + vendorHash ? "sha256-ilEuRNE61UN22Jm6Yyv80S6VdRa1mB6J/Pde1x/DgEk=", }: rec { default = roleypoly; diff --git a/interactions/interactions.go b/interactions/interactions.go index 0a80b2b..3a5bcd1 100644 --- a/interactions/interactions.go +++ b/interactions/interactions.go @@ -27,7 +27,6 @@ type Interactions struct { } func NewInteractions(publicKey string, publicBaseURL string, guildsService *discord.GuildService) *Interactions { - return &Interactions{ PublicKey: ed25519.PublicKey(publicKey), PublicBaseURL: publicBaseURL, @@ -41,6 +40,7 @@ func (i *Interactions) Routes(r fiber.Router) { r.Post("/", i.PostHandler) i.Router.Add("hello-world", i.CmdHelloWorld) + i.Router.Add("roleypoly", i.CmdRoleypoly) } func (i *Interactions) PostHandler(c fiber.Ctx) error { diff --git a/interactions/interactions_test.go b/interactions/interactions_test.go index 1d2f1bf..f16347b 100644 --- a/interactions/interactions_test.go +++ b/interactions/interactions_test.go @@ -7,11 +7,13 @@ import ( "net/http" "testing" + "git.sapphic.engineer/roleypoly/v4/authmiddleware" "git.sapphic.engineer/roleypoly/v4/discord" "git.sapphic.engineer/roleypoly/v4/interactions" "git.sapphic.engineer/roleypoly/v4/roleypoly" "github.com/goccy/go-json" "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/session" "github.com/stretchr/testify/assert" ) @@ -28,7 +30,37 @@ func TestNewInteractions(t *testing.T) { func TestPostHandler(t *testing.T) { i, _, key := makeInteractions(t) - app := fiber.New() + app := roleypoly.CreateFiberApp() + i.Routes(app) + + body, err := json.Marshal(map[string]any{ + "type": 1, + }) + if err != nil { + t.Error(err) + return + } + + sig, ts := sign(t, key, body) + + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(body)) + req.Header.Add("X-Signature-Ed25519", sig) + req.Header.Add("X-Signature-Timestamp", ts) + + res, err := app.Test(req, fiber.TestConfig{}) + + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) +} + +func TestPostHandlerIntegration(t *testing.T) { + i, dsm, key := makeInteractions(t) + app := roleypoly.CreateFiberApp() + sessionMiddleware, sessionStore := session.NewWithStore() + sessionStore.RegisterType(authmiddleware.Session{}) + app.Use(sessionMiddleware) + app.Use(authmiddleware.New(dsm, []string{}, []string{})) + i.Routes(app) body, err := json.Marshal(map[string]any{ @@ -54,6 +86,7 @@ func TestPostHandler(t *testing.T) { func TestPostHandlerSigFail(t *testing.T) { i, _, _ := makeInteractions(t) app := roleypoly.CreateFiberApp() + i.Routes(app) body, err := json.Marshal(map[string]any{ diff --git a/main.go b/main.go index a467e20..b75ce5f 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ func main() { utils.DiscordBotToken, ) + roleypoly.SetupStatic(app) roleypoly.SetupControllers( app, dc, diff --git a/roleypoly/fiber.go b/roleypoly/fiber.go index 33a5706..d546d41 100644 --- a/roleypoly/fiber.go +++ b/roleypoly/fiber.go @@ -19,6 +19,7 @@ import ( staticfs "git.sapphic.engineer/roleypoly/v4/static" "git.sapphic.engineer/roleypoly/v4/templates" "git.sapphic.engineer/roleypoly/v4/testing" + "git.sapphic.engineer/roleypoly/v4/types" ) func CreateFiberApp() *fiber.App { @@ -31,24 +32,6 @@ func CreateFiberApp() *fiber.App { ViewsLayout: "layouts/main", }) - sessionMiddleware, sessionStore := session.NewWithStore() - sessionStore.RegisterType(authmiddleware.Session{}) - - app.Use(sessionMiddleware) - app.Use(csrf.New(csrf.Config{ - Session: sessionStore, - })) - - app.Get("/static*", static.New("", static.Config{ - FS: staticfs.FS, - Compress: true, - CacheDuration: 24 * 8 * time.Hour, - })) - app.Get("/favicon.ico", oneStatic) - app.Get("/manifest.json", oneStatic) - app.Get("/robots.txt", oneStatic) - app.Get("/humans.txt", oneStatic) - return app } @@ -63,16 +46,49 @@ func oneStatic(c fiber.Ctx) error { return c.SendStream(f) } -func SetupControllers(app *fiber.App, dc *discord.DiscordClient, publicKey string, publicBaseURL string) { - gs := discord.NewGuildService(dc) +func SetupStatic(app *fiber.App) { + app.Get("/static*", static.New("", static.Config{ + FS: staticfs.FS, + Compress: true, + CacheDuration: 24 * 8 * time.Hour, + })) + app.Get("/favicon.ico", oneStatic) + app.Get("/manifest.json", oneStatic) + app.Get("/robots.txt", oneStatic) + app.Get("/humans.txt", oneStatic) +} - (&testing.TestingController{ - Guilds: gs, - }).Routes(app.Group("/testing")) +type ControllerConfig struct { + DiscordClient discord.IDiscordClient + PublicKey string + PublicBaseURL string + SupportIDs []string + SuperuserIDs []string +} - interactions.NewInteractions(publicKey, publicBaseURL, gs).Routes(app.Group("/interactions")) +func SetupControllers(app *fiber.App, cfg ControllerConfig) { + app.Use(func(c fiber.Ctx) error { + c.Locals(types.ConfigKey, cfg) + return c.Next() + }) + + sessionMiddleware, sessionStore := session.NewWithStore() + sessionStore.RegisterType(authmiddleware.Session{}) + + app.Use(sessionMiddleware) + app.Use(csrf.New(csrf.Config{ + Session: sessionStore, + })) + app.Use(authmiddleware.New(cfg.DiscordClient, cfg.SupportIDs, cfg.SuperuserIDs)) + + gs := discord.NewGuildService(cfg.DiscordClient) + + (&testing.TestingController{Guilds: gs}). + Routes(app.Group("/testing")) + + interactions.NewInteractions(cfg.PublicKey, cfg.PublicBaseURL, gs). + Routes(app.Group("/interactions")) - app.Use(authmiddleware.New(dc, []string{}, []string{})) } // func getStorageDirectory() string { diff --git a/roleypoly/fiber_test.go b/roleypoly/fiber_test.go new file mode 100644 index 0000000..ea85bcf --- /dev/null +++ b/roleypoly/fiber_test.go @@ -0,0 +1,40 @@ +package roleypoly_test + +import ( + "bytes" + "net/http" + "testing" + + "git.sapphic.engineer/roleypoly/v4/roleypoly" + "github.com/stretchr/testify/assert" +) + +func TestStaticRoot(t *testing.T) { + app := roleypoly.CreateFiberApp() + roleypoly.SetupStatic(app) + + req, err := http.NewRequest("GET", "/humans.txt", nil) + assert.Nil(t, err) + + resp, err := app.Test(req) + assert.Nil(t, err) + + buf := bytes.Buffer{} + buf.ReadFrom(resp.Body) + assert.Equal(t, "you're on thin ice, soup head.", buf.String()) +} + +func TestStaticPath(t *testing.T) { + app := roleypoly.CreateFiberApp() + roleypoly.SetupStatic(app) + + req, err := http.NewRequest("GET", "/static/humans.txt", nil) + assert.Nil(t, err) + + resp, err := app.Test(req) + assert.Nil(t, err) + + buf := bytes.Buffer{} + buf.ReadFrom(resp.Body) + assert.Equal(t, "you're on thin ice, soup head.", buf.String()) +} diff --git a/static/humans.txt b/static/humans.txt new file mode 100644 index 0000000..4c416a0 --- /dev/null +++ b/static/humans.txt @@ -0,0 +1 @@ +you're on thin ice, soup head. \ No newline at end of file diff --git a/types/keys.go b/types/keys.go new file mode 100644 index 0000000..7c38735 --- /dev/null +++ b/types/keys.go @@ -0,0 +1,3 @@ +package types + +var ConfigKey int