feat: add initial API
This commit is contained in:
@@ -1,8 +1,34 @@
|
||||
import { type FastifyPluginAsync } from 'fastify';
|
||||
import { manageEndpoints } from './endpoints/endpoints.manage.ts';
|
||||
import { authPlugin } from './plugins/plugins.auth.ts';
|
||||
import { messageEndpoints } from './endpoints/endpoints.message.ts';
|
||||
import { z } from 'zod';
|
||||
|
||||
const api: FastifyPluginAsync = async (fastify) => {
|
||||
fastify.get('/healthz', () => {
|
||||
return { status: 'ok' };
|
||||
fastify.route({
|
||||
method: 'get',
|
||||
url: '/health',
|
||||
schema: {
|
||||
operationId: 'health.get',
|
||||
summary: 'Get health status',
|
||||
tags: ['system'],
|
||||
response: {
|
||||
200: z.object({
|
||||
status: z.literal('ok'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: () => {
|
||||
return { status: 'ok' };
|
||||
},
|
||||
});
|
||||
await authPlugin(fastify, {});
|
||||
|
||||
await fastify.register(manageEndpoints, {
|
||||
prefix: '/manage',
|
||||
});
|
||||
await fastify.register(messageEndpoints, {
|
||||
prefix: '/message',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
45
src/api/endpoints/endpoints.manage.ts
Normal file
45
src/api/endpoints/endpoints.manage.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { JwtAuth } from '#root/auth/auth.jwt.ts';
|
||||
import { statementSchema } from '#root/auth/auth.schemas.ts';
|
||||
import { Config } from '#root/config/config.ts';
|
||||
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
const manageEndpoints: FastifyPluginAsyncZod = async (fastify) => {
|
||||
const config = fastify.services.get(Config);
|
||||
|
||||
if (config.jwtSecret) {
|
||||
fastify.route({
|
||||
method: 'post',
|
||||
url: '/jwt',
|
||||
schema: {
|
||||
operationId: 'manage.jwt.post',
|
||||
summary: 'Generate a JWT',
|
||||
tags: ['manage'],
|
||||
body: z.object({
|
||||
exp: z.number().optional(),
|
||||
statements: z.array(statementSchema),
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
jwt: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (req, reply) => {
|
||||
if (
|
||||
!req.session.validate({
|
||||
action: 'mgmt:generate-jwt',
|
||||
resource: 'mgmt/',
|
||||
})
|
||||
) {
|
||||
throw reply.unauthorized('not allowed');
|
||||
}
|
||||
const jwtAuth = fastify.services.get(JwtAuth);
|
||||
const jwt = jwtAuth.generate(req.body);
|
||||
reply.send({ jwt });
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export { manageEndpoints };
|
||||
62
src/api/endpoints/endpoints.message.ts
Normal file
62
src/api/endpoints/endpoints.message.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Config } from '#root/config/config.ts';
|
||||
import { MqttServer } from '#root/server/server.ts';
|
||||
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
const messageEndpoints: FastifyPluginAsyncZod = async (fastify) => {
|
||||
const config = fastify.services.get(Config);
|
||||
|
||||
if (config.jwtSecret) {
|
||||
fastify.route({
|
||||
method: 'post',
|
||||
url: '',
|
||||
schema: {
|
||||
summary: 'Post a message to the bus',
|
||||
operationId: 'message.post',
|
||||
tags: ['message'],
|
||||
body: z.object({
|
||||
topic: z.string(),
|
||||
dup: z.boolean(),
|
||||
qos: z.union([z.literal(0), z.literal(1), z.literal(2)]),
|
||||
retain: z.boolean(),
|
||||
payload: z.string(),
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (req, reply) => {
|
||||
if (
|
||||
!req.session.validate({
|
||||
action: 'mqtt:publish',
|
||||
resource: 'mgmt:',
|
||||
})
|
||||
) {
|
||||
throw reply.unauthorized('not allowed');
|
||||
}
|
||||
const server = fastify.services.get(MqttServer);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.bus.publish(
|
||||
{
|
||||
...req.body,
|
||||
cmd: 'publish',
|
||||
payload: Buffer.from(req.body.payload, 'base64'),
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
});
|
||||
reply.send({ success: true });
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export { messageEndpoints };
|
||||
14
src/api/extensions.d.ts
vendored
Normal file
14
src/api/extensions.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Session } from '#root/services/sessions/sessions.session.ts';
|
||||
import type { Services } from '#root/utils/services.ts';
|
||||
import 'fastify';
|
||||
declare module 'fastify' {
|
||||
// eslint-disable-next-line
|
||||
export interface FastifyInstance {
|
||||
services: Services;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export interface FastifyRequest {
|
||||
session: Session;
|
||||
}
|
||||
}
|
||||
27
src/api/plugins/plugins.auth.ts
Normal file
27
src/api/plugins/plugins.auth.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { SessionProvider } from '#root/services/sessions/sessions.provider.ts';
|
||||
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
|
||||
|
||||
const authPlugin: FastifyPluginAsyncZod = async (fastify) => {
|
||||
fastify.addHook('onRequest', async (req, reply) => {
|
||||
const authProvider = req.headers['x-auth-provider'];
|
||||
if (!authProvider || Array.isArray(authProvider)) {
|
||||
throw reply.unauthorized('missing x-auth-provider header');
|
||||
}
|
||||
const authorization = req.headers.authorization;
|
||||
if (!authorization) {
|
||||
throw reply.unauthorized('missing authorization header');
|
||||
}
|
||||
const [type, token] = authorization.split(' ');
|
||||
if (type.toLowerCase() !== 'bearer') {
|
||||
throw reply.unauthorized('only bearer tokens are allowed');
|
||||
}
|
||||
if (!token) {
|
||||
throw reply.unauthorized('missing token');
|
||||
}
|
||||
const sessionProvider = fastify.services.get(SessionProvider);
|
||||
const session = await sessionProvider.get(authProvider, token);
|
||||
req.session = session;
|
||||
});
|
||||
};
|
||||
|
||||
export { authPlugin };
|
||||
Reference in New Issue
Block a user