=== Fliegerlogin – SSO for Vereinsflieger ===
Text Domain: sso-for-vereinsflieger
Domain Path: /languages
Contributors: patois87
Tags: SSO, Vereinsflieger, Luftsport
Requires at least: 6.0
Tested up to: 7.1
Stable tag: 3.0.2
Requires PHP: 8.2
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html

Let members log in to WordPress with the account they already have at Vereinsflieger.de.

== Description ==

This plugin allows users to login with their existing account on Vereinsflieger.de.

= Included in this edition =

Everything below is part of this download and stays free. Nothing here is a trial, and nothing switches itself off.

* Login using your Vereinsflieger.de account
* No extra registration for your members needed
* Works with enabled 2 factor authentication
* Keeps your wordpress account up to date (name, email, ...)
* Prevents disabled users from Vereinsflieger.de to login
* Protects the Vereinsflieger API quota with IP-based rate limiting: configure the threshold, the lockout duration and whether it covers every WordPress login, and unblock addresses again from the *Security* page
* Shows how much of the daily Vereinsflieger request quota is left
* Supports local custom translations that are not overwritten by WordPress.org language packs

To get this plugin to work you have to request an app key from the Vereinsflieger.de support. You also need to know your club's CID which you can find in the administration area of your Vereinsfieger.de account.

= What Fliegerlogin Premium adds =

The free edition blocks a single address that guesses passwords. That covers the everyday case and it is the case most clubs ever see.

It does not cover an attack spread across hundreds of addresses, where no single one reaches the threshold. A club in that situation needs to look at the traffic as a whole rather than at one visitor at a time, and it needs a record of what happened. Fliegerlogin Premium is a separate download that adds:

* A **protection overview** with a status badge for every safeguard, active failure counters and the most recent rate limit events, all on one page
* A **circuit breaker** that pauses all Vereinsflieger calls when the overall traffic spikes or the daily quota runs out — distributed attacks from many addresses never reach a per-address limit
* **Geo blocking** by country, with detection via Cloudflare, web server variables, the PHP geoip extension or a MaxMind database, plus automatic GeoLite2 database updates
* A **negative cache** so repeated wrong credentials fail instantly instead of spending API requests
* Guards that skip the Vereinsflieger lookup entirely for **XML-RPC** requests, for **implausible usernames** and for attempts a **captcha plugin** has already rejected — Cloudflare Turnstile is recognised without configuration, any other captcha plugin by naming the error code it sets
* **Username enumeration protection** for ?author=N, author archives, the REST users endpoint and the author sitemap, plus uniform login error messages
* A **debug log** covering the whole authentication flow, with viewer, filters and automatic redaction of passwords and tokens

Premium is licensed under the GPLv3 as well: [pricing and purchase](https://checkout.freemius.com/plugin/36183/plan/59856/). Questions about it are welcome in the support forum on this page.

Disclaimer: Vereinsflieger.de was not involved in the development, neither ordered the development of this plugin.

== Frequently Asked Questions ==

= Where do I get the AppKey? =

Please contact the Vereinsflieger.de support and ask them for one.

= What is a CID? =

The CID is the identifier for you club in Vereinsflieger.de. You can find it in the administration area for your club in Vereinsflieger.de.

= Can I restrict the access to some sites or blog posts to members? =

Sorry, currently this is not possible. For the future this is planned. You will then also be able to restrict access to roles which the user has in Vereinsflieger.de

= Some of our members use 2 factor authentication, does this work? =

Yes, accounts with 2FA enabled can also login using this plugin.

= The login with Vereinsflieger is failing but my credentials are correct. =

Please try to verify if your login form is under attack. Vereinsflieger blocks your AppKey temporary if they detect a potential DOS attack.
You can prevent this by adding a captcha to your login form - e.g. [reCaptcha by BestWebSoft](https://wordpress.org/plugins/google-captcha/) or Cloudflare Turnstile.
A captcha keeps most bots away from the form, but an attempt it rejects still costs a Vereinsflieger request in this edition. Fliegerlogin Premium recognises the rejection and skips the request — Turnstile without configuration, other captcha plugins by naming the error code they set.

= How can I keep custom translations from being overwritten? =

Place custom translation files in `wp-content/languages/sso-for-vereinsflieger/`, e.g. `wp-content/languages/sso-for-vereinsflieger/sso-for-vereinsflieger-de_DE.mo`. Files in this folder are preferred over WordPress.org language packs and over the translations bundled with the plugin.

= Where can I report bugs or suggest new features?  =

Please use the support forum on the plugin page.

== Screenshots ==

1. The configuration page: enter your club's CID and the app key you requested from Vereinsflieger support, pick the role new members receive, and see how much of today's API quota is left.
2. The security page: decide how many rejected logins from one address trigger a block, how long it lasts, and whether it covers every WordPress login. Blocked addresses are listed here and can be released again.
3. Members with two factor authentication enabled are prompted for their one time password, on the normal WordPress login screen.
4. By default an address is locked out for fifteen minutes after five rejected Vereinsflieger logins, so brute force attacks cannot exhaust the club's daily request quota.
5. Password resets are turned off for accounts managed via Vereinsflieger, so a member cannot lock themselves out by changing a password WordPress does not own.

== External Services ==

This plugin connects to two external services.

**Vereinsflieger.de** — the whole point of the plugin. On every login attempt the entered username and a hash of the password are sent to the Vereinsflieger API at https://www.vereinsflieger.de/interface/rest/ to verify the credentials and read the member's name, email address and status. Nothing is transmitted unless someone attempts to log in. Terms: https://www.vereinsflieger.de/agb/ — Privacy policy: https://www.vereinsflieger.de/datenschutz/

**Freemius** — licensing and update handling for the paid edition, and the upgrade prompts in this one. It contacts https://api.freemius.com only after you explicitly opt in, and it never transmits your members' data. If you skip the opt-in, the plugin sends nothing to Freemius; only anonymous update checks remain. Terms: https://freemius.com/terms/ — Privacy policy: https://freemius.com/privacy/

== Credits and License ==

Fliegerlogin is free software licensed under the GNU General Public License version 3 or later. The full license text is included in the LICENSE file.

Copyright (C) 2020-2022 Diginize (https://www.diginize.de)
Copyright (C) 2025-2026 Simon Kuhn

This plugin is a modified version of the "WP Vereinsflieger" plugin originally developed and published by Diginize (https://github.com/diginize/wp-vereinsflieger), whose last release was version 1.1.2 in May 2022. It has been maintained and substantially extended by Simon Kuhn since 2025; every change is documented in the changelog below.

== Changelog ==

= 3.0.2 =
* Updated the bundled Symfony components to 7.4.16 and composer/ca-bundle to 1.5.14 — patch releases within the LTS line. Composer Audit reports no known vulnerabilities.
* Tested against WordPress 7.1. Every core function and hook the plugin uses was checked against the 7.1 sources: none is deprecated, and none has been removed.
* **Fixes a broken login in 3.0.0 and 3.0.1.** Renaming the plugin's prefixes in 3.0.0 also renamed the column in the users table that links a WordPress account to a Vereinsflieger member — but that column already existed in every installation, and no migration came with the rename. The plugin then queried a column that was not there, and no linked member could log in. This release renames the existing column instead of expecting a new one; the link between accounts and members is preserved. Sites set up fresh on 3.0.0 are unaffected and are left untouched.
* **Fixes a fatal error on the login screen.** When the account was found by email address rather than by Vereinsflieger id, the check that refuses to adopt an administrator account was handed a raw database row where it expected a WP_User, and the request died with a TypeError. Reported from a live site.

= 3.0.1 =
* Documentation: the captcha guard and Cloudflare Turnstile are now named in the feature list, in the FAQ and in the upgrade notes for 3.0.0. The guard has been part of the plugin since 2.3.0 and moved to Premium in 3.0.0, which the 3.0.0 notes failed to mention.
* The German translation now covers every string, and has been revised against the glossary and style guide of the German Polyglots team. Swiss variants were added: de_CH (formal) and de_CH_informal.

= 3.0.0 =
* Fliegerlogin is now published in two editions. This free edition keeps the complete Vereinsflieger single sign-on and IP-based rate limiting, including its settings page; the advanced safeguards and the debug log moved into Fliegerlogin Premium, a separate download.
* New *Security* page: switch rate limiting on or off, set the number of failed attempts and the lockout duration, decide whether it covers every WordPress login, and see and unblock the addresses currently blocked.
* Rate limiting stays free and switched on. It protects the Vereinsflieger request quota the whole plugin depends on, so leaving a site without it was never an option.
* **Security:** a WordPress account that can administer the site is no longer linked automatically when a Vereinsflieger account merely shares its email address. Linking such an account is now a deliberate, manual step. Previously anyone able to register that address at Vereinsflieger could have taken the account over.
* **Security:** linking an existing WordPress account no longer replaces its password, and the role given to new members can no longer be one that administers the site or its users.
* **Upgrading from 2.6.x:** every setting is preserved, but this edition no longer acts on the paid ones. Sites that relied on geo blocking, the circuit breaker, the negative cache, the captcha guard or username enumeration protection need Fliegerlogin Premium to keep them. The captcha guard deserves a separate word because it has been in the plugin since 2.3.0: when a captcha plugin such as Cloudflare Turnstile rejects a login, this edition still asks Vereinsflieger about it and spends a request. Honouring that rejection moved to Premium.
* **Now requires WordPress 6.0 and PHP 8.2** (was 5.0 / 8.1). The previous WordPress floor was never accurate: the API quota counter calls wp_date(), which only exists from 5.3 onwards. The PHP floor follows the updated Symfony and phpDocumentor libraries.
* Updated the bundled libraries: Symfony 6.4 to 7.4 LTS and phpDocumentor reflection-docblock 5.6 to 6.0. Composer Audit reports no known vulnerabilities.
* Inline scripts and styles are now registered through wp_add_inline_script() and wp_add_inline_style() instead of being printed directly.
* Every constant, function and form field the plugin defines now carries the same `fliegerlogin` prefix, so it cannot collide with another plugin. If you switched the debug log on from wp-config.php, the constant is now `FLIEGERLOGIN_DEBUG` instead of `WPVF_DEBUG`. Stored settings are untouched.
* Reworked the authentication flow around a single seam for the paid safeguards, which removed a good deal of branching from the login path.
* Hardened output escaping across the admin pages, and fixed a settings link that was never translatable because its text domain was missing.

= 2.6.1 =
* Renamed the plugin to *Fliegerlogin – SSO for Vereinsflieger*. The settings page moved from *Vereinsflieger* to *Fliegerlogin* in the admin menu; all existing settings are preserved and no reconfiguration is needed after the update.
* Custom translations are now read from `wp-content/languages/sso-for-vereinsflieger/` instead of `wp-content/languages/wp-vereinsflieger/`. Move your files if you use this feature.
* Updated the Guzzle HTTP stack (guzzle 7.15.2, psr7 2.13.0) to address three medium-severity advisories published on 2026-07-20: URI fragments leaking into redirect Referer headers, host-only cookie scope not being preserved, and unbounded response cookies. Composer Audit now reports no known vulnerabilities.
* The bundled dependencies no longer contain the PHPUnit test framework, and use an optimised classmap autoloader.

= 2.6.0 =
* Added a username plausibility filter: since a Vereinsflieger login is always an email address (some clubs additionally allow the numeric member number), login attempts with any other username — the classic brute-force names like "admin", "root" or "test" — are now rejected before any Vereinsflieger API request is spent. Can be toggled on the *Security* page (on by default), with a choice of accepted format (email address only, or email address + numeric member number), and is shown in the protection overview.

= 2.5.0 =
* Renamed the *Rate Limits* admin page to *Security* and added a protection overview with status badges for every safeguard.
* Added a global circuit breaker that pauses all Vereinsflieger API calls when the aggregate traffic spikes (sliding-window burst limit) or the daily request quota (max. 500/day per appkey) is exhausted — protecting the shared server IP / appkey against distributed brute-force attacks that per-IP rate limiting cannot stop.
* Added optional geo blocking (allow/block by country) with country detection via Cloudflare, web-server GeoIP variables, the PHP geoip extension, or a MaxMind database, including a live detection diagnostic (that distinguishes "Cloudflare not in path" from "IP Geolocation header off") and a fail-open/fail-closed choice.
* The plugin can now download and auto-update the GeoLite2 country database itself (configurable source URL, daily/weekly/monthly via WP-Cron, plus a manual "Update now"), stored in the uploads folder — geo blocking works without Cloudflare and without placing a database file by hand. Bundles the geoip2/geoip2 reader (pure PHP, no C extension required).
* Generalised the captcha guard to a configurable list of blocking WP_Error codes.
* Added an XML-RPC guard that skips the Vereinsflieger lookup for xmlrpc.php login attempts.
* Added a negative cache that remembers recently rejected credentials (as a salted hash) so repeated wrong logins fail instantly without spending API requests.
* Added username-enumeration protection: blocks ?author=N and author archives, the REST /wp/v2/users endpoint, and the author sitemap for logged-out visitors, and optionally replaces WordPress's "unknown username" vs. "wrong password" login errors with a single generic message (the plugin's own 2FA / rate-limit / Vereinsflieger messages are kept).
* All new safeguards can be toggled independently on the *Security* page.
* Redesigned the *Debug Log* page to match the Security area: an at-a-glance status/entry/error/success overview, colour-coded severity per row with category badges derived from the event, and a live text + level filter.
* Updated the Guzzle HTTP stack (guzzle 7.14.2, psr7 2.12.5, promises 2.5.1); Composer Audit reports no known vulnerabilities.

= 2.4.0 =
* Added a compact rate limit overview to the *Vereinsflieger → Rate Limits* admin page with protection status, blocked IP count, active failure counters, the next automatic unblock time, today's Vereinsflieger API usage, and recent rate-limit events.
* Active failed-login counters below the block threshold are now tracked for visibility on the rate-limit overview and automatically cleaned up when their transient expires.
* Updated Guzzle dependencies to address current medium-severity security advisories reported by Composer Audit.

= 2.3.1 =
* Hardened IP-based rate limiting with a sliding lockout: further blocked login attempts now extend the existing lockout window instead of allowing a new attempt window immediately after the original lockout expires.

= 2.3.0 =
* Added a *Vereinsflieger → Rate Limits* admin page to enable or disable IP-based rate limiting, optionally apply it to all WordPress login attempts, configure the failed-attempt threshold and lockout duration, view currently blocked IPs, and unblock individual addresses.
* Improved compatibility with Cloudflare Turnstile login protection by respecting an earlier `cfturnstile_error` from the Turnstile authentication filter and skipping the Vereinsflieger API fallback for those rejected requests.
* Prefer custom and bundled plugin translations over WordPress.org language packs so local translation changes are not overwritten by automatic translation updates.
* Updated German translations.

= 2.2.0 =
* Added IP-based rate limiting for Vereinsflieger login attempts. After 5 consecutive failed credential checks, the IP is blocked for 15 minutes. Cloudflare deployments use the `CF-Connecting-IP` header so the block targets the real client IP, not the Cloudflare proxy. The counter is stored as a WordPress transient and is automatically cleared on a successful login. This prevents brute-force attacks from exhausting the Vereinsflieger API daily request quota and triggering an AppKey or IP block.

= 2.1.0 =
* Added a built-in debug log under *Vereinsflieger → Debug Log* in the WP admin, with on/off toggle, clear button, and a tabular viewer. Logs are kept in a rolling buffer of the last 500 entries inside a WP option — no SSH or `debug.log` access required. `define('WPVF_DEBUG', true)` in `wp-config.php` still acts as a forced-on override for development. The log covers the whole authentication flow: filter entry, the 2.0.1 skip-existing-WP-user branch, every Vereinsflieger HTTP request/response (status, headers, body excerpt), credential rejections, API errors, 2FA prompts, and final user resolution. Sensitive values (passwords, OTPs, access tokens, app keys) are auto-redacted.

= 2.0.3 =
* Bug fix: login no longer crashes with a fatal `NotNormalizableValueException` when the Vereinsflieger API returns the user id as a string (e.g. `"uid": "564284"`). The deserializer now relies on PHP's own type coercion via the typed setters.
* Bug fix: a non-JSON response from the Vereinsflieger API (e.g. an HTML error page from `/auth/signout`) no longer triggers a fatal `NotEncodableValueException`. Such responses are now wrapped as a regular `HttpException` so existing error handling kicks in — in particular the best-effort logout after a successful login no longer breaks the login flow.

= 2.0.2 =
* Surface the Vereinsflieger API error message on failed logins. Previously, every 403 response was silently mapped to the generic WordPress "incorrect credentials" message, making it impossible to distinguish a wrong password from a misconfigured CID, an invalid AppKey, or an exceeded daily request quota. The plugin now appends the API's error string (e.g. "Application key invalid", "Daily request limit exceeded") to the login screen.
* Added German translations for the new error notices and refreshed source-line references in the .po/.mo files.

= 2.0.1 =
* Bug fix: skip the Vereinsflieger API fallback when the entered username belongs to an existing WordPress user that is not linked to Vereinsflieger. Mistyped passwords for regular WP accounts no longer waste 4 requests against the daily API quota and no longer surface a misleading "Vereinsflieger is offline" message.
* Disabled WordPress's "Lost your password?" flow for Vereinsflieger-linked accounts (both at lostpassword_post and at the actual reset step). Previously, members could reset their WP password and would then get blocked from logging in, because the plugin refuses default-login for linked accounts.
* Added German translations for the new password-reset notices and refreshed source-line references in the .po/.mo files.

= 2.0.0 =
* **Breaking:** Now requires PHP 8.1+ (was 7.2.5).
* Updated all bundled libraries (Guzzle, Symfony Serializer, PSR-7) to current versions and addressed several CVEs in the previously bundled releases.
* Added a daily API request counter on the configuration page so administrators can see how much of the 500-calls-per-day quota is left.
* Removed the donation notice from the configuration page.
* Hardened authentication: hashed-password handoff is now read from $_POST only (not $_REQUEST) and validated as an md5 hex string.
* Replaced uniqid() with wp_generate_password() for placeholder passwords on linked accounts.
* Added a CSRF nonce to the settings form.
* Vereinsflieger session is now released after a successful WordPress login instead of staying open.
* Bug fix: Api::logout() no longer silently swallows API errors via a misplaced finally-return.
* Bug fix: server-error detection in the login error path no longer matched every status code.
* Bug fix: Options::setAllowDefaultLogin() now correctly distinguishes the unset state.
* Fixed the long-standing "Vereinflieger" typo in the configuration page strings.

= 1.1.0 =
* Enhance error reporting in case Vereinsflieger-API returns with a server error.

= 1.0.10 =
* Fix an error caused by changed behaviour of Vereinsflieger API

= 1.0.9 =
* Fix an error in German translated links

= 1.0.8  =
* Fix issue which changes the wordpress user's password on every login
* Added option, to allow users linked with Vereinsflieger still using their wordpress credentials
* Added a link at the plugin page to the plugin's settings

= 1.0.6 =
* Added translations (de + en)

= 1.0.5 =
* Added icons

= 1.0 =
* First version
