Skip to content

feat: importable gRPC transport, Authorizer interface, and user/cert fixes - #28

Merged
maxlandon merged 4 commits into
mainfrom
dev
Jul 20, 2026
Merged

feat: importable gRPC transport, Authorizer interface, and user/cert fixes#28
maxlandon merged 4 commits into
mainfrom
dev

Conversation

@maxlandon

Copy link
Copy Markdown
Member

Summary

Four commits on dev:

  • feat(transports): importable gRPC transport + client/server boot runner — promotes the gRPC teamserver transport out of example/ into a supported transports/grpc/{server,client,proto} package (mTLS, token auth, audit, panic recovery, PostServe/PostDial hooks, ServeOn for custom listeners). Adds boot.Run() so a single binary resolves client/server run mode with no server/DB side effects, and client.SystemConfig().
  • feat(authz): add transport-agnostic Authorizer interface — the library authenticates identity; authorization stays the app's. One policy can gate any path to a capability, instead of being bolted onto a single transport and bypassed by another.
  • fix(users): render zero LastSeen as "never" and suffix durations with " ago", so a never-authenticated user no longer prints as Mon, 01 Jan 0001 ….
  • fix(users): one identity per name in UserCreate; self-heal server cert on CA rotation — re-creating a user now rotates credentials in place instead of accumulating duplicate rows, and UsersTLSConfig regenerates a server cert that no longer chains to the live CA.

Test plan

go build ./... and go test ./... pass locally on the default (pure-Go WASM SQLite) backend.

🤖 Generated with Claude Code

maxlandon and others added 4 commits July 18, 2026 23:32
…t on CA rotation

Two teamserver user/cert bugs found while exercising Sliver's multiplayer
path:

UserCreate inserted a new db.User row on every call (Name has no unique
index; Save with a zero PK always inserts), so re-provisioning the same
operator name accumulated duplicate identities. Since the authorization
model is keyed by Name, duplicates make per-user state (last-seen,
online, permissions) ambiguous. UserCreate now deletes any user(s)
holding the requested name before inserting, i.e. re-creating a user
rotates its credentials in place and collapses any pre-existing
duplicates to a single record. The token cache is reset so the old
token stops authenticating.

UsersTLSConfig only regenerated the server certificate when it was
entirely absent (ErrCertDoesNotExist). After a users-CA rotation the
server kept presenting an orphaned cert that no longer chained to the
live CA, so every remote handshake failed with "tls: bad certificate"
while clients (carrying the new CA) rejected it. It now verifies the
cached server cert still chains to the current users-CA and is within
its validity window (serverCertValidFor), regenerating and reloading it
otherwise, so the daemon self-heals on restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The users table printed a zero LastSeen (a user that never authenticated)
as the raw zero time.Time, i.e. a confusing "Mon, 01 Jan 0001 ..." date,
and a freshly-seen user as a bare "0s" that reads like "never". Print
"never" for a zero LastSeen and suffix the duration with " ago" ("0s ago")
so the two cases are unambiguous.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The team library authenticates identity; authorization is the application's. Authorizer lets an app plug one authorization policy that can gate any path to a capability (a gRPC interceptor, a detached task, an event handler), closing the gap where authz is bolted onto one transport and bypassed by another.
Promote the gRPC teamserver transport out of example/ into a supported,
importable package based on the production Sliver transport, and add a
run-mode runner so a single binary cleanly separates client and server.

transports/grpc/server (team/server.Handler):
- buffering, panic recovery, audit via AuditLogger(), mTLS, and token
  authentication (Server.Authenticate -> *team.User in context)
- PostServe(hook) to register application services; WithAuthorizer(team.Authorizer)
  to install an authorization interceptor; exported ServeOn(ln) so custom-listener
  transports (e.g. tsnet) reuse the whole stack
- WithCoreServices() registers the built-in Team users/version RPC

transports/grpc/client (team/client.Dialer):
- TLS-if-creds dialing; exposes the *grpc.ClientConn via Conn()/PostDial so apps
  register their own service clients on the shared connection
- implements team.Client (Users/VersionServer) against the core Team service

transports/grpc/proto: teamgrpc package (distinct proto pkg/file name to avoid
global-registry collision with the example transport).

client.SystemConfig(): locate the <app>_<user>_default.teamclient.cfg written by
'teamserver user --system' -- the artifact that drives thin-client detection.

boot.Run(Boot{App, ForceServer, Config, Client, Server}): resolve the run mode
with no server/database side effects and invoke exactly one callback, so the
teamserver (and its DB) is only ever constructed in server mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
return nil
}

func (h *Handler) ServeOn(ln net.Listener) {
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 4.10256% with 561 lines in your changes missing coverage. Please review.
✅ Project coverage is 26.36%. Comparing base (c1756e7) to head (be94171).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
transports/grpc/proto/transport.pb.go 0.00% 196 Missing ⚠️
transports/grpc/server/middleware.go 0.00% 94 Missing ⚠️
transports/grpc/server/server.go 0.00% 79 Missing ⚠️
transports/grpc/client/client.go 0.00% 70 Missing ⚠️
transports/grpc/proto/transport_grpc.pb.go 0.00% 45 Missing ⚠️
transports/grpc/server/rpc.go 0.00% 24 Missing ⚠️
server/users.go 21.73% 13 Missing and 5 partials ⚠️
transports/grpc/client/middleware.go 0.00% 17 Missing ⚠️
client/config.go 0.00% 12 Missing ⚠️
boot/boot.go 82.60% 2 Missing and 2 partials ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #28      +/-   ##
==========================================
- Coverage   29.59%   26.36%   -3.23%     
==========================================
  Files          61       69       +8     
  Lines        4017     4600     +583     
==========================================
+ Hits         1189     1213      +24     
- Misses       2695     3247     +552     
- Partials      133      140       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@maxlandon
maxlandon merged commit 3eddaeb into main Jul 20, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants