Compare commits

...
13 Commits
Author SHA1 Message Date
releaser 2d5c2fc938 chore(release): prepare for 0.8.0 (#444)
Unbound Release / Generate Changelog and Handle PR (push) Successful in 10s
auth0mock / build (push) Successful in 29s
Release / release (push) Successful in 15s
Unbound Release / Check Preconditions (push) Successful in 3s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Create Release (push) Successful in 10s
## [0.8.0] - 2026-10-07

### 🚀 Features

- *(management)* Search users by email in any case (#443)

### 🐛 Bug Fixes

- *(deps)* Update module github.com/lestrrat-go/jwx/v4 to v4.5.0 (#427)

### ⚙️ Miscellaneous Tasks

- *(deps)* Update golang:1.27 docker digest to 7543a96 (#421)
- *(deps)* Update golang:1.27 docker digest to 512690a (#423)
- *(deps)* Update golang:1.27 docker digest to f44f6e8 (#425)
- *(deps)* Update golang:1.27 docker digest to 03fd17c (#429)
- *(deps)* Update golang:1.27 docker digest to 1cfcdb1 (#431)
- *(deps)* Update golang:1.27 docker digest to 3680233 (#433)
- *(deps)* Update golang:1.27 docker digest to e0174e5 (#435)
- *(deps)* Update golang:1.27 docker digest to 23fe807 (#437)
- *(deps)* Update golang:1.27 docker digest to 1e93e00 (#439)
- *(deps)* Update golang:1.27 docker digest to 162be52 (#441)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: https://gitea.unbound.se/unboundsoftware/auth0mock/pulls/444
Co-authored-by: Unbound Releaser <releaser@unbound.se>
2026-10-07 18:14:22 +00:00
peter 87fe84076f feat(management): search users by email in any case (#443)
Unbound Release / Create Release (push) Successful in 22s
Unbound Release / Check Preconditions (push) Successful in 1m46s
Unbound Release / Generate Changelog and Handle PR (push) Successful in 23s
Unbound Release / Create Tag (push) Skipped
Release / release (push) Successful in 2m14s
auth0mock / build (push) Successful in 1m10s
GET /api/v2/users with q=email:"..." answers like Auth0's user search: the
email matches in any letter case, unlike users-by-email, and the answer has
the include_totals shape the Go SDK asks for by default. Any other query is
refused with 400 rather than answered wrong.

Goodfeed's backend checks whether an address is taken with this search
before it lets a user change their email.

Reviewed-on: #443
Co-authored-by: Peter Svensson <peter@sparetimecoders.com>
2026-10-07 14:09:47 +00:00
renovate c558a2330e chore(deps): update golang:1.27 docker digest to 162be52 (#441)
Unbound Release / Create Release (push) Successful in 24s
Unbound Release / Generate Changelog and Handle PR (push) Successful in 38s
Release / release (push) Successful in 1m16s
auth0mock / build (push) Skipped
Unbound Release / Check Preconditions (push) Successful in 27s
Unbound Release / Create Tag (push) Skipped
2026-10-06 18:04:48 +00:00
renovate f00222cb8d chore(deps): update golang:1.27 docker digest to 1e93e00 (#439)
Release / release (push) Successful in 2m24s
Unbound Release / Create Release (push) Successful in 24s
auth0mock / build (push) Skipped
Unbound Release / Check Preconditions (push) Successful in 22s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 43s
2026-10-06 09:05:41 +00:00
renovate d1560cc0fe chore(deps): update golang:1.27 docker digest to 23fe807 (#437)
Unbound Release / Check Preconditions (push) Successful in 22s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 40s
auth0mock / build (push) Skipped
Unbound Release / Create Release (push) Successful in 37s
Release / release (push) Successful in 18m20s
2026-10-06 05:16:38 +00:00
renovate c5f5873c2c chore(deps): update golang:1.27 docker digest to e0174e5 (#435)
Unbound Release / Create Release (push) Successful in 36s
Release / release (push) Successful in 1m44s
Unbound Release / Check Preconditions (push) Successful in 31s
auth0mock / build (push) Skipped
Unbound Release / Create Tag (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 58s
2026-09-30 09:01:41 +00:00
renovate cd6bca11a1 chore(deps): update golang:1.27 docker digest to 3680233 (#433)
Unbound Release / Create Tag (push) Skipped
Unbound Release / Check Preconditions (push) Successful in 26s
auth0mock / build (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 58s
Unbound Release / Create Release (push) Successful in 26s
Release / release (push) Successful in 4m1s
2026-09-19 15:02:50 +00:00
renovate 961ce21fef chore(deps): update golang:1.27 docker digest to 1cfcdb1 (#431)
Unbound Release / Check Preconditions (push) Successful in 33s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Create Release (push) Successful in 29s
auth0mock / build (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 46s
Release / release (push) Successful in 2m40s
2026-09-19 08:05:32 +00:00
renovate aefe77ed1b chore(deps): update golang:1.27 docker digest to 03fd17c (#429)
Unbound Release / Check Preconditions (push) Successful in 27s
Unbound Release / Create Tag (push) Skipped
auth0mock / build (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 42s
Release / release (push) Successful in 3m27s
Unbound Release / Create Release (push) Successful in 29s
2026-09-19 05:03:52 +00:00
renovate 556203ee18 fix(deps): update module github.com/lestrrat-go/jwx/v4 to v4.5.0 (#427)
Unbound Release / Create Release (push) Successful in 24s
Release / release (push) Successful in 1m14s
auth0mock / build (push) Skipped
Unbound Release / Check Preconditions (push) Successful in 22s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 35s
2026-09-11 05:05:11 +00:00
renovate 7b7e17aa55 chore(deps): update golang:1.27 docker digest to f44f6e8 (#425)
auth0mock / build (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 47s
Unbound Release / Check Preconditions (push) Successful in 25s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Create Release (push) Successful in 24s
Release / release (push) Successful in 1m20s
2026-09-10 03:06:08 +00:00
renovate 9f6c5eea6d chore(deps): update golang:1.27 docker digest to 512690a (#423)
Unbound Release / Create Tag (push) Skipped
Unbound Release / Check Preconditions (push) Successful in 39s
auth0mock / build (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 45s
Unbound Release / Create Release (push) Successful in 31s
Release / release (push) Successful in 2m49s
2026-09-02 18:03:06 +00:00
renovate dde19e2d12 chore(deps): update golang:1.27 docker digest to 7543a96 (#421)
Unbound Release / Check Preconditions (push) Successful in 22s
auth0mock / build (push) Skipped
Unbound Release / Create Tag (push) Skipped
Unbound Release / Generate Changelog and Handle PR (push) Successful in 37s
Unbound Release / Create Release (push) Successful in 24s
Release / release (push) Successful in 1m12s
2026-09-02 07:04:11 +00:00
8 changed files with 145 additions and 12 deletions

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"version": "0.7.8" "version": "0.8.0"
} }
+23
View File
@@ -2,6 +2,29 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.8.0] - 2026-10-07
### 🚀 Features
- *(management)* Search users by email in any case (#443)
### 🐛 Bug Fixes
- *(deps)* Update module github.com/lestrrat-go/jwx/v4 to v4.5.0 (#427)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang:1.27 docker digest to 7543a96 (#421)
- *(deps)* Update golang:1.27 docker digest to 512690a (#423)
- *(deps)* Update golang:1.27 docker digest to f44f6e8 (#425)
- *(deps)* Update golang:1.27 docker digest to 03fd17c (#429)
- *(deps)* Update golang:1.27 docker digest to 1cfcdb1 (#431)
- *(deps)* Update golang:1.27 docker digest to 3680233 (#433)
- *(deps)* Update golang:1.27 docker digest to e0174e5 (#435)
- *(deps)* Update golang:1.27 docker digest to 23fe807 (#437)
- *(deps)* Update golang:1.27 docker digest to 1e93e00 (#439)
- *(deps)* Update golang:1.27 docker digest to 162be52 (#441)
## [0.7.8] - 2026-09-02 ## [0.7.8] - 2026-09-02
### 🐛 Bug Fixes ### 🐛 Bug Fixes
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.27@sha256:192b74998e350966280a2cbffbb6c40064754f7ec005096aa64f04d7ece4467e AS build FROM golang:1.27@sha256:162be5298a40ed317005c8339c6de4d10d3eef336d66dc8e9259b03ab9d3a6d2 AS build
WORKDIR /build WORKDIR /build
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
+1
View File
@@ -102,6 +102,7 @@ func main() {
// Management API endpoints // Management API endpoints
mux.HandleFunc("GET /api/v2/users-by-email", managementHandler.GetUsersByEmail) mux.HandleFunc("GET /api/v2/users-by-email", managementHandler.GetUsersByEmail)
mux.HandleFunc("GET /api/v2/users", managementHandler.SearchUsers)
mux.HandleFunc("POST /api/v2/users", managementHandler.CreateUser) mux.HandleFunc("POST /api/v2/users", managementHandler.CreateUser)
mux.HandleFunc("PATCH /api/v2/users/", managementHandler.UpdateUser) mux.HandleFunc("PATCH /api/v2/users/", managementHandler.UpdateUser)
mux.HandleFunc("POST /api/v2/tickets/password-change", managementHandler.PasswordChangeTicket) mux.HandleFunc("POST /api/v2/tickets/password-change", managementHandler.PasswordChangeTicket)
+2 -2
View File
@@ -5,7 +5,7 @@ go 1.26.0
require ( require (
github.com/alecthomas/kong v1.16.1 github.com/alecthomas/kong v1.16.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lestrrat-go/jwx/v4 v4.4.0 github.com/lestrrat-go/jwx/v4 v4.5.0
github.com/rs/cors v1.11.1 github.com/rs/cors v1.11.1
) )
@@ -13,5 +13,5 @@ require (
github.com/lestrrat-go/dsig v1.4.0 // indirect github.com/lestrrat-go/dsig v1.4.0 // indirect
github.com/lestrrat-go/option/v3 v3.0.0-alpha1 // indirect github.com/lestrrat-go/option/v3 v3.0.0-alpha1 // indirect
github.com/valyala/fastjson v1.6.10 // indirect github.com/valyala/fastjson v1.6.10 // indirect
golang.org/x/crypto v0.55.0 // indirect golang.org/x/crypto v0.56.0 // indirect
) )
+8 -8
View File
@@ -10,17 +10,17 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/lestrrat-go/dsig v1.4.0 h1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8= github.com/lestrrat-go/dsig v1.4.0 h1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8=
github.com/lestrrat-go/dsig v1.4.0/go.mod h1:I8Nddg/vN2cUl/h8N7SRRApLnNNeyZPIqLYpvpOtGGo= github.com/lestrrat-go/dsig v1.4.0/go.mod h1:I8Nddg/vN2cUl/h8N7SRRApLnNNeyZPIqLYpvpOtGGo=
github.com/lestrrat-go/jwx/v4 v4.4.0 h1:CzoK8+u++WF7vVEmxx9fB8VaheeXWZ698F6HZbrl6SI= github.com/lestrrat-go/jwx/v4 v4.5.0 h1:lgU8YcqaJo/iZllX4AvSaQTzT2XpWWMrDcPMe6+KzqI=
github.com/lestrrat-go/jwx/v4 v4.4.0/go.mod h1:65utsGK/iSrjgGfu6iqj/TAvSfia6SSXkRpjHcKcTyg= github.com/lestrrat-go/jwx/v4 v4.5.0/go.mod h1:HpoztFC5HrNj9iKkZJ3XTQQgzCY2Zto/29n89QhTC10=
github.com/lestrrat-go/option/v3 v3.0.0-alpha1 h1:dvdzLwm/Ba5CJUF3jQP7w/iNYSLfy7yyh9XXNa1WjxI= github.com/lestrrat-go/option/v3 v3.0.0-alpha1 h1:dvdzLwm/Ba5CJUF3jQP7w/iNYSLfy7yyh9XXNa1WjxI=
github.com/lestrrat-go/option/v3 v3.0.0-alpha1/go.mod h1:5KSg20dfsKkNJtjDmaQRLZVXuUrzuCCcz/gbDK0pfKk= github.com/lestrrat-go/option/v3 v3.0.0-alpha1/go.mod h1:5KSg20dfsKkNJtjDmaQRLZVXuUrzuCCcz/gbDK0pfKk=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
+43
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"regexp"
"strings" "strings"
"git.unbound.se/unboundsoftware/auth0mock/store" "git.unbound.se/unboundsoftware/auth0mock/store"
@@ -152,3 +153,45 @@ func (h *ManagementHandler) PasswordChangeTicket(w http.ResponseWriter, r *http.
"ticket": "https://some-url", "ticket": "https://some-url",
}) })
} }
// emailQuery matches the one Lucene query this mock answers: an email phrase,
// email:"...", with \" and \\ escaped inside it.
var emailQuery = regexp.MustCompile(`^email:"((?:[^"\\]|\\.)*)"$`)
// SearchUsers handles GET /api/v2/users. Like Auth0's user search it matches
// email in any letter case (users-by-email does not), and it answers in the
// shape the Go SDK asks for by default (include_totals=true). Only an email
// phrase is understood; any other query is refused rather than answered wrong.
func (h *ManagementHandler) SearchUsers(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
match := emailQuery.FindStringSubmatch(q)
if match == nil {
http.Error(w, "only q=email:\"...\" is supported", http.StatusBadRequest)
return
}
email := strings.NewReplacer(`\"`, `"`, `\\`, `\`).Replace(match[1])
h.logger.Debug("searching users", "email", email)
users := []UserResponse{}
for _, user := range h.userStore.List() {
if strings.EqualFold(user.Email, email) {
users = append(users, UserResponse{
Email: user.Email,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
UserID: fmt.Sprintf("auth0|%s", user.UserID),
Picture: user.Picture,
})
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"start": 0,
"limit": len(users),
"length": len(users),
"total": len(users),
"users": users,
})
}
+66
View File
@@ -0,0 +1,66 @@
package handlers
import (
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"git.unbound.se/unboundsoftware/auth0mock/store"
)
func searchUsers(t *testing.T, h *ManagementHandler, q string) (int, []UserResponse) {
t.Helper()
req := httptest.NewRequest(http.MethodGet, "/api/v2/users?search_engine=v3&include_totals=true&q="+url.QueryEscape(q), nil)
rec := httptest.NewRecorder()
h.SearchUsers(rec, req)
if rec.Code != http.StatusOK {
return rec.Code, nil
}
var body struct {
Total int `json:"total"`
Users []UserResponse `json:"users"`
}
if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
t.Fatalf("decode: %v", err)
}
if body.Total != len(body.Users) {
t.Fatalf("total %d, users %d", body.Total, len(body.Users))
}
return rec.Code, body.Users
}
// The search matches email in any letter case, as Auth0's does: a backend that
// checks whether an address is taken must find it however it was typed.
func TestSearchUsersMatchesEmailInAnyCase(t *testing.T) {
users := store.NewUserStore()
users.Create("anna@kpmg.se", &store.User{GivenName: "Anna"})
users.Create("bob@acme.se", &store.User{GivenName: "Bob"})
h := NewManagementHandler(users, slog.New(slog.DiscardHandler))
for _, q := range []string{`email:"anna@kpmg.se"`, `email:"ANNA@KPMG.SE"`, `email:"Anna@Kpmg.se"`} {
code, found := searchUsers(t, h, q)
if code != http.StatusOK || len(found) != 1 || found[0].UserID != "auth0|anna@kpmg.se" {
t.Fatalf("%s: code %d, found %+v", q, code, found)
}
}
if _, found := searchUsers(t, h, `email:"nobody@acme.se"`); len(found) != 0 {
t.Fatalf("an unknown address found %+v", found)
}
// A quoted * is part of the address, not a wildcard.
if _, found := searchUsers(t, h, `email:"*@kpmg.se"`); len(found) != 0 {
t.Fatalf("a quoted * matched %+v", found)
}
}
func TestSearchUsersRefusesOtherQueries(t *testing.T) {
h := NewManagementHandler(store.NewUserStore(), slog.New(slog.DiscardHandler))
for _, q := range []string{"", "name:anna", `email:"a@b.se" OR name:x`} {
if code, _ := searchUsers(t, h, q); code != http.StatusBadRequest {
t.Fatalf("%q: code %d, want 400", q, code)
}
}
}