diff --git a/.env.example b/.env.example index 0ef7617..bb7fbfd 100644 --- a/.env.example +++ b/.env.example @@ -64,7 +64,7 @@ GNAUTH_CLIENT_ID= GNAUTH_CLIENT_SECRET= GNAUTH_REDIRECT_URI=https://navi.your-domain.com/auth/callback -# GNAUTH_PROFILE_PATH=/profile # path appended to base URL for profile links +# GNAUTH_PROFILE_PATH=/account/profile # path appended to base URL for profile links # ── Auth encryption ────────────────────────────────────────────────────────────── # Fernet key for encrypting tokens in DB. Generate once with: diff --git a/docs/auth.md b/docs/auth.md index bf5e37a..8392871 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -55,6 +55,10 @@ Role is determined at login by inspecting `client_access_list[].role_ids` from gnexus-auth for the matching `client_id`. +### User avatar + +Navi generates a Gravatar URL from the user's email (MD5 hash, identicon fallback). The gnexus-auth userinfo endpoint does not include an avatar URL in the `profile` object, so Gravatar is used as the universal fallback. Users can set their avatar at https://gravatar.com. + ### Permissions (fine-grained, from gnexus-auth) Permissions are `permission_ids` from gnexus-auth, also read from `client_access_list` for the Navi client. They control what a user can do **inside** their role. @@ -230,6 +234,7 @@ | `GNAUTH_CLIENT_SECRET` | str | `""` | OAuth client secret | | `GNAUTH_REDIRECT_URI` | str | `http://localhost:8000/auth/callback` | Must match the redirect URI registered in gnexus-auth exactly | | `GNAUTH_ADMIN_ROLE_SLUG` | str | `navi_admin` | Role slug that grants admin status | +| `GNAUTH_PROFILE_PATH` | str | `/account/profile` | Path appended to `GNAUTH_BASE_URL` for profile links | | `NAVI_AUTH_ENCRYPTION_KEY` | str | `""` | **Fernet key** (base64, 32 bytes). Generate once, keep constant. See below. | | `NAVI_AUTH_COOKIE_NAME` | str | `navi_auth_session` | Cookie name | | `NAVI_AUTH_COOKIE_SECURE` | bool | `False` | Set `True` when serving over HTTPS | diff --git a/navi/auth/__init__.py b/navi/auth/__init__.py index 52f5209..4ab85a1 100644 --- a/navi/auth/__init__.py +++ b/navi/auth/__init__.py @@ -1,10 +1,18 @@ """Auth models for Navi user identity.""" +import hashlib + from pydantic import BaseModel from ._ddl import _ensure_auth_tables +def make_gravatar_url(email: str, size: int = 128) -> str: + """Generate a Gravatar URL from an email address.""" + email_hash = hashlib.md5(email.lower().strip().encode()).hexdigest() + return f"https://www.gravatar.com/avatar/{email_hash}?s={size}&d=identicon" + + class User(BaseModel): """Authenticated Navi user, resolved from gnexus-auth.""" diff --git a/navi/auth/deps.py b/navi/auth/deps.py index 7990db9..c00314d 100644 --- a/navi/auth/deps.py +++ b/navi/auth/deps.py @@ -9,7 +9,7 @@ from navi.config import settings -from . import User +from . import User, make_gravatar_url from .client import get_gauth_client from .encrypt import get_encryptor @@ -97,7 +97,7 @@ id=auth_user.user_id, email=auth_user.email, display_name=auth_user.profile.get("display_name") or auth_user.email, - avatar_url=auth_user.profile.get("picture") or auth_user.profile.get("avatar_url") or None, + avatar_url=make_gravatar_url(auth_user.email), role=role, permissions=permissions, ) diff --git a/navi/config.py b/navi/config.py index aaef8df..9869954 100644 --- a/navi/config.py +++ b/navi/config.py @@ -77,7 +77,7 @@ gnauth_redirect_uri: str = "http://localhost:8000/auth/callback" gnauth_admin_role_slug: str = "navi_admin" gnauth_user_role_slug: str = "navi_user" - gnauth_profile_path: str = "/profile" # appended to gnauth_base_url for profile links + gnauth_profile_path: str = "/account/profile" # appended to gnauth_base_url for profile links # Auth session cookie encryption (Fernet key, 32-byte base64) navi_auth_encryption_key: str = ""