# Changelog

All notable changes to this project are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] — 2026-07-22

First release of this fork of [techgaun/auth0_ex](https://github.com/techgaun/auth0_ex), whose last release
was 0.9.0 in March 2024. Everything below is relative to that.

**This release is not drop-in compatible with `auth0_ex`.** See [Migrating](#migrating-from-auth0_ex).

### Changed

- **Renamed throughout.** The OTP application is `:auth0_client`, the module namespace is `Auth0Client`,
  and the config key is `config :auth0_client`. The repository has been `auth0_client` since the fork; the
  code now matches it.
- **Req replaces HTTPoison.** hackney leaves the dependency tree entirely, along with the EOL 1.x
  advisories it carried, and the `httpoison ~> 2.2` constraint that blocked consumers from moving to
  httpoison 3.0. TLS trust now comes from OTP's system CA store via Mint rather than from `certifi`.
- **One failure value.** Every function returns `{:ok, term}`, `:ok`, or `{:error, %Auth0Client.Error{}}`.
  The struct carries `reason`, `status`, `body` and `headers`, and is an exception, so `raise` works. This
  replaces two different error shapes that were themselves a source of `FunctionClauseError` downstream.
- **Error bodies are decoded maps**, not raw JSON strings. Callers that ran `Jason.decode!/1` on the body
  themselves must drop that call.
- **Every 2xx is a success, and a 2xx with an empty body is `:ok`.** Previously only 200 and 201 counted,
  which made `Action.deploy/1` (202) look like a failure.
- **Response headers survive the parse**, so `Auth0Client.Error.retry_after/1` can read the rate-limit
  backoff on a 429.
- **`Auth0Client.Utils.mgmt_token/0` returns `{:ok, token} | {:error, reason}`** rather than raising on any
  non-success response. A management call against a rate-limited tenant now returns an error instead of
  crashing, and never reaches the endpoint.

### Added

Management API coverage went from 55 to 253 of 451 endpoints, with 22 resource groups fully covered.

- **RBAC** — `Role`, and the `effective-*` lookups on `User` that resolve access inherited through groups
- **`Group`**, the other half of that story: group members, and the roles a group grants
- **`Organization`** — members, org-scoped roles, invitations, connections, client grants
- **`Action`**, replacing the deprecated `Rule` ahead of its 2026-11-18 end of life
- **`Connection`** complete, including `Connection.Scim` (inbound provisioning) and
  `Connection.DirectoryProvisioning` (outbound sync)
- **`Client` and `ClientGrant`** complete, including credentials, so Private Key JWT works end to end
- **`Guardian`** — MFA factors, policies, enrollment tickets, and provider configuration
- **`CustomDomain` and `Key`** — domain verification, signing-key rotation, bring-your-own-key
- **`Session` and `RefreshToken`** — inspect and revoke one rather than all of them
- **`Job`** complete — bulk user import and export, import via `multipart/form-data`
- **`Grant`, `Tenant`, `Log`, `EmailProvider`, `Ticket`, `ResourceServer`, `DeviceCredential`, `UserBlock`**

Authentication API coverage went from 3 to 21 endpoints, and all 11 documented `/oauth/token` grants are
now reachable.

- **The authorization code flow, end to end** — `Url.authorize/2`, `Token.auth_code/4`, PKCE, refresh,
  revoke, and both logout URL builders
- **MFA** — `Auth0Client.Authentication.Mfa` plus the `mfa-otp`, `mfa-oob` and `mfa-recovery-code` grants,
  closing the gap where a login could report `mfa_required` and nothing could act on it
- **Passwordless**, **Device Authorization Flow** (for devices with no browser), **Pushed Authorization
  Requests**, **native social token exchange**, and **global token revocation**
- **JWKS and OIDC discovery** — `jwks/0` and `openid_configuration/0`

Other additions:

- **A management token cache built for concurrency.** `Auth0Client.TokenState` renews before expiry rather
  than after, keeps one fetch in flight so a cold cache under load makes one request rather than one per
  caller, evicts a token Auth0 rejects with a 401 and replays the request once, and redacts the cached JWT
  from `inspect/1`.
- **`token_refresh_skew` config**, how many seconds before expiry to renew. Defaults to 60.
- **Per-request headers on every verb.**
- **A documentation set** covering [configuration](guides/configuration.md), [login](guides/login.md),
  [MFA](guides/mfa.md), [users](guides/users.md), [RBAC](guides/rbac.md),
  [organizations](guides/organizations.md), [applications](guides/applications.md),
  [connections](guides/connections.md), [actions](guides/actions.md),
  [domains and keys](guides/domains-and-keys.md) and [error handling](guides/error-handling.md).

### Removed

Endpoints Auth0 has deprecated or retired are not supported.

- **`Auth0Ex.Management.Rule`** — deprecated 2023-05-16, read-only since 2024-11-18, end of life
  2026-11-18. Use `Auth0Client.Management.Action`.
- **`Auth0Ex.Authentication.Login`** — `database/6` (`POST /oauth/ro`, deprecated 2017) and `social/4`
  (`POST /oauth/access_token`). Use the Resource Owner Password grant and `Url.authorize/2`.
- **`Auth0Ex.Authentication.tokeninfo/1`** — superseded by `userinfo/1`.
- **`Auth0Ex.Management.Blacklist`** — `/blacklists/tokens` is in neither the OpenAPI document nor the docs.
- **The `:v2_search` config flag** — user search v2 was retired 2019-06-30.

### Fixed

- **`userinfo/1` sent the access token in the query string**, where it leaked into access logs. It is now
  an `Authorization: Bearer` header, the only form Auth0 documents.
- **`auth_code_pkce/5` required a `client_secret`**, which public clients do not have — the entire reason
  PKCE exists. Now `auth_code_pkce/4`, with the secret optional in `opts`.
- **`search_engine=v3` was sent on every `User` call**, including ones that reject it. It is now applied in
  `User.all/1` only, and an explicit `search_engine` wins.
- **`UserBlock.unblock/1` always raised `BadMapError`.**
- **`build_url/2` appended a trailing `?`** when there was no query string, and raised `BadMapError` on a
  keyword list.
- **`http_opts` could silently disable TLS certificate verification.** `verify: :verify_none` is now
  refused, with `dangerously_disable_tls_verification: true` as an explicit, loudly-named opt-out.

### Migrating from `auth0_ex`

```elixir
# mix.exs
{:auth0_client, "~> 1.0"}
```

```sh
# in your application
sed -i 's/Auth0Ex/Auth0Client/g'      lib/**/*.ex
sed -i 's/:auth0_ex/:auth0_client/g'  config/*.exs
```

Then, in order of how quietly each one fails:

1. **The config key is silent when missed.** `config :auth0_ex` is simply never read, and the first call
   raises about a missing domain rather than about the rename.
2. **Error handling changes shape.** Match on `{:error, %Auth0Client.Error{}}` and drop any
   `Jason.decode!/1` you were applying to the body — it raises on an already-decoded map.
3. **Removed modules** — see above for the replacement of each.
4. **`auth_code_pkce/5` is now `/4`**, and `signup/4` takes `(client_id, password, connection, opts)` with
   `email` in `opts`. Both changed shape rather than name; a guard turns an unmigrated `signup` call into a
   `FunctionClauseError` rather than a wrong request.

[1.0.0]: https://github.com/tristanperalta/auth0_client/releases/tag/v1.0.0
