Now with proper authentication
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Guillem Borrell 2024-06-10 08:32:51 +02:00
parent e3cd4fa080
commit 56ec151b70
8 changed files with 134 additions and 17 deletions

View file

@ -8,3 +8,5 @@ aiofiles
duckdb
pyjwt[crypto]
python-multipart
authlib
itsdangerous

View file

@ -2,11 +2,16 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
base_url: str = "http://localhost:8000"
anyscale_api_key: str = "Awesome API"
gcs_access: str = "access"
gcs_secret: str = "secret"
gcs_bucketname: str = "bucket"
auth: bool = True
auth0_client_id: str = ""
auth0_client_secret: str = ""
auth0_domain: str = ""
app_secret_key: str = ""
model_config = SettingsConfigDict(env_file=".env")

View file

@ -1,16 +1,76 @@
from pathlib import Path
from fastapi import FastAPI, status
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from starlette.requests import Request
from authlib.integrations.starlette_client import OAuth, OAuthError
from pydantic import BaseModel
import json
import hellocomputer
from .routers import analysis, files, sessions
from .config import settings
static_path = Path(hellocomputer.__file__).parent / "static"
oauth = OAuth()
oauth.register(
"auth0",
client_id=settings.auth0_client_id,
client_secret=settings.auth0_client_secret,
client_kwargs={"scope": "openid profile email", "verify": False},
server_metadata_url=f"https://{settings.auth0_domain}/.well-known/openid-configuration",
)
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=settings.app_secret_key)
@app.get("/")
async def homepage(request: Request):
user = request.session.get("user")
if user:
print(json.dumps(user))
return RedirectResponse("/app")
with open(static_path / "login.html") as f:
return HTMLResponse(f.read())
@app.route("/login")
async def login(request: Request):
return await oauth.auth0.authorize_redirect(
request,
redirect_uri="http://localhost:8000/callback",
)
@app.route("/callback", methods=["GET", "POST"])
async def callback(request: Request):
try:
token = await oauth.auth0.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f"<h1>{error.error}</h1>")
user = token.get("userinfo")
if user:
request.session["user"] = dict(user)
return RedirectResponse(url="/app")
@app.route("/logout")
async def logout(request: Request):
request.session.pop("user", None)
return RedirectResponse(url="/")
@app.route("/user")
async def user(request: Request):
user = request.session.get("user")
return user
class HealthCheck(BaseModel):
@ -44,7 +104,7 @@ app.include_router(sessions.router)
app.include_router(files.router)
app.include_router(analysis.router)
app.mount(
"/",
"/app",
StaticFiles(directory=static_path, html=True),
name="static",
)

View file

@ -1,10 +1,9 @@
from typing import Annotated
from uuid import uuid4
from fastapi import APIRouter, Depends
from fastapi import APIRouter
from starlette.requests import Request
from fastapi.responses import PlainTextResponse
from ..security import oauth2_scheme
# Scheme for the Authorization header
@ -12,7 +11,9 @@ router = APIRouter()
@router.get("/new_session")
async def get_new_session(token: Annotated[str, Depends(oauth2_scheme)]) -> str:
async def get_new_session(request: Request) -> str:
user = request.session.get("user")
print(user)
return str(uuid4())

View file

@ -1,3 +0,0 @@
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

View file

@ -8,6 +8,7 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
@ -21,9 +22,17 @@
Hello, computer!
</a>
</p>
<a href="#" class="list-group-item list-group-item-action bg-light">How to</a>
<a href="#" class="list-group-item list-group-item-action bg-light">File templates</a>
<a href="#" class="list-group-item list-group-item-action bg-light">About</a>
<a href="#" class="list-group-item list-group-item-action bg-light"><i
class="bi bi-question-circle"></i> How to</a>
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-file-ruled"></i>
File templates</a>
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-info-circle"></i>
About</a>
<a href="/config" class="list-group-item list-group-item-action bg-light"><i class="bi bi-toggles"></i>
Config</a>
<a href="/logout" class="list-group-item list-group-item-action bg-light"><i
class="bi bi-box-arrow-right"></i>
Logout</a>
</div>
</div>
@ -105,7 +114,7 @@
<div class="chat-messages">
<!-- Messages will be appended here -->
<div class="message bg-white p-2 mb-2 rounded">
<img src="/img/assistant.webp" width="50px">
<img src="/app/img/assistant.webp" width="50px">
<div id="content">
<div id="spinner" class="spinner"></div>
<div id="result" class="hidden"></div>

View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
.login-container {
max-width: 400px;
margin: 0 auto;
padding: 50px 0;
}
.logo {
display: block;
margin: 0 auto 20px auto;
}
.techie-font {
font-family: 'Share Tech Mono', monospace;
font-size: 24px;
text-align: center;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="login-container text-center">
<h1 class="h3 mb-3 fw-normal techie-font">Hola, computer!</h1>
<img src="/app/img/assistant.webp" alt="Logo" class="logo img-fluid">
<a href="/login"><button type="submit" class="btn btn-primary w-100">Login</button></a>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View file

@ -37,16 +37,16 @@ async function fetchResponse(message, newMessage) {
const data = await response.text();
// Hide spinner and display result
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px"> <div><pre>' + data + '</pre></div>';
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px"> <div><pre>' + data + '</pre></div>';
} catch (error) {
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px">' + 'Error: ' + error.message;
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px">' + 'Error: ' + error.message;
}
}
function addAIMessage(messageContent) {
const newMessage = document.createElement('div');
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded');
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px"> <div id="spinner" class="spinner"></div>';
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px"> <div id="spinner" class="spinner"></div>';
chatMessages.prepend(newMessage); // Add new message at the top
fetchResponse(messageContent, newMessage);
}
@ -54,7 +54,7 @@ function addAIMessage(messageContent) {
function addAIManualMessage(m) {
const newMessage = document.createElement('div');
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded');
newMessage.innerHTML = '<img src="/img/assistant.webp" width="50px"> <div>' + m + '</div>';
newMessage.innerHTML = '<img src="/app/img/assistant.webp" width="50px"> <div>' + m + '</div>';
chatMessages.prepend(newMessage); // Add new message at the top
}