Add Outlook unread-mail widget with device-code login

- outlook-auth.js: one-time device-code flow against Microsoft login;
  stores the refresh token in the outlook-tokens compose volume
- update-outlook.js: per-minute cron fetching the unread count and the
  latest unread messages via Microsoft Graph; caches access tokens and
  persists rotated refresh tokens
- Widget is hidden until the mailbox is actually authorized, so it
  stays out of sight while (admin) consent is pending
- New external card for Outlook (Office 365) with the official icon
- compose: pass OUTLOOK_CLIENT_ID/OUTLOOK_TENANT/GITEA_TOKEN from .env
- README: Entra ID app registration steps and the tenant-ID hint for
  directories not resolvable via the organizations endpoint
This commit is contained in:
2026-09-04 17:38:17 +02:00
parent c15cfc51a9
commit dfe246b7ca
12 changed files with 564 additions and 6 deletions
+34
View File
@@ -57,6 +57,40 @@ automatically. Non-secret settings go directly in `docker-compose.yml`:
| `GITEA_URL` / `GITLAB_URL` / `NETBIRD_URL` / `OPENWEBUI_URL` | Override the default service URLs |
| `GPU_STATS_URL` | GPU stats upstream (default: the `gpu-stats` compose service) |
| `OLLAMA_URL` | Ollama instance for loaded-model stats (default: `http://100.103.83.12:11435`) |
| `OUTLOOK_CLIENT_ID` | Entra ID app client ID for the unread-mail widget (**set in `.env`**) |
| `OUTLOOK_TENANT` | Azure tenant (default: `organizations`; set to your Directory (tenant) ID if the generic endpoint returns AADSTS50059) |
### Outlook unread mail
The Outlook widget (and the unread count) uses the Microsoft Graph API with a
one-time device login. Setup:
1. **Register an app** in Microsoft Entra ID (portal.azure.com → Entra ID →
App registrations → New registration): any name (e.g. "Homelab Dashboard"),
account type "Accounts in this organizational directory only", no redirect URI.
2. In the app: **Authentication → Allow public client flows → Yes**, then
**API permissions → Add → Microsoft Graph → Delegated → Mail.Read**.
3. Copy the **Application (client) ID** — from App registrations → your app →
Overview. Beware: this is *not* the "Directory (tenant) ID" and not the
"Object ID". A valid-format but wrong GUID produces AADSTS50059
("No tenant-identifying information found"). Put it in `.env` as
`OUTLOOK_CLIENT_ID=`.
4. Set `OUTLOOK_TENANT` in `.env` to your **Directory (tenant) ID**. Some
directories are not resolvable through the generic `organizations`
endpoint (also AADSTS50059 with a valid client ID) — the tenant-specific
endpoint always works.
4. `docker compose up -d --build`
5. Log in once:
```bash
docker exec -it homelab-dashboard node /opt/dashboard/outlook-auth.js
```
Open the printed URL, enter the code and sign in with your work account.
The refresh token is persisted in the `outlook-tokens` volume.
`api/update-outlook.js` then refreshes unread mail every minute (caching the
access token, and re-running `outlook-auth.js` is only needed if login is
revoked). If your tenant requires admin consent, ask your admin to grant it
for `Mail.Read`.
### GPU stats service