Skip to content

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 00:14
· 308 commits to main since this release

Changed

  • Projects created by tango newproject opt in to everything new in this release:

    • LoadConfigFromEnv(tango.WithPortFromEnv()), which replaces the hardcoded :8000 and honours TANGO_ADDR and PORT;
    • RequestID, Recoverer and AccessLogger;
    • a global MaxBodySize(1 << 20);
    • MiddlewareScopeAll.

    Existing applications keep their behaviour unless they opt in.

  • accounts models gain Account.EmailVerifiedAt and a new AccountToken model, for password reset and email verification. Every app that installs accounts must run tango makemigrations and apply the result with -migrate, whether or not it enables mail: the change only adds a column and a table, so it needs no --rename or --allow-drop. Existing accounts read as unverified; nothing is gated on it unless an app uses accounts.RequireVerified.

  • The route attribute is now the route's registered pattern in every log and metric for a request, trailing slash included (/items/{id}/), under both middleware scopes. The tango.http.access, tango.http.panic and tango.http.request_id_generation_failed logs used to drop a trailing slash (/items/{id}), while ctx.Logger(), tango.http.view_error and the HTTP metrics did not, so the same request reported two routes. Dashboards or alerts that match these three events on a trimmed route need the registered spelling. The attribute key is unchanged.

Added

  • A mail package for outgoing email: mail.Sender, Message with in-memory attachments (sent as multipart/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, and smtp+insecure:// is plaintext to a loopback host only, with an explicit port. Errors never contain credentials, recipients or messages. WriterSender is for development, and mail/mailtest is for tests. See ADR 0044 and the mail guide.
  • accounts.WithMail turns on password reset (/accounts/password-reset/) and email verification (/accounts/verify/), and accounts.RequireVerified guards 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 configured BaseURL, and GET never uses a link. Emails go through a bounded in-memory outbox, which logs tango.accounts.mail_failed and tango.accounts.mail_dropped. Without WithMail, accounts behaves as before. See ADR 0045.
  • Config.NotFound and Config.MethodNotAllowed Views replace the router's 404 and 405 responses (tanGO sets Allow for a 405). The Allow header lists every method routed at that path, including a non-standard one the app registered. Under MiddlewareScopeAll they run inside global middleware and are observed; under MiddlewareScopeRoutes they run outside it.
  • Config.MiddlewareScope: with tango.MiddlewareScopeAll, global middleware wraps the whole router, so Unmatched requests (the router's 404 and 405) 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 stays MiddlewareScopeRoutes, today's behaviour, and may become MiddlewareScopeAll only after a minor release of notice; set the scope explicitly to keep yours. See ADR 0043.
  • tango.LoadConfigFromEnv(tango.WithPortFromEnv()) falls back to the PORT variable hosting platforms set: the address is TANGO_ADDR, else ":"+PORT, else :8000. Without the option, LoadConfigFromEnv is unchanged.
  • tango.MaxBodySize(n) middleware caps request bodies: an oversized body gets 413 with {"error":"request body too large"}. When several limits apply, the most restrictive wins.

Security

  • ServeContext (and Serve) 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, and WithReadHeaderTimeout(0) restores the old, unbounded behaviour.

Fixed

  • tango newapp now prints how to install the new app: the import path read from go.mod and the InstalledApps entry to add to main.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, 405 and 429 from /accounts/*. It now promises only the JSON accounts sends, for a rejected CSRF token (403) and rate limiting (429, with Retry-After). Closed registration's 403 is an HTML page, and the router's 405 has no body. A test pins that contract.