Skip to content

Commit 7bff6eb

Browse files
committed
feat(auth): implement Home Assistant Ingress auth
- Add required API permissions and cURL extension to configuration - Implement HomeAssistantAuthMiddleware with graceful Supervisor proxy fallback for direct-port logins - Automate middleware injection via a sequenced S6-overlay v3 oneshot service - Update DOCS.md to detail the new 'enable_ingress_auth' option
1 parent d79b5b6 commit 7bff6eb

10 files changed

Lines changed: 138 additions & 0 deletions

File tree

grocy/DOCS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@ equal Sunday:
218218
- `calendar_first_day_of_week`
219219
- `meal_plan_first_day_of_week`
220220

221+
### Option: `enable_ingress_auth`
222+
223+
Enables authentication through home assistant, automatically logging users in with their current home assistant session when accessed through ingress.
224+
225+
Additionally allows authenticating a user through home assistant's supervisor when accessing directly without ingress.
226+
227+
Default is `false`.
228+
229+
221230
### Option: `grocy_ingress_user`
222231

223232
Allows you to specify a default ingress user if desired (e.g. `admin`).

grocy/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN \
2020
php85-exif=8.5.6-r0 \
2121
php85-fileinfo=8.5.6-r0 \
2222
php85-fpm=8.5.6-r0 \
23+
php85-curl=8.5.6-r0 \
2324
php85-gd=8.5.6-r0 \
2425
php85-iconv=8.5.6-r0 \
2526
php85-intl=8.5.6-r0 \

grocy/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ version: dev
44
slug: grocy
55
description: ERP beyond your fridge! A groceries & household management solution for your home
66
url: https://github.com/hassio-addons/app-grocy
7+
hassio_api: true
8+
auth_api: true
79
ingress: true
810
ingress_stream: true
911
init: false
@@ -49,6 +51,7 @@ options:
4951
ssl: true
5052
certfile: fullchain.pem
5153
keyfile: privkey.pem
54+
enable_ingress_auth: false
5255
grocy_ingress_user: ""
5356
schema:
5457
log_level: list(trace|debug|info|notice|warning|error|fatal)?
@@ -92,4 +95,5 @@ schema:
9295
ssl: bool
9396
certfile: str
9497
keyfile: str
98+
enable_ingress_auth: bool
9599
grocy_ingress_user: str

grocy/rootfs/etc/s6-overlay/s6-rc.d/init-ingress-auth/dependencies.d/init-grocy

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/with-contenv bashio
2+
3+
CONFIG_FILE="/var/www/grocy/data/config.php"
4+
5+
# 1. ALWAYS clean out any previous middleware matches first
6+
# This flushes out legacy non-namespaced leftovers from persistent storage
7+
if [ -f "$CONFIG_FILE" ]; then
8+
sed -i "/HomeAssistantAuthMiddleware/d" "$CONFIG_FILE"
9+
fi
10+
11+
if bashio::config.true 'enable_ingress_auth'; then
12+
bashio::log.info "Ingress Auth enabled. Injecting fully-qualified AUTH_CLASS..."
13+
14+
if [ ! -f "$CONFIG_FILE" ]; then
15+
bashio::log.error "config.php was not found by the script!"
16+
exit 1
17+
fi
18+
19+
# 2. Append a fresh, properly namespaced configuration rule
20+
echo "" >> "$CONFIG_FILE"
21+
echo "Setting('AUTH_CLASS', 'Grocy\\Middleware\\HomeAssistantAuthMiddleware');" >> "$CONFIG_FILE"
22+
bashio::log.green "Custom namespace auth class injected successfully."
23+
else
24+
bashio::log.info "Ingress Auth disabled. Configuration verified clean."
25+
fi
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
oneshot
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/etc/s6-overlay/s6-rc.d/init-ingress-auth/run

grocy/rootfs/etc/s6-overlay/s6-rc.d/php-fpm/dependencies.d/init-ingress-auth

Whitespace-only changes.

grocy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-ingress-auth

Whitespace-only changes.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace Grocy\Middleware;
4+
5+
use Grocy\Services\DatabaseService;
6+
use Grocy\Services\UsersService;
7+
use Grocy\Services\SessionService;
8+
use Psr\Http\Message\ServerRequestInterface as Request;
9+
10+
class HomeAssistantAuthMiddleware extends AuthMiddleware
11+
{
12+
public function authenticate(Request $request)
13+
{
14+
define('GROCY_EXTERNALLY_MANAGED_AUTHENTICATION', true);
15+
16+
$db = DatabaseService::GetInstance()->GetDbConnection();
17+
18+
$auth = new ApiKeyAuthMiddleware($this->AppContainer, $this->ResponseFactory);
19+
$user = $auth->authenticate($request);
20+
if ($user !== null)
21+
{
22+
return $user;
23+
}
24+
25+
$haUserId = $request->getHeaderLine('X-Remote-User-Name');
26+
$haDisplayName = $request->getHeaderLine('X-Remote-User-Display-Name');
27+
28+
if (strlen($haUserId) === 0)
29+
{
30+
$auth = new SessionAuthMiddleware($this->AppContainer, $this->ResponseFactory);
31+
$user = $auth->authenticate($request);
32+
if ($user !== null)
33+
{
34+
return $user;
35+
}
36+
37+
return null;
38+
// throw new \Exception('HomeAssistantAuthMiddleware: X-Remote-User-Id header is missing');
39+
}
40+
41+
$user = $db->users()->where('username', $haUserId)->fetch();
42+
if ($user == null)
43+
{
44+
$user = UsersService::GetInstance()->CreateUser($haUserId, $haDisplayName, '', '');
45+
$user = $db->users()->where('username', $haUserId)->fetch();
46+
}
47+
48+
return $user;
49+
}
50+
51+
public static function ProcessLogin(array $postParams)
52+
{
53+
if (isset($postParams['username']) && isset($postParams['password']))
54+
{
55+
$username = $postParams['username'];
56+
$password = $postParams['password'];
57+
58+
$supervisorToken = getenv('SUPERVISOR_TOKEN');
59+
$payload = json_encode([
60+
'username' => $username,
61+
'password' => $password
62+
]);
63+
64+
$ch = curl_init('http://supervisor/auth');
65+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
66+
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
67+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
68+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
69+
'Content-Type: application/json',
70+
'X-Supervisor-Token: ' . $supervisorToken
71+
]);
72+
73+
$response = curl_exec($ch);
74+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
75+
76+
if ($httpCode === 200)
77+
{
78+
$db = DatabaseService::GetInstance()->GetDbConnection();
79+
$user = $db->users()->where('username', $username)->fetch();
80+
81+
if ($user == null)
82+
{
83+
UsersService::GetInstance()->CreateUser($username, $username, '', '');
84+
$user = $db->users()->where('username', $username)->fetch();
85+
}
86+
87+
$stayLoggedInPermanently = isset($postParams['stay_logged_in']) && $postParams['stay_logged_in'] == 'on';
88+
$sessionKey = SessionService::GetInstance()->CreateSession($user->id, $stayLoggedInPermanently);
89+
self::SetSessionCookie($sessionKey);
90+
91+
return true;
92+
}
93+
}
94+
95+
return false;
96+
}
97+
}

0 commit comments

Comments
 (0)