diff --git a/CUSTOM_FRONTEND_URL_SIMPLE.md b/CUSTOM_FRONTEND_URL_SIMPLE.md new file mode 100644 index 00000000..96c2d052 --- /dev/null +++ b/CUSTOM_FRONTEND_URL_SIMPLE.md @@ -0,0 +1,213 @@ +# Custom Frontend URL - Simple Redirect Approach + +## Overview + +This is the **simplest** approach to integrate LNbits with a custom frontend. LNbits handles all authentication (login, register, password reset) and then redirects users to your custom frontend URL instead of `/wallet`. + +## How It Works + +### Password Reset Flow + +1. **Admin generates reset link** → User receives: `https://lnbits.com/?reset_key=...` +2. **User clicks link** → LNbits shows password reset form +3. **User submits new password** → LNbits sets auth cookies +4. **LNbits redirects** → `https://myapp.com/` (your custom frontend) +5. **Web-app loads** → `checkAuth()` sees valid LNbits cookies → ✅ User is logged in! + +### Login Flow + +1. **User visits** → `https://lnbits.com/` +2. **User logs in** → LNbits validates credentials and sets cookies +3. **LNbits redirects** → `https://myapp.com/` +4. **Web-app loads** → ✅ User is logged in! + +### Register Flow + +1. **User visits** → `https://lnbits.com/` +2. **User registers** → LNbits creates account and sets cookies +3. **LNbits redirects** → `https://myapp.com/` +4. **Web-app loads** → ✅ User is logged in! + +## Configuration + +### Environment Variable + +```bash +# In .env or environment +export LNBITS_CUSTOM_FRONTEND_URL=https://myapp.com +``` + +Or configure through LNbits admin UI: **Settings → Operations → Custom Frontend URL** + +### Default Behavior + +- **If not set**: Redirects to `/wallet` (default LNbits behavior) +- **If set**: Redirects to your custom frontend URL + +## Implementation + +### Changes Made + +1. **Added setting** (`lnbits/settings.py:282-285`): + ```python + class OpsSettings(LNbitsSettings): + lnbits_custom_frontend_url: str | None = Field( + default=None, + description="Custom frontend URL for post-auth redirects" + ) + ``` + +2. **Exposed to frontend** (`lnbits/helpers.py:88`): + ```python + window_settings = { + # ... + "LNBITS_CUSTOM_FRONTEND_URL": settings.lnbits_custom_frontend_url, + # ... + } + ``` + +3. **Updated redirects** (`lnbits/static/js/index.js`): + - `login()` - line 78 + - `register()` - line 56 + - `reset()` - line 68 + - `loginUsr()` - line 88 + + All now use: + ```javascript + window.location.href = this.LNBITS_CUSTOM_FRONTEND_URL || '/wallet' + ``` + +## Advantages + +### ✅ Zero Changes to Web-App +Your custom frontend doesn't need to: +- Parse `?reset_key=` from URLs +- Build password reset UI +- Handle password reset API calls +- Manage error states +- Implement validation + +### ✅ Auth Cookies Work Automatically +LNbits sets httponly cookies that your web-app automatically sees: +- `cookie_access_token` +- `is_lnbits_user_authorized` + +Your existing `auth.checkAuth()` will detect these and log the user in. + +### ✅ Simple & Elegant +Only 3 files changed in LNbits, zero changes in web-app. + +### ✅ Backwards Compatible +Existing LNbits installations continue to work. Setting is optional. + +## Disadvantages + +### ⚠️ Brief Branding Inconsistency +Users see LNbits UI briefly during: +- Login form +- Registration form +- Password reset form + +Then get redirected to your branded web-app. + +**This is usually acceptable** for most use cases, especially for password reset which is infrequent. + +## Testing + +1. **Set the environment variable**: + ```bash + export LNBITS_CUSTOM_FRONTEND_URL=http://localhost:5173 + ``` + +2. **Restart LNbits**: + ```bash + poetry run lnbits + ``` + +3. **Test Login**: + - Visit `http://localhost:5000/` + - Log in with credentials + - Verify redirect to `http://localhost:5173` + - Verify web-app shows you as logged in + +4. **Test Password Reset**: + - Admin generates reset link in users panel + - User clicks link with `?reset_key=...` + - User enters new password + - Verify redirect to custom frontend + - Verify web-app shows you as logged in + +## Security + +### ✅ Secure +- Auth cookies are httponly (can't be accessed by JavaScript) +- LNbits handles all auth logic +- No sensitive data in URLs except one-time reset keys +- Reset keys expire based on `auth_token_expire_minutes` + +### 🔒 HTTPS Required +Always use HTTPS for custom frontend URLs in production: +```bash +export LNBITS_CUSTOM_FRONTEND_URL=https://myapp.com +``` + +## Migration + +**No database migration required!** + +Settings are stored as JSON in `system_settings` table. New fields are automatically included. + +## Alternative: Full Custom Frontend Approach + +If you need **complete branding consistency** (no LNbits UI shown), you would need to: + +1. Build password reset form in web-app +2. Parse `?reset_key=` from URL +3. Add API method to call `/api/v1/auth/reset` +4. Handle validation, errors, loading states +5. Update admin UI to generate links pointing to web-app + +This is **significantly more work** for marginal benefit (users see LNbits UI for ~5 seconds during password reset). + +## Recommendation + +**Use this simple approach** unless you have specific requirements for complete UI consistency. The brief LNbits UI is a small trade-off for the simplicity gained. + +## Related Files + +- `lnbits/settings.py` - Setting definition +- `lnbits/helpers.py` - Expose to frontend +- `lnbits/static/js/index.js` - Redirect logic + +## Example `.env` + +```bash +# LNbits Configuration +LNBITS_DATA_FOLDER=./data +LNBITS_DATABASE_URL=sqlite:///./data/database.sqlite3 + +# Custom Frontend Integration +LNBITS_CUSTOM_FRONTEND_URL=https://myapp.com + +# Other settings... +``` + +## How Web-App Benefits + +Your web-app at `https://myapp.com` can now: + +1. **Receive logged-in users** from LNbits without any code changes +2. **Use existing `auth.checkAuth()`** - it just works +3. **Focus on your features** - don't rebuild auth UI +4. **Trust LNbits security** - it's battle-tested + +The auth cookies LNbits sets are valid for your domain if LNbits is on a subdomain (e.g., `api.myapp.com`) or you're using proper CORS configuration. + +## Future Enhancement + +If you later need the full custom UI approach, all the groundwork is there: +- Setting exists and is configurable +- Just add the web-app UI components +- Update admin panel to generate web-app links + +But start with this simple approach first! 🚀 diff --git a/lnbits/helpers.py b/lnbits/helpers.py index 8780f0cc..45d0424f 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -87,6 +87,7 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates "LNBITS_CUSTOM_IMAGE": settings.lnbits_custom_image, "LNBITS_CUSTOM_BADGE": settings.lnbits_custom_badge, "LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color, + "LNBITS_CUSTOM_FRONTEND_URL": settings.lnbits_custom_frontend_url, "LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all, "LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed, "LNBITS_NODE_UI": settings.lnbits_node_ui and settings.has_nodemanager, diff --git a/lnbits/settings.py b/lnbits/settings.py index 2f469223..4c4ba6f9 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -960,6 +960,10 @@ class EnvSettings(LNbitsSettings): lnbits_title: str = Field(default="LNbits API") lnbits_path: str = Field(default=".") lnbits_extensions_path: str = Field(default="lnbits") + lnbits_custom_frontend_url: str | None = Field( + default=None, + description="Custom frontend URL for redirects after auth (e.g., https://myapp.com). If not set, redirects to /wallet. This is read-only and must be set via environment variable." + ) super_user: str = Field(default="") auth_secret_key: str = Field(default="") version: str = Field(default="0.0.0") diff --git a/lnbits/static/js/pages/home.js b/lnbits/static/js/pages/home.js index d3958d64..26d3105e 100644 --- a/lnbits/static/js/pages/home.js +++ b/lnbits/static/js/pages/home.js @@ -79,7 +79,12 @@ window.PageHome = { this.password, this.passwordRepeat ) - this.refreshAuthUser() + // Redirect to custom frontend URL if configured, otherwise use router + if (this.LNBITS_CUSTOM_FRONTEND_URL) { + window.location.href = this.LNBITS_CUSTOM_FRONTEND_URL + } else { + this.refreshAuthUser() + } } catch (e) { LNbits.utils.notifyApiError(e) } @@ -91,7 +96,12 @@ window.PageHome = { this.password, this.passwordRepeat ) - this.refreshAuthUser() + // Redirect to custom frontend URL if configured, otherwise use router + if (this.LNBITS_CUSTOM_FRONTEND_URL) { + window.location.href = this.LNBITS_CUSTOM_FRONTEND_URL + } else { + this.refreshAuthUser() + } } catch (e) { LNbits.utils.notifyApiError(e) } @@ -99,7 +109,12 @@ window.PageHome = { async login() { try { await LNbits.api.login(this.username, this.password) - this.refreshAuthUser() + // Redirect to custom frontend URL if configured, otherwise use router + if (this.LNBITS_CUSTOM_FRONTEND_URL) { + window.location.href = this.LNBITS_CUSTOM_FRONTEND_URL + } else { + this.refreshAuthUser() + } } catch (e) { LNbits.utils.notifyApiError(e) } @@ -107,7 +122,13 @@ window.PageHome = { async loginUsr() { try { await LNbits.api.loginUsr(this.usr) - this.refreshAuthUser() + this.usr = '' + // Redirect to custom frontend URL if configured, otherwise use router + if (this.LNBITS_CUSTOM_FRONTEND_URL) { + window.location.href = this.LNBITS_CUSTOM_FRONTEND_URL + } else { + this.refreshAuthUser() + } } catch (e) { console.warn(e) LNbits.utils.notifyApiError(e) @@ -138,14 +159,28 @@ window.PageHome = { } }, created() { - if (this.g.isUserAuthorized) { - return this.refreshAuthUser() - } const urlParams = new URLSearchParams(window.location.search) + + // Check for reset_key FIRST - password reset should work even if user is logged in this.reset_key = urlParams.get('reset_key') if (this.reset_key) { this.authAction = 'reset' + // Clear existing auth cookies to allow password reset to work + this.$q.cookies.remove('is_lnbits_user_authorized') + this.$q.cookies.remove('cookie_access_token') + return // Don't redirect to /wallet } + + // Redirect authorized users + if (this.g.isUserAuthorized) { + // Redirect to custom frontend URL if configured, otherwise use router + if (this.LNBITS_CUSTOM_FRONTEND_URL) { + window.location.href = this.LNBITS_CUSTOM_FRONTEND_URL + } else { + return this.refreshAuthUser() + } + } + // check if lightning parameters are present in the URL if (urlParams.has('lightning')) { this.lnurl = urlParams.get('lightning')