v0.2.2
RemoteAuth — authenticate a second Go service against a go-auth server it shares no database with — plus an admin view of any user's organizations, and role filters that reject a bad value instead of silently returning everything.
Remote authentication
-
middleware.RemoteAuthauthenticates requests against a remote go-auth server instead of a local database. It is the counterpart toAuthMiddlewarefor a second process that fronts your API but doesn't own its data — an admin console, a BFF, a gateway. Such a service has no business holding your application's database credentials, and standing up a secondgoauth.Authjust to reuse its middleware means duplicating the whole auth configuration with nothing keeping the two copies in sync.It resolves the caller by forwarding their cookies to the upstream
GET /auth/meand decoding thedomain.Userit returns. Because it stores the user under the same context keyAuthMiddlewareuses,GetUserFromContextandRequireRolework downstream without knowing which one authenticated the request — an existing handler moves behind it untouched.remote, err := middleware.NewRemoteAuth("https://api.example.com") mux.Handle("GET /admin/reports", remote.RequireAdmin(reportsHandler))RequireAuth,RequireRole,RequireAdmin, and aGetUser(ctx, cookieHeader)for code outside a middleware chain. Options:WithRemoteCookieName,WithRemoteHTTPClient,WithRemoteLogger. The service needs no database, no secret, and nogoauth.Config; it never sees a password or any key material. See Remote Auth. -
Three outcomes, not two.
ErrRemoteNoSession→401(and no upstream call is made, so an unauthenticated flood costs upstream nothing),ErrRemoteUnauthorized→401, andErrRemoteUnavailable→503. The last fails closed but is never a claim that the caller is unauthenticated — a401there would log users out en masse during an upstream blip and tell the client its credentials are bad when the truth is that we couldn't ask. -
No caching, deliberately. Every request asks upstream.
AuthMiddlewarere-reads the session and user from the database per request, so a revoked session or demoted admin stops working on the very next call; caching here would reintroduce exactly that window, and sinceUpdateUserRoledoes not revoke sessions, nothing else would catch it. -
Redirects are never followed, including on a client supplied via
WithRemoteHTTPClient./auth/meanswers200or401and never redirects, and Go compares hostnames while ignoring ports when deciding whether to keep theCookieheader — so a3xxwould hand the caller's live session cookie to any other port on the same host. It is reported asErrRemoteUnavailableinstead. A caller's client is copied rather than adopted, and a zeroTimeoutbecomes 10s so a hung upstream can't pin a request goroutine. -
Upstream
Set-Cookieheaders are forwarded before the status is inspected, so a session rotation accompanying a success is never dropped — otherwise the browser would keep a refresh token upstream has already rotated, and the next refresh would read that as reuse.
Organizations
-
GET /admin/users/{id}/orgsandGET /admin/users/{id}/orgs/count— every org a given user belongs to, the inverse of the member listing. The self-serviceGET /auth/orgsonly ever reads the caller's memberships; this reads someone else's, so it requires a platform admin and404s on an unknown{id}. Neither publishes an audit event. Backed byOrgService.AdminListUserOrgs/AdminCountUserOrgs. -
Role filter on user-org listings.
port.UserOrgFilterandservice.ListUserOrgsInputgainedRole *domain.OrgRole, applied as anom.role = $npredicate shared by both the list and count queries, andGET /auth/orgs//auth/orgs/countnow read it as arolequery param alongside the new admin endpoint.role=owneranswers the question you actually have before deleting an account — which orgs would this leave ownerless — instead of discovering it onecannot_remove_last_ownerat a time. -
An unrecognized
roleis now a400on every org listing, not a silently dropped filter. PreviouslyGET /auth/orgs/{orgID}/members,/invites, their/countsiblings, andGET /admin/orgs/{orgID}/membersall ignored a value they didn't recognize and returned everything — so?role=Owner, capital O straight out of a dropdown label, handed back every member of the org with no error anywhere, and a console rendering that under the heading it filtered by was confidently wrong.orderBy/orderDirectionstill fall back to their defaults, and that contrast is the point: a wrong sort is cosmetic and visible on screen, a wrong filter is neither. The service layer already rejected an invalid role on every mutation input; the filters now agree with it. An emptyrole=still means "no filter", so a<select>bound straight to the query param is unaffected. The platform-role filter onGET /admin/users(admin/user, a different type) is unchanged.All eight org-role query parsers now share one
parseOrgRolehelper instead of six hand-rolled copies of the sameif.
Documentation
- New Remote Auth guide;
RemoteAuthalso threaded into the architecture package map and middleware chain, the security access-control model, and the client middleware guide as the Go counterpart to its/auth/meoption. - The two new admin routes documented in the admin guide and the route reference; the
rolefilter and the reject-vs-fall-back policy documented across the organizations guide's three listing tables, with one note covering the whole org area of the route reference. auth_unavailableadded to the error reference.
v0.2.3
One identity endpoint instead of two, a session and its user resolved in a single query, correct client IPs behind a proxy, and the cookie settings a frontend on another host actually needs.
v0.2.1
Row counts split into dedicated /count endpoints, admin-console read-path indexes, trigram search for users and invites, derived invite expiry, admin-only invite service, and bulk invite actions.