Compare commits
24
Commits
0.7.7
...
next-release
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7387d682ca | ||
|
|
09d8306cb1 | ||
|
|
87fe84076f | ||
|
|
c558a2330e | ||
|
|
f00222cb8d | ||
|
|
d1560cc0fe | ||
|
|
c5f5873c2c | ||
|
|
cd6bca11a1 | ||
|
|
961ce21fef | ||
|
|
aefe77ed1b | ||
|
|
556203ee18 | ||
|
|
7b7e17aa55 | ||
|
|
9f6c5eea6d | ||
|
|
dde19e2d12 | ||
|
|
6abec59dd4 | ||
|
|
a740166f42 | ||
|
|
a55085d4d4 | ||
|
|
32f5dca81e | ||
|
|
9b8cee3a51 | ||
|
|
c887bed070 | ||
|
|
359bf01497 | ||
|
|
2472804caf | ||
|
|
59d518e1a2 | ||
|
|
b6f24ef16e |
No files matched your search
+41
-1
@@ -2,7 +2,47 @@
|
|||||||
|
|
||||||
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.7.7] - 2026-08-16
|
## [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
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(deps)* Update module github.com/lestrrat-go/jwx/v4 to v4.3.0 (#405)
|
||||||
|
- *(deps)* Update module github.com/lestrrat-go/jwx/v4 to v4.4.0 (#409)
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(deps)* Update golang docker tag to v1.27 (#403)
|
||||||
|
- *(deps)* Update gcr.io/distroless/static-debian12 docker digest to d75cdd7 (#407)
|
||||||
|
- *(deps)* Update golang:1.27 docker digest to c5b07c1 (#411)
|
||||||
|
- *(deps)* Update golang:1.27 docker digest to f42f854 (#413)
|
||||||
|
- *(deps)* Update golang:1.27 docker digest to 0ecdc2a (#415)
|
||||||
|
- *(deps)* Update golang:1.27 docker digest to 4013ae0 (#417)
|
||||||
|
- *(deps)* Update golang:1.27 docker digest to 192b749 (#419)
|
||||||
|
|
||||||
|
## [0.7.7] - 2026-08-17
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26@sha256:0d1d3a794be25f809dd2cb3160d8c73276c4056a9f8242a138e908ddeee7b6b6 AS build
|
FROM golang:1.27@sha256:162be5298a40ed317005c8339c6de4d10d3eef336d66dc8e9259b03ab9d3a6d2 AS build
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
@@ -10,7 +10,7 @@ RUN go mod download
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN go build -ldflags="-s -w" -o /release/service ./cmd/service
|
RUN go build -ldflags="-s -w" -o /release/service ./cmd/service
|
||||||
|
|
||||||
FROM gcr.io/distroless/static-debian12@sha256:6447365a6337c3732f412d1b74357b30a633831955b2bc45552b0086be907687
|
FROM gcr.io/distroless/static-debian12@sha256:d75cdd72874d4790092fcb1b058493ecf6bb5bf2b2b897045b00ff01d91843f2
|
||||||
|
|
||||||
ENV TZ=Europe/Stockholm
|
ENV TZ=Europe/Stockholm
|
||||||
ENV AUDIENCE="https://shiny.unbound.se"
|
ENV AUDIENCE="https://shiny.unbound.se"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ 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.2.0
|
github.com/lestrrat-go/jwx/v4 v4.5.0
|
||||||
github.com/rs/cors v1.11.1
|
github.com/rs/cors v1.11.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/lestrrat-go/dsig v1.3.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.54.0 // indirect
|
golang.org/x/crypto v0.56.0 // indirect
|
||||||
)
|
)
|
||||||
@@ -4,27 +4,23 @@ github.com/alecthomas/kong v1.16.1 h1:ixhCt93XkJ98kGposQ54+bl0IK6XwqB40AsMynU7Z8
|
|||||||
github.com/alecthomas/kong v1.16.1/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
github.com/alecthomas/kong v1.16.1/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
||||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
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.3.0 h1:phjMOCXvYzhuIgn7Voe2rex8z166vGfxRxmqM25P9/Q=
|
github.com/lestrrat-go/dsig v1.4.0 h1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8=
|
||||||
github.com/lestrrat-go/dsig v1.3.0/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc=
|
github.com/lestrrat-go/dsig v1.4.0/go.mod h1:I8Nddg/vN2cUl/h8N7SRRApLnNNeyZPIqLYpvpOtGGo=
|
||||||
github.com/lestrrat-go/jwx/v4 v4.2.0 h1:YpyEnRqejbDGwOSB8rIKD2EXqfAEA/2LLN2ZZkzm9xs=
|
github.com/lestrrat-go/jwx/v4 v4.5.0 h1:lgU8YcqaJo/iZllX4AvSaQTzT2XpWWMrDcPMe6+KzqI=
|
||||||
github.com/lestrrat-go/jwx/v4 v4.2.0/go.mod h1:1V1wOmyFnMLltrTKXZSRMZ/GWq8UeMW1JmUaBNqD3E4=
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
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=
|
||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user