Compare commits

..

No commits in common. "master" and "e0e69f8c06bd1782307d33c1f081535192163271" have entirely different histories.

17 changed files with 71 additions and 239 deletions

3
.gitignore vendored
View File

@ -4,8 +4,7 @@ __pycache__/
*.py[cod]
*$py.class
requirements.txt
users.json
app/users.json
# C extensions
*.so

View File

@ -1,14 +1,6 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8-slim
ENV DOCKER=1
RUN pip install \
python-jose \
passlib \
python-multipart \
docker \
aiodocker \
sse-starlette \
anyio
COPY ./app /app/app
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
COPY requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt
COPY ./app /app/app

View File

@ -10,9 +10,6 @@ uvicorn = {extras = ["standard"], version = "*"}
python-jose = {extras = ["cryptography"], version = "*"}
passlib = "*"
python-multipart = "*"
docker = "*"
aiodocker = "*"
sse-starlette = "*"
[dev-packages]
pytest = "*"
@ -22,8 +19,8 @@ black = "*"
mypy = "*"
[requires]
python_version = "3.9"
python_version = "3.8"
[scripts]
test = "pytest app/test/test_auth.py app/test/test_server.py -W ignore::DeprecationWarning -s"
test = "pytest app/test/test_main.py -s"
dev = "uvicorn app.main:app --reload"

View File

@ -3,14 +3,11 @@ import json
from datetime import timedelta, datetime
from typing import Optional
from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends, HTTPException, status, APIRouter
from fastapi import Depends, HTTPException, status
from jose import JWTError, jwt
from passlib.context import CryptContext
from app.models import User, UserInDB, TokenData, Token
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from app.models import User, UserInDB, TokenData
router = APIRouter()
# to get a string like this run:
# openssl rand -hex 32
@ -81,27 +78,4 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
def authorise(server, current_user: User = Depends(get_current_active_user)):
print(server, current_user.servers)
if server not in current_user.servers:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
@router.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
return current_user

View File

View File

@ -1,20 +1,35 @@
from fastapi import FastAPI, Depends
from fastapi.middleware.cors import CORSMiddleware
from app import auth, user, server
from os import getenv
app = FastAPI(docs_url="/")
from datetime import timedelta
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
dependencies = list()
if not getenv('DISABLE_AUTH'):
dependencies.append(Depends(auth.authorise))
app.include_router(auth.router)
app.include_router(user.router)
app.include_router(server.router, dependencies=dependencies)
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from app.models import Token, User
from app.auth import get_current_active_user, create_access_token, authenticate_user, fake_users_db, ACCESS_TOKEN_EXPIRE_MINUTES
app = FastAPI()
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
@app.get("/users/me/items/")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
return [{"item_id": "Foo", "owner": current_user.username}]

View File

@ -12,9 +12,9 @@ class TokenData(BaseModel):
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
servers: list
class UserInDB(User):
hashed_password: str

View File

@ -1,76 +0,0 @@
from fastapi import APIRouter, HTTPException, status, WebSocket, Request
from sse_starlette.sse import EventSourceResponse
import base64
import docker
import aiodocker
from asyncio import sleep
router = APIRouter()
client = docker.from_env()
docker = aiodocker.Docker()
@router.post("/server/{server}/start")
async def start(server):
try:
container = await getContainer(server)
await container.start()
except:
raise HTTPException(status_code=500)
return server
@router.post("/server/{server}/stop")
async def stop(server):
try:
container = await getContainer(server)
await container.stop()
except:
raise HTTPException(status_code=500)
return server
@router.post("/server/{server}/command/{command}",
description="Take server and base64 encoded command")
async def commnd(server, command):
try:
container = client.containers.get(server)
print(command)
cmd = base64.urlsafe_b64decode(command).decode('utf_8')
print(cmd)
container.exec_run(cmd="/usr/local/bin/cmd " + str(cmd))
s= 200
running="Success"
except:
raise HTTPException(status_code=500)
return f"{server} {base64.urlsafe_b64decode(command).decode('utf_8')}"
@router.get("/server/{server}/logs")
async def logs(server: str, request: Request, follow: bool = True, tail: int = 1000):
container = await getContainer(server)
#event_generator = logGenerator(request, server)
if follow:
return EventSourceResponse(logStream(container, tail))
else:
return await container.log(stdout=True, follow=False, tail=tail)
async def logStream(container, tail):
async for line in container.log(stdout=True, follow=True, tail=tail):
yield line
yield '\n'
@router.get("/server/{server}/stats")
async def stats(server: str, request: Request, stream: bool = True, delay: int = 2):
container = await getContainer(server)
if stream:
return EventSourceResponse(statStream(request, container, delay))
return await container.stats(stream=False)
async def statStream(request, container, delay):
while True:
yield await container.stats(stream=False)
_delay = delay - 1 if delay else 0
print(_delay)
await sleep(_delay)
async def getContainer(server):
return await docker.containers.get(server)

View File

@ -8,8 +8,7 @@
from fastapi.testclient import TestClient
from datetime import timedelta
from app.main import app
from app.auth import create_access_token
from app.main import app, create_access_token
client = TestClient(app)

View File

@ -1,21 +0,0 @@
#curl -i -X POST http://localhost:8000/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=johndoe&password=secret"
# curl -X 'GET' \
# 'http://localhost:8000/users/me/' \
# -H 'accept: application/json' \
# -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiZXhwIjoxNjMxNDQ4MjQ1fQ.DrM92jgRiry0uXBXn-61rRehATW4zDhHUWoGR6lv6Us'
from fastapi import FastAPI
from fastapi.testclient import TestClient
import docker
from server import *
app = FastAPI()
app.include_router(router)
testclient = TestClient(app)
def test_start():
#response = testclient.post("/server/minecraft/start")
#assert response.status_code == 200
pass

View File

@ -1,11 +0,0 @@
from fastapi import Depends, APIRouter
from app.models import User
from app.auth import get_current_active_user
router = APIRouter()
@router.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user

View File

@ -1,18 +0,0 @@
from distutils.fancy_getopt import fancy_getopt
from site import USER_BASE
from passlib.context import CryptContext
from json import load, dump
from sys import argv
with open("app/users.json", 'r+') as f:
fake_users_db = load(f)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
fake_users_db[argv[1]] = {"username": argv[1], "hashed_password": pwd_context.hash(argv[2]),
"disabled": False, "servers": argv[3:]}
f.seek(0)
dump(fake_users_db, f, indent=2)
print(fake_users_db)

9
app/users.json Executable file → Normal file
View File

@ -0,0 +1,9 @@
{
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": "False"
}
}

View File

@ -1,14 +1,14 @@
version: '3.7'
services:
api:
app:
build: .
env_file:
- .env
ports:
- 8000:8000
tty: true
stdin_open: true
volumes:
- ./app:/app/app
- /var/run/docker.sock:/var/run/docker.sock
- ./users.json:/app/app/users.json
command: uvicorn app.main:app --host 0.0.0.0 --reload

View File

@ -5,7 +5,10 @@ services:
build: .
env_file:
- .env
ports:
- 8000:80
tty: true
stdin_open: true
volumes:
- ./app:/app/app
- /var/run/docker.sock:/var/run/docker.sock
command: pytest app/test/test_main.py -s

View File

@ -1,33 +1,13 @@
version: '3.7'
services:
api:
build: ./
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./users.json:/app/app/users.json
restart: unless-stopped
networks:
- caddy
labels:
caddy: console
caddy.reverse_proxy: "{{upstreams 80}}"
#caddy.tls: "admin@localhost"
caddy.tls: "internal"
services:
app:
build: .
env_file:
- .env
- .env
ports:
- 8000:80
tty: true
stdin_open: true
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- caddy
restart: unless-stopped
networks:
caddy:
name: caddy

View File

@ -1,10 +0,0 @@
{
"test": {
"username": "test",
"hashed_password": "$2b$12$VS1k1fdA4x2EeF1a/LMIyex.evEQGF5EsviIcG22S55YO8uUQCT7q",
"disabled": false,
"servers": [
"test"
]
}
}