A signer-owned Custom Tab never reliably displayed the browser handoff
page, so the token never returned. The Nostr launcher now uses the app's
custom scheme as the callback (einundzwanzig://signed/{k1}/): Amber opens
it directly after signing and the app exchanges the event for a token via
/api/mobile/token — no browser handoff in the loop.
Chrome follows a server 302 internally and never dispatches the /app/auth
App Link, so the handoff page stayed in the browser and the token never
reached the app. The signed callback (and complete/confirm) now render
the handoff page directly with the einundzwanzig:// deep-link button — the
signer opens the callback in the browser, the user lands on the handoff
page and taps once to return to the app, which stores the token.
Amber v6.2.0 rejects a plain nostrsigner: navigation as malformed: it
reads the signer parameters from intent extras, which a window.location
navigation cannot set. An intent:// URL lets the launcher pass the event
as the data URI plus type/returnType/appName/callbackUrl as S.* extras,
so Amber accepts the request and shows its sign dialog. The query is also
kept on the data URI for the EXTRA_APPLICATION_ID web flow.
Note: on the emulator with Amber v6.2.0 the post-approval callback did
not always fire (Amber returns via setResult when callingPackage != null);
needs verification on a real device.
Amber v6.2.0 routes nostrsigner: intents by EXTRA_APPLICATION_ID: present
(web flow, reads the event from the URI) vs absent (app-to-app flow,
reads type/event from intent extras → rejects our URI as malformed).
Browsers only attach that extra when the external-app launch comes from a
user gesture, so the auto-redirect on page load always failed. The
launcher now waits for the user to tap "Mit Amber signieren".
Server-side percent-encoding (rawurlencode/http_build_query) produced a
nostrsigner: URI that Amber rejected as malformed. The launcher view now
assembles it in JS with encodeURIComponent(JSON.stringify(event)) — the
exact encoding Amber accepts (verified working earlier in the session).
The controller only passes k1 and the callback URL.
A direct ACTION_VIEW intent to nostrsigner: (Browser::open from the app)
lacks category.BROWSABLE, so Amber routes it into its app-to-app path
and rejects it as malformed. The app instead opens /auth/mobile/nostr in
an in-app browser; that page fires the signer via window.location, so
the intent carries BROWSABLE and Amber uses its web-signing flow. No
visible login UI, local signing, token returned via the App Link.
The Nostr login is now driven entirely by the app (it launches the
NIP-55 signer via an ACTION_VIEW intent and posts the signed event to
/auth/mobile/signed), so the portal page no longer needs window.nostr or
an Amber button — it only renders the Lightning QR. The path-based
signer callback and token exchange endpoints remain server-side.
Replaces the fragile NIP-55 intent/callback round-trip with the same
mechanism the desktop login uses: openNostrLogin signs the session
challenge via window.nostr — provided by an extension or by
window.nostr.js over a persistent NIP-46 connection (Amber pairing with
permissions). The listener stores a LoginKey for the page's k1 and
navigates to the completion route, which issues the token and redirects
into the app via the verified App Link handoff.
Replaces the custom-scheme auto-redirect (which triggers Chrome's
confirmation prompt) with a verified Android App Link handoff:
- public/.well-known/assetlinks.json for space.einundzwanzig.mobile
(debug cert fingerprint; add the release cert before store builds)
- GET /app/auth handoff: opens the app directly when the App Link is
verified; renders a button-based fallback page otherwise
- POST /api/mobile/token: trades a NIP-55-signed login event for a
Sanctum token — used when Amber's callback opens the app directly
- complete/confirm/signedCallback now redirect to the handoff URL
Amber drops the query string when it rebuilds the callback URL and
appends the signed event directly to the path. The mobile login page now
hands out path-based callback URLs (/auth/mobile/signed/{k1}/) so the
event arrives as the remainder of the path.
The new callback runs in the web middleware group: the signer opens it
in the system browser, which shares cookies with the in-app browser
session, so the flow completes immediately — a bridge page issues the
token and fires the einundzwanzig:// deep link. The LoginKey row is
still written as a fallback for the polling login page.
The Einundzwanzig mobile app opens /auth/mobile in an in-app browser.
After a Lightning (LNURL) or Nostr login the flow issues a personal
access token and hands it back via the einundzwanzig://auth deep link.
- New auth.mobile-login Livewire view: Lightning QR (shared k1) plus
Nostr signing via NIP-55 Android signers (Amber) with server callback,
and a confirmation screen for already authenticated sessions
- MobileAuthController: NIP-55 callback verification, completion route
issuing the token (replacing same-device tokens), redirect whitelist
- Nostr login event verification and npub user resolution extracted to
App\Support\NostrLogin, now shared with the interactive login
- GET /api/user (auth:sanctum) returns the token owner's profile
- 💾 Introduced `restore_point` JSON column in `meetups` table for saving and restoring master data.
- 🛠️ Added methods `captureRestorePoint` and `restoreFromRestorePoint` to `Meetup` model for managing restore points.
- 🔒 Implemented authorization for updating meetups via `updateViaPortal` policy to include pivot members.
- 🔗 Created Artisan commands `meetups:snapshot` and `meetups:restore` for managing restore points from CLI.
- 🚦 Added rate limiter to restrict excessive update attempts in Livewire meetup editing.
- ✅ Developed exhaustive feature tests for snapshot and restore actions, portal editing rules, and rate limiting.
- 🔍 Added `resolveInScope` method to `ResolvesEntities` for scoped entity resolution with stricter control.
- 👥 Introduced `AddMeetupToMineTool` MCP tool for adding external meetups to "My Meetups."
- 🛠️ Updated `ListMyMeetupsTool` and `ShowMyMeetupTool` to include both created and joined meetups.
- 📚 Updated `Meetup` model with `associatedWith` scope for querying user-related meetups.
- ✅ Expanded feature tests for meetup membership, creator permissions, and scoped tool usage.
- 🛡️ Unified access checks across Livewire and APIs to restrict editing meetup details to creators or super-admins.
- 🔗 Registered `AddMeetupToMineTool` in `EinundzwanzigServer`.
- 🔍 Introduced `SearchMeetupsTool` to find existing meetups by name or city before creating new ones.
- ☑️ Updated `CreateMeetupTool` description and logic to enforce pre-checks for existing meetups.
- 🛠️ Adjusted `EinundzwanzigServer` to include `SearchMeetupsTool` in tools list.
- ✅ Added feature tests to verify meetup search functionality and ensure duplication avoidance.
- 📜 Increased `defaultPaginationLength` to 100 to accommodate all tools on a single page.
- ✅ Added feature test to confirm pagination settings align with client requirements.
- 🤖 Introduced `ResolvesEntities` trait to simplify entity resolution by name or ID across MCP tools.
- 📚 Updated tools (Meetups, Cities, Venues, Courses, Lecturers) to use the concern for resolving related entities (e.g., courses, venues, lecturers).
- 🎯 Enhanced tool descriptions and schemas for better name-based parameter handling with fallback support for IDs.
- ✅ Added dedicated feature tests for name resolution logic, partial matches, and error handling scenarios.
- 🛠️ Refactored controllers to utilize `FiltersNumericIds` concern, ensuring secure numeric ID filtering and avoiding type-sensitive errors in queries.
- ➕ Added feature tests to validate robust input hardening for non-numeric or malformed query parameters (`user_id`, `selected[]`).
- 🔒 Introduced `PublicPropertyNotFoundException` handling in Livewire, returning 400 for invalid property probes and suppressing unnecessary log entries.
- ❌ Updated `MeetupEventController` to handle invalid date formats gracefully, aborting with a 400 response instead of 500.
- ✅ Expanded exception handling pipeline for enhanced resilience against malformed input, bot noise, and exploitable probes.
- ➕ Introduced `store`, `update`, `mine`, and `mineShow` endpoints for `Meetups`, `Cities`, `Venues`, and `Lecturers` with validation and authorization.
- 🔒 Added `Policies` for `Meetups`, `Cities`, `Venues`, and `Lecturers` leveraging `ChecksCreatorOwnership` for ownership checks.
- 🌐 Created `Resources` for structured API responses: `MeetupResource`, `CityResource`, `VenueResource`, and `LecturerResource`.
- ✅ Added dedicated `Request` classes for input validation: `Store` and `Update` variants for all models.
- 🛠️ Updated controllers to support new functionalities with localized error messages and proper HTTP responses.
- 📚 Added "API Dokumentation" link to the sidebar and header navigation.
- 🔓 Defined `viewApiDocs` gate for public access to API documentation.
- ✅ Added feature tests for API documentation route accessibility and OpenAPI document serving.
- 🗑️ Removed `SecurityScheme` from `scramble.php` due to non-serializable issues causing `config:cache`/`optimize` failures.
- ✏️ Added clarification on `scheme` omission with default fallback to `SecurityScheme::http('bearer')`.
- 🌐 Added API documentation annotations for multiple controllers (Meetups, Cities, Countries, Courses, Highscores, Venues), improving public and developer-facing endpoint clarity.
- ➕ Integrated and configured the `dedoc/scramble` package for automated OpenAPI documentation generation.
- 🔒 Excluded internal routes and actions from API documentation using `ExcludeRouteFromDocs` attributes.
- 🌍 Added new localization keys for API Token features across multiple languages (`lv`, `es`, etc.).
- 🛠️ Introduced `Group`, `Response`, and `QueryParameter` attributes for better request descriptions and structured documentation.
- 🚀 Enhanced functionality for listing operations in controllers with filters and query parameters like `search` and `selected`.
Adds a "API Tokens" settings page so an authenticated user can create and
revoke Sanctum personal access tokens for the new authenticated write
endpoints — using the official Sanctum API ($user->createToken() / tokens()).
- New Volt component settings/api-tokens (create token, one-time plain-text
reveal with copy-to-clipboard, list + revoke own tokens).
- Registered route settings.api-tokens (country-prefixed, auth group) and
added a nav entry in the settings layout.
- SEO definition for the new page.
- Pest feature tests (create/reveal-once, validation, revoke, ownership
scoping) and a Pest browser screenshot test.
Implements Sanctum-authenticated write endpoints so a lecturer can create
and update their own courses and dated course events programmatically
(e.g. to keep the portal's course events in sync with an external system).
- CourseController@store / @update implemented (validation mirrors the
Livewire course create form; create requires is_lecturer, update is
restricted to the owner or a super-admin).
- New CourseEventController with index/store/update. index returns only the
authenticated user's own events (optional ?course_id= filter) for
idempotent syncing; validation mirrors the Livewire course event form.
- Public `courses` API resource narrowed to index/show; all writes moved
behind an `auth:sanctum` route group (the previous store/update/destroy
actions were empty no-ops).
- Pest feature test covering auth (401), authorization (403/is_lecturer/
ownership), creation (201), validation (422) and ownership-scoped listing.
Ported from Einundzwanzig-Podcast/einundzwanzig-portal#25, adapted to this
repo's conventions (inline authorization instead of policies, Pest tests,
validation mirroring the current Livewire forms) while keeping the same
endpoint outputs.
Co-authored-by: schnuartz-ai <schnuartz@gmail.com>
- ✅ Implemented handling for `CorruptComponentPayloadException` to prevent logging noise and improve exception management.
- 🛠️ Added IP-based throttling (120 requests/min) for the `/livewire/update` endpoint with middleware integration for better traffic control.
- ✅ Introduced unit tests to validate throttle settings and middleware application.
- 🧪 Enhanced tests for ensuring silent handling of corrupt payload scenarios and reduced log noise.
Upgraded several dependencies:
- `guzzlehttp` to 7.11.0
- `laravel/framework` to v13.13.0
- `laravel/horizon` to v5.47.1
- Other minor updates including `livewire`, `psysh`, `symfony` components, and `spatie/laravel-medialibrary`.
Ensured compatibility and added updated packages with enhanced configurations.
- ➕ Introduced `LangCountryDataAvailabilityTest` to validate data file availability and integrity for allowed `lang-country` codes.
- 🛠️ Added Latvian (`lv-LV`) override JSON file with localized settings and validation.
- 🧪 Verified graceful switching between `lang-country` sessions in tests.
- ➕ Added and synced new translations across `pt`, `nl`, `de`, and `lv` language files.
- 🛠️ Fixed missing and duplicate strings in multilingual JSON files.
- ✅ Standardized formatting and indentation of `validation.php` (Latvian).
- ➕ Added Latvian (`lv`) JSON translations and validation/password localization files.
- 🛠️ Updated `lang-country` config to include `lv-LV` and centralized language definitions.
- 🌐 Extended sidebar and navigation with new translatable strings.
- ➕ Introduced 72px width customization for the sidebar layout.
- ✅ Improved timezone mapping in Nostr publishing commands (added `Europe/Riga`).
- 🛠️ Refactored language selector to dynamically source available languages from the config.
- ✅ Added detection logic for `MissingFileUploadsTraitException` to prevent 500 errors.
- 🛠️ Updated exception handling to return a 400 response for these scenarios.
- 🔇 Suppressed logging of `MissingFileUploadsTraitException` to reduce noise.
- ✅ Added tests to verify 400 responses and absence of log entries.
- ✅ Added detection logic to identify missing compiled view files and avoid 500 errors.
- 🛠️ Updated exception handling to return 503 with `Retry-After` for stale compiled views.
- 🔇 Prevented logging of stale compiled view exceptions to reduce noise.
- ✅ Added tests to validate 503 responses, `Retry-After` headers, and logging suppression.
- ✅ Added detection logic for stale Livewire asset patterns to avoid 500 errors.
- 🛠️ Updated exception handling to return 404 for stale asset requests.
- 🔇 Prevented logging of stale asset exceptions to avoid unnecessary noise.
- ✅ Added tests to verify 404 responses and absence of log entries for stale asset scenarios.
- ➕ Added `.codegraph/config.json` for file inclusion/exclusion in analytics.
- ⬆️ Bumped versions of `guzzlehttp`, `laravel/framework`, `laravel/horizon`, `shiki`, `nostr-tools`, and other dependencies in `composer.lock` and `yarn.lock`.
- 🖼️ Enhanced file upload validation and preview support by including `.avif` MIME type in `livewire` components and configuration.
- ✅ Added tests to ensure `.avif` is supported across file upload forms.
- Introduced `recalculateActivity` method in `Meetup` model to centralize activity and event timestamp updates.
- Added `MeetupEventObserver` to trigger activity recalculation on event save/delete.
- Updated `/meetups:update-activity` command to leverage the new model method for cleanup.
- Enhanced tests to cover various `MeetupEvent` scenarios affecting activity states.
- Added `is_active` and `last_event_at` fields to meetups with migration.
- Enhanced UI: Display `Aktiv`/`Inaktiv` badges and last event dates across dashboard, tables, and maps.
- Introduced `/meetups:update-activity` command to manage activity flags and timestamps.
- Validated latitude/longitude to prevent `0,0` inputs in city creation and updates.
- Updated factories and tests to include meetup activity states (`active`, `inactive`).
- Updated `authorizeAccess` to restrict `meetups.edit` views and updates to users in "My-Meetups".
- Attached creators to `meetup_user` pivot for default membership.
- Adjusted related tests to validate membership-based edit permissions.
📱 **Improve sidebar and mobile navigation accessibility**
- Added `aria-labels` to improve screen reader support for sidebar and mobile header elements.
- Updated desktop and mobile user menus alignment for consistency.
⚡ **Enhance Lightning login flow**
- Introduced `lightningLoginInProgress` for smoother polling synchronization with the redirect flow.
- Updated logic to dispatch `lightning-login-ready` event instead of immediate redirect, avoiding race conditions.
- 🔄 Added `x-effect` to lock page scrolling during login progress.
- 🛡️ Disabled login button and added loading indicators when `nostrLoginInProgress`.
- 🖼️ Introduced full-viewport progress overlay with accessible attributes and transitions.
- 🌐 Updated translations for signing states and instructions.
- 🛡️ Added `nostrLoginInProgress` flag to pause `wire:poll` during Nostr login round-trip.
- 🔄 Removed redundant `Session::regenerate()` to avoid session ID conflicts.
- 🪲 Improved error handling for signature serialization and Nostr signer unavailability.
- 🛡️ Avoided redundant `Session::regenerate` call as `Auth::loginUsingId` already updates the session ID.
- 🚀 Replaced `wire:navigate` with a full-page redirect to ensure a fresh CSRF token for Livewire actions.
- 🔄 `requestNostrChallenge` now issues a new challenge when needed.
- 🛡️ Enhanced fallback logic in `nostrLogin.js` to ensure robust challenge retrieval.
- ✅ Added test coverage for fresh challenge issuance.