Add mobile app auth flow with Sanctum token handoff via deep link

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
This commit is contained in:
HolgerHatGarKeineNode
2026-06-11 18:01:50 +02:00
parent f5cf85b438
commit 07169dfee6
8 changed files with 710 additions and 73 deletions
+9
View File
@@ -10,8 +10,10 @@ use App\Http\Controllers\Api\MeetupController;
use App\Http\Controllers\Api\MeetupEventController;
use App\Http\Controllers\Api\MeetupMapController;
use App\Http\Controllers\Api\NostrPlebController;
use App\Http\Controllers\Api\UserController;
use App\Http\Controllers\Api\VenueController;
use App\Http\Controllers\LnurlAuthController;
use App\Http\Controllers\MobileAuthController;
use Illuminate\Support\Facades\Route;
Route::middleware(['throttle:60,1'])
@@ -39,6 +41,8 @@ Route::middleware(['throttle:60,1'])
Route::middleware('auth:sanctum')
->as('api.')
->group(function () {
Route::get('user', UserController::class)->name('user');
Route::post('courses', [CourseController::class, 'store'])
->name('courses.store');
Route::patch('courses/{course}', [CourseController::class, 'update'])
@@ -80,5 +84,10 @@ Route::middleware('auth:sanctum')
Route::get('/lnurl-auth-callback', [LnurlAuthController::class, 'callback'])
->name('auth.ln.callback');
// NIP-55 signer callback (e.g. Amber) for the mobile auth flow.
Route::get('/nostr-login-callback', [MobileAuthController::class, 'nostrCallback'])
->middleware('throttle:30,1')
->name('auth.nostr.callback');
Route::post('/check-auth-error', [LnurlAuthController::class, 'checkError'])
->name('auth.check-error');
+19
View File
@@ -2,6 +2,7 @@
use App\Http\Controllers\Auth\VerifyEmailController;
use App\Http\Controllers\LnurlAuthController;
use App\Http\Controllers\MobileAuthController;
use App\Livewire\Actions\Logout;
use Illuminate\Support\Facades\Route;
@@ -34,5 +35,23 @@ Route::middleware('auth')
->name('password.confirm');
});
/*
* Mobile app auth flow: works for guests (login via Lightning/Nostr) and
* for already authenticated users (confirmation screen), so it lives
* outside the guest group.
*/
Route::livewire('/auth/mobile', 'auth.mobile-login')
->middleware('throttle:30,1')
->name('auth.mobile');
Route::get('/auth/mobile/complete/{k1}', [MobileAuthController::class, 'complete'])
->where('k1', '[a-f0-9]{64}')
->middleware('throttle:30,1')
->name('auth.mobile.complete');
Route::post('/auth/mobile/confirm', [MobileAuthController::class, 'confirm'])
->middleware(['auth', 'throttle:30,1'])
->name('auth.mobile.confirm');
Route::post('logout', Logout::class)
->name('logout');