Adds custom frontend URL redirect after auth
Allows redirecting users to a custom frontend URL after login, registration, or password reset. This simplifies integrating LNbits with existing web applications by eliminating the need to handle authentication logic within the custom frontend. Users are redirected to the configured URL after successful authentication. This feature is backwards compatible and configurable via environment variable or admin UI.
This commit is contained in:
parent
2795ad9ac3
commit
44ba4e4086
4 changed files with 260 additions and 7 deletions
213
CUSTOM_FRONTEND_URL_SIMPLE.md
Normal file
213
CUSTOM_FRONTEND_URL_SIMPLE.md
Normal file
|
|
@ -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! 🚀
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue