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 duckdb
pyjwt[crypto] pyjwt[crypto]
python-multipart python-multipart
authlib
itsdangerous

View file

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

View file

@ -1,16 +1,76 @@
from pathlib import Path from pathlib import Path
from fastapi import FastAPI, status from fastapi import FastAPI, status
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles 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 from pydantic import BaseModel
import json
import hellocomputer import hellocomputer
from .routers import analysis, files, sessions from .routers import analysis, files, sessions
from .config import settings
static_path = Path(hellocomputer.__file__).parent / "static" 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 = 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): class HealthCheck(BaseModel):
@ -44,7 +104,7 @@ app.include_router(sessions.router)
app.include_router(files.router) app.include_router(files.router)
app.include_router(analysis.router) app.include_router(analysis.router)
app.mount( app.mount(
"/", "/app",
StaticFiles(directory=static_path, html=True), StaticFiles(directory=static_path, html=True),
name="static", name="static",
) )

View file

@ -1,10 +1,9 @@
from typing import Annotated
from uuid import uuid4 from uuid import uuid4
from fastapi import APIRouter, Depends from fastapi import APIRouter
from starlette.requests import Request
from fastapi.responses import PlainTextResponse from fastapi.responses import PlainTextResponse
from ..security import oauth2_scheme
# Scheme for the Authorization header # Scheme for the Authorization header
@ -12,7 +11,9 @@ router = APIRouter()
@router.get("/new_session") @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()) 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" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
@ -21,9 +22,17 @@
Hello, computer! Hello, computer!
</a> </a>
</p> </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"><i
<a href="#" class="list-group-item list-group-item-action bg-light">File templates</a> class="bi bi-question-circle"></i> How to</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-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>
</div> </div>
@ -105,7 +114,7 @@
<div class="chat-messages"> <div class="chat-messages">
<!-- Messages will be appended here --> <!-- Messages will be appended here -->
<div class="message bg-white p-2 mb-2 rounded"> <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="content">
<div id="spinner" class="spinner"></div> <div id="spinner" class="spinner"></div>
<div id="result" class="hidden"></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(); const data = await response.text();
// Hide spinner and display result // 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) { } 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) { function addAIMessage(messageContent) {
const newMessage = document.createElement('div'); const newMessage = document.createElement('div');
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded'); 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 chatMessages.prepend(newMessage); // Add new message at the top
fetchResponse(messageContent, newMessage); fetchResponse(messageContent, newMessage);
} }
@ -54,7 +54,7 @@ function addAIMessage(messageContent) {
function addAIManualMessage(m) { function addAIManualMessage(m) {
const newMessage = document.createElement('div'); const newMessage = document.createElement('div');
newMessage.classList.add('message', 'bg-white', 'p-2', 'mb-2', 'rounded'); 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 chatMessages.prepend(newMessage); // Add new message at the top
} }