Refactor auth into user.py, Implement login/out in frontend, Add FontAwesome
This commit is contained in:
parent
563eac3de4
commit
6f7279c049
17
frontend/package-lock.json
generated
17
frontend/package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"bulma": "^0.9.4",
|
"bulma": "^0.9.4",
|
||||||
@ -36,6 +37,16 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-free": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
@ -1492,6 +1503,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"@fortawesome/fontawesome-free": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@jridgewell/resolve-uri": {
|
"@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||||
|
@ -10,15 +10,16 @@
|
|||||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
|
"bulma": "^0.9.4",
|
||||||
|
"sass": "^1.53.0",
|
||||||
"svelte": "^3.49.0",
|
"svelte": "^3.49.0",
|
||||||
"svelte-check": "^2.8.0",
|
"svelte-check": "^2.8.0",
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
||||||
"vite": "^3.0.7",
|
"vite": "^3.0.7"
|
||||||
"sass": "^1.53.0",
|
|
||||||
"bulma": "^0.9.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import { user } from "./stores";
|
||||||
|
|
||||||
import Navbar from "./Navbar.svelte";
|
import Navbar from "./Navbar.svelte";
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const res = await fetch("/user/me");
|
||||||
|
$user = await res.json();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<section class="section">
|
<section class="section">
|
||||||
Hallo i bims 1 frontend
|
<p>Hallo i bims 1 frontend</p>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,13 +1,71 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import logo from "./assets/turtle.png";
|
import logo from "./assets/turtle.png";
|
||||||
|
|
||||||
|
import { user } from "./stores";
|
||||||
|
|
||||||
|
let menu_open = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<div class="navbar-item">
|
<a class="navbar-item" href="/">
|
||||||
<a href="/">
|
<span class="icon-text">
|
||||||
<img src={logo} alt="logo"/>
|
<img class="image is-24x24" src={logo} alt="logo" />
|
||||||
</a>
|
<span><strong>Control Panel</strong></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<a
|
||||||
|
role="button"
|
||||||
|
class="navbar-burger {menu_open ? 'is-active' : ''}"
|
||||||
|
on:click={() => (menu_open = !menu_open)}
|
||||||
|
aria-label="menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-menu {menu_open ? 'is-active' : ''}">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/foo"> Foo </a>
|
||||||
|
|
||||||
|
<a class="navbar-item" href="/bar"> Bar </a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
{#if $user !== null}
|
||||||
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<a class="navbar-link">
|
||||||
|
<img
|
||||||
|
class="image is-24x24 mr-2"
|
||||||
|
src={$user.picture}
|
||||||
|
alt="{$user.name}'s picture"
|
||||||
|
/>
|
||||||
|
<span>{$user.name}</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="navbar-dropdown is-right">
|
||||||
|
<div class="navbar-item">
|
||||||
|
<p>{$user.email}</p>
|
||||||
|
</div>
|
||||||
|
<hr class="navbar-divider" />
|
||||||
|
<a
|
||||||
|
href="/user/logout"
|
||||||
|
class="navbar-item has-text-danger"
|
||||||
|
>
|
||||||
|
Log out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="navbar-item">
|
||||||
|
<a class="button is-primary" href="/user/login">
|
||||||
|
<strong>Log in</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -9,3 +9,10 @@
|
|||||||
@import "bulma/sass/grid/_all";
|
@import "bulma/sass/grid/_all";
|
||||||
@import "bulma/sass/helpers/_all";
|
@import "bulma/sass/helpers/_all";
|
||||||
@import "bulma/sass/layout/_all";
|
@import "bulma/sass/layout/_all";
|
||||||
|
|
||||||
|
$fa-font-path: "@fortawesome/fontawesome-free/webfonts";
|
||||||
|
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
|
||||||
|
@import "@fortawesome/fontawesome-free/scss/regular.scss";
|
||||||
|
@import "@fortawesome/fontawesome-free/scss/solid.scss";
|
||||||
|
@import "@fortawesome/fontawesome-free/scss/brands.scss";
|
||||||
|
@import "@fortawesome/fontawesome-free/scss/v4-shims.scss";
|
||||||
|
9
frontend/src/stores.ts
Normal file
9
frontend/src/stores.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
name: string,
|
||||||
|
email: string,
|
||||||
|
picture: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const user: Writable<User | null> = writable(null);
|
@ -7,18 +7,11 @@ from starlette.responses import HTMLResponse, RedirectResponse
|
|||||||
from authlib.integrations.starlette_client import OAuth, OAuthError
|
from authlib.integrations.starlette_client import OAuth, OAuthError
|
||||||
|
|
||||||
from .settings import settings
|
from .settings import settings
|
||||||
|
from .user import user_auth
|
||||||
config = Config("secret.env") # TODO unify this with settings
|
|
||||||
|
|
||||||
oauth = OAuth(config)
|
|
||||||
oauth.register(
|
|
||||||
name="gitea",
|
|
||||||
server_metadata_url="https://git.leafbla.de/.well-known/openid-configuration",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.add_middleware(SessionMiddleware, secret_key=config.get("SESSION_SECRET_KEY"))
|
|
||||||
|
|
||||||
|
app.mount("/user/", user_auth)
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
@ -38,29 +31,5 @@ async def index(request: Request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/login")
|
|
||||||
async def login(request: Request):
|
|
||||||
redirect_uri = request.url_for("auth")
|
|
||||||
return await oauth.gitea.authorize_redirect(request, redirect_uri)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/auth")
|
|
||||||
async def auth(request: Request):
|
|
||||||
try:
|
|
||||||
token = await oauth.gitea.authorize_access_token(request)
|
|
||||||
except OAuthError as e:
|
|
||||||
return HTMLResponse(f"<h1>{e.error}</h1>")
|
|
||||||
user = await oauth.gitea.userinfo(token=token)
|
|
||||||
if user:
|
|
||||||
request.session["user"] = dict(user)
|
|
||||||
return RedirectResponse(url="/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/logout")
|
|
||||||
async def logout(request: Request):
|
|
||||||
request.session.pop("user", None)
|
|
||||||
return RedirectResponse(url="/")
|
|
||||||
|
|
||||||
|
|
||||||
if not settings.dev_mode:
|
if not settings.dev_mode:
|
||||||
app.mount("/", StaticFiles(directory=settings.frontend_path))
|
app.mount("/", StaticFiles(directory=settings.frontend_path))
|
||||||
|
78
server/server/user.py
Normal file
78
server/server/user.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from pydantic import BaseModel, HttpUrl
|
||||||
|
from starlette.config import Config
|
||||||
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
|
from starlette.responses import HTMLResponse, RedirectResponse
|
||||||
|
from authlib.integrations.starlette_client import OAuth, OAuthError
|
||||||
|
|
||||||
|
config = Config("secret.env") # TODO unify this with settings
|
||||||
|
|
||||||
|
oauth = OAuth(config)
|
||||||
|
oauth.register(
|
||||||
|
name="gitea",
|
||||||
|
server_metadata_url="https://git.leafbla.de/.well-known/openid-configuration",
|
||||||
|
)
|
||||||
|
|
||||||
|
user_auth = FastAPI()
|
||||||
|
user_auth.add_middleware(SessionMiddleware, secret_key=config.get("SESSION_SECRET_KEY"))
|
||||||
|
|
||||||
|
|
||||||
|
@user_auth.get("/login")
|
||||||
|
async def login(request: Request):
|
||||||
|
redirect_uri = request.url_for("auth")
|
||||||
|
return await oauth.gitea.authorize_redirect(request, redirect_uri)
|
||||||
|
|
||||||
|
|
||||||
|
@user_auth.get("/auth")
|
||||||
|
async def auth(request: Request):
|
||||||
|
try:
|
||||||
|
token = await oauth.gitea.authorize_access_token(request)
|
||||||
|
except OAuthError as e:
|
||||||
|
return HTMLResponse(f"<h1>{e.error}</h1>")
|
||||||
|
user = await oauth.gitea.userinfo(token=token)
|
||||||
|
if user:
|
||||||
|
request.session["user"] = dict(user)
|
||||||
|
return RedirectResponse(url="/")
|
||||||
|
|
||||||
|
|
||||||
|
@user_auth.get("/logout")
|
||||||
|
async def logout(request: Request):
|
||||||
|
request.session.pop("user", None)
|
||||||
|
return RedirectResponse(url="/")
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
user={
|
||||||
|
'sub': '1',
|
||||||
|
'name': 'Kai Vogelgesang',
|
||||||
|
'preferred_username': 'kai',
|
||||||
|
'email': 'kai@leafbla.de',
|
||||||
|
'picture': 'https://git.leafbla.de/avatars/279a0c06517e4dd112b291cf78c0c659',
|
||||||
|
'groups': [
|
||||||
|
'gitolite-legacy',
|
||||||
|
'gitolite-legacy:owners',
|
||||||
|
'infrastructure',
|
||||||
|
'infrastructure:owners',
|
||||||
|
'next-website',
|
||||||
|
'next-website:owners',
|
||||||
|
'turtles',
|
||||||
|
'turtles:owners'
|
||||||
|
]
|
||||||
|
} """
|
||||||
|
|
||||||
|
|
||||||
|
class MeResponse(BaseModel):
|
||||||
|
name: str
|
||||||
|
email: str
|
||||||
|
picture: HttpUrl
|
||||||
|
|
||||||
|
|
||||||
|
@user_auth.get("/me", response_model=MeResponse | None)
|
||||||
|
async def display_current_user(request: Request):
|
||||||
|
user = request.session.get("user")
|
||||||
|
if user is None:
|
||||||
|
return None
|
||||||
|
print(f"[/me] {user=}")
|
||||||
|
return MeResponse(
|
||||||
|
name=user["preferred_username"], email=user["email"], picture=user["picture"]
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user