Skip to content

Commit bf75e42

Browse files
author
Alex Prosser
committed
added dashboard with security
1 parent 1fce2e3 commit bf75e42

File tree

10 files changed

+368
-154
lines changed

10 files changed

+368
-154
lines changed

deno.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
{
22
"tasks": {
3-
"dev": "deno run --allow-read --allow-net server.ts"
3+
"run": "deno run -A --env-file --unstable-ffi server.ts"
44
},
55
"imports": {
66
"@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.48",
77
"@codingap/steve": "jsr:@codingap/steve@^2.0.10",
8+
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
89
"@libs/markdown": "jsr:@libs/markdown@^2.0.2",
10+
"@std/crypto": "jsr:@std/crypto@^1.0.3",
11+
"@std/encoding": "jsr:@std/encoding@^1.0.6",
912
"@std/fs": "jsr:@std/fs@^1.0.6",
1013
"@std/html": "jsr:@std/html@^1.0.3",
11-
"@std/media-types": "jsr:@std/media-types@^1.1.0",
14+
"@std/http": "jsr:@std/http@^1.0.12",
1215
"@std/path": "jsr:@std/path@^1.0.8"
1316
},
1417
"lint": {

deno.lock

+70-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server.ts

+11-76
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,28 @@
1-
2-
import { walkSync } from '@std/fs';
3-
import { contentType } from '@std/media-types';
4-
import { extname } from "@std/path";
1+
import { route, type Route } from "@std/http/unstable-route";
2+
import { serveDir } from '@std/http/file-server';
53
import { STEVE } from '@codingap/steve';
6-
import { Handler } from "./src/types.ts";
74
import { getNotFoundResponse } from "./src/middleware.ts";
85

96
const PORT = 1337;
107

118
STEVE.includeDirectory = './views/includes';
129

13-
// read in static files
14-
const staticDirectory = 'static';
15-
const STATIC_CONTENT: { [key: string]: Uint8Array } = {};
16-
Array.from(walkSync(staticDirectory)).filter(file => file.isFile).forEach(file => STATIC_CONTENT[file.path] = Deno.readFileSync(file.path));
17-
1810
// routing time
19-
20-
// ~~~ this should be the only part you need to modify
2111
import MAIN_ROUTES from './src/routes.ts';
2212
import API_ROUTES from './src/api.ts';
2313

24-
const routers = [{ router: MAIN_ROUTES, startingPath: '' }, { router: API_ROUTES, startingPath: '/api' }];
25-
// ~~~ end modification ~~~
26-
27-
// combine all routers together
28-
29-
const ROUTES: { [key: string]: { match: (path: string) => { match: boolean, url: { [key: string]: string }}, handler: Handler }[] } = {};
30-
routers.forEach(({ router, startingPath }) => {
31-
Object.keys(router).forEach(method => {
32-
if (ROUTES[method] === undefined) ROUTES[method] = [];
33-
Object.keys(router[method]).forEach(route => {
34-
const routeParts = (startingPath + route).split('/').filter(part => part.length > 0);
35-
36-
ROUTES[method].push({
37-
match: (path) => {
38-
const pathParts = path.split('/').filter(part => part.length > 0);
39-
if (pathParts.length !== routeParts.length) return { match: false, url: {} };
40-
41-
let match = true;
42-
const url: { [key: string]: string } = {};
43-
44-
for (let i = 0; i < pathParts.length; i++) {
45-
// parse url parameter if needed
46-
if (routeParts[i][0] === ':') url[routeParts[i].slice(1)] = pathParts[i];
47-
else if (routeParts[i] !== pathParts[i]) {
48-
match = false;
49-
break;
50-
}
51-
}
52-
53-
return { match, url };
54-
},
55-
handler: router[method][route]
56-
});
57-
});
58-
});
59-
});
14+
const routers: Route[] = [
15+
...MAIN_ROUTES,
16+
...API_ROUTES,
17+
{
18+
pattern: new URLPattern({ pathname: '/static/*' }),
19+
handler: (request) => serveDir(request, { quiet: true })
20+
}
21+
];
6022

6123
Deno.serve({
6224
port: PORT,
63-
handler: async (request) => {
64-
// handle static files
65-
const url = new URL(request.url);
66-
const path = url.pathname.slice(1);
67-
if (path.startsWith(staticDirectory)) {
68-
const mimeType = contentType(extname(path)) || 'text/plaintext';
69-
if (STATIC_CONTENT[path] !== undefined) {
70-
return new Response(STATIC_CONTENT[path], {
71-
status: 200,
72-
headers: {
73-
'Content-Type': mimeType
74-
}
75-
});
76-
} else return getNotFoundResponse();
77-
}
78-
79-
// try getting routes
80-
const method = request.method.toUpperCase();
81-
if (ROUTES[method] !== undefined) {
82-
for (const route of ROUTES[method]) {
83-
const { match, url } = route.match(path);
84-
if (match) return await route.handler(request, url);
85-
}
86-
}
87-
88-
// else, return not found
89-
return getNotFoundResponse();
90-
},
25+
handler: route(routers, getNotFoundResponse),
9126
onListen: () => {
9227
console.log(`https://codingap.dev is listening on port ${PORT} (http://localhost:${PORT})`);
9328
}

src/api.ts

+41-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
1-
import { Router } from './types.ts';
1+
import { type Route } from '@std/http/unstable-route';
2+
import { setCookie } from '@std/http/cookie';
3+
import { crypto } from '@std/crypto';
4+
import { encodeHex } from '@std/encoding/hex';
5+
import { authenticated, encrypt } from './middleware.ts';
6+
import { LoginRequestBody } from './types.ts';
27

3-
const ROUTES: Router = {};
8+
const defaultHeaders = new Headers();
9+
defaultHeaders.append('Content-Type', 'application/json');
10+
11+
const ROUTES: Route[] = [
12+
{
13+
method: ['POST'],
14+
pattern: new URLPattern({ pathname: '/api/cms/login' }),
15+
handler: async (request) => {
16+
if (request.headers.get('Content-Type') === 'application/json') {
17+
const body = (await request.json()) as LoginRequestBody;
18+
19+
// check to see if logged in
20+
if (body.check && await authenticated(request)) {
21+
return new Response(JSON.stringify({ message: 'Already Logged In!', error: false }), { status: 200, headers: defaultHeaders });
22+
}
23+
24+
// encrypted session token and send to client
25+
const passwordBuffer = new TextEncoder().encode(body.password + Deno.env.get('SALT'));
26+
const hashBuffer = await crypto.subtle.digest('SHA-256', passwordBuffer);
27+
if (Deno.env.get('HASH') === encodeHex(hashBuffer)) {
28+
const cookie = await encrypt(JSON.stringify({ loggedIn: new Date().toISOString(), id: 'codingap' }));
29+
const headers = new Headers();
30+
headers.append('Content-Type', 'application/json');
31+
setCookie(headers, { name: 'session', value: cookie, path: '/', sameSite: 'Lax' });
32+
33+
return new Response(JSON.stringify({ message: 'Success!', error: false }), { status: 200, headers });
34+
} else {
35+
return new Response(JSON.stringify({ message: 'Incorrect password!', error: true }), { status: 400, headers: defaultHeaders });
36+
}
37+
}
38+
39+
return new Response(JSON.stringify({ message: 'Failed to login!', error: true }), { status: 400, headers: defaultHeaders });
40+
}
41+
},
42+
];
443

544
export default ROUTES;

0 commit comments

Comments
 (0)