Repository navigation
v0.2.0
Changed
-
Projects created by
tango newprojectopt in to everything new in this release:LoadConfigFromEnv(tango.WithPortFromEnv()), which replaces the hardcoded:8000and honoursTANGO_ADDRandPORT;RequestID,RecovererandAccessLogger;- a global
MaxBodySize(1 << 20); MiddlewareScopeAll.
Existing applications keep their behaviour unless they opt in.
-
accountsmodels gainAccount.EmailVerifiedAtand a newAccountTokenmodel, for password reset and email verification. Every app that installsaccountsmust runtango makemigrationsand apply the result with-migrate, whether or not it enables mail: the change only adds a column and a table, so it needs no--renameor--allow-drop. Existing accounts read as unverified; nothing is gated on it unless an app usesaccounts.RequireVerified. -
The
routeattribute is now the route's registered pattern in every log and metric for a request, trailing slash included (/items/{id}/), under both middleware scopes. Thetango.http.access,tango.http.panicandtango.http.request_id_generation_failedlogs used to drop a trailing slash (/items/{id}), whilectx.Logger(),tango.http.view_errorand the HTTP metrics did not, so the same request reported two routes. Dashboards or alerts that match these three events on a trimmedrouteneed the registered spelling. The attribute key is unchanged.
Added
- A
mailpackage for outgoing email:mail.Sender,Messagewith in-memory attachments (sent asmultipart/mixed, 10 MiB of raw attachments by default), and three senders.NewSMTPSender/SMTPSenderFromEnv(TANGO_SMTP_URL) always encrypt:smtp://requires STARTTLS,smtps://is TLS from the start, andsmtp+insecure://is plaintext to a loopback host only, with an explicit port. Errors never contain credentials, recipients or messages.WriterSenderis for development, andmail/mailtestis for tests. See ADR 0044 and the mail guide. accounts.WithMailturns on password reset (/accounts/password-reset/) and email verification (/accounts/verify/), andaccounts.RequireVerifiedguards a View on a verified email. Reset never reveals whether an address has an account, links are single-use, hashed, tied to the address they were sent to and built on a configuredBaseURL, and GET never uses a link. Emails go through a bounded in-memory outbox, which logstango.accounts.mail_failedandtango.accounts.mail_dropped. WithoutWithMail,accountsbehaves as before. See ADR 0045.Config.NotFoundandConfig.MethodNotAllowedViews replace the router's404and405responses (tanGO setsAllowfor a405). TheAllowheader lists every method routed at that path, including a non-standard one the app registered. UnderMiddlewareScopeAllthey run inside global middleware and are observed; underMiddlewareScopeRoutesthey run outside it.Config.MiddlewareScope: withtango.MiddlewareScopeAll, global middleware wraps the whole router, so Unmatched requests (the router's404and405) are logged, counted, recovered and body-limited, reported with route"(unmatched)". Global middleware then runs before routing, so it can't read route parameters or rewrite which route answers. The default staysMiddlewareScopeRoutes, today's behaviour, and may becomeMiddlewareScopeAllonly after a minor release of notice; set the scope explicitly to keep yours. See ADR 0043.tango.LoadConfigFromEnv(tango.WithPortFromEnv())falls back to thePORTvariable hosting platforms set: the address isTANGO_ADDR, else":"+PORT, else:8000. Without the option,LoadConfigFromEnvis unchanged.tango.MaxBodySize(n)middleware caps request bodies: an oversized body gets413with{"error":"request body too large"}. When several limits apply, the most restrictive wins.
Security
ServeContext(andServe) now drop a client that takes more than 10 seconds to send a request's headers, so a slow-header (Slowloris-style) client can't hold connections open indefinitely. This changes default behaviour under the compatibility promise's security exception.tango.WithReadHeaderTimeout(d)sets another bound, andWithReadHeaderTimeout(0)restores the old, unbounded behaviour.
Fixed
tango newappnow prints how to install the new app: the import path read fromgo.modand theInstalledAppsentry to add tomain.go. A new app used to be created silently and answer 404 until wired in.- The compatibility page promised a JSON body for every
403,405and429from/accounts/*. It now promises only the JSONaccountssends, for a rejected CSRF token (403) and rate limiting (429, withRetry-After). Closed registration's403is an HTML page, and the router's405has no body. A test pins that contract.