mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-23 06:35:23 +01:00
Add Keycloak
SSO (#2272)
* feat: add `keycloak` SSO --------- Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
This commit is contained in:
parent
b8d295a5b7
commit
526467747e
9 changed files with 113 additions and 6 deletions
15
.env.example
15
.env.example
|
@ -7,7 +7,7 @@
|
||||||
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available.
|
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available.
|
||||||
# Warning: Enabling this will make LNbits ignore most configurations in file. Only the
|
# Warning: Enabling this will make LNbits ignore most configurations in file. Only the
|
||||||
# configurations defined in `ReadOnlySettings` will still be read from the environment variables.
|
# configurations defined in `ReadOnlySettings` will still be read from the environment variables.
|
||||||
# The rest of the settings will be stored in your database and you will be able to change them
|
# The rest of the settings will be stored in your database and you will be able to change them
|
||||||
# only through the Admin UI.
|
# only through the Admin UI.
|
||||||
# Disable this to make LNbits use this config file again.
|
# Disable this to make LNbits use this config file again.
|
||||||
LNBITS_ADMIN_UI=false
|
LNBITS_ADMIN_UI=false
|
||||||
|
@ -107,21 +107,28 @@ LNTIPS_API_ENDPOINT=https://ln.tips
|
||||||
# Secret Key: will default to the hash of the super user. It is strongly recommended that you set your own value.
|
# Secret Key: will default to the hash of the super user. It is strongly recommended that you set your own value.
|
||||||
AUTH_SECRET_KEY=""
|
AUTH_SECRET_KEY=""
|
||||||
AUTH_TOKEN_EXPIRE_MINUTES=525600
|
AUTH_TOKEN_EXPIRE_MINUTES=525600
|
||||||
# Possible authorization methods: user-id-only, username-password, google-auth, github-auth
|
# Possible authorization methods: user-id-only, username-password, google-auth, github-auth, keycloak-auth
|
||||||
AUTH_ALLOWED_METHODS="user-id-only, username-password"
|
AUTH_ALLOWED_METHODS="user-id-only, username-password"
|
||||||
# Set this flag if HTTP is used for OAuth
|
# Set this flag if HTTP is used for OAuth
|
||||||
# OAUTHLIB_INSECURE_TRANSPORT="1"
|
# OAUTHLIB_INSECURE_TRANSPORT="1"
|
||||||
|
|
||||||
# Google OAuth Config
|
# Google OAuth Config
|
||||||
# Make sure thant the authorized redirect URIs contain https://{domain}/api/v1/auth/google/token
|
# Make sure that the authorized redirect URIs contain https://{domain}/api/v1/auth/google/token
|
||||||
GOOGLE_CLIENT_ID=""
|
GOOGLE_CLIENT_ID=""
|
||||||
GOOGLE_CLIENT_SECRET=""
|
GOOGLE_CLIENT_SECRET=""
|
||||||
|
|
||||||
# GitHub OAuth Config
|
# GitHub OAuth Config
|
||||||
# Make sure thant the authorization callback URL is set to https://{domain}/api/v1/auth/github/token
|
# Make sure that the authorization callback URL is set to https://{domain}/api/v1/auth/github/token
|
||||||
GITHUB_CLIENT_ID=""
|
GITHUB_CLIENT_ID=""
|
||||||
GITHUB_CLIENT_SECRET=""
|
GITHUB_CLIENT_SECRET=""
|
||||||
|
|
||||||
|
# Keycloak OAuth Config
|
||||||
|
# Make sure that the valid redirect URIs contain https://{domain}/api/v1/auth/keycloak/token
|
||||||
|
KEYCLOAK_CLIENT_ID=""
|
||||||
|
KEYCLOAK_CLIENT_SECRET=""
|
||||||
|
KEYCLOAK_DISCOVERY_URL=""
|
||||||
|
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
|
|
||||||
# uvicorn variable, uncomment to allow https behind a proxy
|
# uvicorn variable, uncomment to allow https behind a proxy
|
||||||
|
|
37
lnbits/core/sso/keycloak.py
Normal file
37
lnbits/core/sso/keycloak.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"""Keycloak SSO Login Helper
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
|
||||||
|
|
||||||
|
|
||||||
|
class KeycloakSSO(SSOBase):
|
||||||
|
"""Class providing login via Keycloak OAuth"""
|
||||||
|
|
||||||
|
provider = "keycloak"
|
||||||
|
scope = ["openid", "email", "profile"]
|
||||||
|
discovery_url = ""
|
||||||
|
|
||||||
|
async def openid_from_response(
|
||||||
|
self, response: dict, session: Optional["httpx.AsyncClient"] = None
|
||||||
|
) -> OpenID:
|
||||||
|
"""Return OpenID from user information provided by Keycloak"""
|
||||||
|
return OpenID(
|
||||||
|
email=response.get("email", ""),
|
||||||
|
provider=self.provider,
|
||||||
|
id=response.get("sub"),
|
||||||
|
first_name=response.get("given_name"),
|
||||||
|
last_name=response.get("family_name"),
|
||||||
|
display_name=response.get("name"),
|
||||||
|
picture=response.get("picture"),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_discovery_document(self) -> DiscoveryDocument:
|
||||||
|
"""Get document containing handy urls"""
|
||||||
|
async with httpx.AsyncClient() as session:
|
||||||
|
response = await session.get(self.discovery_url)
|
||||||
|
content = response.json()
|
||||||
|
|
||||||
|
return content
|
|
@ -78,6 +78,41 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
<q-card-section
|
||||||
|
v-if="formData.auth_allowed_methods?.includes('keycloak-auth')"
|
||||||
|
class="q-pl-xl"
|
||||||
|
>
|
||||||
|
<strong class="q-my-none q-mb-sm">Keycloak Auth</strong>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 col-sm-12 q-pr-sm">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.keycloak_discovery_url"
|
||||||
|
label="Keycloak Discovey URL"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-sm-12 q-pr-sm">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.keycloak_client_id"
|
||||||
|
label="Keycloak Client ID"
|
||||||
|
hint="Make sure thant the authorization callback URL is set to https://{domain}/api/v1/auth/keycloak/token"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-sm-12">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.keycloak_client_secret"
|
||||||
|
type="password"
|
||||||
|
label="Keycloak Client Secret"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -263,6 +263,25 @@
|
||||||
<div><span v-text="$t('signin_with_github')"></span></div>
|
<div><span v-text="$t('signin_with_github')"></span></div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
|
{%endif%} {% if "keycloak-auth" in LNBITS_AUTH_METHODS %}
|
||||||
|
<div class="col-12 full-width q-pa-sm">
|
||||||
|
<q-btn
|
||||||
|
href="/api/v1/auth/keycloak"
|
||||||
|
type="a"
|
||||||
|
outline
|
||||||
|
no-caps
|
||||||
|
color="grey"
|
||||||
|
rounded
|
||||||
|
class="full-width"
|
||||||
|
>
|
||||||
|
<q-avatar size="32px" class="q-mr-md">
|
||||||
|
<q-img
|
||||||
|
:src="'{{ static_url_for('static', 'images/keycloak-logo.png') }}'"
|
||||||
|
></q-img>
|
||||||
|
</q-avatar>
|
||||||
|
<div><span v-text="$t('signin_with_keycloak')"></span></div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
|
@ -262,6 +262,7 @@ class AuthMethods(Enum):
|
||||||
username_and_password = "username-password"
|
username_and_password = "username-password"
|
||||||
google_auth = "google-auth"
|
google_auth = "google-auth"
|
||||||
github_auth = "github-auth"
|
github_auth = "github-auth"
|
||||||
|
keycloak_auth = "keycloak-auth"
|
||||||
|
|
||||||
|
|
||||||
class AuthSettings(LNbitsSettings):
|
class AuthSettings(LNbitsSettings):
|
||||||
|
@ -288,6 +289,12 @@ class GitHubAuthSettings(LNbitsSettings):
|
||||||
github_client_secret: str = Field(default="")
|
github_client_secret: str = Field(default="")
|
||||||
|
|
||||||
|
|
||||||
|
class KeycloakAuthSettings(LNbitsSettings):
|
||||||
|
keycloak_discovery_url: str = Field(default="")
|
||||||
|
keycloak_client_id: str = Field(default="")
|
||||||
|
keycloak_client_secret: str = Field(default="")
|
||||||
|
|
||||||
|
|
||||||
class EditableSettings(
|
class EditableSettings(
|
||||||
UsersSettings,
|
UsersSettings,
|
||||||
ExtensionsSettings,
|
ExtensionsSettings,
|
||||||
|
@ -301,6 +308,7 @@ class EditableSettings(
|
||||||
AuthSettings,
|
AuthSettings,
|
||||||
GoogleAuthSettings,
|
GoogleAuthSettings,
|
||||||
GitHubAuthSettings,
|
GitHubAuthSettings,
|
||||||
|
KeycloakAuthSettings,
|
||||||
):
|
):
|
||||||
@validator(
|
@validator(
|
||||||
"lnbits_admin_users",
|
"lnbits_admin_users",
|
||||||
|
|
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -209,6 +209,7 @@ window.localisation.en = {
|
||||||
account_settings: 'Account Settings',
|
account_settings: 'Account Settings',
|
||||||
signin_with_google: 'Sign in with Google',
|
signin_with_google: 'Sign in with Google',
|
||||||
signin_with_github: 'Sign in with GitHub',
|
signin_with_github: 'Sign in with GitHub',
|
||||||
|
signin_with_keycloak: 'Sign in with Keycloak',
|
||||||
username_or_email: 'Username or Email',
|
username_or_email: 'Username or Email',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
password_config: 'Password Config',
|
password_config: 'Password Config',
|
||||||
|
|
BIN
lnbits/static/images/keycloak-logo.png
Normal file
BIN
lnbits/static/images/keycloak-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,6 +1,6 @@
|
||||||
// update cache version every time there is a new deployment
|
// update cache version every time there is a new deployment
|
||||||
// so the service worker reinitializes the cache
|
// so the service worker reinitializes the cache
|
||||||
const CACHE_VERSION = 114
|
const CACHE_VERSION = 115
|
||||||
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
|
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
|
||||||
|
|
||||||
const getApiKey = request => {
|
const getApiKey = request => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue