GitHub Security Advisory: GHSA-f6qq-3m3h-4g42
Package: github.com/go-pkgz/auth/v2 (GO)
Severity: CRITICAL (CVSS 9.1)
CWE: CWE-287: Improper Authentication
Vulnerable versions: >= 2.0.0, <= 2.1.1
Patched in: 2.1.2

Summary: auth: Patreon provider assigns the same local user ID to every authenticated Patreon account, enabling cross‑user impersonation

Details:
### Summary
The Patreon OAuth provider maps every authenticated Patreon account to the same local `user.ID`, instead of deriving a unique ID from the Patreon account returned by Patreon.

In practice, this means all Patreon-authenticated users of an application using this library are collapsed into a single local identity. Any application that trusts `token.User.ID` as the stable account key can end up mixing or fully merging unrelated Patreon users, which can lead to cross-account access, privilege confusion, and subscription-state leakage.

### Details
The bug is in the Patreon provider's user-mapping logic.

Both the root module and the `v2` module create a fresh empty `token.User{}` and then derive the Patreon ID from `userInfo.ID` before that field has been populated:

```go
mapUser: func(data UserData, bdata []byte) token.User {
    userInfo := token.User{}

    uinfoJSON := uinfo{}
    if err := json.Unmarshal(bdata, &uinfoJSON); err == nil {
        userInfo.ID = "patreon_" + token.HashID(sha1.New(), userInfo.ID)
        userInfo.Name = uinfoJSON.Data.Attributes.FullName
        userInfo.Picture = uinfoJSON.Data.Attributes.ImageURL
        ...
    }
    return userInfo
}
```

Affected locations:

- `provider/providers.go:257`
- `v2/provider/providers.go:257`

At that point, `userInfo.ID` is still the empty string, so the effective result is always:

```text
patreon_ + sha1("")
```

which is:

```text
patreon_da39a3ee5e6b4b0d3255bfef95601890afd80709
```

for every Patreon user.

The code appears to have intended to hash the Patreon user ID returned by Patreon, i.e. `uinfoJSON.Data.ID`, but instead hashes the uninitialized destination field.

Why this matters:

1. Patreon is a documented, supported provider.
2. The library documents `token.User.ID` as the hashed user ID exposed to consumers.
3. The OAuth flow stores the mapped user object in JWT claims, and middleware later injects that object into the request context verbatim, consuming handlers receive the provider's wrong user.ID as authoritative identity.

Relevant flow in `v2`:

- `v2/provider/oauth2.go:207` calls `u := p.mapUser(...)`
- `v2/provider/oauth2.go:223` stores `u` in token claims
- `v2/middleware/auth.go:154` copies `*claims.User` into request context

The existing tests already encode the broken behavior:

- `provider/providers_test.go:179`
- `provider/providers_test.go:204`
- `v2/provider/providers_test.go:179`
- `v2/provider/providers_test.go:204`

Those tests assert the constant empty-string hash value for Patreon users.

### PoC
This can be reproduced locally without contacting Patreon by exercising the provider's `mapUser` logic with two different Patreon payloads.

From the repository root, create a temporary test file:

`v2/provider/patreon_repro_test.go`

```go
package provider

import "testing"

func TestPatreonSharedIdentity(t *testing.T) {
    r := NewPatreon(Params{
        URL:     "http://example.com",
        Cid:     "cid",
        Csecret: "secret",
    })