Skip to content

Releases: PerfectlySoft/Perfect-Session

Perfect-Session 4.0.0

Choose a tag to compare

@taplin taplin released this 14 Oct 19:43
0b3bf33

Perfect-Session 4.0.0 is the first tagged release of the Swift 6 rewrite. It replaces 3.1.5 (2018). It consolidates the old per-backend repositories into one package with a product per backend: PerfectSessionCore, PerfectSessionMySQL, PerfectSessionPostgreSQL, PerfectSessionRedis, PerfectSessionSQLite and PerfectSessionMongoDB.

.package(url: "https://github.com/PerfectlySoft/Perfect-Session.git", from: "4.0.0"),

Requirements

  • Swift 6.2 or later (swift-tools-version: 6.2, Swift 6 language mode).
  • macOS 12 or later, or Linux. CI covers Swift 6.2 and 6.4 on Ubuntu 24.04, in debug and release, with every backend running against a live server. It also covers Xcode 26.0.1 and 26.6 on macOS.
  • Dependencies: Perfect-MySQL 4.0.0, Perfect-PostgreSQL 5.0.0, Perfect-Redis 4.0.0, Perfect-SQLite 6.0.0 and Perfect-MongoDB 4.0.3 (all from:), plus swift-log (the core now uses it too). Each backend needs its own client library: libmysqlclient, libpq, libsqlite3 on Linux, or libmongoc 2. Redis needs none. Only the backends you depend on get built.

Highlights

  • One package, five backends, all behind an async, Sendable SessionDriver protocol. The core has no database or HTTP-framework dependency.
  • Server-side expiry. Every driver's resume now refuses an expired session (a deliberate change, see below), and every driver's save renews the idle time. A new optional SessionConfig.absoluteTimeout caps a session's lifetime from creation; clean(), the MongoDB TTL index and Redis TTLs honour it too.
  • regenerate(_:) on every driver moves a session to a new token and a new CSRF token, keeping its creation time. Call it at sign-in to prevent session fixation.
  • CSRF and authentication helpers: CSRFSecurity.tokenMatches(_:_:) (a timing-independent token comparison), a stricter CSRFSecurity.isValid (scheme, default ports, case), and AuthFilter.requiresAuthentication(_:using:), which matches by path segment and ignores case. The README has a Perfect-NIO middleware example that uses all three.
  • Logs never contain session tokens: the drivers log PerfectSession.logID(_:), a 12-digit SHA-256 prefix.
  • SessionCookie builds the Set-Cookie header values from SessionConfig. It enforces Secure for SameSite=None and for the __Secure-/__Host- prefixes (in any case), and refuses values that would inject attributes.
  • 256-bit tokens from the system CSPRNG (PerfectSession.newToken()).
  • Works with Perfect-NIO 0.1.0. The README has a session-filter example. A scratch app built against Perfect-NIO 0.1.0 with warnings as errors on Swift 6.2.4 and 6.4, and ran end to end: cookie set, resume, regenerate on sign-in, and the old token refused. A NIO 0.1.0 server gated by requiresAuthentication("/admin") answered 401 for /Admin, /ADMIN, /Admin/users and the other case and slash variants; with the old shouldWeAccept, the review got 200 and the protected content for those.
  • MongoDB backend (new since 3.x): data is stored as a real subdocument, with a TTL index. save doesn't resurrect logged-out sessions, setup() purges legacy Perfect-Session-MongoDB documents, and awkward keys are escaped.

Breaking and behaviour changes

  • The 3.x module and its HTTP filters are gone. That covers PerfectSession with Perfect-HTTPServer's SessionMemoryDriver request and response filters, CSRFFilter and isValid(_ request:). Upgrade: depend on PerfectSessionCore plus a backend product, and write a small middleware that calls a SessionDriver, SessionCookie, CSRFSecurity, CORSSecurity and AuthFilter (see the README's Perfect-NIO example).
  • Drivers are async. resume throws, and save(session:) became save(_:). Upgrade: try await driver.resume(token:), await driver.save(s) and await driver.destroy(token:).
  • The separate Perfect-Session-MySQL, -PostgreSQL, -Redis, -SQLite and -MongoDB packages are superseded. Upgrade: switch to this package's products. Sessions stored by 3.x or by Perfect-Session-MongoDB aren't migrated, so users sign in again. The MongoDB backend's setup() deletes legacy documents from a shared collection.
  • resume throws InvalidSessionError for an expired session. This is deliberate (Tim's decision). It used to return the session and leave the check to isValid(). Upgrade: nothing for most apps. If you relied on resuming expired sessions, read them from storage directly.
  • Every driver's save renews the idle time (touch()); only Redis did, so elsewhere a session expired idle seconds after creation however active it was. 3.1.5's memory driver also touched on save. Upgrade: none; if you relied on a fixed lifetime from creation, set SessionConfig.absoluteTimeout.
  • Redis calls time out after 5 seconds by default (RedisSessionConnector.commandTimeout, was no limit; Tim's decision). A Redis command that takes longer now throws and its connection is closed: create/save/destroy log it, and resume throws InvalidSessionError. Upgrade: set commandTimeout to a larger value, or nil for no limit.
  • PerfectSessionManager is now SessionDriver, async and Sendable, and also requires create(ipaddress:useragent:) (setup, clean and regenerate have default implementations). Upgrade: conform custom stores to SessionDriver and implement create.
  • SessionConfig.couchDatabase and mongoCollection are removed. Upgrade: use the backend's connector (MongoDBSessionConnector.collection).
  • SessionConfig.cookieSameSite is a CookieSameSite (was Perfect-HTTP's HTTPCookie.SameSite), and CORS.methods is [String] (was [HTTPMethod]). Upgrade: .strict/.lax/.none keep working; write methods as strings (["GET", "POST"]).
  • Settings with no effect in 4.0 are deprecated: SessionConfig.CSRF.failAction, checkState, checkHeaders and requireToken; SessionConfig.CORS.methods, customHeaders, withCredentials and maxAge; SessionConfig.purgeInterval, healthCheckRoute and isOAuth2; PerfectSession._isOAuth2; AuthenticationConfig.denied. Nothing reads them, so setting them (in 3.x, requireToken enforced the CSRF token) does nothing. Upgrade: do it in your middleware: CSRFSecurity.tokenMatches and isValid for CSRF, your own Access-Control-* headers for CORS, clean() on your own schedule.
  • AuthFilter.shouldWeAccept is deprecated for requiresAuthentication(_:using:) (same meaning: true means authentication is required), and matching changed: paths are compared by / segment ignoring empty and . segments and, by default, case; "/admin" covers everything below it but not /administrator; * is a wildcard only as a whole final segment (an inclusion that uses it otherwise requires authentication everywhere; such an exclusion matches nothing); when anything is protected, paths with .., %, ; or \ require authentication. Upgrade: check your patterns; set AuthenticationConfig.caseInsensitive = false only if your router is case-sensitive.
  • CSRFSecurity.isValid compares the scheme (an http:// origin no longer passes for an https:// host), treats :443/:80 like no port, ignores case, and rejects a null origin. Upgrade: list exact origins in acceptableHostnames, with a scheme if you want to restrict it.
  • A session-data value JSON can't hold drops only its own key (logged by name), not all of data; a non-JSON csrf is replaced. resume adds a CSRF token to a stored session that lacks one. Upgrade: none.
  • Tokens are 64 lowercase hex characters, not UUIDs. Upgrade: if anything validated the UUID format, accept 64 hex characters instead. Every backend's column or key already fits.
  • Redis keys now have a prefix: RedisSessionConnector.keyPrefix + token, default perfect-session:. Upgrade: sessions written by the pre-release main aren't found, so users sign in again. Setting keyPrefix = "" keeps the old keys but brings back the key-space exposure described under Fixes.
  • Redis TTL follows the session's own idle, not SessionConfig.idle. A session with idle ≤ 0 is now deleted instead of being stored forever. Upgrade: set idle on the session or in SessionConfig.idle as before. Don't use 0 to mean "never"; use a large value.
  • MemorySessionDriver.save only updates a stored session, like the other drivers. Upgrade: create sessions through create(), not by constructing PerfectSession with your own token.
  • New SQL tables use 64-bit created/updated/idle columns (MySQL bigint, PostgreSQL int8). Tables that setup() created before 4.0 keep their 32-bit columns, and still work (verified on MySQL 8.4 and PostgreSQL 17). Upgrade: run the README's ALTER TABLE to support an idle beyond about 68 years and times after 2038.
  • isValid, CSRFSecurity.isValid and CORSSecurity.isAcceptable gained optional parameters for explicit lock and hostname config, defaulting to SessionConfig. Upgrade: none; existing calls compile unchanged.
  • SessionDriver gained a regenerate(_:) requirement, with a default implementation. Upgrade: none for custom drivers, unless one already had a regenerate method with a different meaning.
  • CORS wildcard patterns now have to match on both sides of the * (see Fixes). Upgrade: if you relied on https://* matching any https origin, list your origins, or use "*" deliberately.

Fixes

  • AuthFilter: behind Perfect-NIO 0.1.0, whose router ignores case, /Admin, /ADMIN and /Admin/users reached /admin handlers without authentication (the review got 200s from a live app); //admin, /./admin, /%61dmin and /admin;x=1 weren't matched; "*.json" never matched and "/admin*" covered /administrator. (#17)
  • CSRF token loss: one non-JSON value in session.data made every backend but memory save the session with no data...
Read more

Fixing obsolete url.

Choose a tag to compare

@RockfordWei RockfordWei released this 30 Jan 18:04
4de6a38
3.1.1

Fixing obsolete url.