Skip to content

Commit

Permalink
Integrate with Neon Users Service (#33)
Browse files Browse the repository at this point in the history
# Description
Update `client_manager` to use `mq_connector` for authentication via
`neon-users-service`
Update tokens to include more data, maintaining backwards-compat and
adding `TokenConfig` compat.
Update tokens for Klat token compat
Update permissions handling to respect user configuration values
Update auth request to include token_name for User database integration
Add UserProfile.from_user_config for database compat. Update MQ
connector to integrate with users service


# Issues
<!-- If this is related to or closes an issue/other PR, please note them
here -->

# Other Notes
This includes breaking changes to JWT handling. Existing tokens do not
follow
[RFC7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1);
these changes update token contents to use Registered and Public Claim
names where available.

This includes a change to permissions handling by using roles defined in
[neon-data-models](https://github.com/NeonGeckoCom/neon-data-models).
The affected code has not been included in a stable release and behavior
is unchanged when interacting with the HTTP endpoints.

---------

Co-authored-by: Mike <mike@graywind.org>
  • Loading branch information
NeonDaniel and mikejgray authored Dec 26, 2024
1 parent 1cc78f6 commit d387765
Show file tree
Hide file tree
Showing 21 changed files with 596 additions and 516 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ FROM python:3.9-slim
LABEL vendor=neon.ai \
ai.neon.name="neon-hana"

ENV OVOS_CONFIG_BASE_FOLDER neon
ENV OVOS_CONFIG_FILENAME diana.yaml
ENV XDG_CONFIG_HOME /config
ENV OVOS_CONFIG_BASE_FOLDER=neon
ENV OVOS_CONFIG_FILENAME=diana.yaml
ENV XDG_CONFIG_HOME=/config

RUN apt update && apt install -y swig gcc libpulse-dev portaudio19-dev

Expand Down
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ hana:
mq_default_timeout: 10
access_token_ttl: 86400 # 1 day
refresh_token_ttl: 604800 # 1 week
requests_per_minute: 60
requests_per_minute: 60 # All other requests (auth, registration, etc) also count towards this limit
auth_requests_per_minute: 6 # This counts valid and invalid requests from an IP address
registration_requests_per_hour: 4 # This is low to prevent malicious user creation that will pollute the database
access_token_secret: a800445648142061fc238d1f84e96200da87f4f9fa7835cac90db8b4391b117b
refresh_token_secret: 833d369ac73d883123743a44b4a7fe21203cffc956f4c8fec712e71aafa8e1aa
jwt_issuer: neon.ai # Used in the `iss` field of generated JWT tokens.
fastapi_title: "My HANA API Host"
fastapi_summary: "Personal HTTP API to access my DIANA backend."
disable_auth: True
disable_auth: True # If true, no authentication will be attempted; all connections will be allowed
stt_max_length_encoded: 500000 # Arbitrary limit that is larger than any expected voice command
tts_max_words: 128 # Arbitrary limit that is longer than any default LLM token limit
enable_email: True # Disabled by default; anyone with access to the API will be able to send emails from the configured address
node_username: node_user # Username to authenticate Node API access; leave empty to disable Node API access
node_password: node_password # Password associated with node_username
max_streaming_clients: -1 # Maximum audio streaming clients allowed (including 0). Default unset value allows infinite clients
```
It is recommended to generate unique values for configured tokens, these are 32
Expand All @@ -45,7 +45,27 @@ docker run -p 8080:8080 -v ~/.config/neon:/config/neon ghcr.io/neongeckocom/neon
are using the default port 8080

## Usage
Full API documentation is available at `/docs`. The `/auth/login` endpoint should
be used to generate a `client_id`, `access_token`, and `refresh_token`. The
`access_token` should be included in every request and upon expiration of the
`access_token`, a new token can be obtained from the `auth/refresh` endpoint.
Full API documentation is available at `/docs`.

### Registration
The `/auth/register` endpoint may be used to create a new user if auth is enabled.
If auth is disabled, any login requests will return a successful response.

### Token Generation
The `/auth/login` endpoint should be used to generate a `client_id`,
`access_token`, and `refresh_token`. The `access_token` should be included in
every request and upon expiration of the `access_token`, a new token can be
obtained from the `auth/refresh` endpoint. Tokens are client-specific and clients
are expected to include the same `client_id` and valid tokens for that client
with every request.

### Token Management
`access_token`s should not be saved to persistent storage; they are only valid
for a short period of time and a new `access_token` should be generated for
every new session.

`refresh_token`s should be saved to persistent storage and used to generate a new
`access_token` and `refresh_token` at the beginning of a session, or when the
current `access_token` expires. A `refresh_token` may only be used once; a new
`refresh_token` returned from the `/auth/refresh` endpoint will replace the one
included in the request.
2 changes: 2 additions & 0 deletions neon_hana/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from neon_hana.app.routers.llm import llm_route
from neon_hana.app.routers.mq_backend import mq_route
from neon_hana.app.routers.auth import auth_route
from neon_hana.app.routers.user import user_route
from neon_hana.app.routers.util import util_route
from neon_hana.app.routers.node_server import node_route
from neon_hana.version import __version__
Expand All @@ -49,5 +50,6 @@ def create_app(config: dict):
app.include_router(llm_route)
app.include_router(util_route)
app.include_router(node_route)
app.include_router(user_route)

return app
2 changes: 1 addition & 1 deletion neon_hana/app/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@

config = Configuration().get("hana") or dict()
mq_connector = MQServiceManager(config)
client_manager = ClientManager(config)
client_manager = ClientManager(config, mq_connector)
jwt_bearer = UserTokenAuth(client_manager)
8 changes: 8 additions & 0 deletions neon_hana/app/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from neon_hana.app.dependencies import client_manager
from neon_hana.schema.auth_requests import *
from neon_data_models.models.user import User

auth_route = APIRouter(prefix="/auth", tags=["authentication"])

Expand All @@ -43,3 +44,10 @@ async def check_login(auth_request: AuthenticationRequest,
@auth_route.post("/refresh")
async def check_refresh(request: RefreshRequest) -> AuthenticationResponse:
return client_manager.check_refresh_request(**dict(request))


@auth_route.post("/register")
async def register_user(register_request: RegistrationRequest,
request: Request) -> User:
return client_manager.check_registration_request(**dict(register_request),
origin_ip=request.client.host)
19 changes: 11 additions & 8 deletions neon_hana/app/routers/node_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

from asyncio import Event
from signal import signal, SIGINT
from time import sleep
from typing import Optional, Union

from fastapi import APIRouter, WebSocket, HTTPException
Expand All @@ -36,13 +35,17 @@
from neon_hana.app.dependencies import config, client_manager
from neon_hana.mq_websocket_api import MQWebsocketAPI, ClientNotKnown

from neon_hana.schema.node_v1 import (NodeAudioInput, NodeGetStt,
NodeGetTts, NodeKlatResponse,
NodeAudioInputResponse,
NodeGetSttResponse,
NodeGetTtsResponse, CoreWWDetected,
CoreIntentFailure, CoreErrorResponse,
CoreClearData, CoreAlertExpired)
from neon_data_models.models.api.node_v1 import (NodeAudioInput, NodeGetStt,
NodeGetTts, NodeKlatResponse,
NodeAudioInputResponse,
NodeGetSttResponse,
NodeGetTtsResponse,
CoreWWDetected,
CoreIntentFailure,
CoreErrorResponse,
CoreClearData,
CoreAlertExpired)

node_route = APIRouter(prefix="/node", tags=["node"])

socket_api = MQWebsocketAPI(config)
Expand Down
48 changes: 48 additions & 0 deletions neon_hana/app/routers/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
# All trademark and other rights reserved by their respective owners
# Copyright 2008-2024 Neongecko.com Inc.
# BSD-3
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from fastapi import APIRouter, Depends
from neon_hana.app.dependencies import jwt_bearer, mq_connector
from neon_hana.schema.user_requests import GetUserRequest, UpdateUserRequest
from neon_data_models.models.user import User

user_route = APIRouter(tags=["user"], dependencies=[Depends(jwt_bearer)])


@user_route.post("/get")
async def get_user(request: GetUserRequest,
token: str = Depends(jwt_bearer)) -> User:
hana_token = jwt_bearer.client_manager.get_token_data(token)
return mq_connector.read_user(access_token=hana_token,
auth_user=hana_token.sub,
**dict(request))


@user_route.post("/update")
async def update_user(request: UpdateUserRequest,
token: str = Depends(jwt_bearer)) -> User:
return mq_connector.update_user(access_token=token,
**dict(request))
2 changes: 2 additions & 0 deletions neon_hana/app/routers/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ async def api_client_ip(request: Request) -> str:
# Reported host is a hostname, not an IP address. Return a generic
# loopback value
ip_addr = "127.0.0.1"
# Validation will fail, but this increments the rate-limiting
client_manager.validate_auth("", ip_addr)
return ip_addr


@util_route.get("/headers")
async def api_headers(request: Request):
ip_addr = request.client.host if request.client else "127.0.0.1"
# Validation will fail, but this increments the rate-limiting
client_manager.validate_auth("", ip_addr)
return request.headers
Loading

0 comments on commit d387765

Please sign in to comment.