@axium/server 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icons/brands.svg +1493 -0
- package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
- package/dist/api/index.js +5 -0
- package/dist/api/metadata.d.ts +1 -0
- package/dist/api/metadata.js +28 -0
- package/dist/api/passkeys.d.ts +1 -0
- package/dist/api/passkeys.js +50 -0
- package/dist/api/register.d.ts +1 -0
- package/dist/api/register.js +70 -0
- package/dist/api/session.d.ts +1 -0
- package/dist/api/session.js +31 -0
- package/dist/api/users.d.ts +1 -0
- package/dist/api/users.js +244 -0
- package/dist/apps.d.ts +0 -5
- package/dist/apps.js +2 -9
- package/dist/auth.d.ts +14 -30
- package/dist/auth.js +12 -18
- package/dist/cli.js +289 -32
- package/dist/config.d.ts +21 -8
- package/dist/config.js +46 -17
- package/dist/database.d.ts +12 -12
- package/dist/database.js +83 -84
- package/dist/io.d.ts +19 -20
- package/dist/io.js +85 -56
- package/dist/linking.d.ts +10 -0
- package/dist/linking.js +76 -0
- package/dist/plugins.d.ts +28 -12
- package/dist/plugins.js +29 -25
- package/dist/requests.d.ts +14 -0
- package/dist/requests.js +67 -0
- package/dist/routes.d.ts +12 -13
- package/dist/routes.js +21 -22
- package/dist/serve.d.ts +7 -0
- package/dist/serve.js +11 -0
- package/dist/state.d.ts +4 -0
- package/dist/state.js +22 -0
- package/dist/sveltekit.d.ts +8 -0
- package/dist/sveltekit.js +94 -0
- package/package.json +17 -8
- package/{web/routes → routes}/account/+page.svelte +6 -5
- package/svelte.config.js +37 -0
- package/web/hooks.server.ts +8 -3
- package/web/lib/Dialog.svelte +0 -1
- package/web/lib/FormDialog.svelte +0 -1
- package/web/lib/Upload.svelte +58 -0
- package/web/lib/icons/Icon.svelte +2 -7
- package/web/lib/icons/index.ts +6 -3
- package/web/lib/icons/mime.json +2 -1
- package/web/template.html +18 -0
- package/web/tsconfig.json +2 -2
- package/web/api/metadata.ts +0 -35
- package/web/api/passkeys.ts +0 -56
- package/web/api/readme.md +0 -1
- package/web/api/register.ts +0 -83
- package/web/api/schemas.ts +0 -22
- package/web/api/session.ts +0 -33
- package/web/api/users.ts +0 -351
- package/web/api/utils.ts +0 -66
- package/web/app.html +0 -14
- package/web/auth.ts +0 -8
- package/web/index.server.ts +0 -1
- package/web/index.ts +0 -1
- package/web/lib/auth.ts +0 -12
- package/web/lib/index.ts +0 -5
- package/web/routes/+layout.svelte +0 -6
- package/web/routes/[...path]/+page.server.ts +0 -13
- package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
- package/web/routes/api/[...path]/+server.ts +0 -49
- package/web/utils.ts +0 -26
- /package/{web/lib → assets}/icons/light.svg +0 -0
- /package/{web/lib → assets}/icons/regular.svg +0 -0
- /package/{web/lib → assets}/icons/solid.svg +0 -0
- /package/{web/lib → assets}/styles.css +0 -0
- /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
- /package/{web/routes → routes}/login/+page.svelte +0 -0
- /package/{web/routes → routes}/logout/+page.svelte +0 -0
- /package/{web/routes → routes}/register/+page.svelte +0 -0
package/dist/io.js
CHANGED
|
@@ -2,29 +2,32 @@ import { Logger } from 'logzen';
|
|
|
2
2
|
import { exec } from 'node:child_process';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path/posix';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path/posix';
|
|
6
6
|
import { styleText } from 'node:util';
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
fs.mkdirSync(
|
|
7
|
+
import { _unique } from './state.js';
|
|
8
|
+
export const systemDir = '/etc/axium';
|
|
9
|
+
export const userDir = join(homedir(), '.axium');
|
|
10
|
+
export const dirs = _unique('dirs', [systemDir, userDir]);
|
|
11
|
+
for (let dir = resolve(process.cwd()); dir !== '/'; dir = dirname(dir)) {
|
|
12
|
+
if (fs.existsSync(join(dir, '.axium')))
|
|
13
|
+
dirs.push(join(dir, '.axium'));
|
|
14
|
+
}
|
|
15
|
+
if (process.env.AXIUM_DIR)
|
|
16
|
+
dirs.push(process.env.AXIUM_DIR);
|
|
17
|
+
try {
|
|
18
|
+
fs.mkdirSync(systemDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Missing permissions
|
|
22
|
+
}
|
|
23
|
+
fs.mkdirSync(userDir, { recursive: true });
|
|
24
24
|
export const logger = new Logger({
|
|
25
25
|
hideWarningStack: true,
|
|
26
26
|
noGlobalConsole: true,
|
|
27
27
|
});
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
28
31
|
export const output = {
|
|
29
32
|
constructor: { name: 'Console' },
|
|
30
33
|
error(message) {
|
|
@@ -40,20 +43,24 @@ export const output = {
|
|
|
40
43
|
console.log(message);
|
|
41
44
|
},
|
|
42
45
|
debug(message) {
|
|
43
|
-
console.debug(message.startsWith('\x1b') ? message : styleText('gray', message));
|
|
46
|
+
_debugOutput && console.debug(message.startsWith('\x1b') ? message : styleText('gray', message));
|
|
44
47
|
},
|
|
45
48
|
};
|
|
46
49
|
logger.attach(output);
|
|
50
|
+
let timeout = 1000;
|
|
51
|
+
export function setCommandTimeout(value) {
|
|
52
|
+
timeout = value;
|
|
53
|
+
}
|
|
47
54
|
/**
|
|
48
55
|
* Run a system command with the fancy "Example... done."
|
|
49
56
|
* @internal
|
|
50
57
|
*/
|
|
51
|
-
export async function run(
|
|
58
|
+
export async function run(message, command) {
|
|
52
59
|
let stderr;
|
|
53
60
|
try {
|
|
54
|
-
|
|
61
|
+
start(message);
|
|
55
62
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
56
|
-
exec(command,
|
|
63
|
+
exec(command, { timeout }, (err, stdout, _stderr) => {
|
|
57
64
|
stderr = _stderr.startsWith('ERROR:') ? _stderr.slice(6).trim() : _stderr;
|
|
58
65
|
if (err)
|
|
59
66
|
reject('[command]');
|
|
@@ -61,7 +68,7 @@ export async function run(opts, message, command) {
|
|
|
61
68
|
resolve(stdout);
|
|
62
69
|
});
|
|
63
70
|
const value = await promise;
|
|
64
|
-
|
|
71
|
+
done();
|
|
65
72
|
return value;
|
|
66
73
|
}
|
|
67
74
|
catch (error) {
|
|
@@ -85,10 +92,17 @@ export function handleError(e) {
|
|
|
85
92
|
else
|
|
86
93
|
exit(e);
|
|
87
94
|
}
|
|
88
|
-
|
|
95
|
+
let _debugOutput = false;
|
|
96
|
+
/**
|
|
97
|
+
* Enable or disable debug output.
|
|
98
|
+
*/
|
|
99
|
+
export function _setDebugOutput(enabled) {
|
|
100
|
+
_debugOutput = enabled;
|
|
101
|
+
}
|
|
102
|
+
function defaultOutput(tag, message = '') {
|
|
89
103
|
switch (tag) {
|
|
90
104
|
case 'debug':
|
|
91
|
-
|
|
105
|
+
_debugOutput && output.debug(message);
|
|
92
106
|
break;
|
|
93
107
|
case 'info':
|
|
94
108
|
console.log(message);
|
|
@@ -109,15 +123,31 @@ export function defaultOutput(tag, message = '') {
|
|
|
109
123
|
console.log(styleText('whiteBright', 'Running plugin: ' + message));
|
|
110
124
|
}
|
|
111
125
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
let _taggedOutput = defaultOutput;
|
|
127
|
+
export function useTaggedOutput(output) {
|
|
128
|
+
_taggedOutput = output;
|
|
129
|
+
}
|
|
130
|
+
// Shortcuts for tagged output
|
|
131
|
+
export function done() {
|
|
132
|
+
_taggedOutput?.('done');
|
|
133
|
+
}
|
|
134
|
+
export function start(message) {
|
|
135
|
+
_taggedOutput?.('start', message);
|
|
136
|
+
}
|
|
137
|
+
export function plugin(name) {
|
|
138
|
+
_taggedOutput?.('plugin', name);
|
|
139
|
+
}
|
|
140
|
+
export function debug(message) {
|
|
141
|
+
_taggedOutput?.('debug', message);
|
|
142
|
+
}
|
|
143
|
+
export function info(message) {
|
|
144
|
+
_taggedOutput?.('info', message);
|
|
145
|
+
}
|
|
146
|
+
export function warn(message) {
|
|
147
|
+
_taggedOutput?.('warn', message);
|
|
148
|
+
}
|
|
149
|
+
export function error(message) {
|
|
150
|
+
_taggedOutput?.('error', message);
|
|
121
151
|
}
|
|
122
152
|
/** @internal */
|
|
123
153
|
export const _portMethods = ['node-cap'];
|
|
@@ -129,46 +159,45 @@ export const _portActions = ['enable', 'disable'];
|
|
|
129
159
|
* If the origin has a port, passkeys do not work correctly with some password managers.
|
|
130
160
|
*/
|
|
131
161
|
export async function restrictedPorts(opt) {
|
|
132
|
-
|
|
133
|
-
opt.output('start', 'Checking for root privileges');
|
|
162
|
+
start('Checking for root privileges');
|
|
134
163
|
if (process.getuid?.() != 0)
|
|
135
164
|
throw 'root privileges are needed to change restricted ports.';
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
done();
|
|
166
|
+
start('Checking ports method');
|
|
138
167
|
if (!_portMethods.includes(opt.method))
|
|
139
168
|
throw 'invalid';
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
done();
|
|
170
|
+
start('Checking ports action');
|
|
142
171
|
if (!_portActions.includes(opt.action))
|
|
143
172
|
throw 'invalid';
|
|
144
|
-
|
|
173
|
+
done();
|
|
145
174
|
switch (opt.method) {
|
|
146
175
|
case 'node-cap': {
|
|
147
|
-
const setcap = await run(
|
|
176
|
+
const setcap = await run('Finding setcap', 'command -v setcap')
|
|
148
177
|
.then(e => e.trim())
|
|
149
178
|
.catch(() => {
|
|
150
|
-
|
|
151
|
-
|
|
179
|
+
warn('not in path.');
|
|
180
|
+
start('Checking for /usr/sbin/setcap');
|
|
152
181
|
fs.accessSync('/usr/sbin/setcap', fs.constants.X_OK);
|
|
153
|
-
|
|
182
|
+
done();
|
|
154
183
|
return '/usr/sbin/setcap';
|
|
155
184
|
});
|
|
156
|
-
|
|
185
|
+
debug('Using setcap at ' + setcap);
|
|
157
186
|
let { node } = opt;
|
|
158
|
-
node ||= await run(
|
|
187
|
+
node ||= await run('Finding node', 'command -v node')
|
|
159
188
|
.then(e => e.trim())
|
|
160
189
|
.catch(() => {
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
warn('not in path.');
|
|
191
|
+
start('Checking for /usr/bin/node');
|
|
163
192
|
fs.accessSync('/usr/bin/node', fs.constants.X_OK);
|
|
164
|
-
|
|
193
|
+
done();
|
|
165
194
|
return '/usr/bin/node';
|
|
166
195
|
});
|
|
167
|
-
|
|
196
|
+
start('Resolving real path for node');
|
|
168
197
|
node = fs.realpathSync(node);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
await run(
|
|
198
|
+
done();
|
|
199
|
+
debug('Using node at ' + node);
|
|
200
|
+
await run('Setting ports capability', `${setcap} cap_net_bind_service=${opt.action == 'enable' ? '+' : '-'}ep ${node}`);
|
|
172
201
|
break;
|
|
173
202
|
}
|
|
174
203
|
}
|
|
@@ -178,13 +207,13 @@ export async function restrictedPorts(opt) {
|
|
|
178
207
|
* The handler will allow the parent scope to continue if a relation already exists,
|
|
179
208
|
* rather than fatally exiting.
|
|
180
209
|
*/
|
|
181
|
-
export function someWarnings(
|
|
210
|
+
export function someWarnings(...allowList) {
|
|
182
211
|
return (error) => {
|
|
183
212
|
error = typeof error == 'object' && 'message' in error ? error.message : error;
|
|
184
213
|
for (const [pattern, message = error] of allowList) {
|
|
185
214
|
if (!pattern.test(error))
|
|
186
215
|
continue;
|
|
187
|
-
|
|
216
|
+
warn(message);
|
|
188
217
|
return;
|
|
189
218
|
}
|
|
190
219
|
throw error;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function listRouteLinks(): Generator<{
|
|
2
|
+
id: string;
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Symlinks .svelte page routes for plugins and the server into a project's routes directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function linkRoutes(): void;
|
|
10
|
+
export declare function unlinkRoutes(): void;
|
package/dist/linking.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, symlinkSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path/posix';
|
|
3
|
+
import config from './config.js';
|
|
4
|
+
import * as io from './io.js';
|
|
5
|
+
import { getSpecifier, plugins } from './plugins.js';
|
|
6
|
+
const textFor = {
|
|
7
|
+
builtin: 'built-in routes',
|
|
8
|
+
};
|
|
9
|
+
function info(id) {
|
|
10
|
+
const text = id.startsWith('#') ? textFor[id.slice(1)] : `routes for plugin: ${id}`;
|
|
11
|
+
const link = join(config.web.routes, `(${id.startsWith('#') ? id.slice(1) : id.replaceAll('/', '__')}`);
|
|
12
|
+
return [text, link];
|
|
13
|
+
}
|
|
14
|
+
export function* listRouteLinks() {
|
|
15
|
+
const [, link] = info('#builtin');
|
|
16
|
+
yield { id: '#builtin', from: resolve(import.meta.dirname, '../routes'), to: link };
|
|
17
|
+
for (const plugin of plugins) {
|
|
18
|
+
if (!plugin.routes)
|
|
19
|
+
continue;
|
|
20
|
+
const [, link] = info(plugin.name);
|
|
21
|
+
yield { id: plugin.name, from: join(import.meta.resolve(getSpecifier(plugin)), plugin.routes), to: link };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function createLink(id, routes) {
|
|
25
|
+
const [text, link] = info(id);
|
|
26
|
+
if (existsSync(link)) {
|
|
27
|
+
io.warn('already exists.');
|
|
28
|
+
io.start('Unlinking ' + text);
|
|
29
|
+
try {
|
|
30
|
+
unlinkSync(link);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
io.exit(e && e instanceof Error ? e.message : e.toString());
|
|
34
|
+
}
|
|
35
|
+
io.done();
|
|
36
|
+
io.start('Re-linking ' + text);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
symlinkSync(routes, link, 'dir');
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
io.exit(e && e instanceof Error ? e.message : e.toString());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Symlinks .svelte page routes for plugins and the server into a project's routes directory.
|
|
47
|
+
*/
|
|
48
|
+
export function linkRoutes() {
|
|
49
|
+
io.start('Linking built-in routes');
|
|
50
|
+
createLink('#builtin', resolve(import.meta.dirname, '../routes'));
|
|
51
|
+
for (const plugin of plugins) {
|
|
52
|
+
if (!plugin.routes)
|
|
53
|
+
continue;
|
|
54
|
+
io.start(`Linking routes for plugin: ${plugin.name}`);
|
|
55
|
+
createLink(plugin.name, join(import.meta.resolve(getSpecifier(plugin)), plugin.routes));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function removeLink(id) {
|
|
59
|
+
const [text, link] = info(id);
|
|
60
|
+
if (!existsSync(link))
|
|
61
|
+
return;
|
|
62
|
+
io.start('Unlinking ' + text);
|
|
63
|
+
try {
|
|
64
|
+
unlinkSync(link);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
io.exit(e && e instanceof Error ? e.message : e.toString());
|
|
68
|
+
}
|
|
69
|
+
io.done();
|
|
70
|
+
}
|
|
71
|
+
export function unlinkRoutes() {
|
|
72
|
+
removeLink('#builtin');
|
|
73
|
+
for (const plugin of plugins) {
|
|
74
|
+
removeLink(plugin.name);
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/plugins.d.ts
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import z from 'zod/v4';
|
|
2
|
-
|
|
2
|
+
import type { Database, InitOptions, OpOptions } from './database.js';
|
|
3
|
+
export declare const PluginMetadata: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
version: z.ZodString;
|
|
6
|
+
description: z.ZodOptional<z.ZodString>;
|
|
7
|
+
routes: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$loose>;
|
|
3
9
|
export declare const Plugin: z.ZodObject<{
|
|
4
|
-
id: z.ZodString;
|
|
5
10
|
name: z.ZodString;
|
|
6
11
|
version: z.ZodString;
|
|
7
12
|
description: z.ZodOptional<z.ZodString>;
|
|
13
|
+
routes: z.ZodOptional<z.ZodString>;
|
|
8
14
|
statusText: z.ZodCustom<z.core.$InferInnerFunctionTypeAsync<z.core.$ZodTuple<[], null>, z.ZodString>, z.core.$InferInnerFunctionTypeAsync<z.core.$ZodTuple<[], null>, z.ZodString>>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
hooks: z.ZodOptional<z.ZodRecord<z.ZodUnion<[z.ZodLiteral<"remove" | "db_init" | "db_wipe" | "clean">, z.ZodNever]>, z.ZodCustom<(...args: any[]) => any, (...args: any[]) => any>>>;
|
|
16
|
+
}, z.core.$loose>;
|
|
17
|
+
declare const kSpecifier: unique symbol;
|
|
18
|
+
export type Plugin = z.infer<typeof Plugin>;
|
|
19
|
+
interface PluginInternal extends Plugin {
|
|
20
|
+
[kSpecifier]: string;
|
|
21
|
+
hooks: Hooks;
|
|
22
|
+
}
|
|
23
|
+
export interface Hooks {
|
|
24
|
+
db_init?: (opt: InitOptions, db: Database) => void | Promise<void>;
|
|
25
|
+
remove?: (opt: {
|
|
26
|
+
force?: boolean;
|
|
27
|
+
}, db: Database) => void | Promise<void>;
|
|
28
|
+
db_wipe?: (opt: OpOptions, db: Database) => void | Promise<void>;
|
|
29
|
+
clean?: (opt: Partial<OpOptions>, db: Database) => void | Promise<void>;
|
|
15
30
|
}
|
|
16
|
-
export declare const plugins: Set<
|
|
17
|
-
export declare function resolvePlugin(search: string):
|
|
18
|
-
export declare function pluginText(plugin:
|
|
31
|
+
export declare const plugins: Set<PluginInternal>;
|
|
32
|
+
export declare function resolvePlugin(search: string): PluginInternal | undefined;
|
|
33
|
+
export declare function pluginText(plugin: PluginInternal): string;
|
|
19
34
|
export declare function loadPlugin(specifier: string): Promise<void>;
|
|
35
|
+
export declare function getSpecifier(plugin: PluginInternal): string;
|
|
20
36
|
export declare function loadPlugins(dir: string): Promise<void>;
|
|
21
|
-
export
|
|
37
|
+
export {};
|
package/dist/plugins.js
CHANGED
|
@@ -1,52 +1,60 @@
|
|
|
1
|
+
import { zAsyncFunction } from '@axium/core/schemas';
|
|
1
2
|
import * as fs from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
+
import { resolve } from 'node:path/posix';
|
|
3
4
|
import { styleText } from 'node:util';
|
|
4
5
|
import z from 'zod/v4';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
export const
|
|
8
|
-
export const Plugin = z.object({
|
|
9
|
-
id: z.string(),
|
|
6
|
+
import { output } from './io.js';
|
|
7
|
+
import { _unique } from './state.js';
|
|
8
|
+
export const PluginMetadata = z.looseObject({
|
|
10
9
|
name: z.string(),
|
|
11
10
|
version: z.string(),
|
|
12
11
|
description: z.string().optional(),
|
|
12
|
+
routes: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
const hookNames = ['db_init', 'remove', 'db_wipe', 'clean'];
|
|
15
|
+
const fn = z.custom(data => typeof data === 'function');
|
|
16
|
+
export const Plugin = PluginMetadata.extend({
|
|
13
17
|
statusText: zAsyncFunction(z.function({ input: [], output: z.string() })),
|
|
14
|
-
|
|
15
|
-
db_remove: fn.optional(),
|
|
16
|
-
db_wipe: fn.optional(),
|
|
17
|
-
db_clean: fn.optional(),
|
|
18
|
+
hooks: z.partialRecord(z.literal(hookNames), fn).optional(),
|
|
18
19
|
});
|
|
19
|
-
|
|
20
|
+
const kSpecifier = Symbol('specifier');
|
|
21
|
+
export const plugins = _unique('plugins', new Set());
|
|
20
22
|
export function resolvePlugin(search) {
|
|
21
23
|
for (const plugin of plugins) {
|
|
22
|
-
if (plugin.name
|
|
24
|
+
if (plugin.name === search)
|
|
23
25
|
return plugin;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
export function pluginText(plugin) {
|
|
27
29
|
return [
|
|
28
30
|
styleText('whiteBright', plugin.name),
|
|
29
|
-
plugin.id,
|
|
30
31
|
`Version: ${plugin.version}`,
|
|
31
32
|
`Description: ${plugin.description ?? styleText('dim', '(none)')}`,
|
|
32
|
-
`
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.join(', ') || styleText('dim', '(none)')}`,
|
|
33
|
+
`Hooks: ${Object.keys(plugin.hooks).join(', ') || styleText('dim', '(none)')}`,
|
|
34
|
+
// @todo list the routes when debug output is enabled
|
|
35
|
+
`Routes: ${plugin.routes || styleText('dim', '(none)')}`,
|
|
36
36
|
].join('\n');
|
|
37
37
|
}
|
|
38
38
|
export async function loadPlugin(specifier) {
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
40
|
+
const imported = await import(/* @vite-ignore */ specifier);
|
|
41
|
+
const maybePlugin = 'default' in imported ? imported.default : imported;
|
|
42
|
+
const plugin = Object.assign({ hooks: {}, [kSpecifier]: specifier }, await Plugin.parseAsync(maybePlugin).catch(e => {
|
|
41
43
|
throw z.prettifyError(e);
|
|
42
|
-
});
|
|
44
|
+
}));
|
|
45
|
+
if (plugin.name.startsWith('#') || plugin.name.includes(' ')) {
|
|
46
|
+
throw 'Invalid plugin name. Plugin names can not start with a hash or contain spaces.';
|
|
47
|
+
}
|
|
43
48
|
plugins.add(plugin);
|
|
44
|
-
output.debug(`Loaded plugin:
|
|
49
|
+
output.debug(`Loaded plugin: ${plugin.name} ${plugin.version}`);
|
|
45
50
|
}
|
|
46
51
|
catch (e) {
|
|
47
|
-
output.debug(`Failed to load plugin from ${specifier}: ${e.message
|
|
52
|
+
output.debug(`Failed to load plugin from ${specifier}: ${e ? (e instanceof Error ? e.message : e.toString()) : e}`);
|
|
48
53
|
}
|
|
49
54
|
}
|
|
55
|
+
export function getSpecifier(plugin) {
|
|
56
|
+
return plugin[kSpecifier];
|
|
57
|
+
}
|
|
50
58
|
export async function loadPlugins(dir) {
|
|
51
59
|
fs.mkdirSync(dir, { recursive: true });
|
|
52
60
|
const files = fs.readdirSync(dir);
|
|
@@ -58,7 +66,3 @@ export async function loadPlugins(dir) {
|
|
|
58
66
|
await loadPlugin(path);
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
|
-
export async function loadDefaultPlugins() {
|
|
62
|
-
await loadPlugins(join(findDir(true), 'plugins'));
|
|
63
|
-
await loadPlugins(join(findDir(false), 'plugins'));
|
|
64
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type User } from '@axium/core/user';
|
|
2
|
+
import { type HttpError, type RequestEvent } from '@sveltejs/kit';
|
|
3
|
+
import z from 'zod/v4';
|
|
4
|
+
import { type SessionAndUser, type UserInternal } from './auth.js';
|
|
5
|
+
export declare function parseBody<const Schema extends z.ZodType, const Result extends z.infer<Schema> = z.infer<Schema>>(event: RequestEvent, schema: Schema): Promise<Result>;
|
|
6
|
+
export declare function getToken(event: RequestEvent, sensitive?: boolean): string | undefined;
|
|
7
|
+
export interface AuthResult extends SessionAndUser {
|
|
8
|
+
/** The user authenticating the request. */
|
|
9
|
+
accessor: UserInternal;
|
|
10
|
+
}
|
|
11
|
+
export declare function checkAuth(event: RequestEvent, userId: string, sensitive?: boolean): Promise<AuthResult>;
|
|
12
|
+
export declare function createSessionData(userId: string, elevated?: boolean): Promise<Response>;
|
|
13
|
+
export declare function stripUser(user: UserInternal, includeProtected?: boolean): User;
|
|
14
|
+
export declare function withError(text: string, code?: number): (e: Error | HttpError) => never;
|
package/dist/requests.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { userProtectedFields, userPublicFields } from '@axium/core/user';
|
|
2
|
+
import { error, json } from '@sveltejs/kit';
|
|
3
|
+
import { serialize as serializeCookie } from 'cookie';
|
|
4
|
+
import { pick } from 'utilium';
|
|
5
|
+
import z from 'zod/v4';
|
|
6
|
+
import { createSession, getSessionAndUser, getUser } from './auth.js';
|
|
7
|
+
import { config } from './config.js';
|
|
8
|
+
export async function parseBody(event, schema) {
|
|
9
|
+
const contentType = event.request.headers.get('content-type');
|
|
10
|
+
if (!contentType || !contentType.includes('application/json'))
|
|
11
|
+
error(415, { message: 'Invalid content type' });
|
|
12
|
+
const body = await event.request.json().catch(() => error(415, { message: 'Invalid JSON' }));
|
|
13
|
+
try {
|
|
14
|
+
return schema.parse(body);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
error(400, { message: z.prettifyError(e) });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function getToken(event, sensitive = false) {
|
|
21
|
+
const header_token = event.request.headers.get('Authorization')?.replace('Bearer ', '');
|
|
22
|
+
if (header_token)
|
|
23
|
+
return header_token;
|
|
24
|
+
if (config.debug || config.api.cookie_auth) {
|
|
25
|
+
return event.cookies.get(sensitive ? 'elevated_token' : 'session_token');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function checkAuth(event, userId, sensitive = false) {
|
|
29
|
+
const token = getToken(event, sensitive);
|
|
30
|
+
if (!token)
|
|
31
|
+
throw error(401, { message: 'Missing token' });
|
|
32
|
+
const session = await getSessionAndUser(token).catch(() => error(401, { message: 'Invalid or expired session' }));
|
|
33
|
+
if (session.userId !== userId) {
|
|
34
|
+
if (!session.user?.isAdmin)
|
|
35
|
+
error(403, { message: 'User ID mismatch' });
|
|
36
|
+
// Admins are allowed to manage other users.
|
|
37
|
+
const accessor = session.user;
|
|
38
|
+
session.user = await getUser(userId).catch(() => error(404, { message: 'Target user not found' }));
|
|
39
|
+
return Object.assign(session, { accessor });
|
|
40
|
+
}
|
|
41
|
+
if (!session.elevated && sensitive)
|
|
42
|
+
error(403, 'This token can not be used for sensitive actions');
|
|
43
|
+
return Object.assign(session, { accessor: session.user });
|
|
44
|
+
}
|
|
45
|
+
export async function createSessionData(userId, elevated = false) {
|
|
46
|
+
const { token, expires } = await createSession(userId, elevated);
|
|
47
|
+
const response = json({ userId, token: elevated ? '[[redacted:elevated]]' : token }, { status: 201 });
|
|
48
|
+
const cookies = serializeCookie(elevated ? 'elevated_token' : 'session_token', token, {
|
|
49
|
+
httpOnly: true,
|
|
50
|
+
path: '/',
|
|
51
|
+
expires,
|
|
52
|
+
secure: config.auth.secure_cookies,
|
|
53
|
+
sameSite: 'lax',
|
|
54
|
+
});
|
|
55
|
+
response.headers.set('Set-Cookie', cookies);
|
|
56
|
+
return response;
|
|
57
|
+
}
|
|
58
|
+
export function stripUser(user, includeProtected = false) {
|
|
59
|
+
return pick(user, ...userPublicFields, ...(includeProtected ? userProtectedFields : []));
|
|
60
|
+
}
|
|
61
|
+
export function withError(text, code = 500) {
|
|
62
|
+
return function (e) {
|
|
63
|
+
if ('body' in e)
|
|
64
|
+
throw e;
|
|
65
|
+
error(code, { message: text + (config.debug && e.message ? `: ${e.message}` : '') });
|
|
66
|
+
};
|
|
67
|
+
}
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { RequestMethod } from '@axium/core/requests';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RequestEvent } from '@sveltejs/kit';
|
|
3
3
|
import type { Component } from 'svelte';
|
|
4
4
|
import type z from 'zod/v4';
|
|
5
5
|
type _Params = Partial<Record<string, string>>;
|
|
6
|
-
|
|
6
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
7
|
+
export type EndpointHandlers<Params extends _Params = _Params> = Partial<Record<RequestMethod, (event: RequestEvent<Params>) => MaybePromise<object | Response>>>;
|
|
7
8
|
export type RouteParamOptions = z.ZodType;
|
|
8
9
|
export interface CommonRouteOptions<Params extends _Params = _Params> {
|
|
9
10
|
path: string;
|
|
@@ -15,6 +16,7 @@ export interface CommonRouteOptions<Params extends _Params = _Params> {
|
|
|
15
16
|
* A route with server-side handlers for different HTTP methods.
|
|
16
17
|
*/
|
|
17
18
|
export interface ServerRouteOptions<Params extends _Params = _Params> extends CommonRouteOptions<Params>, EndpointHandlers<Params> {
|
|
19
|
+
api?: boolean;
|
|
18
20
|
}
|
|
19
21
|
export interface WebRouteOptions extends CommonRouteOptions {
|
|
20
22
|
load?(event: RequestEvent): object | Promise<object>;
|
|
@@ -25,31 +27,28 @@ export type RouteOptions = ServerRouteOptions | WebRouteOptions;
|
|
|
25
27
|
export interface RouteCommon {
|
|
26
28
|
path: string;
|
|
27
29
|
params?: Record<string, RouteParamOptions>;
|
|
28
|
-
[kBuiltin]: boolean;
|
|
29
30
|
}
|
|
30
31
|
export interface ServerRoute extends RouteCommon, EndpointHandlers {
|
|
32
|
+
api: boolean;
|
|
31
33
|
server: true;
|
|
32
34
|
}
|
|
33
35
|
export interface WebRoute extends RouteCommon {
|
|
34
36
|
server: false;
|
|
35
|
-
load?(event:
|
|
36
|
-
page
|
|
37
|
+
load?(event: RequestEvent): object | Promise<object>;
|
|
38
|
+
page: Component;
|
|
37
39
|
}
|
|
38
40
|
export type Route = ServerRoute | WebRoute;
|
|
39
41
|
/**
|
|
40
42
|
* @internal
|
|
41
43
|
*/
|
|
42
44
|
export declare const routes: Map<string, Route>;
|
|
43
|
-
declare
|
|
44
|
-
export declare function addRoute(opt: RouteOptions, _routeMap?: Map<string, Route>): void;
|
|
45
|
+
export declare function addRoute(opt: RouteOptions): void;
|
|
45
46
|
/**
|
|
46
47
|
* Resolve a request URL into a route.
|
|
47
48
|
* This handles parsing of parameters in the URL.
|
|
48
49
|
*/
|
|
49
|
-
export declare function resolveRoute
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*/
|
|
54
|
-
export declare function _markDefaults(): void;
|
|
50
|
+
export declare function resolveRoute(event: {
|
|
51
|
+
url: URL;
|
|
52
|
+
params?: object;
|
|
53
|
+
}): Route | undefined;
|
|
55
54
|
export {};
|