From 29bda964b0fa1eeec7a79d05ab215d30324bcb6a Mon Sep 17 00:00:00 2001 From: Mawoka Date: Tue, 26 Jul 2022 22:50:39 +0200 Subject: [PATCH] :sparkles: Cleand authentication-code up --- classquiz/routers/users.py | 46 ++++++++++++++++++ frontend/Dockerfile | 5 +- frontend/package.json | 1 - frontend/pnpm-lock.yaml | 84 --------------------------------- frontend/src/app.d.ts | 18 +++++++ frontend/src/hooks.ts | 85 ++++++++++++++++------------------ frontend/src/lib/footer.svelte | 8 ++-- 7 files changed, 111 insertions(+), 136 deletions(-) diff --git a/classquiz/routers/users.py b/classquiz/routers/users.py index 9c3285e..d766ef0 100644 --- a/classquiz/routers/users.py +++ b/classquiz/routers/users.py @@ -12,6 +12,9 @@ from fastapi.background import BackgroundTasks from fastapi.responses import JSONResponse, RedirectResponse, PlainTextResponse from fastapi.security import OAuth2PasswordRequestForm import html + +from jose import jwt, JWTError + from classquiz import oauth from classquiz.helpers.avatar import gzipped_user_avatar import base64 @@ -266,3 +269,46 @@ async def get_other_avatar(respo: Response, user_id: uuid.UUID): raise HTTPException(status_code=404, detail="User not found") respo.headers.append("Content-Type", "image/svg+xml") return gzip.decompress(base64.b64decode(user.avatar)) + + +class InternalAuthData(BaseModel): + rememberme: str + jwt: str | None + + +@router.post("/auth/internal") +async def internal_auth(data: InternalAuthData, resp: Response): + try: + data.jwt = data.jwt.replace("Bearer ", "") + except AttributeError: + pass + if data.jwt is not None: + try: + payload = jwt.decode(data.jwt, settings.secret_key, algorithms=["HS256"]) + email: str = payload.get("sub") + if email is None: + resp.status_code = 401 + return resp + except JWTError: + resp.status_code = 401 + return resp + else: + return await rememberme_check(data.rememberme, resp) + + +class GetEmailFromJWT(BaseModel): + jwt: str + + +@router.post("/auth/internal/email") +async def get_email_from_jwt(data: GetEmailFromJWT): + try: + data.jwt = data.jwt.replace("Bearer ", "") + except AttributeError: + pass + try: + payload = jwt.decode(data.jwt, settings.secret_key, algorithms=["HS256"]) + return payload.get("sub") + except JWTError as e: + print(e) + raise HTTPException(status_code=401) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index e23e43c..21fe3c7 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -25,11 +25,8 @@ RUN corepack enable && corepack prepare pnpm@7.5.0 --activate && pnpm i # copy the generated modules and all other files to the container COPY . . -RUN apt update && \ - apt install -y redis-server - # build the application -RUN nohup bash -c "redis-server &" && sleep 4 && pnpm run build +RUN pnpm run build ### Serve Step # pull the Node.js Docker image diff --git a/frontend/package.json b/frontend/package.json index 86310ae..f5c222f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,7 +48,6 @@ "felte": "^1.2.2", "highlight.js": "^11.5.1", "i18next-browser-languagedetector": "^6.1.4", - "ioredis": "^5.1.0", "js-cookie": "^3.0.1", "luxon": "^2.4.0", "mapbox-gl": "^2.9.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 3bad167..545fc76 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -36,7 +36,6 @@ specifiers: highlight.js: ^11.5.1 i18next: ^21.8.13 i18next-browser-languagedetector: ^6.1.4 - ioredis: ^5.1.0 js-cookie: ^3.0.1 luxon: ^2.4.0 mapbox-gl: ^2.9.1 @@ -101,7 +100,6 @@ devDependencies: felte: 1.2.2_svelte@3.49.0 highlight.js: 11.6.0 i18next-browser-languagedetector: 6.1.4 - ioredis: 5.2.1 js-cookie: 3.0.1 luxon: 2.5.0 mapbox-gl: 2.9.2 @@ -251,13 +249,6 @@ packages: } dev: true - /@ioredis/commands/1.2.0: - resolution: - { - integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== - } - dev: true - /@jridgewell/resolve-uri/3.1.0: resolution: { @@ -1439,14 +1430,6 @@ packages: } dev: true - /cluster-key-slot/1.1.0: - resolution: - { - integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== - } - engines: { node: '>=0.10.0' } - dev: true - /color-convert/2.0.1: resolution: { @@ -1724,14 +1707,6 @@ packages: } dev: true - /denque/2.1.0: - resolution: - { - integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - } - engines: { node: '>=0.10' } - dev: true - /detect-indent/6.1.0: resolution: { @@ -2797,26 +2772,6 @@ packages: } dev: true - /ioredis/5.2.1: - resolution: - { - integrity: sha512-+rgPUIawiT+L3am9FbUcwjI7R7KtLDUSJNYWF82NZmlUXCZELws7iJkVDuECQoNUHqZTf/EGn3uw9IA+PlBiyg== - } - engines: { node: '>=12.22.0' } - dependencies: - '@ioredis/commands': 1.2.0 - cluster-key-slot: 1.1.0 - debug: 4.3.4 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - dev: true - /is-binary-path/2.1.0: resolution: { @@ -2979,20 +2934,6 @@ packages: } dev: true - /lodash.defaults/4.2.0: - resolution: - { - integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - } - dev: true - - /lodash.isarguments/3.1.0: - resolution: - { - integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - } - dev: true - /lodash.isplainobject/4.0.6: resolution: { @@ -4088,24 +4029,6 @@ packages: picomatch: 2.3.1 dev: true - /redis-errors/1.2.0: - resolution: - { - integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - } - engines: { node: '>=4' } - dev: true - - /redis-parser/3.0.0: - resolution: - { - integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - } - engines: { node: '>=4' } - dependencies: - redis-errors: 1.2.0 - dev: true - /regenerator-runtime/0.13.9: resolution: { @@ -4418,13 +4341,6 @@ packages: deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' dev: true - /standard-as-callback/2.1.0: - resolution: - { - integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - } - dev: true - /string-width/4.2.3: resolution: { diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts index 780fa14..689ff31 100644 --- a/frontend/src/app.d.ts +++ b/frontend/src/app.d.ts @@ -3,3 +3,21 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/// + +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +// and what to do when importing types +declare namespace App { + interface Locals { + email: string | null; + } + + // interface Platform {} + interface Session { + authenticated: boolean; + email: string | null; + } + + // interface Stuff {} +} diff --git a/frontend/src/hooks.ts b/frontend/src/hooks.ts index 9b6b68f..622b086 100644 --- a/frontend/src/hooks.ts +++ b/frontend/src/hooks.ts @@ -5,56 +5,53 @@ */ import * as cookie from 'cookie'; -import Redis from 'ioredis'; - -const redis = new Redis(process.env.REDIS_URL); +import type { Handle, GetSession } from '@sveltejs/kit'; /** @type {import('@sveltejs/kit').Handle} */ -export async function handle({ event, resolve }) { +export const handle: Handle = async ({ event, resolve }) => { const cookies = cookie.parse(event.request.headers.get('cookie') || ''); - const regex_token = /^Bearer (.*)$/gm.exec(cookies.access_token); - if (regex_token === null) { - event.locals.token = null; - } else { - event.locals.token = regex_token[1]; - } - event.locals.rememberme = cookies.rememberme_token; - - return await resolve(event); -} - -/** @type {import('@sveltejs/kit').GetSession} */ -export async function getSession(event) { - const redis_res = await redis.get(event.locals.token); - let user_email: string; - if (redis_res === null) { - const res = await fetch(`${process.env.API_URL}/api/v1/users/check`, { + const jwt = /^Bearer (.*)$/gm.exec(cookies.access_token); + const rememberme_token = cookies.rememberme_token; + if (rememberme_token) { + const res = await fetch(`${process.env.API_URL}/api/v1/users/auth/internal`, { + method: 'POST', headers: { - Cookie: `access_token=Bearer ${event.locals.token}` // skipcq: JS-0378 - } + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + rememberme: rememberme_token, + jwt: jwt === null ? undefined : jwt[0] + }) }); - if (res.ok) { - const json = await res.json(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore // skipcq: JS-0295 - user_email = json.email; + let new_jwt; + if (jwt) { + new_jwt = jwt[0]; } else { - user_email = null; + new_jwt = cookie.parse(res.headers.get('set-cookie')).access_token; } + event.locals.email = await ( + await fetch(`${process.env.API_URL}/api/v1/users/auth/internal/email`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jwt: new_jwt + }) + }) + ).text(); + const resp = await resolve(event); + resp.headers.set('Set-Cookie', res.headers.get('set-cookie')); + return resp; } else { - user_email = redis_res; + event.locals.email = null; + return resolve(event); } - if (user_email === null) { - return { - authenticated: false, - token: event.locals.token, - email: null - }; - } else { - return { - authenticated: true, - token: event.locals.token, - email: user_email - }; - } -} +}; + +export const getSession: GetSession = async (event) => { + return { + email: event.locals.email, + authenticated: Boolean(event.locals.email) + }; +}; diff --git a/frontend/src/lib/footer.svelte b/frontend/src/lib/footer.svelte index abbbc26..1f02f5c 100644 --- a/frontend/src/lib/footer.svelte +++ b/frontend/src/lib/footer.svelte @@ -9,7 +9,7 @@