From 3d149e0d1c2a3daee9c4ecebb382b9dc101e2819 Mon Sep 17 00:00:00 2001 From: Francesco Minnocci Date: Mon, 9 Jan 2023 23:44:01 +0100 Subject: [PATCH] feat: add Oauth authentication --- .env.development | 2 +- package.json | 2 ++ pnpm-lock.yaml | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ server/auth.ts | 52 +++++++++++++++++++++++++++++++++ server/routes.ts | 12 ++++++-- 5 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 server/auth.ts diff --git a/.env.development b/.env.development index 38e2a1d..eec76ed 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,3 @@ BASE_URL=/ -DATABASE_PATH=db.local.json +DATABASE_PATH=db.local.json \ No newline at end of file diff --git a/package.json b/package.json index 088c23f..b5e15e3 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "remark-parse": "^10.0.1", "remark-rehype": "^10.1.0", "sass": "^1.55.0", + "simple-oauth2": "^5.0.0", "unified": "^10.1.2", "url-pattern": "^1.0.3", "vite": "^3.2.2" @@ -43,6 +44,7 @@ "@types/express": "^4.17.14", "@types/morgan": "^1.9.3", "@types/node": "^18.11.9", + "@types/simple-oauth2": "^4.1.1", "concurrently": "^7.5.0", "esbuild": "^0.15.13", "npm-run-all": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ad2b74..6eb1748 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ specifiers: '@types/express': ^4.17.14 '@types/morgan': ^1.9.3 '@types/node': ^18.11.9 + '@types/simple-oauth2': ^4.1.1 body-parser: ^1.20.1 chalk: ^5.1.2 concurrently: ^7.5.0 @@ -27,6 +28,7 @@ specifiers: remark-parse: ^10.0.1 remark-rehype: ^10.1.0 sass: ^1.55.0 + simple-oauth2: ^5.0.0 ts-node: ^10.9.1 typescript: ^4.8.4 unified: ^10.1.2 @@ -53,6 +55,7 @@ dependencies: remark-parse: 10.0.1 remark-rehype: 10.1.0 sass: 1.56.0 + simple-oauth2: 5.0.0 unified: 10.1.2 url-pattern: 1.0.3 vite: 3.2.2_sass@1.56.0 @@ -62,6 +65,7 @@ devDependencies: '@types/express': 4.17.14 '@types/morgan': 1.9.3 '@types/node': 18.11.9 + '@types/simple-oauth2': 4.1.1 concurrently: 7.5.0 esbuild: 0.15.13 npm-run-all: 4.1.5 @@ -337,6 +341,38 @@ packages: requiresBuild: true optional: true + /@hapi/boom/10.0.0: + resolution: {integrity: sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==} + dependencies: + '@hapi/hoek': 10.0.1 + dev: false + + /@hapi/bourne/3.0.0: + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + dev: false + + /@hapi/hoek/10.0.1: + resolution: {integrity: sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw==} + dev: false + + /@hapi/hoek/9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + dev: false + + /@hapi/topo/5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/wreck/18.0.0: + resolution: {integrity: sha512-Yk9STxoM06Hjjq58cH0KFG91u9F2h9eVE72o8vUr3AfK80qt7I2POG5+cDGTEntbnvvzm0ERow2sjG3QsqCWUA==} + dependencies: + '@hapi/boom': 10.0.0 + '@hapi/bourne': 3.0.0 + '@hapi/hoek': 10.0.1 + dev: false + /@jridgewell/gen-mapping/0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -454,6 +490,20 @@ packages: picomatch: 2.3.1 dev: false + /@sideway/address/4.1.4: + resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@sideway/formula/3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: false + + /@sideway/pinpoint/2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: false + /@tsconfig/node10/1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true @@ -565,6 +615,10 @@ packages: '@types/node': 18.11.9 dev: true + /@types/simple-oauth2/4.1.1: + resolution: {integrity: sha512-8jqhfUFb0FoCl2Od4czprB7ubM8/Fuo3tg+vQZ00zYtPcNLogGyDgm2DVfVvD3NYXK7AscKOXSaW33NHUWpNBg==} + dev: true + /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: false @@ -1630,6 +1684,16 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /joi/17.7.0: + resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==} + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.4 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + dev: false + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: false @@ -2455,6 +2519,17 @@ packages: get-intrinsic: 1.1.3 object-inspect: 1.12.2 + /simple-oauth2/5.0.0: + resolution: {integrity: sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ==} + dependencies: + '@hapi/hoek': 10.0.1 + '@hapi/wreck': 18.0.0 + debug: 4.3.4 + joi: 17.7.0 + transitivePeerDependencies: + - supports-color + dev: false + /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} diff --git a/server/auth.ts b/server/auth.ts new file mode 100644 index 0000000..2d0f314 --- /dev/null +++ b/server/auth.ts @@ -0,0 +1,52 @@ +import { Router } from 'express'; +import { AuthorizationCode } from 'simple-oauth2' + +export function setupOauth(r: Router) { + const config = { + client: { + id: process.env.OAUTH_CLIENT_ID ?? '', + secret: process.env.OAUTH_CLIENT_SECRET ?? '', + }, + auth: { + authorizePath: process.env.OAUTH_AUTH_URL ?? '', + tokenHost: process.env.OAUTH_TOKEN_HOST ?? '', + tokenPath: process.env.OAUTH_TOKEN_PATH ?? '' + }, + }; + + const conf = { + redirect_uri: process.env.OAUTH_REDIRECT_URL ?? '', + scope: process.env.OAUTH_SCOPES ?? '', + } + + const client = new AuthorizationCode(config); + + const authorizationUri = client.authorizeURL({ + redirect_uri: conf.redirect_uri, + scope: conf.scope, + state: '' + }); + + r.get('/redirect', (req, res) => { + res.redirect(authorizationUri); + }); + + // Callback service parsing the authorization token and asking for the access token + r.get('/callback', async (req, res) => { + const code = req.query.code as string; + + const options = { + code, + redirect_uri: conf.redirect_uri + }; + + try { + const accessToken = await client.getToken(options); + + return res.status(200).json(accessToken.token); + } catch (error) { + console.error('Access Token Error', error.message); + return res.status(500).redirect(`/error?message=${encodeURIComponent('Autenticazione fallita')}`); + } + }); +} diff --git a/server/routes.ts b/server/routes.ts index 98452d5..c936943 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -38,6 +38,7 @@ import { import { initialDatabaseValue } from './db/example-data' import { validateObjectKeys } from '../shared/utils' +import { setupOauth } from './auth' export async function createApiRouter() { type SessionId = Opaque @@ -70,6 +71,11 @@ export async function createApiRouter() { r.use(bodyParser.json()) r.use(cookieParser()) + const authRouter: Router = express.Router() + + setupOauth(authRouter) + r.use('/auth', authRouter) + r.get('/api/status', (req, res) => { res.json({ url: req.originalUrl, status: 'ok' }) }) @@ -243,7 +249,7 @@ export async function createApiRouter() { let queryUser = (req.query.user ?? null) as UserId | null let queryProblem = (req.query.problem ?? null) as ProblemId | null let isPublic = (req.query.public === '') - + const requestUser = await getRequestUser(req) let solutions = await getSolutions(db) @@ -256,10 +262,10 @@ export async function createApiRouter() { solutions = solutions.filter(s => s.sentBy === queryUser) } - if(isPublic) { + if (isPublic) { solutions = solutions.filter(s => s.visible) } - + // filtra rispetto ai problemi if (queryProblem !== null) { solutions = solutions.filter(s => s.forProblem === queryProblem)