Add Keycloak SSO (#2272)

* feat: add `keycloak` SSO

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
This commit is contained in:
Vlad Stan 2024-02-14 10:23:37 +02:00 committed by GitHub
parent b8d295a5b7
commit 526467747e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 113 additions and 6 deletions

View file

@ -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

View 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

View file

@ -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 />

View file

@ -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>

View file

@ -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",

File diff suppressed because one or more lines are too long

View file

@ -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',

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -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 => {