goauthentik / goauthentik/authentik

invitation/…/send_email fails with 405 Method not allowed

Open
#24,410 4 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
25.6k
Forks
2k
Avg merge
1d 2h
Merged PRs (30d)
659

Description

### Describe the bug

`POST .../stages/invitation/invitations/{id}/send_email/` (`send_email` action) fails with `405 Method Not Allowed` via the Go client, while an otherwise byte-identical manual HTTP request succeeds

*(Filed here rather than in `client-go` since the `send_email` action itself - `authentik/stages/invitation/api.py` - lives in this repo, and it's not yet clear whether the root cause is server-side or client-generation-side; happy to have this moved if it turns out to be purely a `client-go` codegen issue.)*

## Environment

- `goauthentik.io/api/v3` (client-go): `v3.2026050.6`
- authentik server: `2026.5.6` (confirmed matching, ruled out client/server version drift)
- Go: 1.26
- Reverse proxy in front of authentik: nginx (no WAF/custom filtering configured, confirmed by re-running the same request from the exact same host/container)

## Summary

Calling `StagesAPI.StagesInvitationInvitationsSendEmailCreate(ctx, inviteUuid).InvitationSendEmailRequest(req).Execute()` reproducibly fails with `405 Method Not Allowed`. The response has an **empty body** and `Content-Type: text/html; charset=utf-8` — not the JSON error body authentik/DRF normally returns for a 405. The `Allow` header on the failing response is `GET, HEAD, OPTIONS` (no `POST`), which does not match the `send_email` action's own registered methods (`methods=["post"]` in `authentik/stages/invitation/api.py`).

A **manually constructed** `net/http` request to the exact same URL, with the exact same method, headers (including `Authorization`, `Content-Type`, `Accept`, `User-Agent`), and JSON body, succeeds reliably with `204 No Content` and sends the invitation email correctly.

### How to reproduce

1. Create an invitation via `StagesInvitationInvitationsCreate` (this succeeds every time, `201 Created`).
2. Immediately call `StagesInvitationInvitationsSendEmailCreate(ctx, invite.Pk)` with a minimal `InvitationSendEmailRequest{EmailAddresses: []interface{}{"test@example.com"}}`.
3. Observe `405 Method Not Allowed`, empty body, `Allow: GET, HEAD, OPTIONS`.

```go
sendReq := authentik.NewInvitationSendEmailRequest([]interface{}{"test@example.com"})
_, err := client.StagesAPI.StagesInvitationInvitationsSendEmailCreate(ctx, invite.Pk).
InvitationSendEmailRequest(*sendReq).Execute()
// err: 405 Method Not Allowed
```

4. Manually issue the equivalent request and observe it succeeds:

```bash
curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email_addresses": ["test@example.com"]}' \
"https:///api/v3/stages/invitation/invitations//send_email/" -i
# -> HTTP/2 204
```

### Expected behavior

204 and mail sent

### Screenshots

_No response_

### Additional context

## What was ruled out

Since the SDK-generated request and the manual curl request produced different results despite looking identical, the following were investigated and eliminated as the cause:

- **Permissions**: the service account has `view`/`change`/`add` on the `Invitation` model; the manual request uses the exact same bearer token.
- **URL/method/headers/body**: logged the actual outgoing Go request (method, full URL, all headers, body bytes, `Content-Length`) immediately before it hit the transport — byte-identical to the working curl request (confirmed via a custom `http.RoundTripper` wrapper).
- **Connection reuse / keep-alive**: disabled keep-alives on the Go transport (`DisableKeepAlives: true`) — no change.
- **HTTP/2 vs HTTP/1.1**: forced HTTP/1.1 on the Go transport (`TLSNextProto` set to an empty map) — no change, still `405`.
- **Timing / eventual consistency**: added a delay between create and send_email — no change.
- **DNS / load-balancing**: the hostname resolves to a single IP; ruled out different backend replicas.
- **Network path / WAF**: re-ran the exact same curl command **from the same container/host** that runs the Go client — curl still succeeds, Go client still fails, ruling out any network-path or IP-based filtering difference.
- **User-Agent**: set curl's `-A` to match the Go client's exact User-Agent (`OpenAPI-Generator/1.0.0/go`) — still succeeds via curl.
- **Accept header**: Go sends `Accept: application/json`; matched that explicitly in curl — still succeeds via curl.
- **Worker/server-side caching**: restarted the authentik server/workers — no change.

After eliminating all of the above, the only remaining difference is the HTTP client implementation itself (Go's `net/http` stack vs. curl/libcurl), for reasons not identified further (possibly something at the TLS/HTTP2 framing level not visible via Go's own request/response object inspection).

## Workaround

Bypassing the generated `StagesInvitationInvitationsSendEmailCreateExecute` and issuing the identical request via plain `net/http` directly resolves the issue reliably. This strongly suggests the bug is in the generated client code path for this specific operation rather than in application code or authentik server-side behavior.

### Deployment Method

Docker

### Version

2026.5.6

### Relevant log output

```shell

```

Contributor guide

Open the contributing guide

Research direction

Start with authentik/stages/invitation/api.py and the generated StagesAPI.StagesInvitationInvitationsSendEmailCreate entry point. Reproduce the operation with the Go client while comparing it with the working net/http or curl request, then inspect the generated client request and transport path. Done means the SDK operation reliably returns 204 and sends the invitation email.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, python
Domain
api, authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.