Compare commits
32 Commits
v0.6.2-alp
...
v0.6.5-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e77b321fd | ||
|
|
b688cc872f | ||
|
|
2dea5c6b58 | ||
|
|
5465c1d213 | ||
|
|
759ea57bc8 | ||
|
|
a0e833ba69 | ||
|
|
9bf2372b7e | ||
|
|
653c716ea8 | ||
|
|
f53c16b20e | ||
|
|
d54046245f | ||
|
|
951f85816d | ||
|
|
99603b4b25 | ||
|
|
6d32757829 | ||
|
|
833360a66d | ||
|
|
c821ac19f7 | ||
|
|
e601a63a3d | ||
|
|
17a96da715 | ||
|
|
11b29f2f3b | ||
|
|
b1519b865d | ||
|
|
7a31b10d0e | ||
|
|
a6955e64a0 | ||
|
|
c217e46909 | ||
|
|
f614092af2 | ||
|
|
4f29740fa0 | ||
|
|
b4d5d7f2bf | ||
|
|
cb4d72a148 | ||
|
|
be59c415a0 | ||
|
|
ccd5fb2459 | ||
|
|
fbc47d11ef | ||
|
|
881a6cb0be | ||
|
|
178cd4df49 | ||
|
|
de321c8817 |
5
.github/workflows/hacs.yaml
vendored
5
.github/workflows/hacs.yaml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/*
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: "0 0 * * *"
|
||||||
@@ -13,10 +14,8 @@ jobs:
|
|||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: HACS validation
|
- name: HACS validation
|
||||||
uses: hacs/action@22.5.0
|
uses: hacs/action@22.5.0
|
||||||
with:
|
with:
|
||||||
category: "integration"
|
category: "integration"
|
||||||
ignore: brands
|
|
||||||
|
|
||||||
3
.github/workflows/hassfest.yaml
vendored
3
.github/workflows/hassfest.yaml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/*
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: "0 0 * * *"
|
||||||
@@ -13,5 +14,5 @@ jobs:
|
|||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: home-assistant/actions/hassfest@master
|
- uses: home-assistant/actions/hassfest@master
|
||||||
17
.github/workflows/lint.yaml
vendored
17
.github/workflows/lint.yaml
vendored
@@ -5,16 +5,21 @@ on:
|
|||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install the latest version of rye
|
- name: "Set up Python"
|
||||||
uses: eifinger/setup-rye@v4
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version-file: ".python-version"
|
||||||
|
- name: Install the latest version of uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
- name: Sync dependencies
|
- name: Sync dependencies
|
||||||
run: rye sync
|
run: scripts/sync
|
||||||
- name: Lint (pylint/rye lint)
|
- name: Lint (pylint/ruff lint)
|
||||||
run: rye run check
|
run: scripts/check
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -108,3 +108,8 @@ dmypy.json
|
|||||||
config/
|
config/
|
||||||
|
|
||||||
.venv
|
.venv
|
||||||
|
|
||||||
|
.pytest_logs.log
|
||||||
|
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.13.1
|
3.14.2
|
||||||
@@ -13,9 +13,36 @@ If you are not a programmer, you can still contribute by:
|
|||||||
You may also submit Pull Requests (PRs) to add features yourself! You can find a list that we are currently working on below. Please note that workflows will be run on your pull request and a pull request will only be merged when all checks pass and a review has been conducted (together with a manual test).
|
You may also submit Pull Requests (PRs) to add features yourself! You can find a list that we are currently working on below. Please note that workflows will be run on your pull request and a pull request will only be merged when all checks pass and a review has been conducted (together with a manual test).
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
This project uses the Rye package manager for development. You can find installation instructions here: https://rye.astral.sh/guide/installation/. Start by installing the dependencies using rye sync and then point your editor towards the environment created in the .venv directory.
|
This project uses the uv package manager for development. You can find installation instructions here: https://docs.astral.sh/uv/getting-started/installation/. Start by installing the dependencies using `uv sync` and then point your editor towards the environment created in the .venv directory.
|
||||||
You can then run Home Assistant and put the `custom_components/auth_oidc` directory in your HA `config` folder.
|
You can then run Home Assistant and put the `custom_components/auth_oidc` directory in your HA `config` folder.
|
||||||
|
|
||||||
|
#### Other useful commands
|
||||||
|
Some useful scripts are in the `scripts` directory. If you run Linux (or WSL under Windows), you can run these directly:
|
||||||
|
|
||||||
|
- `scripts/check` will check your Python files for linting errors
|
||||||
|
- `scripts/fix` will fix some formatting mistakes automatically
|
||||||
|
|
||||||
|
You can also run these commands manually on Windows:
|
||||||
|
|
||||||
|
##### Compiling css
|
||||||
|
|
||||||
|
To compile tailwind css styles for the pages you need the NodeJS and NPM installed.
|
||||||
|
|
||||||
|
You can run the `npm run css` script to generate the css once and you can run the `npm run css:watch` to recompile the css every time the templates change
|
||||||
|
|
||||||
|
##### Check
|
||||||
|
```
|
||||||
|
uv run ruff check
|
||||||
|
uv run ruff format --check
|
||||||
|
uv run pylint custom_components
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Fix
|
||||||
|
```
|
||||||
|
uv run ruff check --fix
|
||||||
|
uv run ruff format
|
||||||
|
```
|
||||||
|
|
||||||
### Docker Compose Development Environment
|
### Docker Compose Development Environment
|
||||||
You can also use the following Docker Compose configuration to automatically start up the latest HA release with the `auth_oidc` integration:
|
You can also use the following Docker Compose configuration to automatically start up the latest HA release with the `auth_oidc` integration:
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2024 Christiaan Goossens
|
Copyright 2024-2025 Christiaan Goossens
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,14 @@
|
|||||||
Provides an OpenID Connect (OIDC) implementation for Home Assistant through a custom component/integration. Through this integration, you can create an SSO (single-sign-on) environment within your self-hosted application stack / homelab.
|
Provides an OpenID Connect (OIDC) implementation for Home Assistant through a custom component/integration. Through this integration, you can create an SSO (single-sign-on) environment within your self-hosted application stack / homelab.
|
||||||
|
|
||||||
### Background
|
### Background
|
||||||
If you would like to read the background/open letter that lead to this component, please see https://community.home-assistant.io/t/open-letter-for-improving-home-assistants-authentication-system-oidc-sso/494223. It is currently one of the most upvoted feature requests for Home Assistant.
|
If you would like to read the background/open letter that lead to this component, you can find the original post at https://community.home-assistant.io/t/open-letter-for-improving-home-assistants-authentication-system-oidc-sso/494223. It is currently one of the most upvoted feature requests for Home Assistant.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If you support the addition of this feature to the Home Assistant core, please upvote https://github.com/orgs/home-assistant/discussions/48. It's the successor of the Home Assistant Community post mentioned above (with almost 900 upvotes).
|
||||||
|
|
||||||
## Installation guide
|
## Installation guide
|
||||||
|
|
||||||
1. Add this repository to [HACS](https://hacs.xyz/).
|
1. Add this repository to [HACS](https://hacs.xyz/) (or search for "OpenID Connect" in HACS).
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=christiaangoossens&repository=hass-oidc-auth&category=Integration)
|
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=christiaangoossens&repository=hass-oidc-auth&category=Integration)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
from typing import OrderedDict
|
from typing import OrderedDict
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.components.http import StaticPathConfig
|
||||||
|
|
||||||
# Import and re-export config schema explictly
|
# Import and re-export config schema explictly
|
||||||
# pylint: disable=useless-import-alias
|
# pylint: disable=useless-import-alias
|
||||||
@@ -17,11 +18,13 @@ from .config import (
|
|||||||
DISPLAY_NAME,
|
DISPLAY_NAME,
|
||||||
ID_TOKEN_SIGNING_ALGORITHM,
|
ID_TOKEN_SIGNING_ALGORITHM,
|
||||||
GROUPS_SCOPE,
|
GROUPS_SCOPE,
|
||||||
|
ADDITIONAL_SCOPES,
|
||||||
FEATURES,
|
FEATURES,
|
||||||
CLAIMS,
|
CLAIMS,
|
||||||
ROLES,
|
ROLES,
|
||||||
NETWORK,
|
NETWORK,
|
||||||
FEATURES_INCLUDE_GROUPS_SCOPE,
|
FEATURES_INCLUDE_GROUPS_SCOPE,
|
||||||
|
FEATURES_FORCE_HTTPS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: enable=useless-import-alias
|
# pylint: enable=useless-import-alias
|
||||||
@@ -39,6 +42,13 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config):
|
async def async_setup(hass: HomeAssistant, config):
|
||||||
"""Add the OIDC Auth Provider to the providers in Home Assistant"""
|
"""Add the OIDC Auth Provider to the providers in Home Assistant"""
|
||||||
|
if DOMAIN not in config:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Setup was triggered, but no configuration was found. "
|
||||||
|
+ "Did you downgrade from 0.7+ without deleting the OIDC UI configuration?"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
my_config = config[DOMAIN]
|
my_config = config[DOMAIN]
|
||||||
|
|
||||||
providers = OrderedDict()
|
providers = OrderedDict()
|
||||||
@@ -66,6 +76,13 @@ async def async_setup(hass: HomeAssistant, config):
|
|||||||
groups_scope = my_config.get(GROUPS_SCOPE, "groups")
|
groups_scope = my_config.get(GROUPS_SCOPE, "groups")
|
||||||
if include_groups_scope:
|
if include_groups_scope:
|
||||||
scope += " " + groups_scope
|
scope += " " + groups_scope
|
||||||
|
# Add additional scopes if configured
|
||||||
|
additional_scopes = my_config.get(ADDITIONAL_SCOPES, [])
|
||||||
|
if additional_scopes:
|
||||||
|
# Ensure we have a space before adding additional scopes
|
||||||
|
if scope:
|
||||||
|
scope += " "
|
||||||
|
scope += " ".join(additional_scopes)
|
||||||
|
|
||||||
# Create the OIDC client
|
# Create the OIDC client
|
||||||
oidc_client = oidc_client = OIDCClient(
|
oidc_client = oidc_client = OIDCClient(
|
||||||
@@ -83,12 +100,23 @@ async def async_setup(hass: HomeAssistant, config):
|
|||||||
|
|
||||||
# Register the views
|
# Register the views
|
||||||
name = config[DOMAIN].get(DISPLAY_NAME, DEFAULT_TITLE)
|
name = config[DOMAIN].get(DISPLAY_NAME, DEFAULT_TITLE)
|
||||||
|
force_https = features_config.get(FEATURES_FORCE_HTTPS, False)
|
||||||
|
|
||||||
hass.http.register_view(OIDCWelcomeView(name))
|
hass.http.register_view(OIDCWelcomeView(name))
|
||||||
hass.http.register_view(OIDCRedirectView(oidc_client))
|
hass.http.register_view(OIDCRedirectView(oidc_client, force_https))
|
||||||
hass.http.register_view(OIDCCallbackView(oidc_client, provider))
|
hass.http.register_view(OIDCCallbackView(oidc_client, provider, force_https))
|
||||||
hass.http.register_view(OIDCFinishView())
|
hass.http.register_view(OIDCFinishView())
|
||||||
|
|
||||||
|
await hass.http.async_register_static_paths(
|
||||||
|
[
|
||||||
|
StaticPathConfig(
|
||||||
|
"/auth/oidc/static/style.css",
|
||||||
|
hass.config.path("custom_components/auth_oidc/static/style.css"),
|
||||||
|
cache_headers=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.info("Registered OIDC views")
|
_LOGGER.info("Registered OIDC views")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ DISCOVERY_URL = "discovery_url"
|
|||||||
DISPLAY_NAME = "display_name"
|
DISPLAY_NAME = "display_name"
|
||||||
ID_TOKEN_SIGNING_ALGORITHM = "id_token_signing_alg"
|
ID_TOKEN_SIGNING_ALGORITHM = "id_token_signing_alg"
|
||||||
GROUPS_SCOPE = "groups_scope"
|
GROUPS_SCOPE = "groups_scope"
|
||||||
|
ADDITIONAL_SCOPES = "additional_scopes"
|
||||||
FEATURES = "features"
|
FEATURES = "features"
|
||||||
FEATURES_AUTOMATIC_USER_LINKING = "automatic_user_linking"
|
FEATURES_AUTOMATIC_USER_LINKING = "automatic_user_linking"
|
||||||
FEATURES_AUTOMATIC_PERSON_CREATION = "automatic_person_creation"
|
FEATURES_AUTOMATIC_PERSON_CREATION = "automatic_person_creation"
|
||||||
FEATURES_DISABLE_PKCE = "disable_rfc7636"
|
FEATURES_DISABLE_PKCE = "disable_rfc7636"
|
||||||
FEATURES_INCLUDE_GROUPS_SCOPE = "include_groups_scope"
|
FEATURES_INCLUDE_GROUPS_SCOPE = "include_groups_scope"
|
||||||
|
FEATURES_FORCE_HTTPS = "force_https"
|
||||||
CLAIMS = "claims"
|
CLAIMS = "claims"
|
||||||
CLAIMS_DISPLAY_NAME = "display_name"
|
CLAIMS_DISPLAY_NAME = "display_name"
|
||||||
CLAIMS_USERNAME = "username"
|
CLAIMS_USERNAME = "username"
|
||||||
@@ -46,6 +48,9 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
# String value to allow changing the groups scope
|
# String value to allow changing the groups scope
|
||||||
# Defaults to 'groups' which is used by Authelia and Authentik
|
# Defaults to 'groups' which is used by Authelia and Authentik
|
||||||
vol.Optional(GROUPS_SCOPE, default="groups"): vol.Coerce(str),
|
vol.Optional(GROUPS_SCOPE, default="groups"): vol.Coerce(str),
|
||||||
|
# Additional scopes to request from the OIDC provider
|
||||||
|
# Optional, this field is unnecessary if you only use the openid and profile scopes.
|
||||||
|
vol.Optional(ADDITIONAL_SCOPES, default=[]): vol.Coerce(list[str]),
|
||||||
# Which features should be enabled/disabled?
|
# Which features should be enabled/disabled?
|
||||||
# Optional, defaults to sane/secure defaults
|
# Optional, defaults to sane/secure defaults
|
||||||
vol.Optional(FEATURES): vol.Schema(
|
vol.Optional(FEATURES): vol.Schema(
|
||||||
@@ -65,6 +70,10 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(
|
vol.Optional(
|
||||||
FEATURES_INCLUDE_GROUPS_SCOPE, default=True
|
FEATURES_INCLUDE_GROUPS_SCOPE, default=True
|
||||||
): vol.Coerce(bool),
|
): vol.Coerce(bool),
|
||||||
|
# Force HTTPS on all generated URLs (like redirect_uri)
|
||||||
|
vol.Optional(FEATURES_FORCE_HTTPS, default=False): vol.Coerce(
|
||||||
|
bool
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
# Determine which specific claims will be used from the id_token
|
# Determine which specific claims will be used from the id_token
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ class OIDCCallbackView(HomeAssistantView):
|
|||||||
name = "auth:oidc:callback"
|
name = "auth:oidc:callback"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, oidc_client: OIDCClient, oidc_provider: OpenIDAuthProvider
|
self,
|
||||||
|
oidc_client: OIDCClient,
|
||||||
|
oidc_provider: OpenIDAuthProvider,
|
||||||
|
force_https: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.oidc_client = oidc_client
|
self.oidc_client = oidc_client
|
||||||
self.oidc_provider = oidc_provider
|
self.oidc_provider = oidc_provider
|
||||||
|
self.force_https = force_https
|
||||||
|
|
||||||
async def get(self, request: web.Request) -> web.Response:
|
async def get(self, request: web.Request) -> web.Response:
|
||||||
"""Receive response."""
|
"""Receive response."""
|
||||||
@@ -38,7 +42,7 @@ class OIDCCallbackView(HomeAssistantView):
|
|||||||
)
|
)
|
||||||
return web.Response(text=view_html, content_type="text/html")
|
return web.Response(text=view_html, content_type="text/html")
|
||||||
|
|
||||||
redirect_uri = get_url("/auth/oidc/callback")
|
redirect_uri = get_url("/auth/oidc/callback", self.force_https)
|
||||||
user_details = await self.oidc_client.async_complete_token_flow(
|
user_details = await self.oidc_client.async_complete_token_flow(
|
||||||
redirect_uri, code, state
|
redirect_uri, code, state
|
||||||
)
|
)
|
||||||
@@ -63,4 +67,6 @@ class OIDCCallbackView(HomeAssistantView):
|
|||||||
return web.Response(text=view_html, content_type="text/html")
|
return web.Response(text=view_html, content_type="text/html")
|
||||||
|
|
||||||
code = await self.oidc_provider.async_save_user_info(user_details)
|
code = await self.oidc_provider.async_save_user_info(user_details)
|
||||||
return web.HTTPFound(get_url("/auth/oidc/finish?code=" + code))
|
return web.HTTPFound(
|
||||||
|
get_url("/auth/oidc/finish?code=" + code, self.force_https)
|
||||||
|
)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class OIDCFinishView(HomeAssistantView):
|
|||||||
|
|
||||||
# Return redirect to the main page for sign in with a cookie
|
# Return redirect to the main page for sign in with a cookie
|
||||||
return web.HTTPFound(
|
return web.HTTPFound(
|
||||||
location="/",
|
location="/?storeToken=true",
|
||||||
headers={
|
headers={
|
||||||
# Set a cookie to enable autologin on only the specific path used
|
# Set a cookie to enable autologin on only the specific path used
|
||||||
# for the POST request, with all strict parameters set
|
# for the POST request, with all strict parameters set
|
||||||
|
|||||||
@@ -17,17 +17,21 @@ class OIDCRedirectView(HomeAssistantView):
|
|||||||
url = PATH
|
url = PATH
|
||||||
name = "auth:oidc:redirect"
|
name = "auth:oidc:redirect"
|
||||||
|
|
||||||
def __init__(self, oidc_client: OIDCClient) -> None:
|
def __init__(self, oidc_client: OIDCClient, force_https: bool) -> None:
|
||||||
self.oidc_client = oidc_client
|
self.oidc_client = oidc_client
|
||||||
|
self.force_https = force_https
|
||||||
|
|
||||||
async def get(self, _: web.Request) -> web.Response:
|
async def get(self, _: web.Request) -> web.Response:
|
||||||
"""Receive response."""
|
"""Receive response."""
|
||||||
|
|
||||||
redirect_uri = get_url("/auth/oidc/callback")
|
try:
|
||||||
|
redirect_uri = get_url("/auth/oidc/callback", self.force_https)
|
||||||
auth_url = await self.oidc_client.async_get_authorization_url(redirect_uri)
|
auth_url = await self.oidc_client.async_get_authorization_url(redirect_uri)
|
||||||
|
|
||||||
if auth_url:
|
if auth_url:
|
||||||
return web.HTTPFound(auth_url)
|
raise web.HTTPFound(auth_url)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
view_html = await get_view(
|
view_html = await get_view(
|
||||||
"error",
|
"error",
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ from homeassistant.components import http
|
|||||||
from .views.loader import AsyncTemplateRenderer
|
from .views.loader import AsyncTemplateRenderer
|
||||||
|
|
||||||
|
|
||||||
def get_url(path: str) -> str:
|
def get_url(path: str, force_https: bool) -> str:
|
||||||
"""Returns the requested path appended to the current request base URL."""
|
"""Returns the requested path appended to the current request base URL."""
|
||||||
if (req := http.current_request.get()) is None:
|
if (req := http.current_request.get()) is None:
|
||||||
raise RuntimeError("No current request in context")
|
raise RuntimeError("No current request in context")
|
||||||
|
|
||||||
base_uri = str(req.url).split("/auth", 2)[0]
|
base_uri = str(req.url).split("/auth", 2)[0]
|
||||||
|
if force_https:
|
||||||
|
base_uri = base_uri.replace("http://", "https://")
|
||||||
return f"{base_uri}{path}"
|
return f"{base_uri}{path}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,15 @@
|
|||||||
"auth",
|
"auth",
|
||||||
"http"
|
"http"
|
||||||
],
|
],
|
||||||
"documentation": "https://github.com/christiaangoossens/hass-oidc-auth",
|
"documentation": "https://github.com/christiaangoossens/hass-oidc-auth/blob/v0.6.4-alpha/docs/configuration.md",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "calculated",
|
"iot_class": "calculated",
|
||||||
"issue_tracker": "https://github.com/christiaangoossens/hass-oidc-auth/issues",
|
"issue_tracker": "https://github.com/christiaangoossens/hass-oidc-auth/issues",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"python-jose>=3.3.0",
|
"aiofiles",
|
||||||
"aiofiles>=24.1.0",
|
"jinja2",
|
||||||
"jinja2>=3.1.4",
|
"bcrypt",
|
||||||
"bcrypt>=4.2.0"
|
"joserfc"
|
||||||
],
|
],
|
||||||
"version": "0.6.2"
|
"version": "0.6.5"
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import ssl
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from jose import jwt, jwk
|
from joserfc import jwt, jwk, jws, errors as joserfc_errors
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .types import UserDetails
|
from .types import UserDetails
|
||||||
@@ -237,9 +237,7 @@ class OIDCClient:
|
|||||||
_LOGGER.warning("Error fetching userinfo: %s", e)
|
_LOGGER.warning("Error fetching userinfo: %s", e)
|
||||||
raise OIDCUserinfoInvalid from e
|
raise OIDCUserinfoInvalid from e
|
||||||
|
|
||||||
async def _parse_id_token(
|
async def _parse_id_token(self, id_token: str) -> Optional[dict]:
|
||||||
self, id_token: str, access_token: str | None
|
|
||||||
) -> Optional[dict]:
|
|
||||||
"""Parses the ID token into a dict containing token contents."""
|
"""Parses the ID token into a dict containing token contents."""
|
||||||
if self.discovery_document is None:
|
if self.discovery_document is None:
|
||||||
self.discovery_document = await self._fetch_discovery_document()
|
self.discovery_document = await self._fetch_discovery_document()
|
||||||
@@ -249,7 +247,8 @@ class OIDCClient:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Obtain the id_token header
|
# Obtain the id_token header
|
||||||
unverified_header = jwt.get_unverified_header(id_token)
|
token_obj = jws.extract_compact(id_token.encode())
|
||||||
|
unverified_header = token_obj.protected
|
||||||
if not unverified_header:
|
if not unverified_header:
|
||||||
_LOGGER.warning("Could not get header from received id_token.")
|
_LOGGER.warning("Could not get header from received id_token.")
|
||||||
return None
|
return None
|
||||||
@@ -278,7 +277,7 @@ class OIDCClient:
|
|||||||
)
|
)
|
||||||
raise OIDCIdTokenSigningAlgorithmInvalid()
|
raise OIDCIdTokenSigningAlgorithmInvalid()
|
||||||
|
|
||||||
jwk_obj = jwk.construct(
|
jwk_obj = jwk.import_key(
|
||||||
{
|
{
|
||||||
"kty": "oct",
|
"kty": "oct",
|
||||||
"k": base64.urlsafe_b64encode(
|
"k": base64.urlsafe_b64encode(
|
||||||
@@ -311,9 +310,9 @@ class OIDCClient:
|
|||||||
signing_key["alg"] = alg
|
signing_key["alg"] = alg
|
||||||
|
|
||||||
# Construct the JWK from the RSA key
|
# Construct the JWK from the RSA key
|
||||||
jwk_obj = jwk.construct(signing_key)
|
jwk_obj = jwk.import_key(signing_key)
|
||||||
|
|
||||||
# Verify the token
|
# Decode the token, decode does not verify it
|
||||||
decoded_token = jwt.decode(
|
decoded_token = jwt.decode(
|
||||||
id_token,
|
id_token,
|
||||||
jwk_obj,
|
jwk_obj,
|
||||||
@@ -322,48 +321,31 @@ class OIDCClient:
|
|||||||
# according to JWS [JWS] using the algorithm specified in the JWT
|
# according to JWS [JWS] using the algorithm specified in the JWT
|
||||||
# alg Header Parameter.
|
# alg Header Parameter.
|
||||||
algorithms=[self.id_token_signing_alg],
|
algorithms=[self.id_token_signing_alg],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Claims Registry for validation
|
||||||
|
id_token_validator = jwt.JWTClaimsRegistry(
|
||||||
|
leeway=5,
|
||||||
# OpenID Connect Core 1.0 Section 3.1.3.7.3
|
# OpenID Connect Core 1.0 Section 3.1.3.7.3
|
||||||
# The Client MUST validate that the aud (audience) Claim contains
|
# The Client MUST validate that the aud (audience) Claim contains
|
||||||
# its client_id value registered at the Issuer identified by the
|
# its client_id value registered at the Issuer identified by the
|
||||||
# iss (issuer) Claim as an audience.
|
# iss (issuer) Claim as an audience.
|
||||||
audience=self.client_id,
|
aud={"essential": True, "value": self.client_id},
|
||||||
# OpenID Connect Core 1.0 Section 3.1.3.7.2
|
# OpenID Connect Core 1.0 Section 3.1.3.7.2
|
||||||
# The Issuer Identifier for the OpenID Provider MUST exactly
|
# The Issuer Identifier for the OpenID Provider MUST exactly
|
||||||
# match the value of the iss (issuer) Claim.
|
# match the value of the iss (issuer) Claim.
|
||||||
issuer=self.discovery_document["issuer"],
|
iss={"essential": True, "value": self.discovery_document["issuer"]},
|
||||||
access_token=access_token,
|
|
||||||
options={
|
|
||||||
# Verify everything if present
|
|
||||||
"verify_signature": True,
|
|
||||||
"verify_aud": True,
|
|
||||||
"verify_iat": True,
|
|
||||||
"verify_exp": True,
|
|
||||||
"verify_nbf": True,
|
|
||||||
"verify_iss": True,
|
|
||||||
"verify_sub": True,
|
|
||||||
"verify_jti": True,
|
|
||||||
"verify_at_hash": True,
|
|
||||||
# OpenID Connect Core 1.0 Section 3.1.3.7.3
|
|
||||||
"require_aud": True,
|
|
||||||
# OpenID Connect Core 1.0 Section 3.1.3.7.10
|
|
||||||
"require_iat": True,
|
|
||||||
# OpenID Connect Core 1.0 Section 3.1.3.7.9
|
# OpenID Connect Core 1.0 Section 3.1.3.7.9
|
||||||
"require_exp": True,
|
# OpenID Connect Core 1.0 Section 3.1.3.7.10
|
||||||
# OpenID Connect Core 1.0 Section 3.1.3.7.2
|
# No need to specify exp, nbf, iat, they are in here by default
|
||||||
"require_iss": True,
|
sub={"essential": True},
|
||||||
# We need the sub as it's used to identify the user
|
|
||||||
"require_sub": True,
|
|
||||||
# Other values, not required.
|
|
||||||
"require_nbf": False,
|
|
||||||
"require_jti": False,
|
|
||||||
"require_at_hash": False,
|
|
||||||
"leeway": 5,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return decoded_token
|
|
||||||
|
|
||||||
except jwt.JWTError as e:
|
id_token_validator.validate(decoded_token.claims)
|
||||||
_LOGGER.warning("JWT Verification failed: %s", e)
|
return decoded_token.claims
|
||||||
|
|
||||||
|
except joserfc_errors.JoseError as e:
|
||||||
|
_LOGGER.warning("JWT verification failed: %s", e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def async_get_authorization_url(self, redirect_uri: str) -> Optional[str]:
|
async def async_get_authorization_url(self, redirect_uri: str) -> Optional[str]:
|
||||||
@@ -501,11 +483,9 @@ class OIDCClient:
|
|||||||
)
|
)
|
||||||
|
|
||||||
id_token = token_response.get("id_token")
|
id_token = token_response.get("id_token")
|
||||||
access_token = token_response.get("access_token")
|
|
||||||
|
|
||||||
# Parse the id token to obtain the relevant details
|
# Parse the id token to obtain the relevant details
|
||||||
# Access token is supplied to check at_hash if present
|
id_token = await self._parse_id_token(id_token)
|
||||||
id_token = await self._parse_id_token(id_token, access_token)
|
|
||||||
|
|
||||||
if id_token is None:
|
if id_token is None:
|
||||||
_LOGGER.warning("ID token could not be parsed!")
|
_LOGGER.warning("ID token could not be parsed!")
|
||||||
@@ -519,6 +499,7 @@ class OIDCClient:
|
|||||||
_LOGGER.warning("Nonce mismatch!")
|
_LOGGER.warning("Nonce mismatch!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
access_token = token_response.get("access_token")
|
||||||
data = await self.parse_user_details(id_token, access_token)
|
data = await self.parse_user_details(id_token, access_token)
|
||||||
|
|
||||||
# Log which details were obtained for debugging
|
# Log which details were obtained for debugging
|
||||||
|
|||||||
3
custom_components/auth_oidc/static/input.css
Normal file
3
custom_components/auth_oidc/static/input.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@source "../views/templates";
|
||||||
2
custom_components/auth_oidc/static/style.css
Normal file
2
custom_components/auth_oidc/static/style.css
Normal file
File diff suppressed because one or more lines are too long
@@ -54,7 +54,9 @@ class AsyncTemplateRenderer:
|
|||||||
if template_name not in templates:
|
if template_name not in templates:
|
||||||
raise ValueError(f"Template '{template_name}' not found.")
|
raise ValueError(f"Template '{template_name}' not found.")
|
||||||
|
|
||||||
env = Environment(loader=DictLoader(templates), enable_async=True)
|
env = Environment(
|
||||||
|
loader=DictLoader(templates), enable_async=True, autoescape=True
|
||||||
|
)
|
||||||
template = env.get_template(template_name)
|
template = env.get_template(template_name)
|
||||||
|
|
||||||
# Render template
|
# Render template
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<link rel="stylesheet" href="/auth/oidc/static/style.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ You don't have to configure other settings in most cases, as they have secure de
|
|||||||
## Provider Configurations
|
## Provider Configurations
|
||||||
Here are some documentation links for specific providers that you may want to follow:
|
Here are some documentation links for specific providers that you may want to follow:
|
||||||
|
|
||||||
| <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" width="100"> | <img src="https://www.authelia.com/images/branding/logo-cropped.png" width="100"> | <img src="https://github.com/user-attachments/assets/4ceb2708-9f29-4694-b797-be833efce17d" width="100"> |
|
* [Authentik](./provider-configurations/authentik.md)
|
||||||
|:-----------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|
|
* [Authelia](./provider-configurations/authelia.md)
|
||||||
| [Authentik](./provider-configurations/authentik.md) | [Authelia](./provider-configurations/authelia.md) | [Pocket ID](./provider-configurations/pocket-id.md) |
|
* [Pocket ID](./provider-configurations/pocket-id.md)
|
||||||
|
* [Kanidm](./provider-configurations/kanidm.md)
|
||||||
|
* [Microsoft Entra ID](./provider-configurations/microsoft-entra.md)
|
||||||
|
|
||||||
|
_Missing a provider? Submit your guide using a PR._
|
||||||
Are you using another provider? Another user might have added configuration instructions here: [Other providers](./provider-configurations/other.md)
|
|
||||||
|
|
||||||
## Common Configurations
|
## Common Configurations
|
||||||
### Configuring Client Secret
|
### Configuring Client Secret
|
||||||
@@ -48,7 +49,7 @@ If your provider isn't listed above, you might want to configure OIDC settings y
|
|||||||
auth_oidc:
|
auth_oidc:
|
||||||
client_id: ""
|
client_id: ""
|
||||||
discovery_url: ""
|
discovery_url: ""
|
||||||
id_token_signing_alg: <HS256 or RS256>
|
id_token_signing_alg: <HS256, RS256, ES256, ...>
|
||||||
groups_scope: <groups scope>
|
groups_scope: <groups scope>
|
||||||
claims:
|
claims:
|
||||||
display_name: <display name claim from your provider>
|
display_name: <display name claim from your provider>
|
||||||
@@ -73,6 +74,28 @@ auth_oidc:
|
|||||||
|
|
||||||
This will show the provider on the login screen as: "Login with Example".
|
This will show the provider on the login screen as: "Login with Example".
|
||||||
|
|
||||||
|
### Forcing HTTPS
|
||||||
|
First check if you are setting the header `X-Forwarded-Proto` in your proxy and if the [proxy settings for Home Assistant](https://www.home-assistant.io/integrations/http/#use_x_forwarded_for) are configured correctly. You should also check if IP addresses in your logs actually match the origin IP (instead of proxy IP). If you cannot find any mistakes, you may use the following config option to force HTTPS regardless:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auth_oidc:
|
||||||
|
features:
|
||||||
|
force_https: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabling registration for new users
|
||||||
|
This integration does not allow disabling registration for new users, as there is no way to abort registration that late in the process while providing a good user experience.
|
||||||
|
You can however set both roles to groups that only contain certain users or to a non-existant group.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auth_oidc:
|
||||||
|
roles:
|
||||||
|
user: "non_existent"
|
||||||
|
admin: "admins"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that if you put both on non-existent groups, no users will be able to login.
|
||||||
|
|
||||||
### Migrating from HA username/password users to OIDC users
|
### Migrating from HA username/password users to OIDC users
|
||||||
If you already have users created within Home Assistant and would like to re-use the current user profile for your OIDC login, you can (temporarily) enable `features.automatic_user_linking`, with the following config (example):
|
If you already have users created within Home Assistant and would like to re-use the current user profile for your OIDC login, you can (temporarily) enable `features.automatic_user_linking`, with the following config (example):
|
||||||
|
|
||||||
@@ -92,6 +115,8 @@ Upon login, OIDC users will then automatically be linked to the HA user with the
|
|||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> MFA is ignored when using this setting, thus bypassing any MFA configuration the user has originally configured, as long as the username is an exact match. This is dangerous if you are not aware of it!
|
> MFA is ignored when using this setting, thus bypassing any MFA configuration the user has originally configured, as long as the username is an exact match. This is dangerous if you are not aware of it!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Using a private certificate authority
|
### Using a private certificate authority
|
||||||
If you use a private certificate authority to secure your OIDC provider, you must configure the root certificates of your private certificate authority. Otherwise you will get an error (`[SSL: CERTIFICATE_VERIFY_FAILED]`) when connecting to the OIDC provider.
|
If you use a private certificate authority to secure your OIDC provider, you must configure the root certificates of your private certificate authority. Otherwise you will get an error (`[SSL: CERTIFICATE_VERIFY_FAILED]`) when connecting to the OIDC provider.
|
||||||
|
|
||||||
@@ -126,10 +151,12 @@ Here's a table of all options that you can set:
|
|||||||
| `display_name` | `string` | No | `"OpenID Connect (SSO)"` | The name to display on the login screen, both for the Home Assistant screen and the OIDC welcome screen. |
|
| `display_name` | `string` | No | `"OpenID Connect (SSO)"` | The name to display on the login screen, both for the Home Assistant screen and the OIDC welcome screen. |
|
||||||
| `id_token_signing_alg` | `string` | No | `RS256` | The signing algorithm that is used for your id_tokens.
|
| `id_token_signing_alg` | `string` | No | `RS256` | The signing algorithm that is used for your id_tokens.
|
||||||
| `groups_scope` | `string` | No | `groups` | Override the default grups scope with another scope of your choice. |
|
| `groups_scope` | `string` | No | `groups` | Override the default grups scope with another scope of your choice. |
|
||||||
|
| `additional_scopes`|`list of strings`| No | `empty list` | Add additional scopes to request for custom identity provider configurations in addition to the automatic `openid` and `profile` scopes and the `groups_scope` configuration option |
|
||||||
| `features.automatic_user_linking` | `boolean`| No | `false` | Automatically links users to existing Home Assistant users based on the OIDC username claim. Disabled by default for security. When disabled, OIDC users will get their own new user profile upon first login. |
|
| `features.automatic_user_linking` | `boolean`| No | `false` | Automatically links users to existing Home Assistant users based on the OIDC username claim. Disabled by default for security. When disabled, OIDC users will get their own new user profile upon first login. |
|
||||||
| `features.automatic_person_creation` | `boolean` | No | `true` | Automatically creates a person entry for new user profiles created by this integration. Recommended if you would like to assign presence detection to OIDC users. |
|
| `features.automatic_person_creation` | `boolean` | No | `true` | Automatically creates a person entry for new user profiles created by this integration. Recommended if you would like to assign presence detection to OIDC users. |
|
||||||
| `features.disable_rfc7636` | `boolean`| No | `false` | Disables PKCE (RFC 7636) for OIDC providers that don't support it. You should not need this with most providers. |
|
| `features.disable_rfc7636` | `boolean`| No | `false` | Disables PKCE (RFC 7636) for OIDC providers that don't support it. You should not need this with most providers. |
|
||||||
| `features.include_groups_scope` | `boolean` | No | `true` | Include the 'groups' scope in the OIDC request. Set to `false` to exclude it. |
|
| `features.include_groups_scope` | `boolean` | No | `true` | Include the 'groups' scope in the OIDC request. Set to `false` to exclude it. |
|
||||||
|
| `features.force_https` | `boolean` | No | `false` | Set to `true` to force all URLs generated to use `https` instead of automatically determining based on the request scheme or `X-Forwarded-Proto`. |
|
||||||
| `claims.display_name` | `string` | No | `name` | The claim to use to obtain the display name.
|
| `claims.display_name` | `string` | No | `name` | The claim to use to obtain the display name.
|
||||||
| `claims.username` | `string` | No | `preferred_username` | The claim to use to obtain the username.
|
| `claims.username` | `string` | No | `preferred_username` | The claim to use to obtain the username.
|
||||||
| `claims.groups` | `string` | No | `groups` | The claim to use to obtain the user's group(s). |
|
| `claims.groups` | `string` | No | `groups` | The claim to use to obtain the user's group(s). |
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ identity_providers:
|
|||||||
- 'openid'
|
- 'openid'
|
||||||
- 'profile'
|
- 'profile'
|
||||||
- 'groups'
|
- 'groups'
|
||||||
userinfo_signed_response_alg: 'RS256'
|
id_token_signed_response_alg: 'RS256'
|
||||||
```
|
```
|
||||||
|
|
||||||
Home Assistant `configuration.yaml`
|
Home Assistant `configuration.yaml`
|
||||||
@@ -56,7 +56,7 @@ identity_providers:
|
|||||||
- 'openid'
|
- 'openid'
|
||||||
- 'profile'
|
- 'profile'
|
||||||
- 'groups'
|
- 'groups'
|
||||||
userinfo_signed_response_alg: 'RS256'
|
id_token_signed_response_alg: 'RS256'
|
||||||
token_endpoint_auth_method: 'client_secret_post'
|
token_endpoint_auth_method: 'client_secret_post'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
145
docs/provider-configurations/kanidm.md
Normal file
145
docs/provider-configurations/kanidm.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Kanidm
|
||||||
|
|
||||||
|
## Public client configuration
|
||||||
|
|
||||||
|
[Home Assistant](https://github.com/home-assistant/core) `/var/lib/hass/configuration.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auth_oidc:
|
||||||
|
client_id: "homeassistant"
|
||||||
|
discovery_url: "https://idm.example.org/oauth2/openid/homeassistant/.well-known/openid-configuration"
|
||||||
|
features:
|
||||||
|
automatic_person_creation: true
|
||||||
|
id_token_signing_alg: "ES256"
|
||||||
|
roles:
|
||||||
|
admin: "homeassistant_admins@idm.example.org"
|
||||||
|
user: "idm_all_persons@idm.example.org"
|
||||||
|
```
|
||||||
|
|
||||||
|
[Kanidm](https://github.com/kanidm/kanidm)
|
||||||
|
|
||||||
|
1. Create your Kanidm account, if you don't have one already:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm person create "your_username" "Your Username" --name "idm_admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a new Kanidm group for your HomeAssistant administrators (`homeassistant_admins`), and add your regular account to it:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm group create "homeassistant_admins" --name "idm_admin"
|
||||||
|
kanidm group add-members "homeassistant_admins" "your_username" --name "idm_admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a new OAuth2 application configuration in Kanidm (`homeassistant`), configure the redirect URL, and scope access:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm system oauth2 create-public "homeassistant" "Home Assistant" "https://hass.example.org/auth/oidc/welcome" --name "idm_admin"
|
||||||
|
kanidm system oauth2 add-redirect-url "homeassistant" "https://hass.example.org/auth/oidc/callback" --name "idm_admin"
|
||||||
|
kanidm system oauth2 update-scope-map "homeassistant" "homeassistant_users" "email" "groups" "openid" "profile" --name "idm_admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
[Kanidm Provision](https://github.com/oddlama/kanidm-provision) `state.json`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"groups": {
|
||||||
|
"homeassistant_admins": {
|
||||||
|
"members": ["your_username"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"persons": {
|
||||||
|
"your_username": {
|
||||||
|
"displayName": "Your Username"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"oauth2": {
|
||||||
|
"homeassistant": {
|
||||||
|
"displayName": "Home Assistant",
|
||||||
|
"originLanding": "https://hass.example.org/auth/oidc/welcome",
|
||||||
|
"originUrl": "https://hass.example.org/auth/oidc/callback",
|
||||||
|
"public": true,
|
||||||
|
"scopeMaps": {
|
||||||
|
"homeassistant_users": ["email", "groups", "openid", "profile"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Confidential client configuration
|
||||||
|
|
||||||
|
[Home Assistant](https://github.com/home-assistant/core) `/var/lib/hass/configuration.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auth_oidc:
|
||||||
|
client_id: "homeassistant"
|
||||||
|
client_secret: !secret oidc_client_secret
|
||||||
|
discovery_url: "https://idm.example.org/oauth2/openid/homeassistant/.well-known/openid-configuration"
|
||||||
|
features:
|
||||||
|
automatic_person_creation: true
|
||||||
|
id_token_signing_alg: "ES256"
|
||||||
|
roles:
|
||||||
|
admin: "homeassistant_admins@idm.example.org"
|
||||||
|
user: "idm_all_persons@idm.example.org"
|
||||||
|
```
|
||||||
|
|
||||||
|
[Kanidm](https://github.com/kanidm/kanidm)
|
||||||
|
|
||||||
|
1. Create your Kanidm account, if you don't have one already:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm person create "your_username" "Your Username" --name "idm_admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a new Kanidm group for your HomeAssistant administrators (`homeassistant_admins`), and add your regular account to it:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm group create "homeassistant_admins" --name "idm_admin"
|
||||||
|
kanidm group add-members "homeassistant_admins" "your_username" --name "idm_admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a new OAuth2 application configuration in Kanidm (`homeassistant`), configure the redirect URL, and scope access:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm system oauth2 create "homeassistant" "Home Assistant" "https://hass.example.org/auth/oidc/welcome" --name "idm_admin"
|
||||||
|
kanidm system oauth2 add-redirect-url "homeassistant" "https://hass.example.org/auth/oidc/callback" --name "idm_admin"
|
||||||
|
kanidm system oauth2 update-scope-map "homeassistant" "homeassistant_users" "email" "groups" "openid" "profile" --name "idm_admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Get the `homeassistant` OAuth2 client secret from Kanidm:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm system oauth2 show-basic-secret "homeassistant" --name "idm_admin" | xargs echo 'oidc_client_secret: {}' | tee --append "/var/lib/hass/secrets.yaml"
|
||||||
|
```
|
||||||
|
|
||||||
|
[Kanidm Provision](https://github.com/oddlama/kanidm-provision) `state.json`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"groups": {
|
||||||
|
"homeassistant_admins": {
|
||||||
|
"members": ["your_username"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"persons": {
|
||||||
|
"your_username": {
|
||||||
|
"displayName": "Your Username"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"oauth2": {
|
||||||
|
"homeassistant": {
|
||||||
|
"displayName": "Home Assistant",
|
||||||
|
"originLanding": "https://hass.example.org/auth/oidc/welcome",
|
||||||
|
"originUrl": "https://hass.example.org/auth/oidc/callback",
|
||||||
|
"scopeMaps": {
|
||||||
|
"homeassistant_users": ["email", "groups", "openid", "profile"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
# Other providers
|
# Microsoft Entra ID
|
||||||
Under construction.
|
|
||||||
|
|
||||||
## Microsoft Entra ID
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Microsoft Entra ID does not support public clients that are not Single Page Applications (SPA's). Therefore, you will have to use a client secret.
|
> Microsoft Entra ID does not support public clients that are not Single Page Applications (SPA's). Therefore, you will have to use a client secret.
|
||||||
|
## Basic configuration
|
||||||
1. Go to app registrations in Entra ID.
|
1. Go to app registrations in Entra ID.
|
||||||
2. Create a new app, use the "Web" type for the redirect URI and fill in your URL: `<ha url>/auth/oidc/callback`. Note that you either have to use localhost, or HTTPS.
|
2. Create a new app, use the "Web" type for the redirect URI and fill in your URL: `<ha url>/auth/oidc/callback`. Note that you either have to use localhost, or HTTPS.
|
||||||
3. Copy the 'Application (client) ID' on the overview page of your app and use it as your `client_id`.
|
3. Copy the 'Application (client) ID' on the overview page of your app and use it as your `client_id`.
|
||||||
@@ -28,3 +25,27 @@ auth_oidc:
|
|||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Be careful! Configuring Entra ID wrong may leave your Home Assistant install open for anyone with a Microsoft account. Please use "Single tenant" account types only. Do not enable "Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)" or personal account modes without enabling the mode to only allow specific accounts first!
|
> Be careful! Configuring Entra ID wrong may leave your Home Assistant install open for anyone with a Microsoft account. Please use "Single tenant" account types only. Do not enable "Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)" or personal account modes without enabling the mode to only allow specific accounts first!
|
||||||
|
|
||||||
|
## Configuring user roles
|
||||||
|
If you like to configure the Home Assistant users roles based on your Entra ID settings, you have to create 2 roles within your Entra ID app registration.
|
||||||
|
Go to "App registrations" and select app roles. Create two new roles for admins and users, giving them sensible names and values (the example uses `users` and `admins`), that you will need later in your HA configuration.
|
||||||
|
|
||||||
|
<img width="1205" height="965" alt="Entra-HA-Roles" src="https://github.com/user-attachments/assets/568a1526-0607-4f88-945f-7c4f1fcc0ac2" />
|
||||||
|
|
||||||
|
Then you need to create the users and assign them a role of your choice.
|
||||||
|
Go to "Enterprise apps" chose your app registration again and select "Users and groups" within the manage section. Add users, or groups from your tenant or AD-sync and assign them a role, from the ones you created before.
|
||||||
|
|
||||||
|
<img width="1112" height="570" alt="Entra-HA-Users" src="https://github.com/user-attachments/assets/13a49cee-798b-4b53-8fee-d2792ccd7763" />
|
||||||
|
|
||||||
|
Last thing to do is to include
|
||||||
|
```
|
||||||
|
claims:
|
||||||
|
groups: "roles"
|
||||||
|
roles:
|
||||||
|
admin: "admins"
|
||||||
|
user: "users"
|
||||||
|
```
|
||||||
|
in your auth_oidc config, where the roles values correspond to the ones you chose in your Entra ID roles.
|
||||||
|
Make sure, you keep the "include_groups_scope: False" from the basic configuration, as the claim needed for Entra ID is "roles".
|
||||||
|
|
||||||
|
Newly created users will get the role assigned in Entra ID, but there is no update to user roles. A user created with user role in HA will not get the admin role, if you change the assignment later on in Entra ID.
|
||||||
@@ -1,2 +1,58 @@
|
|||||||
# Pocket ID
|
# Pocket ID
|
||||||
Under construction.
|
|
||||||
|
## Public client configuration
|
||||||
|
|
||||||
|
### Pocket ID configuration
|
||||||
|
1. Login to Pocket ID and go to `OIDC Clients`
|
||||||
|
|
||||||
|
2. Click on `Add OIDC Client`
|
||||||
|
|
||||||
|
3. Fill the following details:
|
||||||
|
- Name: `Home Assistant`
|
||||||
|
- Callback URLs: `<your-homeassistant-url>/auth/oidc/callback` (for example: https://hass.example.com/auth/oidc/callback)
|
||||||
|
- Click on `Public Client` (PKCE will be automatically marked when doing this)
|
||||||
|
|
||||||
|
4. Click on `Save`
|
||||||
|
|
||||||
|
5. Click on `Show more details` and note down your `Client ID` and `OIDC Discovery URL` since you will need them later.
|
||||||
|
|
||||||
|
### Home Assistant configuration
|
||||||
|
1. Add following configuration in Home Assistant's configuration.yaml:
|
||||||
|
```yaml
|
||||||
|
auth_oidc:
|
||||||
|
client_id: <The Client ID you have noted down>
|
||||||
|
discovery_url: <The OIDC Discovery URL you have noted down> (for example: https://id.example.com/.well-known/openid-configuration)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Restart Home Assistant and go to your Home Assistant OIDC URL (for example: https://hass.example.com/auth/oidc/welcome)
|
||||||
|
|
||||||
|
## Confidential client configuration
|
||||||
|
|
||||||
|
### Pocket ID configuration
|
||||||
|
1. Login to Pocket ID and go to `OIDC Clients`
|
||||||
|
|
||||||
|
2. Click on `Add OIDC Client`
|
||||||
|
|
||||||
|
3. Fill the following details:
|
||||||
|
- Name: `Home Assistant`
|
||||||
|
- Callback URLs: `<your-homeassistant-url>/auth/oidc/callback` (for example: https://hass.example.com/auth/oidc/callback)
|
||||||
|
|
||||||
|
4. Click on `Save`
|
||||||
|
|
||||||
|
5. Click on `Show more details` and note down your:
|
||||||
|
- `Client ID`
|
||||||
|
- `Client secret`
|
||||||
|
- `OIDC Discovery URL`
|
||||||
|
|
||||||
|
### Home Assistant configuration
|
||||||
|
1. Add following configuration in Home Assistant's configuration.yaml:
|
||||||
|
```yaml
|
||||||
|
auth_oidc:
|
||||||
|
client_id: <The Client ID you have noted down>
|
||||||
|
client_secret: <The Client secret you have noted down>
|
||||||
|
discovery_url: <The OIDC Discovery URL you have noted down> (for example: https://id.example.com/.well-known/openid-configuration)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Restart Home Assistant and go to your Home Assistant OIDC URL (for example: https://hass.example.com/auth/oidc/welcome)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Install the integration through [HACS](https://hacs.xyz/). You can add it automa
|
|||||||
|
|
||||||
|
|
||||||
### Step 2: Configuration of the integration
|
### Step 2: Configuration of the integration
|
||||||
The integration is currently configurable through YAML only. See the [Configuration Guide](./docs/configuration.md) for more details or pick your OIDC provider below:
|
The integration is currently configurable through YAML only. See the [Configuration Guide](./configuration.md) for more details or pick your OIDC provider below (additional providers are available in the Configuration Guide):
|
||||||
|
|
||||||
| <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" width="100"> | <img src="https://www.authelia.com/images/branding/logo-cropped.png" width="100"> | <img src="https://github.com/user-attachments/assets/4ceb2708-9f29-4694-b797-be833efce17d" width="100"> |
|
| <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" width="100"> | <img src="https://www.authelia.com/images/branding/logo-cropped.png" width="100"> | <img src="https://github.com/user-attachments/assets/4ceb2708-9f29-4694-b797-be833efce17d" width="100"> |
|
||||||
|:-----------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|
|
|:-----------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"name": "OpenID Connect",
|
"name": "OpenID Connect",
|
||||||
"hide_default_branch": true,
|
"hide_default_branch": true,
|
||||||
"render_readme": true,
|
"render_readme": true,
|
||||||
"homeassistant": "2024.12"
|
"homeassistant": "2025.08"
|
||||||
}
|
}
|
||||||
1123
package-lock.json
generated
Normal file
1123
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
Normal file
11
package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "hass-oidc-auth",
|
||||||
|
"scripts": {
|
||||||
|
"css": "tailwindcss -i ./custom_components/auth_oidc/static/input.css -o ./custom_components/auth_oidc/static/style.css --minify",
|
||||||
|
"css:watch": "tailwindcss -i ./custom_components/auth_oidc/static/input.css -o ./custom_components/auth_oidc/static/style.css --watch --minify"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/cli": "^4.1.14",
|
||||||
|
"tailwindcss": "^4.1.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,33 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "hass-oidc-auth"
|
name = "hass-oidc-auth"
|
||||||
version = "0.6.2"
|
version = "0.6.4"
|
||||||
description = "OIDC component for Home Assistant"
|
description = "OIDC component for Home Assistant"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Christiaan Goossens", email = "contact@christiaangoossens.nl" }
|
{ name = "Christiaan Goossens", email = "contact@christiaangoossens.nl" }
|
||||||
]
|
]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"python-jose>=3.3.0",
|
"aiofiles~=25.1",
|
||||||
"aiofiles>=24.1.0",
|
"jinja2~=3.1",
|
||||||
"jinja2>=3.1.4",
|
"bcrypt~=5.0",
|
||||||
"bcrypt>=4.2.0",
|
"joserfc~=1.6.0",
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">= 3.13"
|
requires-python = "~=3.14.2"
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"homeassistant~=2026.1",
|
||||||
|
"pylint~=4.0",
|
||||||
|
"ruff~=0.12",
|
||||||
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.rye]
|
[tool.uv]
|
||||||
managed = true
|
managed = true
|
||||||
dev-dependencies = [
|
|
||||||
"homeassistant~=2024.12",
|
|
||||||
"pylint~=3.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.hatch.metadata]
|
[tool.hatch.metadata]
|
||||||
allow-direct-references = true
|
allow-direct-references = true
|
||||||
@@ -32,11 +35,5 @@ allow-direct-references = true
|
|||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["custom_components/auth_oidc"]
|
packages = ["custom_components/auth_oidc"]
|
||||||
|
|
||||||
[tool.rye.scripts]
|
[tool.ruff]
|
||||||
check = { chain = ["check-lint", "check-fmt", "check-pylint" ] }
|
target-version = "py313"
|
||||||
"check-lint" = "rye lint"
|
|
||||||
"check-fmt" = "rye fmt --check"
|
|
||||||
"check-pylint" = "pylint custom_components"
|
|
||||||
fix = { chain = ["fix-lint", "fix-fmt" ] }
|
|
||||||
"fix-lint" = "rye lint --fix"
|
|
||||||
"fix-fmt" = "rye fmt"
|
|
||||||
@@ -14,11 +14,16 @@
|
|||||||
],
|
],
|
||||||
"prCreation": "immediate"
|
"prCreation": "immediate"
|
||||||
},
|
},
|
||||||
|
"lockFileMaintenance": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"description": "Group all GitHub Actions updates",
|
"description": "Group all GitHub Actions updates",
|
||||||
"matchDatasources": [
|
"matchDatasources": [
|
||||||
"github-actions"
|
"github-actions",
|
||||||
|
"github-tags",
|
||||||
|
"github-runners"
|
||||||
],
|
],
|
||||||
"groupName": "Github Actions Updates",
|
"groupName": "Github Actions Updates",
|
||||||
"automerge": true
|
"automerge": true
|
||||||
@@ -34,7 +39,7 @@
|
|||||||
"automerge": false
|
"automerge": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Version updates for other pip packages",
|
"description": "Version updates for other Python packages",
|
||||||
"matchDatasources": [
|
"matchDatasources": [
|
||||||
"pypi"
|
"pypi"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
# generated by rye
|
|
||||||
# use `rye lock` or `rye sync` to update this lockfile
|
|
||||||
#
|
|
||||||
# last locked with the following flags:
|
|
||||||
# pre: false
|
|
||||||
# features: []
|
|
||||||
# all-features: false
|
|
||||||
# with-sources: false
|
|
||||||
# generate-hashes: false
|
|
||||||
# universal: false
|
|
||||||
|
|
||||||
-e file:.
|
|
||||||
acme==3.0.1
|
|
||||||
# via hass-nabucasa
|
|
||||||
aiodns==3.2.0
|
|
||||||
# via homeassistant
|
|
||||||
aiofiles==24.1.0
|
|
||||||
# via hass-oidc-auth
|
|
||||||
aiohappyeyeballs==2.4.4
|
|
||||||
# via aiohttp
|
|
||||||
aiohasupervisor==0.2.1
|
|
||||||
# via homeassistant
|
|
||||||
aiohttp==3.11.11
|
|
||||||
# via aiohasupervisor
|
|
||||||
# via aiohttp-cors
|
|
||||||
# via aiohttp-fast-zlib
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
# via snitun
|
|
||||||
aiohttp-cors==0.7.0
|
|
||||||
# via homeassistant
|
|
||||||
aiohttp-fast-zlib==0.2.0
|
|
||||||
# via homeassistant
|
|
||||||
aiooui==0.1.7
|
|
||||||
# via bluetooth-adapters
|
|
||||||
aiosignal==1.3.2
|
|
||||||
# via aiohttp
|
|
||||||
aiozoneinfo==0.2.1
|
|
||||||
# via homeassistant
|
|
||||||
anyio==4.7.0
|
|
||||||
# via httpx
|
|
||||||
astral==2.2
|
|
||||||
# via homeassistant
|
|
||||||
astroid==3.3.8
|
|
||||||
# via pylint
|
|
||||||
async-interrupt==1.2.0
|
|
||||||
# via habluetooth
|
|
||||||
# via homeassistant
|
|
||||||
async-timeout==5.0.1
|
|
||||||
# via snitun
|
|
||||||
atomicwrites-homeassistant==1.4.1
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
attrs==24.2.0
|
|
||||||
# via aiohttp
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
# via snitun
|
|
||||||
audioop-lts==0.2.1
|
|
||||||
# via homeassistant
|
|
||||||
# via standard-aifc
|
|
||||||
awesomeversion==24.6.0
|
|
||||||
# via homeassistant
|
|
||||||
bcrypt==4.2.0
|
|
||||||
# via hass-oidc-auth
|
|
||||||
# via homeassistant
|
|
||||||
bleak==0.22.3
|
|
||||||
# via bleak-retry-connector
|
|
||||||
# via bluetooth-adapters
|
|
||||||
# via habluetooth
|
|
||||||
bleak-retry-connector==3.6.0
|
|
||||||
# via habluetooth
|
|
||||||
bluetooth-adapters==0.20.2
|
|
||||||
# via bleak-retry-connector
|
|
||||||
# via bluetooth-auto-recovery
|
|
||||||
# via habluetooth
|
|
||||||
bluetooth-auto-recovery==1.4.2
|
|
||||||
# via habluetooth
|
|
||||||
bluetooth-data-tools==1.20.0
|
|
||||||
# via habluetooth
|
|
||||||
boto3==1.35.87
|
|
||||||
# via pycognito
|
|
||||||
botocore==1.35.87
|
|
||||||
# via boto3
|
|
||||||
# via s3transfer
|
|
||||||
btsocket==0.3.0
|
|
||||||
# via bluetooth-auto-recovery
|
|
||||||
certifi==2024.12.14
|
|
||||||
# via homeassistant
|
|
||||||
# via httpcore
|
|
||||||
# via httpx
|
|
||||||
# via requests
|
|
||||||
cffi==1.17.1
|
|
||||||
# via cryptography
|
|
||||||
# via pycares
|
|
||||||
charset-normalizer==3.4.0
|
|
||||||
# via requests
|
|
||||||
ciso8601==2.3.1
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
cryptography==43.0.1
|
|
||||||
# via acme
|
|
||||||
# via bluetooth-data-tools
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
# via josepy
|
|
||||||
# via pyjwt
|
|
||||||
# via pyopenssl
|
|
||||||
# via securetar
|
|
||||||
# via snitun
|
|
||||||
dbus-fast==2.24.4
|
|
||||||
# via bleak
|
|
||||||
# via bleak-retry-connector
|
|
||||||
# via bluetooth-adapters
|
|
||||||
dill==0.3.9
|
|
||||||
# via pylint
|
|
||||||
ecdsa==0.19.0
|
|
||||||
# via python-jose
|
|
||||||
envs==1.4
|
|
||||||
# via pycognito
|
|
||||||
fnv-hash-fast==1.0.2
|
|
||||||
# via homeassistant
|
|
||||||
fnvhash==0.1.0
|
|
||||||
# via fnv-hash-fast
|
|
||||||
frozenlist==1.5.0
|
|
||||||
# via aiohttp
|
|
||||||
# via aiosignal
|
|
||||||
h11==0.14.0
|
|
||||||
# via httpcore
|
|
||||||
habluetooth==3.6.0
|
|
||||||
# via home-assistant-bluetooth
|
|
||||||
hass-nabucasa==0.86.0
|
|
||||||
# via homeassistant
|
|
||||||
home-assistant-bluetooth==1.13.0
|
|
||||||
# via homeassistant
|
|
||||||
homeassistant==2024.12.5
|
|
||||||
httpcore==1.0.7
|
|
||||||
# via httpx
|
|
||||||
httpx==0.27.2
|
|
||||||
# via homeassistant
|
|
||||||
idna==3.10
|
|
||||||
# via anyio
|
|
||||||
# via httpx
|
|
||||||
# via requests
|
|
||||||
# via yarl
|
|
||||||
ifaddr==0.2.0
|
|
||||||
# via homeassistant
|
|
||||||
isort==5.13.2
|
|
||||||
# via pylint
|
|
||||||
jinja2==3.1.4
|
|
||||||
# via hass-oidc-auth
|
|
||||||
# via homeassistant
|
|
||||||
jmespath==1.0.1
|
|
||||||
# via boto3
|
|
||||||
# via botocore
|
|
||||||
josepy==1.14.0
|
|
||||||
# via acme
|
|
||||||
lru-dict==1.3.0
|
|
||||||
# via homeassistant
|
|
||||||
markupsafe==3.0.2
|
|
||||||
# via jinja2
|
|
||||||
mashumaro==3.15
|
|
||||||
# via aiohasupervisor
|
|
||||||
# via webrtc-models
|
|
||||||
mccabe==0.7.0
|
|
||||||
# via pylint
|
|
||||||
multidict==6.1.0
|
|
||||||
# via aiohttp
|
|
||||||
# via yarl
|
|
||||||
orjson==3.10.12
|
|
||||||
# via aiohasupervisor
|
|
||||||
# via homeassistant
|
|
||||||
# via webrtc-models
|
|
||||||
packaging==24.2
|
|
||||||
# via homeassistant
|
|
||||||
pillow==11.0.0
|
|
||||||
# via homeassistant
|
|
||||||
platformdirs==4.3.6
|
|
||||||
# via pylint
|
|
||||||
propcache==0.2.1
|
|
||||||
# via aiohttp
|
|
||||||
# via homeassistant
|
|
||||||
# via yarl
|
|
||||||
psutil==6.1.1
|
|
||||||
# via psutil-home-assistant
|
|
||||||
psutil-home-assistant==0.0.1
|
|
||||||
# via homeassistant
|
|
||||||
pyasn1==0.6.1
|
|
||||||
# via python-jose
|
|
||||||
# via rsa
|
|
||||||
pycares==4.5.0
|
|
||||||
# via aiodns
|
|
||||||
pycognito==2024.5.1
|
|
||||||
# via hass-nabucasa
|
|
||||||
pycparser==2.22
|
|
||||||
# via cffi
|
|
||||||
pyjwt==2.10.1
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
# via pycognito
|
|
||||||
pylint==3.3.3
|
|
||||||
pyopenssl==24.2.1
|
|
||||||
# via acme
|
|
||||||
# via homeassistant
|
|
||||||
# via josepy
|
|
||||||
pyrfc3339==2.0.1
|
|
||||||
# via acme
|
|
||||||
pyric==0.1.6.3
|
|
||||||
# via bluetooth-auto-recovery
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
# via botocore
|
|
||||||
python-jose==3.3.0
|
|
||||||
# via hass-oidc-auth
|
|
||||||
python-slugify==8.0.4
|
|
||||||
# via homeassistant
|
|
||||||
pytz==2024.2
|
|
||||||
# via acme
|
|
||||||
# via astral
|
|
||||||
pyyaml==6.0.2
|
|
||||||
# via homeassistant
|
|
||||||
requests==2.32.3
|
|
||||||
# via acme
|
|
||||||
# via homeassistant
|
|
||||||
# via pycognito
|
|
||||||
rsa==4.9
|
|
||||||
# via python-jose
|
|
||||||
s3transfer==0.10.4
|
|
||||||
# via boto3
|
|
||||||
securetar==2024.11.0
|
|
||||||
# via homeassistant
|
|
||||||
setuptools==75.6.0
|
|
||||||
# via acme
|
|
||||||
six==1.17.0
|
|
||||||
# via ecdsa
|
|
||||||
# via python-dateutil
|
|
||||||
sniffio==1.3.1
|
|
||||||
# via anyio
|
|
||||||
# via httpx
|
|
||||||
snitun==0.39.1
|
|
||||||
# via hass-nabucasa
|
|
||||||
sqlalchemy==2.0.36
|
|
||||||
# via homeassistant
|
|
||||||
standard-aifc==3.13.0
|
|
||||||
# via homeassistant
|
|
||||||
standard-chunk==3.13.0
|
|
||||||
# via standard-aifc
|
|
||||||
standard-telnetlib==3.13.0
|
|
||||||
# via homeassistant
|
|
||||||
text-unidecode==1.3
|
|
||||||
# via python-slugify
|
|
||||||
tomlkit==0.13.2
|
|
||||||
# via pylint
|
|
||||||
typing-extensions==4.12.2
|
|
||||||
# via homeassistant
|
|
||||||
# via mashumaro
|
|
||||||
# via sqlalchemy
|
|
||||||
tzdata==2024.2
|
|
||||||
# via aiozoneinfo
|
|
||||||
uart-devices==0.1.0
|
|
||||||
# via bluetooth-adapters
|
|
||||||
ulid-transform==1.0.2
|
|
||||||
# via homeassistant
|
|
||||||
urllib3==1.26.20
|
|
||||||
# via botocore
|
|
||||||
# via homeassistant
|
|
||||||
# via requests
|
|
||||||
usb-devices==0.4.5
|
|
||||||
# via bluetooth-adapters
|
|
||||||
# via bluetooth-auto-recovery
|
|
||||||
uv==0.5.4
|
|
||||||
# via homeassistant
|
|
||||||
voluptuous==0.15.2
|
|
||||||
# via homeassistant
|
|
||||||
# via voluptuous-openapi
|
|
||||||
# via voluptuous-serialize
|
|
||||||
voluptuous-openapi==0.0.5
|
|
||||||
# via homeassistant
|
|
||||||
voluptuous-serialize==2.6.0
|
|
||||||
# via homeassistant
|
|
||||||
webrtc-models==0.3.0
|
|
||||||
# via hass-nabucasa
|
|
||||||
# via homeassistant
|
|
||||||
yarl==1.18.3
|
|
||||||
# via aiohasupervisor
|
|
||||||
# via aiohttp
|
|
||||||
# via homeassistant
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# generated by rye
|
|
||||||
# use `rye lock` or `rye sync` to update this lockfile
|
|
||||||
#
|
|
||||||
# last locked with the following flags:
|
|
||||||
# pre: false
|
|
||||||
# features: []
|
|
||||||
# all-features: false
|
|
||||||
# with-sources: false
|
|
||||||
# generate-hashes: false
|
|
||||||
# universal: false
|
|
||||||
|
|
||||||
-e file:.
|
|
||||||
aiofiles==24.1.0
|
|
||||||
# via hass-oidc-auth
|
|
||||||
bcrypt==4.2.1
|
|
||||||
# via hass-oidc-auth
|
|
||||||
ecdsa==0.19.0
|
|
||||||
# via python-jose
|
|
||||||
jinja2==3.1.5
|
|
||||||
# via hass-oidc-auth
|
|
||||||
markupsafe==3.0.2
|
|
||||||
# via jinja2
|
|
||||||
pyasn1==0.6.1
|
|
||||||
# via python-jose
|
|
||||||
# via rsa
|
|
||||||
python-jose==3.3.0
|
|
||||||
# via hass-oidc-auth
|
|
||||||
rsa==4.9
|
|
||||||
# via python-jose
|
|
||||||
six==1.17.0
|
|
||||||
# via ecdsa
|
|
||||||
4
scripts/check
Executable file
4
scripts/check
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
uv run ruff check
|
||||||
|
uv run ruff format --check
|
||||||
|
uv run pylint custom_components
|
||||||
3
scripts/fix
Executable file
3
scripts/fix
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
uv run ruff check --fix
|
||||||
|
uv run ruff format
|
||||||
2
scripts/sync
Executable file
2
scripts/sync
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
uv sync --locked
|
||||||
Reference in New Issue
Block a user