checkpoint: added metadata endpoint

This commit is contained in:
Dan Anglin 2024-10-13 20:28:01 +01:00
parent 4fdad2d805
commit de9c02c102
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
9 changed files with 155 additions and 27 deletions

View file

@ -28,6 +28,10 @@ jobs:
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
with:
target: gosec
- name: Run go vet
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
with:
target: govet
style:
name: Style

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/__build/*
!__build/.gitkeep
/.environment/*

35
internal/config/config.go Normal file
View file

@ -0,0 +1,35 @@
package config
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
BindAddress string `json:"bindAddress"`
Port int32 `json:"port"`
Domain string `json:"domain"`
}
func NewConfig(path string) (Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return Config{}, fmt.Errorf(
"unable to read the config from %q: %w",
path,
err,
)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return Config{}, fmt.Errorf(
"unable to decode the JSON data: %w",
err,
)
}
return cfg, nil
}

View file

@ -3,18 +3,15 @@ package executors
import (
"flag"
"fmt"
"log/slog"
"net/http"
"time"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/router"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/config"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/server"
)
type serveExecutor struct {
*flag.FlagSet
address string
configFile string
}
func executeServeCommand(args []string) error {
@ -24,7 +21,7 @@ func executeServeCommand(args []string) error {
FlagSet: flag.NewFlagSet(executorName, flag.ExitOnError),
}
executor.StringVar(&executor.address, "address", "0.0.0.0:8080", "The address that the server will listen on")
executor.StringVar(&executor.configFile, "config", "", "The path to the config file")
if err := executor.Parse(args); err != nil {
return fmt.Errorf("(%s) flag parsing error: %w", executorName, err)
@ -38,18 +35,12 @@ func executeServeCommand(args []string) error {
}
func (e *serveExecutor) execute() error {
server := http.Server{
Addr: e.address,
Handler: router.NewServeMux(),
ReadHeaderTimeout: 1 * time.Second,
}
slog.Info(info.ApplicationName+" is listening for web requests", "address", e.address)
err := server.ListenAndServe()
cfg, err := config.NewConfig(e.configFile)
if err != nil {
return fmt.Errorf("error running the server: %w", err)
return fmt.Errorf("unable to load the config: %w", err)
}
return nil
srv := server.NewServer(cfg)
return srv.Serve()
}

View file

@ -1,9 +0,0 @@
package router
import "net/http"
func NewServeMux() *http.ServeMux {
mux := http.NewServeMux()
return mux
}

View file

@ -0,0 +1,22 @@
package server
import (
"encoding/json"
"log/slog"
"net/http"
)
func sendJSONResponse(w http.ResponseWriter, statusCode int, payload any) {
data, err := json.Marshal(payload)
if err != nil {
slog.Error("Error marshalling the response to JSON", "error", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
_, _ = w.Write(data)
}

36
internal/server/router.go Normal file
View file

@ -0,0 +1,36 @@
package server
import (
"fmt"
"net/http"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/config"
)
func newMux(cfg config.Config) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("GET /.well-known/oauth-authorization-server", metadataHandleFunc(cfg.Domain))
return mux
}
func metadataHandleFunc(domain string) http.HandlerFunc {
return func(writer http.ResponseWriter, _ *http.Request) {
metadata := struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
ServiceDocumentation string `json:"service_documentation"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
}{
Issuer: fmt.Sprintf("https://%s/", domain),
AuthorizationEndpoint: fmt.Sprintf("https://%s/auth", domain),
TokenEndpoint: fmt.Sprintf("https://%s/token", domain),
ServiceDocumentation: "https://indieauth.spec.indieweb.org",
CodeChallengeMethodsSupported: []string{"S256"},
}
sendJSONResponse(writer, http.StatusOK, metadata)
}
}

41
internal/server/server.go Normal file
View file

@ -0,0 +1,41 @@
package server
import (
"fmt"
"log/slog"
"net/http"
"time"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/config"
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
)
type Server struct {
httpServer *http.Server
}
func NewServer(cfg config.Config) *Server {
address := fmt.Sprintf("%s:%d", cfg.BindAddress, cfg.Port)
httpServer := http.Server{
Addr: address,
ReadHeaderTimeout: 1 * time.Second,
Handler: newMux(cfg),
}
server := Server{
httpServer: &httpServer,
}
return &server
}
func (s *Server) Serve() error {
slog.Info(info.ApplicationName+" is now ready to serve web requests", "address", s.httpServer.Addr)
if err := s.httpServer.ListenAndServe(); err != nil {
return fmt.Errorf("error running the server: %w", err)
}
return nil
}

View file

@ -55,10 +55,12 @@ func Lint() error {
return sh.RunV("golangci-lint", "run", "--color", "always")
}
// Gosec runs gosec against the code.
func Gosec() error {
return sh.RunV("gosec", "./...")
}
// Staticcheck runs staticcheck against the code.
func Staticcheck() error {
return sh.RunV("staticcheck", "./...")
}
@ -90,6 +92,11 @@ func Gofmt() error {
return nil
}
// Govet runs go vet against the code.
func Govet() error {
return sh.RunV("go", "vet", "./...")
}
// Build build the executable.
// To rebuild packages that are already up-to-date set PROJECT_BUILD_REBUILD_ALL=1
// To enable verbose mode set PROJECT_BUILD_VERBOSE=1