Compare commits

...

24 commits

Author SHA1 Message Date
Game_Time 74da9607c5
Merge f5715df218 into 998139d4d8 2023-08-21 21:09:26 +02:00
nsde 998139d4d8 More secury (thanks �) 2023-08-21 21:09:22 +02:00
nsde eb6ebd2112 Made code more robust (dotenv) and working on tests 2023-08-21 20:58:05 +02:00
Game_Time f5715df218
Update LICENSE 2023-08-20 17:43:38 +05:00
Game_Time 79467ddbf2
Merge branch 'NovaOSS:main' into main 2023-08-20 17:42:36 +05:00
Game_Time 99977b487f
Merge branch 'NovaOSS:main' into main 2023-08-20 17:31:04 +05:00
Game_Time 09e2b870e2
Merge branch 'NovaOSS:main' into main 2023-08-20 17:10:13 +05:00
Game_Time 109ebbea50
Merge branch 'NovaOSS:main' into main 2023-08-20 16:08:27 +05:00
Game_Time ca4aa9ecf0
Merge branch 'NovaOSS:main' into main 2023-08-20 16:03:42 +05:00
Game_Time 4118430aa4
Merge branch 'NovaOSS:main' into main 2023-08-20 15:48:48 +05:00
Game_Time b107d5ca98
Merge branch 'NovaOSS:main' into main 2023-08-20 15:33:38 +05:00
Game_Time 1a2ab86bad
Merge branch 'NovaOSS:main' into main 2023-08-20 15:29:44 +05:00
Game_Time f1bb0dc0bd
Merge branch 'NovaOSS:main' into main 2023-08-20 15:20:30 +05:00
Game_Time d233ae0c93
Merge branch 'NovaOSS:main' into main 2023-08-20 15:19:16 +05:00
Game_Time 8fe14135fd
Merge branch 'NovaOSS:main' into main 2023-08-20 06:54:14 +05:00
Game_Time 7ccd93c423
Merge branch 'NovaOSS:main' into main 2023-08-20 06:49:20 +05:00
Game_Time f7f37ddd59
Merge branch 'NovaOSS:main' into main 2023-08-19 19:28:31 +05:00
Game_Time e1d9bf042f
Merge branch 'NovaOSS:main' into main 2023-08-19 19:26:50 +05:00
Game_Time cb734f717c
Merge branch 'NovaOSS:main' into main 2023-08-19 19:23:04 +05:00
Game_Time 6098ed44b6
Merge branch 'NovaOSS:main' into main 2023-08-19 19:20:07 +05:00
Game_Time b32f4c5a7e
Merge branch 'NovaOSS:main' into main 2023-08-19 19:17:14 +05:00
Game_Time 9078ab6356
Merge branch 'NovaOSS:main' into main 2023-08-19 19:12:41 +05:00
Game_Time 12d5375f83
Merge branch 'NovaOSS:main' into main 2023-08-19 19:06:52 +05:00
Game_Time 87b113d93d
hehehaw 2023-08-19 19:05:14 +05:00
14 changed files with 109 additions and 59 deletions

View file

@ -28,5 +28,5 @@ jobs:
- name: Start API server & run tests! - name: Start API server & run tests!
run: | run: |
python checks
python run python run

View file

@ -12,7 +12,7 @@ software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software. cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast, to take away your freedom to share and change the works. By contrast
our General Public Licenses are intended to guarantee your freedom to our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free share and change all versions of a program--to make sure it remains free
software for all its users. software for all its users.

View file

@ -1,14 +1,25 @@
"""User management.""" """User management."""
import os import os
import json import sys
import fastapi
from db.users import UserManager project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(project_root)
# the code above is to allow importing from the root folder
import os
import json
import hmac
import fastapi
from dhooks import Webhook, Embed from dhooks import Webhook, Embed
from dotenv import load_dotenv from dotenv import load_dotenv
import checks.client
from db.users import UserManager
load_dotenv() load_dotenv()
router = fastapi.APIRouter(tags=['core']) router = fastapi.APIRouter(tags=['core'])
@ -21,11 +32,16 @@ async def check_core_auth(request):
""" """
received_auth = request.headers.get('Authorization') received_auth = request.headers.get('Authorization')
if received_auth != os.getenv('CORE_API_KEY'): correct_core_api = os.environ['CORE_API_KEY']
# use hmac.compare_digest to prevent timing attacks
if received_auth and hmac.compare_digest(received_auth, correct_core_api):
return fastapi.Response(status_code=403, content='Invalid or no API key given.') return fastapi.Response(status_code=403, content='Invalid or no API key given.')
@router.get('/users') @router.get('/users')
async def get_users(discord_id: int, incoming_request: fastapi.Request): async def get_users(discord_id: int, incoming_request: fastapi.Request):
"""Returns a user by their discord ID. Requires a core API key."""
auth = await check_core_auth(incoming_request) auth = await check_core_auth(incoming_request)
if auth: if auth:
return auth return auth
@ -39,7 +55,9 @@ async def get_users(discord_id: int, incoming_request: fastapi.Request):
return user return user
async def new_user_webhook(user: dict) -> None: async def new_user_webhook(user: dict) -> None:
dhook = Webhook(os.getenv('DISCORD_WEBHOOK__USER_CREATED')) """Runs when a new user is created."""
dhook = Webhook(os.environ['DISCORD_WEBHOOK__USER_CREATED'])
embed = Embed( embed = Embed(
description='New User', description='New User',
@ -54,6 +72,8 @@ async def new_user_webhook(user: dict) -> None:
@router.post('/users') @router.post('/users')
async def create_user(incoming_request: fastapi.Request): async def create_user(incoming_request: fastapi.Request):
"""Creates a user. Requires a core API key."""
auth_error = await check_core_auth(incoming_request) auth_error = await check_core_auth(incoming_request)
if auth_error: if auth_error:
@ -74,6 +94,8 @@ async def create_user(incoming_request: fastapi.Request):
@router.put('/users') @router.put('/users')
async def update_user(incoming_request: fastapi.Request): async def update_user(incoming_request: fastapi.Request):
"""Updates a user. Requires a core API key."""
auth_error = await check_core_auth(incoming_request) auth_error = await check_core_auth(incoming_request)
if auth_error: if auth_error:
@ -91,3 +113,19 @@ async def update_user(incoming_request: fastapi.Request):
user = await manager.update_by_discord_id(discord_id, updates) user = await manager.update_by_discord_id(discord_id, updates)
return user return user
@router.get('/checks')
async def run_checks(incoming_request: fastapi.Request):
"""Tests the API. Requires a core API key."""
auth_error = await check_core_auth(incoming_request)
if auth_error:
return auth_error
return {
'library': await checks.client.test_library(),
'library_moderation': await checks.client.test_library_moderation(),
'api_moderation': await checks.client.test_api_moderation(),
'models': await checks.client.test_models()
}

View file

@ -89,7 +89,7 @@ class UserManager:
db = await self._get_collection('users') db = await self._get_collection('users')
return await db.update_one({'_id': user_id}, update) return await db.update_one({'_id': user_id}, update)
async def upate_by_discord_id(self, discord_id: str, update): async def update_by_discord_id(self, discord_id: str, update):
db = await self._get_collection('users') db = await self._get_collection('users')
return await db.update_one({'auth.discord': str(int(discord_id))}, update) return await db.update_one({'auth.discord': str(int(discord_id))}, update)

View file

@ -24,21 +24,19 @@ app.include_router(core.router)
@app.on_event('startup') @app.on_event('startup')
async def startup_event(): async def startup_event():
# DATABASE FIX https://stackoverflow.com/questions/65970988/python-mongodb-motor-objectid-object-is-not-iterable-error-while-trying-to-f """Runs when the API starts up."""
import pydantic, bson
# pydantic.json.ENCODERS_BY_TYPE[bson.objectid.ObjectId]=str
@app.get('/') @app.get('/')
async def root(): async def root():
""" """
Returns the root endpoint. Returns general information about the API.
""" """
return { return {
'status': 'ok', 'hi': 'Welcome to the Nova API!',
'usage_docs': 'https://nova-oss.com', 'learn_more_here': 'https://nova-oss.com',
'core_api_docs_for_developers': '/docs', 'github': 'https://github.com/novaoss/nova-api',
'github': 'https://github.com/novaoss/nova-api' 'core_api_docs_for_nova_developers': '/docs'
} }
app.add_route('/{path:path}', transfer.handle, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) app.add_route('/{path:path}', transfer.handle, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])

View file

@ -93,7 +93,7 @@ async def stream(
'cookies': incoming_request.cookies 'cookies': incoming_request.cookies
}) })
except ValueError as exc: except ValueError as exc:
webhook = dhooks.Webhook(os.getenv('DISCORD_WEBHOOK__API_ISSUE')) webhook = dhooks.Webhook(os.environ['DISCORD_WEBHOOK__API_ISSUE'])
webhook.send(content=f'API Issue: **`{exc}`**\nhttps://i.imgflip.com/7uv122.jpg') webhook.send(content=f'API Issue: **`{exc}`**\nhttps://i.imgflip.com/7uv122.jpg')
yield await errors.yield_error(500, 'Sorry, the API has no working keys anymore.', 'The admins have been messaged automatically.') yield await errors.yield_error(500, 'Sorry, the API has no working keys anymore.', 'The admins have been messaged automatically.')
return return

View file

@ -44,16 +44,16 @@ async def handle(incoming_request):
received_key = incoming_request.headers.get('Authorization') received_key = incoming_request.headers.get('Authorization')
if not received_key or not received_key.startswith('Bearer '): if not received_key or not received_key.startswith('Bearer '):
return await errors.error(401, 'No NovaAI API key given!', 'Add "Authorization: Bearer nv-..." to your request headers.') return await errors.error(403, 'No NovaAI API key given!', 'Add \'Authorization: Bearer nv-...\' to your request headers.')
user = await users.user_by_api_key(received_key.split('Bearer ')[1].strip()) user = await users.user_by_api_key(received_key.split('Bearer ')[1].strip())
if not user or not user['status']['active']: if not user or not user['status']['active']:
return await errors.error(401, 'Invalid or inactive NovaAI API key!', 'Create a new NovaOSS API key or reactivate your account.') return await errors.error(403, 'Invalid or inactive NovaAI API key!', 'Create a new NovaOSS API key or reactivate your account.')
ban_reason = user['status']['ban_reason'] ban_reason = user['status']['ban_reason']
if ban_reason: if ban_reason:
return await errors.error(403, f'Your NovaAI account has been banned. Reason: "{ban_reason}".', 'Contact the staff for an appeal.') return await errors.error(403, f'Your NovaAI account has been banned. Reason: \'{ban_reason}\'.', 'Contact the staff for an appeal.')
costs = config['costs'] costs = config['costs']
cost = costs['other'] cost = costs['other']

1
checks/__init__.py Normal file
View file

@ -0,0 +1 @@
from . import client

2
checks/__main__.py Normal file
View file

@ -0,0 +1,2 @@
import client
client.demo()

View file

@ -1,9 +1,11 @@
"""Tests the API.""" """Tests the API."""
import os import os
import openai as closedai
import httpx
import time import time
import httpx
import openai
import asyncio
import traceback
from rich import print from rich import print
from typing import List from typing import List
@ -22,7 +24,7 @@ MESSAGES = [
api_endpoint = 'http://localhost:2332' api_endpoint = 'http://localhost:2332'
def test_server(): async def test_server():
"""Tests if the API server is running.""" """Tests if the API server is running."""
try: try:
@ -30,7 +32,7 @@ def test_server():
except httpx.ConnectError as exc: except httpx.ConnectError as exc:
raise ConnectionError(f'API is not running on port {api_endpoint}.') from exc raise ConnectionError(f'API is not running on port {api_endpoint}.') from exc
def test_api(model: str=MODEL, messages: List[dict]=None) -> dict: async def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
"""Tests an API api_endpoint.""" """Tests an API api_endpoint."""
json_data = { json_data = {
@ -49,10 +51,10 @@ def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
return response.text return response.text
def test_library(): async def test_library():
"""Tests if the api_endpoint is working with the Python library.""" """Tests if the api_endpoint is working with the OpenAI Python library."""
completion = closedai.ChatCompletion.create( completion = openai.ChatCompletion.create(
model=MODEL, model=MODEL,
messages=MESSAGES messages=MESSAGES
) )
@ -61,13 +63,13 @@ def test_library():
return completion['choices'][0]['message']['content'] return completion['choices'][0]['message']['content']
def test_library_moderation(): async def test_library_moderation():
try: try:
return closedai.Moderation.create('I wanna kill myself, I wanna kill myself; It\'s all I hear right now, it\'s all I hear right now') return openai.Moderation.create('I wanna kill myself, I wanna kill myself; It\'s all I hear right now, it\'s all I hear right now')
except closedai.error.InvalidRequestError: except openai.error.InvalidRequestError:
return True return True
def test_models(): async def test_models():
response = httpx.get( response = httpx.get(
url=f'{api_endpoint}/models', url=f'{api_endpoint}/models',
headers=HEADERS, headers=HEADERS,
@ -76,7 +78,7 @@ def test_models():
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
def test_api_moderation() -> dict: async def test_api_moderation() -> dict:
"""Tests an API api_endpoint.""" """Tests an API api_endpoint."""
response = httpx.get( response = httpx.get(
@ -90,38 +92,43 @@ def test_api_moderation() -> dict:
# ========================================================================================== # ==========================================================================================
def test_all(): def demo():
"""Runs all tests.""" """Runs all tests."""
try:
print("Waiting until API Server is started up...")
time.sleep(6)
print('[lightblue]Running test on API server to check if its running...') try:
print(test_server()) for _ in range(30):
if test_server():
break
print('Waiting until API Server is started up...')
time.sleep(1)
else:
raise ConnectionError('API Server is not running.')
print('[lightblue]Running a api endpoint to see if requests can go through...') print('[lightblue]Running a api endpoint to see if requests can go through...')
print(test_api('gpt-3.5-trubo')) print(asyncio.run(test_api('gpt-3.5-turbo')))
print('[lightblue]Checking if the API works with the python library...') print('[lightblue]Checking if the API works with the python library...')
print(test_library()) print(asyncio.run(test_library()))
print('[lightblue]Checking if the moderation endpoint works...') print('[lightblue]Checking if the moderation endpoint works...')
print(test_library_moderation()) print(asyncio.run(test_library_moderation()))
print('[lightblue]Checking the /v1/models endpoint...') print('[lightblue]Checking the /v1/models endpoint...')
print(test_models()) print(asyncio.run(test_models()))
except Exception as e:
print('[red]Error: ') except Exception as exc:
print(e) print('[red]Error: ' + str(exc))
traceback.print_exc()
exit(500) exit(500)
if __name__ == '__main__': openai.api_base = api_endpoint
closedai.api_base = api_endpoint openai.api_key = os.environ['NOVA_KEY']
closedai.api_key = os.environ['NOVA_KEY']
HEADERS = { HEADERS = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + closedai.api_key 'Authorization': 'Bearer ' + openai.api_key
} }
test_all() if __name__ == '__main__':
demo()

View file

@ -1,2 +1,3 @@
import main import main
main.launch() main.launch()

View file

@ -12,5 +12,8 @@ cp env/.prod.env /home/nova-prod/.env
# Change directory # Change directory
cd /home/nova-prod cd /home/nova-prod
# Kill the production server
fuser -k 2333/tcp
# Start screen # Start screen
screen -S nova-api python run prod screen -S nova-api python run prod

View file

@ -100,7 +100,7 @@ python run 1337
``` ```
## Test if it works ## Test if it works
`python tests` `python checks`
## Ports ## Ports
```yml ```yml