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
41 changes: 41 additions & 0 deletions CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,47 @@
"grokify"
],
"releases": [
{
"version": "v0.32.1",
"date": "2026-11-06",
"added": [
{
"description": "`systemauth.Server.Principal` returns a human principal's profile from social login's directory",
"commit": "dd0381d"
},
{
"description": "`console.Options.Profile` supplies the signed-in person's name, email and avatar to the console's session",
"commit": "4571bca"
},
{
"description": "The connections API returns each connection's `health.details`, what its last successful check found the credential reaches, such as the Search Console properties an account can read",
"commit": "846ed7b"
}
],
"fixed": [
{
"description": "The organization console's profile menu showed no name or picture; its session now carries them, filled by `systemauthsvc` from SystemAuth's directory",
"commit": "4571bca"
}
],
"tests": [
{
"description": "The console's session with and without a profile, and `Server.Principal` with and without social login",
"commit": "e54e806"
}
],
"dependencies": [
{
"description": "Bump `github.com/plexusone/connectforge` from 0.6.0 to 0.7.0",
"commit": "846ed7b"
}
],
"documentation": [
{
"description": "The console guide describes the person's profile in the session"
}
]
},
{
"version": "v0.32.0",
"date": "2026-11-05",
Expand Down
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro

## [Unreleased]

## [v0.32.1] - 2026-11-06

### Added

- `systemauth.Server.Principal` returns a human principal's profile from social login's directory ([`dd0381d`](https://github.com/plexusone/systemforge/commit/dd0381d))
- `console.Options.Profile` supplies the signed-in person's name, email and avatar to the console's session ([`4571bca`](https://github.com/plexusone/systemforge/commit/4571bca))
- The connections API returns each connection's `health.details`, what its last successful check found the credential reaches, such as the Search Console properties an account can read ([`846ed7b`](https://github.com/plexusone/systemforge/commit/846ed7b))

### Fixed

- The organization console's profile menu showed no name or picture; its session now carries them, filled by `systemauthsvc` from SystemAuth's directory ([`4571bca`](https://github.com/plexusone/systemforge/commit/4571bca))

### Dependencies

- _1 dependency update_

### Documentation

- The console guide describes the person's profile in the session

### Tests

- The console's session with and without a profile, and `Server.Principal` with and without social login ([`e54e806`](https://github.com/plexusone/systemforge/commit/e54e806))

## [v0.32.0] - 2026-11-05

### Highlights
Expand Down Expand Up @@ -1855,7 +1879,8 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro
- GitHub Actions lint workflow with golangci-lint ([`e2f98d5`](https://github.com/plexusone/systemforge/commit/e2f98d5))
- Dependabot configuration for Go modules and GitHub Actions ([`e2f98d5`](https://github.com/plexusone/systemforge/commit/e2f98d5))

[unreleased]: https://github.com/plexusone/systemforge/compare/v0.32.0...HEAD
[unreleased]: https://github.com/plexusone/systemforge/compare/v0.32.1...HEAD
[v0.32.1]: https://github.com/plexusone/systemforge/compare/v0.32.0...v0.32.1
[v0.32.0]: https://github.com/plexusone/systemforge/compare/v0.31.0...v0.32.0
[v0.31.0]: https://github.com/plexusone/systemforge/compare/v0.30.5...v0.31.0
[v0.30.5]: https://github.com/plexusone/systemforge/compare/v0.30.4...v0.30.5
Expand Down
27 changes: 26 additions & 1 deletion console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ type Organization struct {
Apps map[string]string `json:"apps,omitempty"`
}

// Profile is how the signed-in person appears in the console's chrome: the
// name and picture in the profile menu.
type Profile struct {
Name string
Email string
AvatarURL string
}

// App is an application in the deployment's catalog.
type App struct {
ID string `json:"id"`
Expand All @@ -92,6 +100,10 @@ type Options struct {
// Apps lists the deployment's application catalog, for the console's
// product switcher. Nil lists none.
Apps func(ctx context.Context) ([]App, error)
// Profile returns the person's name, email and picture, for the
// profile menu, as the applications show them. Nil, or an error, shows
// the person without them; the console still works.
Profile func(ctx context.Context, principal uuid.UUID) (Profile, error)
// Connections, if set, serves the connections section.
Connections *connections.Connections
// WebDir holds the built console pages. Empty serves only the API.
Expand Down Expand Up @@ -198,6 +210,9 @@ type wireSession struct {

type wireUser struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
Memberships []wireMembership `json:"memberships"`
}

Expand Down Expand Up @@ -248,9 +263,19 @@ func (c *console) session(w http.ResponseWriter, r *http.Request) {
}
catalog = append(catalog, apps...)
}
user := &wireUser{ID: pid.String(), Memberships: ms}
if c.o.Profile != nil {
p, err := c.o.Profile(r.Context(), pid)
if err != nil {
// The profile menu then shows no name or picture.
c.o.Logger.Error("console: reading the profile", "principal", pid, "error", err)
} else {
user.Name, user.Email, user.AvatarURL = p.Name, p.Email, p.AvatarURL
}
}
writeJSON(w, http.StatusOK, wireSession{
Authenticated: true, UserID: pid.String(),
User: &wireUser{ID: pid.String(), Memberships: ms},
User: user,
Apps: catalog,
})
}
Expand Down
48 changes: 48 additions & 0 deletions console/console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type env struct {
admin uuid.UUID
member uuid.UUID
sessionError error
profileError error
signedOut bool
}

Expand Down Expand Up @@ -79,6 +80,18 @@ func newEnv(t *testing.T, webDir string, withConnections bool) *env {
Apps: func(context.Context) ([]console.App, error) {
return []console.App{{ID: "portfolio", Name: "Portfolio", URL: "https://portfolio.example.com", Order: 3}}, nil
},
Profile: func(_ context.Context, p uuid.UUID) (console.Profile, error) {
if e.profileError != nil {
return console.Profile{}, e.profileError
}
switch p {
case e.admin:
return console.Profile{Name: "Ada Admin", Email: "ada@example.com", AvatarURL: "https://img.example.com/ada.png"}, nil
case e.member:
return console.Profile{Name: "Max Member", Email: "max@example.com", AvatarURL: "https://img.example.com/max.png"}, nil
}
return console.Profile{}, errors.New("no such person")
},
ReturnTo: func(raw string) (string, error) {
if strings.HasPrefix(raw, "/") && !strings.HasPrefix(raw, "//") || strings.HasPrefix(raw, "https://app.example.com/") {
return raw, nil
Expand Down Expand Up @@ -169,6 +182,9 @@ func TestSession(t *testing.T) {
Apps []console.App
User struct {
ID string
Name string
Email string
AvatarURL string `json:"avatar_url"`
Memberships []struct {
OrganizationID string `json:"organization_id"`
OrganizationName string `json:"organization_name"`
Expand All @@ -183,6 +199,10 @@ func TestSession(t *testing.T) {
if !s.Authenticated || s.User.ID != e.admin.String() || len(s.User.Memberships) != 1 {
t.Fatalf("session: %+v", s)
}
// The profile menu shows the person as the applications do.
if s.User.Name != "Ada Admin" || s.User.Email != "ada@example.com" || s.User.AvatarURL != "https://img.example.com/ada.png" {
t.Errorf("profile: %+v", s.User)
}
m := s.User.Memberships[0]
if m.OrganizationID != e.org.String() || m.OrganizationName != "Acme" || m.Role != "admin" || m.Applications[console.AppID] != "admin" {
t.Fatalf("membership: %+v", m)
Expand All @@ -193,6 +213,34 @@ func TestSession(t *testing.T) {
t.Fatalf("products: %+v, catalog %+v", m.Applications, s.Apps)
}

// Each person's session carries their own profile.
resp, body = e.do(t, e.member, http.MethodGet, console.SessionPath, nil, nil)
if resp.StatusCode != http.StatusOK {
t.Fatalf("member's session: %d %s", resp.StatusCode, body)
}
var ms struct {
User struct {
ID string
Name string
Email string
AvatarURL string `json:"avatar_url"`
}
}
if err := json.Unmarshal([]byte(body), &ms); err != nil {
t.Fatal(err)
}
if ms.User.ID != e.member.String() || ms.User.Name != "Max Member" || ms.User.Email != "max@example.com" || ms.User.AvatarURL != "https://img.example.com/max.png" {
t.Errorf("member's profile: %+v", ms.User)
}

// A profile that cannot be read leaves it out; the session still works.
e.profileError = errors.New("directory down")
resp, body = e.do(t, e.admin, http.MethodGet, console.SessionPath, nil, nil)
if resp.StatusCode != http.StatusOK || !strings.Contains(body, `"authenticated":true`) || strings.Contains(body, `"name":"Ada`) {
t.Fatalf("without a profile: %d %s", resp.StatusCode, body)
}
e.profileError = nil

e.sessionError = errors.New("store down")
if resp, _ := e.do(t, e.admin, http.MethodGet, console.SessionPath, nil, nil); resp.StatusCode != http.StatusInternalServerError {
t.Fatalf("a failing session store: %d", resp.StatusCode)
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The session routes under `/console/bff` speak the format `@plexusone/auth`'s

| Call | Does |
|---|---|
| `GET /console/bff/session` | The person and their organizations, personal first, and the deployment's app catalog. Each organization grants the application `console` with the person's organization role, besides their roles in the products, so the console's product switcher links back to them. Without a session: `{"authenticated":false}` |
| `GET /console/bff/session` | The person, with their name, email and avatar as the applications show them, and their organizations, personal first, and the deployment's app catalog. Each organization grants the application `console` with the person's organization role, besides their roles in the products, so the console's product switcher links back to them. Without a session: `{"authenticated":false}` |
| `GET /console/bff/auth/login?return_to=` | Sends the person to `/login`, and back to `return_to`, a path on this host |
| `GET /console/bff/auth/{provider}?return_to=` | The same, straight to that provider's sign-in |
| `POST /console/bff/auth/logout` | Signs the person out of SystemAuth, as `POST /logout` does, and answers `204` |
Expand Down
27 changes: 26 additions & 1 deletion docs/releases/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro

## [Unreleased]

## [v0.32.1] - 2026-11-06

### Added

- `systemauth.Server.Principal` returns a human principal's profile from social login's directory ([`dd0381d`](https://github.com/plexusone/systemforge/commit/dd0381d))
- `console.Options.Profile` supplies the signed-in person's name, email and avatar to the console's session ([`4571bca`](https://github.com/plexusone/systemforge/commit/4571bca))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
- The connections API returns each connection's `health.details`, what its last successful check found the credential reaches, such as the Search Console properties an account can read ([`846ed7b`](https://github.com/plexusone/systemforge/commit/846ed7b))

### Fixed

- The organization console's profile menu showed no name or picture; its session now carries them, filled by `systemauthsvc` from SystemAuth's directory ([`4571bca`](https://github.com/plexusone/systemforge/commit/4571bca))

### Dependencies

- _1 dependency update_

### Documentation

- The console guide describes the person's profile in the session

### Tests

- The console's session with and without a profile, and `Server.Principal` with and without social login ([`e54e806`](https://github.com/plexusone/systemforge/commit/e54e806))

## [v0.32.0] - 2026-11-05

### Highlights
Expand Down Expand Up @@ -1855,7 +1879,8 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro
- GitHub Actions lint workflow with golangci-lint ([`e2f98d5`](https://github.com/plexusone/systemforge/commit/e2f98d5))
- Dependabot configuration for Go modules and GitHub Actions ([`e2f98d5`](https://github.com/plexusone/systemforge/commit/e2f98d5))

[unreleased]: https://github.com/plexusone/systemforge/compare/v0.32.0...HEAD
[unreleased]: https://github.com/plexusone/systemforge/compare/v0.32.1...HEAD
[v0.32.1]: https://github.com/plexusone/systemforge/compare/v0.32.0...v0.32.1
[v0.32.0]: https://github.com/plexusone/systemforge/compare/v0.31.0...v0.32.0
[v0.31.0]: https://github.com/plexusone/systemforge/compare/v0.30.5...v0.31.0
[v0.30.5]: https://github.com/plexusone/systemforge/compare/v0.30.4...v0.30.5
Expand Down
6 changes: 5 additions & 1 deletion docs/releases/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ Release notes, one file per version, newest first. The
[changelog](changelog.md) is generated from `CHANGELOG.json` and lists every
change with its commit.

The latest release, [v0.32.0](v0.32.0.md), lets a manager replace a
The latest release, [v0.32.1](v0.32.1.md), shows the signed-in person's
name and picture in the organization console, as the applications do. It
adds no breaking change.

Before it, [v0.32.0](v0.32.0.md) let a manager replace a
managed principal's login tokens in one call, so a lost printed card is
retired and reprinted in one step. Its sign-in methods page shows what is
linked and links back to the application. It adds no breaking change.
Expand Down
31 changes: 31 additions & 0 deletions docs/releases/v0.32.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# v0.32.1

**Release Date:** 2026-11-06

The organization console shows the signed-in person the way the
applications do, and connections show what their last check found.

- **Name and picture in the console.** The console's session,
`GET /console/bff/session`, now carries the person's name, email and
avatar, so its profile menu shows their picture instead of a placeholder.
`console.Options.Profile` supplies them; `systemauthsvc` fills it from
SystemAuth's directory, the same source as the `name` and `picture`
claims. Without it, or when it fails, the console works as before.
- **`systemauth.Server.Principal`** returns a human principal's profile
from social login's directory.
- **What a connection reaches.** With connectforge v0.7.0, each
connection's `health.details` in the connections API lists what its
last successful check found, such as the Search Console properties an
account can read. A failed check keeps the last ones.

The release adds no breaking change.

## Upgrading

Nothing is required. A deployment running the console through
`systemauthsvc` shows the person's name and picture after upgrading.
Existing connections have no details until their next check.

## Dependencies

- `github.com/plexusone/connectforge` 0.7.0.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ require (
github.com/jackc/pgx/v5 v5.12.0
github.com/mattn/go-sqlite3 v1.14.52
github.com/ory/fosite v0.49.0
github.com/plexusone/connectforge v0.6.0
github.com/plexusone/connectforge v0.7.0
github.com/plexusone/keyforge v0.2.0
github.com/plexusone/omni-aws v0.11.0
github.com/plexusone/omnimail v0.1.0
Expand Down Expand Up @@ -235,7 +235,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/valyala/fastjson v1.6.10 // indirect
github.com/yaronf/httpsign v0.5.2 // indirect
github.com/zclconf/go-cty v1.19.0 // indirect
github.com/zclconf/go-cty v1.20.0 // indirect
github.com/zclconf/go-cty-yaml v1.2.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY=
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/plexusone/connectforge v0.6.0 h1:s/1upvVidylAd9cbafnxoAX+rVrhSIgtnjpgar1VZZ0=
github.com/plexusone/connectforge v0.6.0/go.mod h1:QJKDTQYcfAiguR9PIRcugR7aNnA6wnxGhCcT5CGUigg=
github.com/plexusone/connectforge v0.7.0 h1:ypR3yXqAVLkJ6CSKCqk6tieCxmchktVpqpKycfmO1Hw=
github.com/plexusone/connectforge v0.7.0/go.mod h1:s6gApsI+mzXxFlhjCdbv4a4Y1l9MPkYUCmQk3xVcHjg=
github.com/plexusone/keyforge v0.2.0 h1:Dr/KfFuQBAi2hW/fZkqe5u/QLQ43is4oWOFvUhandFQ=
github.com/plexusone/keyforge v0.2.0/go.mod h1:LXDweBp3uodk4iYWmxyqvFptjgEwFpWzzhdB86BQl4U=
github.com/plexusone/omni-aws v0.11.0 h1:hQM8wadLpy55K/Q30ci1wcRZ/MHOmHOlYi4ooW7ZFyE=
Expand Down Expand Up @@ -680,8 +680,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zclconf/go-cty v1.19.0 h1:IV8WdqYZc2c5rLX9bEoLNXKojBAp0MZPBHMIrCoa/s4=
github.com/zclconf/go-cty v1.19.0/go.mod h1:12W89jGn3JCOIQi7infWr9m80rOkb5RNYJqXMZcN4c8=
github.com/zclconf/go-cty v1.20.0 h1:V2Mh9pu2xWPATCY7US3LnGBNSq/AAZDsqQZmmOAV7DI=
github.com/zclconf/go-cty v1.20.0/go.mod h1:12W89jGn3JCOIQi7infWr9m80rOkb5RNYJqXMZcN4c8=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-yaml v1.2.0 h1:GDyL4+e/Qe/S0B7YaecMLbVvAR/Mp21CXMOSiCTOi1M=
Expand Down
10 changes: 10 additions & 0 deletions identity/systemauth/social_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,16 @@ func (s *Server) CurrentLoginSession(r *http.Request) (*LoginSession, error) {
return s.social.currentSession(r)
}

// Principal returns a human principal's profile from the directory social
// login uses, such as its display name and avatar. It returns
// ErrPrincipalNotFound when there is none or social login is not enabled.
func (s *Server) Principal(ctx context.Context, id uuid.UUID) (*HumanPrincipal, error) {
if s.social == nil {
return nil, ErrPrincipalNotFound
}
return s.social.directory.GetPrincipal(ctx, id)
}

// ValidateReturnTo checks a place to send a person after an action, by the
// policy sign-in uses for return_to: an absolute path on this host, or a URL
// on an allowed redirect origin. It returns the URL to use, or
Expand Down
Loading
Loading