Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. Before 1.0.0, minor releases may contain breaking changes; they are always listed here.
0.2.0 - Unreleased
A complete rewrite. Almost every public API changed; the migration guide shows the before and after code for each of them.
Added
web: a standard-library-only web framework. Trie router with:paramand*wildcardsegments, 404/405 withAllow, automaticOPTIONSandHEAD, route groups, middleware (global, group, per-route),Context(which is acontext.Context), JSON binding with a body size limit,HTTPErrorhelpers, a replaceable error handler, panic recovery,CORS,RateLimit,RequestLogger,WrapHandler,WrapMiddleware,Runwith graceful shutdown,Serve,Shutdown.web:RateLimiter,NewRateLimiterandRateLimiter.Allow, the limiter behindRateLimit, for limits keyed by anything (for example an email address);RateLimitConfig.MaxKeys(defaultDefaultRateLimitMaxKeys, 100000) bounds the tracked keys, with one shared overflow bucket beyond it;ClientKey, the defaultRateLimitkey (the client IP, IPv6 addresses grouped by their /64 prefix).CORSanswers the"*"origin with the literalAccess-Control-Allow-Origin: *.auth: a store-agnostic authentication service (auth.New(Config, Store)): sign-up, sign-in, opaque server-side sessions with remember-me, session listing and revocation, email verification (SendVerificationEmail,RequestEmailVerification,VerifyEmail), password reset (RequestPasswordReset,ResetPassword),ChangePassword,ChangeEmail,CheckPassword, profile updates,DeleteUser,ListAccounts,ListUsers,CountUsers,BanUser/UnbanUserwith optional expiry,CleanupExpiredandShutdown.auth: optimistic concurrency for users and accounts:User.VersionandAccount.Version(managed by the stores) and the store sentinelErrConflict. TheStorecontract:CreateUser/CreateAccountsetVersionto 1,UpdateUser/UpdateAccountwrite only when the storedVersionmatches and returnErrConflictotherwise. The service retries a conflicting change up to 5 times and then returnsErrConflict.auth:ErrNameTooLong(names longer than 256 characters) andErrInvalidImageURL(images that are not empty or an absolutehttp/httpsURL of at most 2048 bytes), returned bySignUpandUpdateUser.auth:Config.MaxPendingEmails(defaultDefaultMaxPendingEmails, 100) bounds the background email jobs in flight; further jobs are dropped and logged at Warn level.auth: snake_case JSON tags onUser,Session,AccountandVerificationToken. Password hashes, token hashes andVersionare never marshalled;Userincludes the ban fields.auth: request-context helpersMustUser,UserFromContext,SessionFromContextandContextWithSession.auth: theStoreinterface,MemoryStore, theMailerinterface andDefaultMailer(plain text and HTML emails with verification and reset links).auth/authhttp: the HTTP layer of the auth service. 17 JSON routes under a configurable base path (/authby default), bearer-token and optional HttpOnly-cookie transport,RequireAuthmiddleware,OptionalAuthmiddleware for routes that also serve anonymous visitors,Middlewarefor plainnet/http,TokenandBasePathhelpers,Config.ValidateandConfig.ResolvedBasePath,Config.TrustedOriginsfor the cross-site request forgery check of cookie-authenticated requests, per-email rate limits on sign-in, forgot-password and verification requests, stable status codes and error messages, and aGET /verify-emailroute that serves the link in verification emails (redirect or a built-in page).auth/mongostore:auth.Storeon MongoDB withEnsureIndexes(unique and TTL indexes), a configurable collection prefix and an int64versionfield on users and accounts.auth/pgstore:auth.Storeon PostgreSQL withMigrate, a versioned, advisory-locked migration runner, a configurable table prefix and aversion BIGINT NOT NULL DEFAULT 1column on users and accounts (migration 1). Development databases migrated by a pre-release build of 0.2.0 lack that column and must be dropped and migrated again (see auth-storage.md).mail: theSenderinterface withSMTPSender(STARTTLS, implicit TLS or plaintext for local servers),LogSender,CaptureSenderandSenderFunc;Message.Validate; quoted-printable bodies and RFC 2047 encoded headers.corego:Core.Server(the web server with request logging, optional CORS and the auth routes mounted),Core.RequireAuth,Core.OptionalAuth,Core.Run,Core.AuthHandlers,AuthOptions(service config,MailSender, store selection,TablePrefix,CustomStore, HTTP config),ServerOptions(CORS, proxy headers, request log),Config.Loggerand theStoreMemory,StoreMongo,StorePostgresconstants.corego: the explicit CORS origins (ServerOptions.CORSorCORS_ALLOWED_ORIGINS) become the auth routes' trusted origins unlessAuthOptions.HTTP.TrustedOriginsis set; startup warnings for a wildcardCORS_ALLOWED_ORIGINSand for a session cookie withoutSecureoutside localhost.corego: automatic auth store selection (AuthOptions.Store, thenCOREGO_AUTH_STORE, then the first connected database, then memory with a warning), automatic indexes and migrations, and an SMTP mailer built from theSMTP_*variables (port 465 uses implicit TLS, other ports STARTTLS).env:Load,LoadFile, the typed gettersGet,GetOrDefault,Int,Bool,DurationandStrings, and the constantsDefaultFileandDefaultPort.database: every helper takes acontext.Context;ErrNotFound;MongoDB.Count,Name,Database,Ping;PostgresDB.QueryMaps,WithTx,Ping; the genericQueryAll[T]andQueryOne[T]with theQuerierinterface;DefaultMongoDatabase; the MongoDB database name is also read from the connection URL.- Environment variables
APP_URL,MONGODB_DATABASE,COREGO_AUTH_STORE,SMTP_HOST,SMTP_PORT,SMTP_USERNAME,SMTP_PASSWORD,SMTP_FROM,CORS_ALLOWED_ORIGINS,TRUST_PROXY_HEADERS,COREGO_RESET_PASSWORD_URLandCOREGO_VERIFY_EMAIL_REDIRECT_URL, and a commented.env.example. - Environment switches for the common auth settings, so an application can
be configured from
.envalone:COREGO_REQUIRE_VERIFIED_EMAIL,COREGO_AUTH_COOKIE,COREGO_AUTH_TABLE_PREFIXandCOREGO_MAIL(smtp,logfor development,none, or empty for automatic selection). Invalid values, including an invalidTRUST_PROXY_HEADERS, makecorego.Newreturn an error instead of being ignored. corego.Config.Env, to handNewan environment the application already loaded, and onecorego: auth readyInfo line at startup that summarizes the store, table prefix, base path, mailer and switches.web.AsHTTPError(the default error mapping, reusable in custom error handlers) andweb.Context.BindJSONStrict, which rejects unknown fields.Grouproutes registered with an empty path serve the group's own path.mail.LogSenderlogs one Info line per message with the links found in it, so development emails can be followed without Debug logging.authhttp.CookieConfig.OmitTokenFromBody, to keep the session token out of the JSON body when the cookie transport is used, and a Debug log line with the reason for every blocked cross-site cookie request.- A startup banner printed by
Core.Runwhen the output is a terminal: the address, the services in use (auth with its store and mailer, MongoDB and PostgreSQL with their database and host, CORS) and every registered route, the auth routes marked when they need a session.ServerOptions.DisableBannerandCOREGO_BANNERcontrol it. - A compact, colored console logger the facade uses by default when
standard error is a terminal and slog's default handler has not been
replaced (
NO_COLORandFORCE_COLORare honored);Config.Loggerand production log pipelines are unaffected.corego.Version. authhttp.Handlers.Routes, listing the auth routes in registration order with whether they need a session.examples/quickstart, a runnable reference application, and a complete documentation set underdocs/.
Changed
- The
webdefault error handler answers the body errors with the same descriptive messages as the auth routes ("Request body is not valid JSON.", "Content-Type must be application/json.", "Request body exceeds the configured size limit.") instead of the bare status text. - Breaking: authentication uses opaque, server-side sessions instead of JWTs. Sessions can be listed and revoked, and sign-out takes effect immediately. No signing secret is needed any more.
- Breaking:
auth.Manageris replaced byauth.Auth;auth.New(*auth.Config, *database.MongoDB)becameauth.New(auth.Config, auth.Store).Signup/LoginbecameSignUp/SignInwithcontext.Context, input structs and outputs carrying the user, the session token and the session. A display name is required at sign-up. - Breaking:
auth.UserlostPassword(hashes now live onauth.Account) andCustom; it gainedName,EmailVerified,Image, ban fields andUpdatedAt. Store application-specific user data in your own table or collection keyed byUser.ID. - Breaking: auth data lives in
corego_users,corego_accounts,corego_sessionsandcorego_verification_tokens(prefix configurable) instead of auserscollection, and works on MongoDB, PostgreSQL or memory instead of MongoDB only. - Breaking:
corego.Config.Authis a*corego.AuthOptionsinstead of a*auth.Config, andcorego.Core.Authis an*auth.Auth. - Breaking:
corego.Core.Closenow also closes PostgreSQL and first waits (up to 10 s) for auth emails still being delivered. - Breaking: the environment is loaded with
env.Load()(see Removed), which returns an error for a malformed.envfile. TheEnvfields were renamed (URL→AppURL,PORT→Port,MONGODB_CONNECTION_URL→MongoURL,POSTGRES_CONNECTION_URL→PostgresURL) and are plain strings instead of pointers.APP_URLreplacesURL(still read as a fallback), and the default application URL now includes the port (http://localhost:8080). - Breaking: loading the environment no longer writes
.envvalues into the process environment; read them throughEnv. A missing.envfile is no longer logged. - Breaking:
database.NewMongoDBanddatabase.NewPostgresDBtake their config by value and never modify it; the accessors areClient,PoolandClose(see Removed). - Breaking: every
databasehelper takes the caller'scontext.Contextas its first argument; the hidden 5 second timeout per operation is gone. - Breaking:
MongoDB.InsertOnereturns the inserted_idas the driver reports it (any, usually abson.ObjectID) instead of a hex string;MongoDB.Finddecodes into a slice pointer and accepts driver find options instead of returning[]map[string]any;UpdateOneandDeleteOnereturnErrNotFoundwhen nothing matched;UpdateManyandDeleteManyreturn the affected count; the write helpers reject a nil filter. - Breaking:
PostgresDB.Queryreturns the rawpgx.Rows; the old map-returning behavior isQueryMaps.PostgresConfig.Databasewas removed (the database is part of the URL). - Breaking: the MongoDB driver moved from
go.mongodb.org/mongo-driver(v1) togo.mongodb.org/mongo-driver/v2; usebson.ObjectIDinstead ofprimitive.ObjectID. - Error responses are JSON objects of the form
{"error": "<message>"}with stable, user-presentable messages and documented status codes, including409forauth.ErrConflict,400forauth.ErrNameTooLongandauth.ErrInvalidImageURL, and403 Cross-site request blocked. - All auth timestamps are stored and returned in UTC.
Removed
- Breaking: the Gin integration:
Manager.SignupHandler,LoginHandler,GetProfileHandler,UpdateProfileHandler,ChangePasswordHandler,DeleteAccountHandlerandManager.Middleware. The routes are served byauth/authhttp, mounted byCore.Server; Gin is no longer a dependency. - Breaking:
auth.Config.Secret,TokenExpiryandDatabaseName;Manager.GenerateTokenandValidateToken(useAuth.GetSession);Manager.GetUserByEmail,GetUserByID(useAuth.GetUser),UpdateProfile(useAuth.UpdateUser),DeleteAccount(useAuth.DeleteUser); the typesSignupRequest,LoginRequest,AuthResponse,UpdateProfileRequestandChangePasswordRequest. - Breaking:
env.LoadEnv(useenv.Load) and theEnv.MANUALmap (useEnv.Getand the typed getters). - Breaking:
MongoDB.GetClient(useClient),PostgresDB.GetPool(usePool) andDisconnecton both (useClose). - The
github.com/golang-jwt/jwt/v5andgithub.com/gin-gonic/gindependencies and the separatetest/example module (replaced byexamples/quickstart).
Fixed
POSTGRES_CONNECTION_URLnow connects PostgreSQL automatically; it was never read before.corego.Newcloses every connection it opened when a later step fails, and the database constructors close the client or pool when the initial ping fails, so nothing leaks.
Security
- Token purpose separation: email verification and password reset tokens are stored under different purpose-prefixed identifiers, so a verification token can never reset a password, and the reverse.
- No stale tokens after an email change: changing the email address deletes every outstanding token of the old address, deleting an account deletes the user's tokens, a successful reset or password change deletes all outstanding reset tokens, and tokens issued before the current owner of an address registered are rejected.
RequireVerifiedEmailis enforced everywhere: sign-up creates no session, and sign-in and every session lookup (GetSession,RequireAuth) reject unverified users, including sessions created before an email change.- Enumeration-safe and timing-safe email flows: the public "resend
verification" and "forgot password" flows answer identically (
202) for registered, unknown, verified and malformed addresses and when no mailer is configured, and emails are delivered in the background so neither SMTP latency nor SMTP failures are observable. Sign-in failures are uniform and unknown addresses still pay for a bcrypt comparison. - Password-confirmed sensitive changes: changing the email address and deleting the account over HTTP require the current password; changing the password requires it too and revokes every session.
- Rate limiting: sign-up, sign-in, the email and password flows, email change and account deletion are rate limited per client IP (10 requests per minute per route by default).
- No lost updates: users and accounts are written with optimistic concurrency, so concurrent changes (for example a profile update racing a ban) can never overwrite each other or silently revert a ban.
- No address squatting through stale links: a verification link only verifies the address it was sent to, never an address the user changed to afterwards, and the credential account always follows the user's current address.
- No token double-spend:
VerifyEmailandResetPasswordconsume the token before applying the change, so two simultaneous requests can never both redeem one token (a failure after consumption needs a new token; a rejected new password does not consume a reset token). - Bounded background work: email jobs are capped by
auth.Config.MaxPendingEmails(extra jobs are dropped and logged) instead of one unbounded goroutine per request, so a slow or hanging mail server cannot exhaust memory. - Forgot-password timing:
RequestPasswordResetandRequestEmailVerificationdo only the user lookup synchronously; the token write runs in the background job with the delivery, so the response time does not tell registered and unknown addresses apart. - No recipient addresses in logs:
mailerrors identify addresses by field and index (mail: to[1]: invalid address,RCPT TO for cc[0]) and redact addresses quoted in SMTP replies; the reply stays available as a*textproto.Errorthrougherrors.As. - CSRF protection for cookie authentication: cookie-authenticated
requests with a method other than
GET,HEADandOPTIONSmust be same-site (perSec-Fetch-Site/Origin) or come fromauthhttp.Config.TrustedOrigins; others get403. Bearer tokens are unaffected. - No CORS wildcard with credentials:
web.CORSpanics whenAllowCredentialsis combined with"*", andCORS_ALLOWED_ORIGINS=*allows every origin without credentials (with a startup warning), so no website can make cookie-authenticated requests on a visitor's behalf. - IPv6 rate-limit bypass: clients are keyed by their IPv6 /64 prefix, so rotating addresses within one subscriber's network never yields a fresh budget.
- Bounded limiter memory: rate limiters track at most
MaxKeyskeys and share one overflow bucket beyond that, so key rotation can neither exhaust memory nor escape the limit. - Per-email rate limits: sign-in, forgot-password and verification requests are also limited per email address (uniformly for unknown addresses), which stops distributed password guessing against one account and inbox flooding.
HEADin any letter case:HEADrequests toGET /verify-email(sent by link previewers), whatever the letter case of the method, never consume the verification token.- Profile field validation: names are limited to 256 characters and
images to absolute
http(s)URLs of at most 2048 bytes, so profiles cannot carryjavascript:URLs or unbounded text. - Proxy misconfiguration warning: the web server logs a one-time
warning when requests carry
X-Forwarded-ForwhileTrustProxyHeadersis off, the setup in which every client shares the proxy's rate limit. - Server timeouts: the web server sets
ReadHeaderTimeout(10 s),ReadTimeout(30 s),WriteTimeout(30 s) andIdleTimeout(120 s) by default, so slow clients cannot hold connections open, and limits request bodies to 1 MiB. - Spoof-resistant client IP: proxy headers are ignored unless
TrustProxyHeaders(TRUST_PROXY_HEADERS) is enabled, and then the rightmostX-Forwarded-Forentry (the one your proxy appended) is used, so clients cannot choose their own IP for logging or rate limiting. - Prefixed tables and collections: the auth stores only touch their
own
corego_-prefixed tables and collections, never an application'susersorsessions. - Session, verification and reset tokens carry 256 bits of entropy and are
stored only as SHA-256 hashes; session cookies are always
HttpOnlyand becomeSecureautomatically with anhttps://APP_URL; auth responses carryCache-Control: no-store. - Email headers are protected against header injection, and SMTP credentials are only ever sent over TLS.
0.1.0 - 2025-11-28
Initial release: JWT authentication on MongoDB with Gin handlers and
middleware, a MongoDB wrapper and .env loading.