Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
name: Build & Test

on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
build-and-test:
Expand All @@ -23,15 +28,19 @@ jobs:
env:
FMSG_TEST_DATABASE_URL: postgres://postgres@localhost:5432/postgres?sslmode=disable
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: actions/setup-go@v5
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
go-version: "1.27.x"

- name: Build
run: go build ./...

- name: Test
run: |
FMSG_TEST_DD="$(go list -m -f '{{.Dir}}' github.com/markmnl/fmsgd)/dd.sql" go test -race ./...

- name: Vet
run: go vet ./...
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[![Build & Test](https://github.com/markmnl/fmsg-webapi/actions/workflows/build-test.yml/badge.svg)](https://github.com/markmnl/fmsg-webapi/actions/workflows/build-test.yml)
[![Build & Test](https://github.com/markmnl/fmsg-webapi/actions/workflows/build-test.yml/badge.svg?branch=main)](https://github.com/markmnl/fmsg-webapi/actions/workflows/build-test.yml?query=branch%3Amain)
[![Go 1.27+](https://img.shields.io/badge/Go-1.27%2B-00ADD8?logo=go&logoColor=white)](https://go.dev/dl/)

# fmsg-webapi

Expand Down Expand Up @@ -179,7 +180,7 @@ go run ./cmd/fmsg-webapi api-key rotate-delegation \

## Building

Requires **Go 1.25** or newer.
Requires **Go 1.27** or newer.

```bash
go build ./...
Expand Down
2 changes: 1 addition & 1 deletion cmd/fmsg-webapi/apikey_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func prepareCLIGrantInputs(owner, agent, cidrsRaw, expiresRaw string) ([]string,
}
var allowed []string
if strings.TrimSpace(cidrsRaw) != "" {
for _, cidr := range strings.Split(cidrsRaw, ",") {
for cidr := range strings.SplitSeq(cidrsRaw, ",") {
allowed = append(allowed, strings.TrimSpace(cidr))
}
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/markmnl/fmsg-webapi

go 1.25.0
go 1.27.0

require (
github.com/MicahParks/keyfunc/v3 v3.8.0
Expand Down
12 changes: 5 additions & 7 deletions internal/apiauth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,11 @@ func (i *TokenIssuer) Mint(ownerAddr, subAddr, keyID string, now time.Time) (str
claims := TokenClaims{
OwnerAddr: ownerAddr,
APIKeyID: keyID,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: i.issuer,
Subject: subAddr,
Audience: jwt.ClaimStrings{i.audience},
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(expires),
},
Issuer: i.issuer,
Subject: subAddr,
Audience: jwt.ClaimStrings{i.audience},
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(expires),
}
tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
signed, err := tok.SignedString(i.privateKey)
Expand Down
10 changes: 5 additions & 5 deletions internal/emoji/gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ func parseTest(r io.Reader) ([]string, string) {
sc := bufio.NewScanner(r)
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, "# Version:") {
version = strings.TrimSpace(strings.TrimPrefix(line, "# Version:"))
if after, ok := strings.CutPrefix(line, "# Version:"); ok {
version = strings.TrimSpace(after)
continue
}
if line == "" || strings.HasPrefix(line, "#") {
Expand All @@ -120,7 +120,7 @@ func parseTest(r io.Reader) ([]string, string) {
continue
}
var b strings.Builder
for _, cp := range strings.Fields(line[:semi]) {
for cp := range strings.FieldsSeq(line[:semi]) {
n, err := strconv.ParseUint(cp, 16, 32)
if err != nil {
fmt.Fprintf(os.Stderr, "bad code point %q\n", cp)
Expand Down Expand Up @@ -153,8 +153,8 @@ func parseData(r io.Reader) map[string][]span {
}
rng, prop := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
lo, hi := rng, rng
if dots := strings.Index(rng, ".."); dots >= 0 {
lo, hi = rng[:dots], rng[dots+2:]
if before, after, ok := strings.Cut(rng, ".."); ok {
lo, hi = before, after
}
l, err1 := strconv.ParseUint(lo, 16, 32)
h, err2 := strconv.ParseUint(hi, 16, 32)
Expand Down
7 changes: 3 additions & 4 deletions internal/handlers/finalization_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,8 @@ func TestFinalizationRollbackAndConcurrentSend(t *testing.T) {
id = a.draft(t, alice, "race", nil)
var wg sync.WaitGroup
codes := make(chan int, 8)
for i := 0; i < 8; i++ {
wg.Add(1)
go func() { defer wg.Done(); codes <- a.request(alice, "POST", "/fmsg/"+id+"/send", nil).Code }()
for range 8 {
wg.Go(func() { codes <- a.request(alice, "POST", "/fmsg/"+id+"/send", nil).Code })
}
wg.Wait()
close(codes)
Expand All @@ -290,7 +289,7 @@ func TestFinalizationRollbackAndConcurrentSend(t *testing.T) {
}
// Race a full draft edit with send. Either order is valid; the committed
// digest must describe exactly the content that remains downloadable.
for i := 0; i < 5; i++ {
for range 5 {
id = a.draft(t, alice, "before", nil)
wg.Add(2)
go func(id string) { defer wg.Done(); a.request(alice, "POST", "/fmsg/"+id+"/send", nil) }(id)
Expand Down
12 changes: 6 additions & 6 deletions internal/handlers/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const (
// wsEnvelope is the JSON shape of every frame pushed over a WebSocket. The
// Type field lets clients route events; Data carries the event-specific body.
type wsEnvelope struct {
Type string `json:"type"`
Data interface{} `json:"data"`
Type string `json:"type"`
Data any `json:"data"`
}

// Hub maintains the set of connected WebSocket clients and fans out database
Expand Down Expand Up @@ -189,15 +189,15 @@ func (h *Hub) listen(ctx context.Context, onConnected func()) error {

// parseNotifyPayload parses a new_msg payload of the form "msgID,addr".
func parseNotifyPayload(payload string) (msgID int64, addr string, ok bool) {
comma := strings.IndexByte(payload, ',')
if comma < 0 {
before, after, ok := strings.Cut(payload, ",")
if !ok {
return 0, "", false
}
id, err := strconv.ParseInt(payload[:comma], 10, 64)
id, err := strconv.ParseInt(before, 10, 64)
if err != nil {
return 0, "", false
}
addr = payload[comma+1:]
addr = after
if addr == "" {
return 0, "", false
}
Expand Down
7 changes: 3 additions & 4 deletions internal/handlers/ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"log"
"net/http"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -57,10 +58,8 @@ func NewWSHandler(verifier *middleware.Verifier, hub *Hub, allowedOrigins []stri
if len(allowedOrigins) == 0 {
return true
}
for _, o := range allowedOrigins {
if o == origin {
return true
}
if slices.Contains(allowedOrigins, origin) {
return true
}
log.Printf("ws: rejected upgrade from origin %q", origin)
return false
Expand Down
9 changes: 4 additions & 5 deletions internal/middleware/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func NewVerifier(cfg Config) (*Verifier, error) {
if cfg.AddressClaim == "" {
return nil, errors.New("middleware: EdDSA mode requires an AddressClaim")
}
v.idpKeyFunc = func(t *jwt.Token) (interface{}, error) {
v.idpKeyFunc = func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodEd25519); !ok {
return nil, fmt.Errorf("unexpected signing method: %s", t.Method.Alg())
}
Expand Down Expand Up @@ -220,7 +220,7 @@ func (v *Verifier) authenticateAPIToken(ctx context.Context, tokenStr, remoteAdd
return authResult{}, authError{status: http.StatusForbidden, msg: "act-as is only available with identity-provider authentication"}
}
claims := &apiauth.TokenClaims{}
_, err := v.apiParser.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
_, err := v.apiParser.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodEd25519); !ok {
return nil, fmt.Errorf("unexpected signing method: %s", t.Method.Alg())
}
Expand Down Expand Up @@ -252,8 +252,7 @@ func (e authError) Error() string {
}

func authFailureFromError(err error) (int, string, bool) {
var ae authError
if errors.As(err, &ae) {
if ae, ok := errors.AsType[authError](err); ok {
return ae.status, ae.msg, true
}
switch {
Expand Down Expand Up @@ -415,7 +414,7 @@ func CheckFmsgID(idURL, addr string) (int, bool, error) {
fmsgIDCache.Delete(addr)
}

v, err, _ := fmsgIDGroup.Do(addr, func() (interface{}, error) {
v, err, _ := fmsgIDGroup.Do(addr, func() (any, error) {
if v, ok := fmsgIDCache.Load(addr); ok {
entry := v.(fmsgIDEntry)
if time.Now().Before(entry.expires) {
Expand Down
2 changes: 1 addition & 1 deletion internal/middleware/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestIsValidAddr(t *testing.T) {
}

func fakeJWKS(kid string, pub ed25519.PublicKey) jwt.Keyfunc {
return func(t *jwt.Token) (interface{}, error) {
return func(t *jwt.Token) (any, error) {
k, _ := t.Header["kid"].(string)
if k != kid {
return nil, jwt.ErrTokenSignatureInvalid
Expand Down
2 changes: 1 addition & 1 deletion internal/middleware/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import (
"io"
)

func decodeJSON(r io.Reader, v interface{}) error {
func decodeJSON(r io.Reader, v any) error {
return json.NewDecoder(r).Decode(v)
}