Skip to content

Commit

Permalink
/config api
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanbataire committed Feb 20, 2025
1 parent cd48fc9 commit abbd43f
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 131 deletions.
4 changes: 2 additions & 2 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class Config {

public static async getContactType(name: string) : Promise<ContactType> {
const {config} = await init();
const contactMatch = config.contact_types.find(c => c?.name === name);
const contactMatch = config.contact_types.find(c => c.name === name);
if (!contactMatch) {
throw new Error(`unrecognized contact type: "${name}"`);
}
Expand All @@ -93,7 +93,7 @@ export class Config {
public static getParentProperty(contactType: ContactType): HierarchyConstraint {
const parentMatch = contactType.hierarchy.find(c => c.level === 1);
if (!parentMatch) {
throw new Error(`hierarchy at level 1 is required: "${contactType?.name}"`);
throw new Error(`hierarchy at level 1 is required: "${contactType.name}"`);
}

return parentMatch;
Expand Down
13 changes: 13 additions & 0 deletions src/lib/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import process from 'process';
import jwt from 'jsonwebtoken';
import ChtSession from './cht-session';
import { Config } from '../config';

const LOGIN_EXPIRES_AFTER_MS = 4 * 24 * 60 * 60 * 1000;
const QUEUE_SESSION_EXPIRATION = '96h';
Expand Down Expand Up @@ -57,4 +58,16 @@ export default class Auth {
const { data } = jwt.verify(token, signingKey) as any;
return ChtSession.createFromDataString(data);
}

public static async apiAuth (username: string, password: string, domain: string) {
const authInfo = await Config.getAuthenticationInfo(domain);
try {
const chtSession = await ChtSession.create(authInfo, username, password);
return chtSession;
} catch (e: any) {
console.error(`Login error: ${e}`);
return {};
}
}

}
67 changes: 0 additions & 67 deletions src/liquid/app/config_upload.html

This file was deleted.

3 changes: 0 additions & 3 deletions src/liquid/app/form_switch.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
{% elsif op == "merge" %} Merge Two
{% elsif op == "delete" %} Delete a
{% elsif op == "edit" %} Edit
{% elsif op == "config" %} Upload Configuration file
{% else %} New
{% endif %}
{% endcapture %}
Expand Down Expand Up @@ -41,8 +40,6 @@
{% include "place/bulk_create_form.html" %}
{% elsif op == "move" or op == "merge" or op == "delete" %}
{% include "place/manage_hierarchy_form.html" %}
{% elsif op == "config" %}
{% include "app/config_upload.html" %}
{% else %}
{% include "place/create_form.html" %}
{% endif %}
Expand Down
6 changes: 0 additions & 6 deletions src/liquid/app/nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,6 @@
<div class="navbar-dropdown is-right">
{% include "components/button_upload.html" className="navbar-item" %}
{% include "components/button_save_credentials.html" className="navbar-item" %}
<hr>
{% if session.isAdmin %}
<a class="navbar-item" href="/app/config">
<span class="material-symbols-outlined">settings</span> Upload Configuration
</a>
{% endif %}
<a class="navbar-item" href="/logout">
<span class="material-symbols-outlined">logout</span> Logout
</a>
Expand Down
37 changes: 37 additions & 0 deletions src/routes/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { readConfig, writeConfig } from '../config/config-factory';
import { Config, ConfigSystem } from '../config';

export default async function api(fastify: FastifyInstance) {
fastify.get('/api', (req: FastifyRequest, reply: FastifyReply) => {
reply.status(200).send({
name: 'UMT API',
version: '1.0',
status: 'healthy'
});
});

fastify.get('/api/config', async (req: FastifyRequest, reply: FastifyReply) => {
try {
const { config } = await readConfig();
reply.status(200);
return config;
} catch (error) {
reply.status(404).send({ message: 'No configuration found' });
}

});

fastify.post('/api/config', async (req: FastifyRequest, reply: FastifyReply) => {
try {
const config = await req.body as ConfigSystem;
await Config.assertValid({ config });
await writeConfig(config);
reply.send({ message: 'configuration updated' });
return;
} catch (error) {
console.error('Route api/config: ', error);
reply.send(error);
}
});
}
45 changes: 1 addition & 44 deletions src/routes/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { FastifyInstance } from 'fastify';

import Auth from '../lib/authentication';
import { ChtApi } from '../lib/cht-api';
Expand Down Expand Up @@ -117,47 +117,4 @@ export default async function sessionCache(fastify: FastifyInstance) {

resp.header('HX-Redirect', '/');
});

fastify.get('/app/config', async (req: FastifyRequest, resp: FastifyReply) => {
const contactTypes = await Config.contactTypes();
const tmplData = {
view: 'add',
logo: Config.getLogoBase64(),
session: req.chtSession,
op: 'config',
contactTypes,
};

return resp.view('src/liquid/app/view.html', tmplData);
});

fastify.post('/app/config', async (req: FastifyRequest, res: FastifyReply) => {
try {
const data = await req.file();
if (!data) {
res.status(400).send('No file uploaded');
throw new Error('No file uploaded');
}

if (data.mimetype !== 'application/json') {
res.status(400).send('Invalid file type');
throw new Error('Invalid file type');
}

const fileBuffer = await data.toBuffer();
const config = JSON.parse(fileBuffer.toString('utf-8')) as ConfigSystem;

await Config.assertValid({ config });
await writeConfig(config);

res.header('HX-Redirect', '/');
} catch (error) {
console.error('Route app/config: ', error);
return fastify.view('src/liquid/app/config_upload.html', {
errors: {
message: error,
},
});
}
});
}
44 changes: 35 additions & 9 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ const build = (opts: FastifyServerOptions): FastifyInstance => {
fastify.register(fastifyCompress);
fastify.register(view, {
engine: {
liquid: new Liquid({
extname: '.html',
root: 'src/liquid',
cache: process.env.NODE_ENV === 'production',
jekyllInclude: true,
dynamicPartials: true
liquid: new Liquid({
extname: '.html',
root: 'src/liquid',
cache: process.env.NODE_ENV === 'production',
jekyllInclude: true,
dynamicPartials: true
}),
},
});
Expand All @@ -44,7 +44,7 @@ const build = (opts: FastifyServerOptions): FastifyInstance => {
prefix: '/public/',
serve: true,
});

fastify.register(metricsPlugin, {
endpoint: '/metrics',
routeMetrics: {
Expand All @@ -63,6 +63,33 @@ const build = (opts: FastifyServerOptions): FastifyInstance => {
return;
}

const authHeader = req.headers.authorization as string;
const { domain } = req.query as { [key: string]: string };

if (authHeader && authHeader.startsWith('Basic ')) {
if (!req.routeOptions.url?.startsWith('/api')) {
reply.send({error: 'not found'}).status(404);
return;
}

if (!domain) {
reply.send({ error: 'no authentication domain found' });
return;
}

const credentialsBase64 = authHeader.split(' ')[1];
const credentials = Buffer.from(credentialsBase64, 'base64').toString('utf-8');
const [username, password] = credentials.split(':');

const { isAdmin }: any = await Auth.apiAuth(username, password, domain);

if (isAdmin) {
return;
}
reply.status(401).send({ error: 'unauthorized' });
return;
}

const cookieToken = req.cookies[Auth.AUTH_COOKIE_NAME] as string;
if (!cookieToken) {
reply.redirect('/login');
Expand All @@ -71,9 +98,8 @@ const build = (opts: FastifyServerOptions): FastifyInstance => {

try {
const chtSession = Auth.createCookieSession(cookieToken);
if (!chtSession?.isAdmin && req.routeOptions.url === '/app/config') {
if (req.routeOptions.url === '/api/config' && !chtSession?.isAdmin) {
reply.status(401).send({ error: 'unauthorized' });
console.error('Unauthorized access to config. User must be admin');
return;
}
req.chtSession = chtSession;
Expand Down

0 comments on commit abbd43f

Please sign in to comment.