From 9ae74fa6494022bc12c1ebb651e388d65b145aa3 Mon Sep 17 00:00:00 2001 From: Francesco Minnocci Date: Tue, 10 Jan 2023 20:07:25 +0100 Subject: [PATCH] chore: rewrite /login page for OAuth, write createUser for the database --- client/api.tsx | 2 ++ client/hooks/useCurrentUser.tsx | 2 ++ client/pages/LoginPage.tsx | 28 ++-------------------------- client/styles/main.scss | 2 +- server.ts | 9 --------- server/auth.ts | 30 +++++++++++++++++++++++++++--- server/db/database.ts | 9 +++++++-- server/routes.ts | 33 +++++++++++++++++++-------------- shared/model.ts | 1 + 9 files changed, 61 insertions(+), 55 deletions(-) diff --git a/client/api.tsx b/client/api.tsx index 628b6e9..2641cde 100644 --- a/client/api.tsx +++ b/client/api.tsx @@ -1,3 +1,5 @@ +// TODO: move to `shared/` and distinguish client and server (fix imports) + export function prependBaseUrl(route: string) { return import.meta.env.BASE_URL + (route.startsWith('/') ? route.substring(1) : route) } diff --git a/client/hooks/useCurrentUser.tsx b/client/hooks/useCurrentUser.tsx index 29e4ead..30f244e 100644 --- a/client/hooks/useCurrentUser.tsx +++ b/client/hooks/useCurrentUser.tsx @@ -29,6 +29,8 @@ type UserFunctionsHook = () => { logout: () => Promise } +// TODO: remove login as it is deprecated + export const useUserFunctions: UserFunctionsHook = () => { const login = async (username: string) => { await fetch(prependBaseUrl(`/api/login`), { diff --git a/client/pages/LoginPage.tsx b/client/pages/LoginPage.tsx index 3e1747a..3ec88b1 100644 --- a/client/pages/LoginPage.tsx +++ b/client/pages/LoginPage.tsx @@ -1,37 +1,13 @@ -import { route } from 'preact-router' - -import { useState } from 'preact/hooks' import { prependBaseUrl } from '../api.js' import { Header } from '../components/Header.jsx' -import { useUserFunctions } from '../hooks/useCurrentUser.js' export const LoginPage = () => { - const [username, setUsername] = useState('') - const { login } = useUserFunctions() - - const handleLogin = async () => { - await login(username) - route(prependBaseUrl('/')) - } - return ( <>
-
Accedi
-
- - setUsername(e.target instanceof HTMLInputElement ? e.target.value : '')} - onKeyDown={e => e.key === 'Enter' && handleLogin()} - /> - -
- -
+
diff --git a/client/styles/main.scss b/client/styles/main.scss index dab9ed1..bc34408 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -119,7 +119,7 @@ input[type='text'] { } } -button { +button, .button { cursor: pointer; font-family: 'Lato'; diff --git a/server.ts b/server.ts index 489a990..34c6c74 100644 --- a/server.ts +++ b/server.ts @@ -14,8 +14,6 @@ import { RenderFunction } from './shared/ssr' // Load ".env" dotenv.config() -// const HTML_ROUTES = ['/', '/login', '/problem/:id', '/u/:uid', '/admin', '/profile', '/scores'] - const config = { isDevelopment: process.env.MODE === 'development', port: process.env.PORT || 3000, @@ -26,13 +24,6 @@ if (config.isDevelopment) { console.log(`[Config] PORT = ${config.port}`) } -// function mountIndexHtmlRoutes(r: Router, serveIndexHtml: Handler) { -// for (const route of HTML_ROUTES) { -// console.log(`[Server] Mounted index html for "${route}"`) -// r.get(route, serveIndexHtml) -// } -// } - async function createDevRouter() { const r = express.Router() diff --git a/server/auth.ts b/server/auth.ts index 0049e5e..fe49ef2 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -1,8 +1,11 @@ import { Router } from 'express' import { AuthorizationCode } from 'simple-oauth2' import fetch from 'node-fetch' +import { DatabaseConnection, getUser, createUser } from './db/database' +import { UserId } from '../shared/model' +import { SessionService } from './routes' -export function setupOauth(r: Router) { +export function setupOauth(r: Router, db: DatabaseConnection, sessions: SessionService) { const config = { client: { id: process.env.OAUTH_CLIENT_ID ?? '', @@ -55,8 +58,29 @@ export function setupOauth(r: Router) { }) ).json() - // TODO: call to db && login - return res.status(200).json(userInfo) + type UserInfo = { name: string; email: string } + + // Parse user info into a sensible struct + const authUser = { + id: (userInfo as UserInfo).email.split('@')[0] as UserId, + fullName: (userInfo as UserInfo).name + .split(' ') + .map(s => s.substring(0, 1) + s.substring(1).toLowerCase()) + .join(' '), + } + + const user = await getUser(db, authUser.id) + if (!user) { + await createUser(db, { + id: authUser.id, + fullName: authUser.fullName, + role: 'student', + }) + } + + res.cookie('sid', sessions.createSession(authUser.id), { maxAge: 1000 * 60 * 60 * 24 * 7 }) + + return res.status(200).redirect('/') } catch (error) { console.error('Access Token Error', error.message) return res.status(500).redirect(`/error?message=${encodeURIComponent('Autenticazione fallita')}`) diff --git a/server/db/database.ts b/server/db/database.ts index 14b689a..e06499a 100644 --- a/server/db/database.ts +++ b/server/db/database.ts @@ -2,7 +2,7 @@ import crypto from 'crypto' import { readFile, writeFile, access, constants } from 'fs/promises' -import { MetadataProps as MetaProps, Problem, ProblemId, Solution, SolutionId, User } from '../../shared/model' +import { MetadataProps as MetaProps, Problem, ProblemId, Solution, SolutionId, User, UserId, UserRole } from '../../shared/model' function once any>(fn: T, message: string): T { let flag = false @@ -53,7 +53,7 @@ function createMutex(): Mutex { return { lock } } -type DatabaseConnection = { +export type DatabaseConnection = { path: string initialValue: Database mu: Mutex @@ -110,6 +110,11 @@ export const getUser: (db: DatabaseConnection, id: string) => Promise Promise = (db, user) => + withDatabase(db, state => { + state.users[user.id] = user + }) + // // Problems // diff --git a/server/routes.ts b/server/routes.ts index 0364a09..8ce832e 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -40,11 +40,16 @@ import { initialDatabaseValue } from './db/example-data' import { validateObjectKeys } from '../shared/utils' import { setupOauth } from './auth' -export async function createApiRouter() { - type SessionId = Opaque +type SessionId = Opaque + +export interface SessionService { + createSession(userId: UserId): SessionId + getUserForSession(sid: SessionId): UserId | null +} +export async function createApiRouter() { const sessionStore: Record = {} - const sessions = { + const sessions: SessionService = { createSession(userId: UserId) { const sid = crypto.randomBytes(10).toString('hex') as SessionId sessionStore[sid] = userId @@ -73,7 +78,7 @@ export async function createApiRouter() { const authRouter: Router = express.Router() - setupOauth(authRouter) + setupOauth(authRouter, db, sessions) r.use('/auth', authRouter) r.get('/api/status', (req, res) => { @@ -84,18 +89,18 @@ export async function createApiRouter() { res.json(await getRequestUser(req)) }) - r.post('/api/login', async (req, res) => { - const { id, name } = req.body + // r.post('/api/login', async (req, res) => { + // const { id, name } = req.body - const user = await getUser(db, id) - if (!user) { - res.sendStatus(StatusCodes.FORBIDDEN) - return - } + // const user = await getUser(db, id) + // if (!user) { + // res.sendStatus(StatusCodes.FORBIDDEN) + // return + // } - res.cookie('sid', sessions.createSession(id), { maxAge: 1000 * 60 * 60 * 24 * 7 }) - res.json({ status: 'ok' }) - }) + // res.cookie('sid', sessions.createSession(id), { maxAge: 1000 * 60 * 60 * 24 * 7 }) + // res.json({ status: 'ok' }) + // }) r.post('/api/logout', (req, res) => { res.cookie('sid', '', { expires: new Date() }) diff --git a/shared/model.ts b/shared/model.ts index a72c23c..2f78a4f 100644 --- a/shared/model.ts +++ b/shared/model.ts @@ -20,6 +20,7 @@ export type UserId = Id export type User = { id: UserId + fullName: string role: UserRole }