diff --git a/packages/repco-cli/src/client.ts b/packages/repco-cli/src/client.ts new file mode 100644 index 00000000..786ce18e --- /dev/null +++ b/packages/repco-cli/src/client.ts @@ -0,0 +1,29 @@ +import { fetch, Headers, RequestInit, HeadersInit } from 'undici' + +interface RepcoRequestInit extends RequestInit { body?: any } +export async function request(path: string, init: RepcoRequestInit = {}) { + const url = process.env.REPCO_URL + '/api/admin' + path + const token = process.env.REPCO_ADMIN_TOKEN + const headers = new Headers(init.headers) + headers.set('authorization', 'Bearer ' + token) + headers.set('accept', 'application/json') + + if (init.body) { + init.body = JSON.stringify(init.body) + headers.set('content-type', 'application/json') + } + + const res = await fetch(url, { ...init, headers }) + if (!res.ok) { + const text = await res.text() + let error + try { + const data = JSON.parse(text) + error = data.error + } catch (_err) { + error = text + } + throw new Error('Remote error: ' + error) + } + return res.json() +} \ No newline at end of file diff --git a/packages/repco-cli/src/commands/debug.ts b/packages/repco-cli/src/commands/debug.ts index cb8f07cc..1ea2afec 100644 --- a/packages/repco-cli/src/commands/debug.ts +++ b/packages/repco-cli/src/commands/debug.ts @@ -3,9 +3,27 @@ import prettyMs from 'pretty-ms' import { SingleBar } from 'cli-progress' import { EntityForm, Repo } from 'repco-core' import { createCommand, createCommandGroup } from '../parse.js' +import { request } from '../client.js' const round = (x: number) => Math.round(x * 100) / 100 +export const authTest = createCommand({ + name: 'auth-test', + help: 'Test authentication to repco server', + options: { + }, + async run(opts, args) { + try { + const res = await request('/test'); + console.log('GET /test', res) + const res2 = await request('/test', { method: 'POST' , body: { foo: 'bar'}}); + console.log('GET /test', res2) + } catch (err) { + console.error('got error', err) + } + } +}) + export const createContent = createCommand({ name: 'create-content', help: 'Create dummy content', @@ -70,5 +88,5 @@ function createItem() { export const commands = createCommandGroup({ name: 'debug', help: 'Development helpers', - commands: [createContent], + commands: [createContent, authTest], }) diff --git a/packages/repco-server/src/lib.ts b/packages/repco-server/src/lib.ts index c128037c..27deb307 100644 --- a/packages/repco-server/src/lib.ts +++ b/packages/repco-server/src/lib.ts @@ -44,6 +44,7 @@ export function runServer(prisma: PrismaClient, port: number) { app.use(graphqlHandler) app.use((_req, res, next) => { res.locals.prisma = prisma + res.locals.log = logger.logger next() }) app.use((req, _res, next) => { diff --git a/packages/repco-server/src/routes/admin.ts b/packages/repco-server/src/routes/admin.ts new file mode 100644 index 00000000..dea0f15d --- /dev/null +++ b/packages/repco-server/src/routes/admin.ts @@ -0,0 +1,81 @@ +import express from 'express' +import { ServerError } from '../error.js' + +export const router = express.Router() + +const ADMIN_TOKEN = process.env.REPCO_ADMIN_TOKEN + +// check auth +router.use((req, res, next) => { + if (!ADMIN_TOKEN || ADMIN_TOKEN.length < 16) { + res.locals.log.warn('ADMIN_TOKEN is not set or too short (min 16 characters needed). Admin access disabled.') + return next(new ServerError(403, 'Unauthorized')) + } + + const authHeader = req.headers['authorization']; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next(new ServerError(403, 'Unauthorized')) + } + + const token = authHeader.substring(7) + if (token != ADMIN_TOKEN) { + return next(new ServerError(403, 'Unauthorized')) + } + + next() +}) + +router.get('/test', async (req, res) => { + await new Promise((resolve) => setTimeout(resolve, 1000)) + res.send({ ok: true }) +}) + +router.post('/test', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) + + +// create repo +router.post('/repo', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) +// list repos +router.get('/repo', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) + + +// create datasource +router.post('/repo/:repodid/ds', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) + +// modify datasource +router.put('/repo/:repodid/ds/:dsuid', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) +// get datasource +router.get('/repo/:repodid/ds/:dsuid', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) +// list datasources +router.get('/repo/:repodid/ds', async (req, res) => { + const body = req.body + console.log('received body', body) + res.send({ ok: true }) +}) + + + diff --git a/packages/repco-server/src/routes/api.ts b/packages/repco-server/src/routes/api.ts index c64c5bc2..b03221a3 100644 --- a/packages/repco-server/src/routes/api.ts +++ b/packages/repco-server/src/routes/api.ts @@ -20,17 +20,20 @@ import { flattenStream, sendNdJsonStream, } from '../util.js' +import { router as adminRouter } from './admin.js' const router = express.Router() // const HEADER_JSON = 'application/json' const HEADER_CAR = 'application/vnd.ipld.car' +router.use('/admin', adminRouter) + router.get('/repos', async (_req, res) => { res.json(await Repo.list(getLocals(res).prisma)) }) -router.get('/health', (_req, res) => { +router.get('/health', (_req, res, next) => { res.send({ ok: true }) })