Skip to content

Commit

Permalink
feat(@xen-orchestra/rest-api): setup simple auth middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
MathieuRA committed Feb 10, 2025
1 parent d854789 commit 947e71c
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 8 deletions.
5 changes: 4 additions & 1 deletion @xen-orchestra/rest-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
"typescript-eslint": "^8.23.0"
},
"dependencies": {
"inversify": "^6.2.2",
"inversify-binding-decorators": "^4.0.0",
"swagger-ui-express": "^5.0.1",
"tsoa": "^6.6.0"
"tsoa": "^6.6.0",
"xo-common": "^0.8.0"
},
"files": [
"dist"
Expand Down
5 changes: 4 additions & 1 deletion @xen-orchestra/rest-api/src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import type { Express } from 'express'

import swaggerOpenApiSpec from './open-api/spec/swagger.json' assert { type: 'json' }
import { RegisterRoutes } from './open-api/routes/routes.js'
import { setupContainer } from './ioc/ioc.mjs'
import type { XoApp } from './rest-api/rest-api.type.mjs'

export default function setupRestApi(express: Express) {
export default function setupRestApi(express: Express, xoApp: XoApp) {
setupContainer(xoApp)
RegisterRoutes(express)

express.use('/rest/v0', swaggerUi.serve, swaggerUi.setup(swaggerOpenApiSpec))
Expand Down
24 changes: 24 additions & 0 deletions @xen-orchestra/rest-api/src/ioc/ioc.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { buildProviderModule } from 'inversify-binding-decorators'
import { Container, decorate, injectable } from 'inversify'
import { Controller } from 'tsoa'

import { RestApi } from '../rest-api/rest-api.mjs'
import { XoApp } from '../rest-api/rest-api.type.mjs'

const iocContainer = new Container()

decorate(injectable(), Controller)
iocContainer.load(buildProviderModule())

export function setupContainer(xoApp: XoApp) {
if (iocContainer.isBound(RestApi)) {
iocContainer.unbind(RestApi)
}

iocContainer
.bind(RestApi)
.toDynamicValue(() => new RestApi(xoApp))
.inSingletonScope()
}

export { iocContainer }
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Request } from 'express'
import { unauthorized } from 'xo-common/api-errors.js'

import { iocContainer } from '../ioc/ioc.mjs'
import { RestApi } from '../rest-api/rest-api.mjs'

const noop = () => {}

// TODO: correctly handle ACL/Resource set users
// for now only support "super-admin"
export async function expressAuthentication(req: Request) {
const restApi = iocContainer.get(RestApi)
const ip = req.ip
const token = req.cookies.token ?? req.cookies.authenticationToken

const { user } = await restApi.authenticateUser({ token }, { ip })
if (user.permission !== 'admin') {
/* throw */ unauthorized()
}

await restApi.runWithApiContext(user, noop)
Promise.resolve(user)
}
11 changes: 11 additions & 0 deletions @xen-orchestra/rest-api/src/rest-api/rest-api.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { XoApp } from './rest-api.type.mjs'

export class RestApi {
authenticateUser: XoApp['authenticateUser']
runWithApiContext: XoApp['runWithApiContext']

constructor(xoApp: XoApp) {
this.authenticateUser = (params, optional) => xoApp.authenticateUser(params, optional)
this.runWithApiContext = (user, cb) => xoApp.runWithApiContext(user, cb)
}
}
13 changes: 13 additions & 0 deletions @xen-orchestra/rest-api/src/rest-api/rest-api.type.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// TODO: Use from @vates/types instead
type XoUser = {
id: string
permission: string
}

export type XoApp = {
authenticateUser: (
params: { token?: string; username?: string; password?: string },
optional?: { ip?: string }
) => Promise<{ bypassOtp: boolean; user: XoUser }>
runWithApiContext: (user: XoUser, fn: () => void) => Promise<unknown>
}
2 changes: 2 additions & 0 deletions @xen-orchestra/rest-api/src/vms/vm.controller.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Controller, Route } from 'tsoa'
import { provide } from 'inversify-binding-decorators'

@Route('vms')
@provide(VmController)
export class VmController extends Controller {}
1 change: 1 addition & 0 deletions @xen-orchestra/rest-api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"moduleResolution": "bundler",
"target": "ESNext",
"skipLibCheck": true,
"noImplicitAny": false,

"resolveJsonModule": true,

Expand Down
13 changes: 11 additions & 2 deletions @xen-orchestra/rest-api/tsoa.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@
"spec": {
"outputDirectory": "./src/open-api/spec",
"specVersion": 3,
"basePath": "/rest/v0"
"basePath": "/rest/v0",
"securityDefinitions": {
"token": {
"type": "apiKey",
"name": "authenticationToken",
"in": "cookie"
}
}
},
"routes": {
"routesDir": "./src/open-api/routes",
"esm": true,
"middleware": "express",
"basePath": "/rest/v0"
"basePath": "/rest/v0",
"authenticationModule": "./src/middleware/authentication.middleware.mts",
"iocModule": "./src/ioc/ioc.mts"
}
}
2 changes: 1 addition & 1 deletion packages/xo-server/src/xo-mixins/rest-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ export default class RestApi {
})
)

setupRestApi(express)
setupRestApi(express, app)
}

registerRestApi(spec, base = '/') {
Expand Down
37 changes: 34 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2052,9 +2052,9 @@
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==

"@eslint/js@^9.19.0":
version "9.19.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.19.0.tgz#51dbb140ed6b49d05adc0b171c41e1a8713b7789"
integrity sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==
version "9.20.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4"
integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==

"@floating-ui/core@^1.6.0":
version "1.6.9"
Expand Down Expand Up @@ -2494,6 +2494,24 @@
source-map-js "^1.0.2"
unplugin "^1.1.0"

"@inversifyjs/[email protected]":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@inversifyjs/common/-/common-1.4.0.tgz#4df42e8cb012a1630ebf2f3c65bb76ac5b0f3e4c"
integrity sha512-qfRJ/3iOlCL/VfJq8+4o5X4oA14cZSBbpAmHsYj8EsIit1xDndoOl0xKOyglKtQD4u4gdNVxMHx4RWARk/I4QA==

"@inversifyjs/[email protected]":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@inversifyjs/core/-/core-1.3.5.tgz#c02ee3ed036aae40189302794f16a9f4e0ed4557"
integrity sha512-B4MFXabhNTAmrfgB+yeD6wd/GIvmvWC6IQ8Rh/j2C3Ix69kmqwz9pr8Jt3E+Nho9aEHOQCZaGmrALgtqRd+oEQ==
dependencies:
"@inversifyjs/common" "1.4.0"
"@inversifyjs/reflect-metadata-utils" "0.2.4"

"@inversifyjs/[email protected]":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@inversifyjs/reflect-metadata-utils/-/reflect-metadata-utils-0.2.4.tgz#c65172283db9516c4a27e8d673ca7a31a07d528b"
integrity sha512-u95rV3lKfG+NT2Uy/5vNzoDujos8vN8O18SSA5UyhxsGYd4GLQn/eUsGXfOsfa7m34eKrDelTKRUX1m/BcNX5w==

"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
Expand Down Expand Up @@ -11952,6 +11970,19 @@ invariant@^2.0.0, invariant@^2.1.0, invariant@^2.1.1, invariant@^2.1.2, invarian
dependencies:
loose-envify "^1.0.0"

inversify-binding-decorators@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/inversify-binding-decorators/-/inversify-binding-decorators-4.0.0.tgz#c4d1ac50d7d6531f465ee7894e92031f7471c865"
integrity sha512-r8au/oH3vS7ttHj0RivAznwElySeRohLfg8lvOSzbrX6abf/8ik8ptk49XbzdShgrnalvl7CM6MjcskfM7MMqQ==

inversify@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/inversify/-/inversify-6.2.2.tgz#53b164d0c5753a47477a0d68be56f8e8bc3bafeb"
integrity sha512-KB836KHbZ9WrUnB8ax5MtadOwnqQYa+ZJO3KWbPFgcr4RIEnHM621VaqFZzOZd9+U7ln6upt9n0wJei7x2BNqw==
dependencies:
"@inversifyjs/common" "1.4.0"
"@inversifyjs/core" "1.3.5"

invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
Expand Down

0 comments on commit 947e71c

Please sign in to comment.