@axium/server 0.0.1 → 0.0.3
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/README.md +1 -0
- package/dist/auth.d.ts +28 -0
- package/dist/auth.js +136 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +153 -234
- package/dist/config.d.ts +270 -0
- package/dist/config.js +122 -0
- package/dist/database.d.ts +80 -0
- package/dist/database.js +193 -34
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/io.d.ts +21 -0
- package/dist/io.js +63 -0
- package/package.json +22 -7
- package/web/app.html +15 -0
- package/web/auth.ts +12 -0
- package/web/hooks.server.ts +1 -0
- package/web/lib/Icon.svelte +42 -0
- package/web/routes/+page.server.ts +14 -0
- package/web/routes/+page.svelte +99 -0
- package/web/routes/edit/email/+page.server.ts +35 -0
- package/web/routes/edit/email/+page.svelte +25 -0
- package/web/routes/edit/name/+page.server.ts +35 -0
- package/web/routes/edit/name/+page.svelte +25 -0
- package/web/routes/signup/+page.server.ts +33 -0
- package/web/routes/signup/+page.svelte +27 -0
- package/web/static/axium.css +67 -0
- package/web/tsconfig.json +7 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Axium Server
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Adapter } from '@auth/core/adapters';
|
|
2
|
+
import type { Provider } from '@auth/core/providers';
|
|
3
|
+
import type { AuthConfig } from '@auth/core/types';
|
|
4
|
+
import { Registration } from '@axium/core/schemas';
|
|
5
|
+
declare module '@auth/core/adapters' {
|
|
6
|
+
interface AdapterUser {
|
|
7
|
+
password: string | null;
|
|
8
|
+
salt: string | null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export declare let adapter: Adapter;
|
|
12
|
+
export declare function createAdapter(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Login using credentials
|
|
15
|
+
*/
|
|
16
|
+
export declare function register(credentials: Registration): Promise<{
|
|
17
|
+
user: import("@auth/core/adapters").AdapterUser;
|
|
18
|
+
session: import("@auth/core/adapters").AdapterSession;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Authorize using credentials
|
|
22
|
+
*/
|
|
23
|
+
export declare function authorize(credentials: Partial<Record<string, unknown>>): Promise<Omit<import("@auth/core/adapters").AdapterUser, "password" | "salt"> | null>;
|
|
24
|
+
type Providers = Exclude<Provider, (...args: any[]) => any>[];
|
|
25
|
+
export declare function getConfig(): AuthConfig & {
|
|
26
|
+
providers: Providers;
|
|
27
|
+
};
|
|
28
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { CredentialsSignin } from '@auth/core/errors';
|
|
2
|
+
import Credentials from '@auth/core/providers/credentials';
|
|
3
|
+
import Passkey from '@auth/core/providers/passkey';
|
|
4
|
+
import { KyselyAdapter } from '@auth/kysely-adapter';
|
|
5
|
+
import { Login, Registration } from '@axium/core/schemas';
|
|
6
|
+
import { genSaltSync, hashSync } from 'bcryptjs';
|
|
7
|
+
import { randomBytes } from 'node:crypto';
|
|
8
|
+
import { omit } from 'utilium';
|
|
9
|
+
import * as config from './config.js';
|
|
10
|
+
import * as db from './database.js';
|
|
11
|
+
import { logger } from './io.js';
|
|
12
|
+
export let adapter;
|
|
13
|
+
export function createAdapter() {
|
|
14
|
+
if (adapter)
|
|
15
|
+
return;
|
|
16
|
+
const conn = db.connect();
|
|
17
|
+
adapter = Object.assign(KyselyAdapter(conn), {
|
|
18
|
+
async getAccount(providerAccountId, provider) {
|
|
19
|
+
const result = await conn.selectFrom('Account').selectAll().where('providerAccountId', '=', providerAccountId).where('provider', '=', provider).executeTakeFirst();
|
|
20
|
+
return result ?? null;
|
|
21
|
+
},
|
|
22
|
+
async getAuthenticator(credentialID) {
|
|
23
|
+
const result = await conn.selectFrom('Authenticator').selectAll().where('credentialID', '=', credentialID).executeTakeFirst();
|
|
24
|
+
return result ?? null;
|
|
25
|
+
},
|
|
26
|
+
async createAuthenticator(authenticator) {
|
|
27
|
+
await conn.insertInto('Authenticator').values(authenticator).executeTakeFirstOrThrow();
|
|
28
|
+
return authenticator;
|
|
29
|
+
},
|
|
30
|
+
async listAuthenticatorsByUserId(userId) {
|
|
31
|
+
const result = await conn.selectFrom('Authenticator').selectAll().where('userId', '=', userId).execute();
|
|
32
|
+
return result;
|
|
33
|
+
},
|
|
34
|
+
async updateAuthenticatorCounter(credentialID, newCounter) {
|
|
35
|
+
await conn.updateTable('Authenticator').set({ counter: newCounter }).where('credentialID', '=', credentialID).executeTakeFirstOrThrow();
|
|
36
|
+
const authenticator = await adapter.getAuthenticator?.(credentialID);
|
|
37
|
+
if (!authenticator)
|
|
38
|
+
throw new Error('Authenticator not found');
|
|
39
|
+
return authenticator;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Login using credentials
|
|
45
|
+
*/
|
|
46
|
+
export async function register(credentials) {
|
|
47
|
+
const { email, password, name } = Registration.parse(credentials);
|
|
48
|
+
const existing = await adapter.getUserByEmail?.(email);
|
|
49
|
+
if (existing)
|
|
50
|
+
throw 'User already exists';
|
|
51
|
+
let id = crypto.randomUUID();
|
|
52
|
+
while (await adapter.getUser?.(id))
|
|
53
|
+
id = crypto.randomUUID();
|
|
54
|
+
const salt = genSaltSync(10);
|
|
55
|
+
const user = await adapter.createUser({
|
|
56
|
+
id,
|
|
57
|
+
name,
|
|
58
|
+
email,
|
|
59
|
+
emailVerified: null,
|
|
60
|
+
salt: password ? salt : null,
|
|
61
|
+
password: password ? hashSync(password, salt) : null,
|
|
62
|
+
});
|
|
63
|
+
const expires = new Date();
|
|
64
|
+
expires.setMonth(expires.getMonth() + 1);
|
|
65
|
+
const session = await adapter.createSession({
|
|
66
|
+
sessionToken: randomBytes(64).toString('base64'),
|
|
67
|
+
userId: id,
|
|
68
|
+
expires,
|
|
69
|
+
});
|
|
70
|
+
return { user, session };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Authorize using credentials
|
|
74
|
+
*/
|
|
75
|
+
export async function authorize(credentials) {
|
|
76
|
+
const { success, error, data } = Login.safeParse(credentials);
|
|
77
|
+
if (!success)
|
|
78
|
+
throw new CredentialsSignin(error);
|
|
79
|
+
const user = await adapter.getUserByEmail?.(data.email);
|
|
80
|
+
if (!user || !data.password || !user.salt)
|
|
81
|
+
return null;
|
|
82
|
+
if (user.password !== hashSync(data.password, user.salt))
|
|
83
|
+
return null;
|
|
84
|
+
return omit(user, 'password', 'salt');
|
|
85
|
+
}
|
|
86
|
+
export function getConfig() {
|
|
87
|
+
createAdapter();
|
|
88
|
+
const providers = [Passkey({})];
|
|
89
|
+
if (config.auth.credentials) {
|
|
90
|
+
providers.push(Credentials({
|
|
91
|
+
credentials: {
|
|
92
|
+
email: { label: 'Email', type: 'email' },
|
|
93
|
+
password: { label: 'Password', type: 'password' },
|
|
94
|
+
},
|
|
95
|
+
authorize,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
const debug = config.auth.debug ?? config.debug;
|
|
99
|
+
return {
|
|
100
|
+
adapter,
|
|
101
|
+
providers,
|
|
102
|
+
debug,
|
|
103
|
+
experimental: { enableWebAuthn: true },
|
|
104
|
+
secret: config.auth.secret,
|
|
105
|
+
useSecureCookies: config.auth.secure_cookies,
|
|
106
|
+
session: { strategy: 'database' },
|
|
107
|
+
logger: {
|
|
108
|
+
error(error) {
|
|
109
|
+
logger.error('[auth] ' + error.message);
|
|
110
|
+
},
|
|
111
|
+
warn(code) {
|
|
112
|
+
switch (code) {
|
|
113
|
+
case 'experimental-webauthn':
|
|
114
|
+
case 'debug-enabled':
|
|
115
|
+
return;
|
|
116
|
+
case 'csrf-disabled':
|
|
117
|
+
logger.warn('CSRF protection is disabled.');
|
|
118
|
+
break;
|
|
119
|
+
case 'env-url-basepath-redundant':
|
|
120
|
+
case 'env-url-basepath-mismatch':
|
|
121
|
+
default:
|
|
122
|
+
logger.warn('[auth] ' + code);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
debug(message, metadata) {
|
|
126
|
+
debug && logger.debug('[auth]', message, metadata ? JSON.stringify(metadata, (k, v) => (k && JSON.stringify(v).length > 100 ? '...' : v)) : '');
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
callbacks: {
|
|
130
|
+
signIn({ user }) {
|
|
131
|
+
logger.info('[auth] signin', user.id ?? '', user.email ? `(${user.email})` : '');
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -1,273 +1,192 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
-
if (value !== null && value !== void 0) {
|
|
4
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
-
var dispose, inner;
|
|
6
|
-
if (async) {
|
|
7
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
-
dispose = value[Symbol.asyncDispose];
|
|
9
|
-
}
|
|
10
|
-
if (dispose === void 0) {
|
|
11
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
-
dispose = value[Symbol.dispose];
|
|
13
|
-
if (async) inner = dispose;
|
|
14
|
-
}
|
|
15
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
-
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
-
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
-
}
|
|
19
|
-
else if (async) {
|
|
20
|
-
env.stack.push({ async: true });
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
};
|
|
24
|
-
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
-
return function (env) {
|
|
26
|
-
function fail(e) {
|
|
27
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
-
env.hasError = true;
|
|
29
|
-
}
|
|
30
|
-
var r, s = 0;
|
|
31
|
-
function next() {
|
|
32
|
-
while (r = env.stack.pop()) {
|
|
33
|
-
try {
|
|
34
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
-
if (r.dispose) {
|
|
36
|
-
var result = r.dispose.call(r.value);
|
|
37
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
-
}
|
|
39
|
-
else s |= 1;
|
|
40
|
-
}
|
|
41
|
-
catch (e) {
|
|
42
|
-
fail(e);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
-
if (env.hasError) throw env.error;
|
|
47
|
-
}
|
|
48
|
-
return next();
|
|
49
|
-
};
|
|
50
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
-
var e = new Error(message);
|
|
52
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
-
});
|
|
54
|
-
import chalk from 'chalk';
|
|
55
2
|
import { Option, program } from 'commander';
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import
|
|
59
|
-
import * as
|
|
60
|
-
|
|
61
|
-
|
|
3
|
+
import { styleText } from 'node:util';
|
|
4
|
+
import { getByString, isJSON, pick, setByString } from 'utilium';
|
|
5
|
+
import $pkg from '../package.json' with { type: 'json' };
|
|
6
|
+
import * as config from './config.js';
|
|
7
|
+
import * as db from './database.js';
|
|
8
|
+
import { exit, output } from './io.js';
|
|
9
|
+
program
|
|
10
|
+
.version($pkg.version)
|
|
11
|
+
.name('axium')
|
|
12
|
+
.description('Axium server CLI')
|
|
13
|
+
.configureHelp({ showGlobalOptions: true })
|
|
14
|
+
.option('-D, --debug', 'override debug mode', false)
|
|
15
|
+
.option('-c, --config <path>', 'path to the config file');
|
|
16
|
+
program.on('option:debug', () => config.set(pick(program.opts(), 'debug')));
|
|
17
|
+
program.on('option:config', () => config.load(program.opts().config));
|
|
18
|
+
program.hook('preAction', function (_, action) {
|
|
19
|
+
config.loadDefaults();
|
|
20
|
+
const opt = action.optsWithGlobals();
|
|
21
|
+
opt.force && output.warn('--force: Protections disabled.');
|
|
22
|
+
});
|
|
23
|
+
// Options shared by multiple (sub)commands
|
|
62
24
|
const opts = {
|
|
63
|
-
|
|
64
|
-
|
|
25
|
+
// database specific
|
|
26
|
+
host: new Option('-H, --host <host>', 'the host of the database.').argParser(value => {
|
|
27
|
+
const [hostname, port] = value?.split(':') ?? [];
|
|
28
|
+
config.db.host = hostname || config.db.host;
|
|
29
|
+
config.db.port = port && Number.isSafeInteger(parseInt(port)) ? parseInt(port) : config.db.port;
|
|
30
|
+
}),
|
|
65
31
|
force: new Option('-f, --force', 'force the operation').default(false),
|
|
66
|
-
verbose: new Option('-v, --verbose', 'verbose output').default(false),
|
|
67
32
|
};
|
|
68
|
-
const axiumDB = program
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
33
|
+
const axiumDB = program
|
|
34
|
+
.command('db')
|
|
35
|
+
.alias('database')
|
|
36
|
+
.description('manage the database')
|
|
37
|
+
.option('-t, --timeout <ms>', 'how long to wait for commands to complete.', '1000')
|
|
38
|
+
.addOption(opts.host);
|
|
39
|
+
function db_output(state, message = '') {
|
|
40
|
+
switch (state) {
|
|
41
|
+
case 'start':
|
|
42
|
+
process.stdout.write(message + '... ');
|
|
43
|
+
break;
|
|
44
|
+
case 'log':
|
|
45
|
+
case 'warn':
|
|
46
|
+
process.stdout.write(styleText('yellow', message));
|
|
47
|
+
break;
|
|
48
|
+
case 'error':
|
|
49
|
+
process.stdout.write(styleText('red', message));
|
|
50
|
+
break;
|
|
51
|
+
case 'done':
|
|
52
|
+
console.log('done.');
|
|
53
|
+
break;
|
|
79
54
|
}
|
|
80
55
|
}
|
|
81
|
-
/** Convenience function for `sudo -u postgres psql -c "${command}"`, plus `report` coolness */
|
|
82
|
-
async function runSQL(opts, command, message) {
|
|
83
|
-
let stderr;
|
|
84
|
-
try {
|
|
85
|
-
process.stdout.write(message + '... ');
|
|
86
|
-
const { promise, resolve, reject } = Promise.withResolvers();
|
|
87
|
-
exec(`sudo -u postgres psql -c "${command}"`, { timeout: 1000, ...opts }, (err, _, _stderr) => {
|
|
88
|
-
stderr = _stderr.startsWith('ERROR:') ? _stderr.slice(6).trim() : _stderr;
|
|
89
|
-
if (err)
|
|
90
|
-
reject('[command]');
|
|
91
|
-
else
|
|
92
|
-
resolve();
|
|
93
|
-
});
|
|
94
|
-
await promise;
|
|
95
|
-
console.log('done.');
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
throw error == '[command]' ? stderr?.slice(0, 100) || 'failed.' : typeof error == 'object' && 'message' in error ? error.message : error;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function err(message) {
|
|
102
|
-
if (message instanceof Error)
|
|
103
|
-
message = message.message;
|
|
104
|
-
console.error(message.startsWith('\x1b') ? message : chalk.red(message));
|
|
105
|
-
}
|
|
106
|
-
/** Yet another convenience function */
|
|
107
|
-
function exit(message, code = 1) {
|
|
108
|
-
err(message);
|
|
109
|
-
process.exit(code);
|
|
110
|
-
}
|
|
111
|
-
function shouldRecreate(opt) {
|
|
112
|
-
if (opt.skip) {
|
|
113
|
-
console.warn(chalk.yellow('already exists. (skipped)'));
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
if (opt.force) {
|
|
117
|
-
console.warn(chalk.yellow('already exists. (re-creating)'));
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
console.warn(chalk.yellow('already exists. Use --skip to skip or --force to re-create.'));
|
|
121
|
-
process.exit(2);
|
|
122
|
-
}
|
|
123
56
|
axiumDB
|
|
124
57
|
.command('init')
|
|
125
58
|
.description('initialize the database')
|
|
126
|
-
.addOption(opts.host)
|
|
127
|
-
.addOption(opts.timeout)
|
|
128
59
|
.addOption(opts.force)
|
|
129
|
-
.addOption(opts.verbose)
|
|
130
60
|
.option('-s, --skip', 'Skip existing database and/or user')
|
|
131
61
|
.action(async (opt) => {
|
|
132
|
-
|
|
62
|
+
await db.init({ ...opt, output: db_output }).catch((e) => {
|
|
63
|
+
if (typeof e == 'number')
|
|
64
|
+
process.exit(e);
|
|
65
|
+
else
|
|
66
|
+
exit(e);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
axiumDB
|
|
70
|
+
.command('status')
|
|
71
|
+
.alias('stats')
|
|
72
|
+
.description('check the status of the database')
|
|
73
|
+
.action(async () => {
|
|
133
74
|
try {
|
|
134
|
-
|
|
135
|
-
const config = _db.normalizeConfig(opt);
|
|
136
|
-
config.password ??= process.env.PGPASSWORD || randomBytes(32).toString('base64').replaceAll('=', '').replaceAll('/', '_').replaceAll('+', '-');
|
|
137
|
-
await runSQL(opt, 'CREATE DATABASE axium', 'Creating database')
|
|
138
|
-
.catch(async (error) => {
|
|
139
|
-
if (error != 'database "axium" already exists')
|
|
140
|
-
exit(error);
|
|
141
|
-
if (shouldRecreate(opt))
|
|
142
|
-
return;
|
|
143
|
-
await runSQL(opt, 'DROP DATABASE axium', 'Dropping database');
|
|
144
|
-
await runSQL(opt, 'CREATE DATABASE axium', 'Re-creating database');
|
|
145
|
-
})
|
|
146
|
-
.catch(exit);
|
|
147
|
-
const createQuery = `CREATE USER axium WITH ENCRYPTED PASSWORD '${config.password}' LOGIN`;
|
|
148
|
-
await runSQL(opt, createQuery, 'Creating user')
|
|
149
|
-
.catch(async (error) => {
|
|
150
|
-
if (error != 'role "axium" already exists')
|
|
151
|
-
exit(error);
|
|
152
|
-
if (shouldRecreate(opt))
|
|
153
|
-
return;
|
|
154
|
-
await runSQL(opt, 'REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
|
|
155
|
-
await runSQL(opt, 'DROP USER axium', 'Dropping user');
|
|
156
|
-
await runSQL(opt, createQuery, 'Re-creating user');
|
|
157
|
-
})
|
|
158
|
-
.catch(exit);
|
|
159
|
-
await runSQL(opt, 'GRANT ALL PRIVILEGES ON DATABASE axium TO axium', 'Granting database privileges').catch(exit);
|
|
160
|
-
await runSQL(opt, 'GRANT ALL PRIVILEGES ON SCHEMA public TO axium', 'Granting schema privileges').catch(exit);
|
|
161
|
-
await runSQL(opt, 'ALTER DATABASE axium OWNER TO axium', 'Setting database owner').catch(exit);
|
|
162
|
-
await runSQL(opt, 'SELECT pg_reload_conf()', 'Reloading configuration').catch(exit);
|
|
163
|
-
const db = __addDisposableResource(env_1, _db.connect(config), true);
|
|
164
|
-
const relationExists = (table) => (error) => (error == `relation "${table}" already exists` ? console.warn(chalk.yellow('already exists.')) : exit(error));
|
|
165
|
-
const created = chalk.green('created.');
|
|
166
|
-
await report(db.schema
|
|
167
|
-
.createTable('User')
|
|
168
|
-
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
169
|
-
.addColumn('name', 'text')
|
|
170
|
-
.addColumn('email', 'text', col => col.unique().notNull())
|
|
171
|
-
.addColumn('emailVerified', 'timestamptz')
|
|
172
|
-
.addColumn('image', 'text')
|
|
173
|
-
.execute(), 'Creating table User', created).catch(relationExists('User'));
|
|
174
|
-
await report(db.schema
|
|
175
|
-
.createTable('Account')
|
|
176
|
-
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
177
|
-
.addColumn('userId', 'uuid', col => col.references('User.id').onDelete('cascade').notNull())
|
|
178
|
-
.addColumn('type', 'text', col => col.notNull())
|
|
179
|
-
.addColumn('provider', 'text', col => col.notNull())
|
|
180
|
-
.addColumn('providerAccountId', 'text', col => col.notNull())
|
|
181
|
-
.addColumn('refresh_token', 'text')
|
|
182
|
-
.addColumn('access_token', 'text')
|
|
183
|
-
.addColumn('expires_at', 'bigint')
|
|
184
|
-
.addColumn('token_type', 'text')
|
|
185
|
-
.addColumn('scope', 'text')
|
|
186
|
-
.addColumn('id_token', 'text')
|
|
187
|
-
.addColumn('session_state', 'text')
|
|
188
|
-
.execute(), 'Creating table Account', created).catch(relationExists('Account'));
|
|
189
|
-
await report(db.schema.createIndex('Account_userId_index').on('Account').column('userId').execute(), 'Creating index for Account.userId', created).catch(relationExists('Account_userId_index'));
|
|
190
|
-
await report(db.schema
|
|
191
|
-
.createTable('Session')
|
|
192
|
-
.addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
|
|
193
|
-
.addColumn('userId', 'uuid', col => col.references('User.id').onDelete('cascade').notNull())
|
|
194
|
-
.addColumn('sessionToken', 'text', col => col.notNull().unique())
|
|
195
|
-
.addColumn('expires', 'timestamptz', col => col.notNull())
|
|
196
|
-
.execute(), 'Creating table Session', created).catch(relationExists('Session'));
|
|
197
|
-
await report(db.schema.createIndex('Session_userId_index').on('Session').column('userId').execute(), 'Creating index for Session.userId', created).catch(relationExists('Session_userId_index'));
|
|
198
|
-
await report(db.schema
|
|
199
|
-
.createTable('VerificationToken')
|
|
200
|
-
.addColumn('identifier', 'text', col => col.notNull())
|
|
201
|
-
.addColumn('token', 'text', col => col.notNull().unique())
|
|
202
|
-
.addColumn('expires', 'timestamptz', col => col.notNull())
|
|
203
|
-
.execute(), 'Creating table VerificationToken', created).catch(relationExists('VerificationToken'));
|
|
204
|
-
await report(db.schema
|
|
205
|
-
.createTable('Authenticator')
|
|
206
|
-
.addColumn('credentialID', 'text', col => col.primaryKey().notNull())
|
|
207
|
-
.addColumn('userId', 'uuid', col => col.notNull().references('User.id').onDelete('cascade').onUpdate('cascade'))
|
|
208
|
-
.addColumn('providerAccountId', 'text', col => col.notNull())
|
|
209
|
-
.addColumn('credentialPublicKey', 'text', col => col.notNull())
|
|
210
|
-
.addColumn('counter', 'integer', col => col.notNull())
|
|
211
|
-
.addColumn('credentialDeviceType', 'text', col => col.notNull())
|
|
212
|
-
.addColumn('credentialBackedUp', 'boolean', col => col.notNull())
|
|
213
|
-
.addColumn('transports', 'text')
|
|
214
|
-
.execute(), 'Creating table Authenticator', created).catch(relationExists('Authenticator'));
|
|
215
|
-
await report(db.schema.createIndex('Authenticator_credentialID_key').on('Authenticator').column('credentialID').execute(), 'Creating index for Authenticator.credentialID', created).catch(relationExists('Authenticator_credentialID_key'));
|
|
216
|
-
console.log('Done!\nPassword: ' + config.password);
|
|
75
|
+
console.log(await db.statusText());
|
|
217
76
|
}
|
|
218
|
-
catch
|
|
219
|
-
|
|
220
|
-
|
|
77
|
+
catch {
|
|
78
|
+
output.error('Unavailable');
|
|
79
|
+
process.exitCode = 1;
|
|
221
80
|
}
|
|
222
81
|
finally {
|
|
223
|
-
|
|
224
|
-
if (result_1)
|
|
225
|
-
await result_1;
|
|
82
|
+
await db.database.destroy();
|
|
226
83
|
}
|
|
227
84
|
});
|
|
228
|
-
axiumDB
|
|
229
|
-
.command('status')
|
|
230
|
-
.alias('stats')
|
|
231
|
-
.description('check the status of the database')
|
|
232
|
-
.addOption(opts.host)
|
|
233
|
-
.addOption(opts.verbose)
|
|
234
|
-
.action(async (opt) => _db
|
|
235
|
-
.statusText(opt)
|
|
236
|
-
.then(console.log)
|
|
237
|
-
.catch(() => exit('Unavailable')));
|
|
238
85
|
axiumDB
|
|
239
86
|
.command('drop')
|
|
240
87
|
.description('drop the database')
|
|
241
|
-
.addOption(opts.host)
|
|
242
|
-
.addOption(opts.verbose)
|
|
243
88
|
.addOption(opts.force)
|
|
244
|
-
.addOption(opts.timeout)
|
|
245
89
|
.action(async (opt) => {
|
|
246
|
-
|
|
247
|
-
_db.normalizeConfig(opt);
|
|
248
|
-
const stats = await _db.status(opt).catch(exit);
|
|
90
|
+
const stats = await db.status().catch(exit);
|
|
249
91
|
if (!opt.force)
|
|
250
92
|
for (const key of ['users', 'accounts', 'sessions']) {
|
|
251
93
|
if (stats[key] == 0)
|
|
252
94
|
continue;
|
|
253
|
-
|
|
95
|
+
output.warn(`Database has existing ${key}. Use --force if you really want to drop the database.`);
|
|
254
96
|
process.exit(2);
|
|
255
97
|
}
|
|
256
|
-
await
|
|
257
|
-
await
|
|
258
|
-
|
|
98
|
+
await db.uninstall({ ...opt, output: db_output }).catch(exit);
|
|
99
|
+
await db.database.destroy();
|
|
100
|
+
});
|
|
101
|
+
axiumDB
|
|
102
|
+
.command('wipe')
|
|
103
|
+
.description('wipe the database')
|
|
104
|
+
.addOption(opts.force)
|
|
105
|
+
.action(async (opt) => {
|
|
106
|
+
const stats = await db.status().catch(exit);
|
|
107
|
+
if (!opt.force)
|
|
108
|
+
for (const key of ['users', 'accounts', 'sessions']) {
|
|
109
|
+
if (stats[key] == 0)
|
|
110
|
+
continue;
|
|
111
|
+
output.warn(`Database has existing ${key}. Use --force if you really want to wipe the database.`);
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
await db.wipe({ ...opt, output: db_output }).catch(exit);
|
|
115
|
+
await db.database.destroy();
|
|
116
|
+
});
|
|
117
|
+
const axiumConfig = program
|
|
118
|
+
.command('config')
|
|
119
|
+
.alias('conf')
|
|
120
|
+
.description('manage the configuration')
|
|
121
|
+
.option('-j, --json', 'values are JSON encoded')
|
|
122
|
+
.option('-g, --global', 'apply to the global config');
|
|
123
|
+
axiumConfig
|
|
124
|
+
.command('dump')
|
|
125
|
+
.description('Output the entire current configuration')
|
|
126
|
+
.action(() => {
|
|
127
|
+
const value = config.get();
|
|
128
|
+
console.log(axiumConfig.optsWithGlobals().json ? JSON.stringify(value) : value);
|
|
129
|
+
});
|
|
130
|
+
axiumConfig
|
|
131
|
+
.command('get')
|
|
132
|
+
.description('get a config value')
|
|
133
|
+
.argument('<key>', 'the key to get')
|
|
134
|
+
.action((key) => {
|
|
135
|
+
const value = getByString(config, key);
|
|
136
|
+
console.log(axiumConfig.optsWithGlobals().json ? JSON.stringify(value) : value);
|
|
137
|
+
});
|
|
138
|
+
axiumConfig
|
|
139
|
+
.command('set')
|
|
140
|
+
.description('Set a config value. Note setting objects is not supported.')
|
|
141
|
+
.argument('<key>', 'the key to set')
|
|
142
|
+
.argument('<value>', 'the value')
|
|
143
|
+
.action((key, value, opt) => {
|
|
144
|
+
const useJSON = axiumConfig.optsWithGlobals().json;
|
|
145
|
+
if (useJSON && !isJSON(value))
|
|
146
|
+
exit('Invalid JSON');
|
|
147
|
+
const obj = {};
|
|
148
|
+
setByString(obj, key, useJSON ? JSON.parse(value) : value);
|
|
149
|
+
config.save(obj, opt.global);
|
|
150
|
+
});
|
|
151
|
+
axiumConfig
|
|
152
|
+
.command('list')
|
|
153
|
+
.alias('ls')
|
|
154
|
+
.alias('files')
|
|
155
|
+
.description('List loaded config files')
|
|
156
|
+
.action(() => {
|
|
157
|
+
for (const path of config.files.keys())
|
|
158
|
+
console.log(path);
|
|
259
159
|
});
|
|
260
160
|
program
|
|
261
161
|
.command('status')
|
|
262
162
|
.alias('stats')
|
|
263
163
|
.description('get information about the server')
|
|
264
|
-
.
|
|
265
|
-
.action(async (
|
|
164
|
+
.addOption(opts.host)
|
|
165
|
+
.action(async () => {
|
|
266
166
|
console.log('Axium Server v' + program.version());
|
|
167
|
+
console.log('Debug mode:', config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
168
|
+
console.log('Loaded config files:', config.files.keys().toArray().join(', '));
|
|
267
169
|
process.stdout.write('Database: ');
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
|
|
170
|
+
try {
|
|
171
|
+
console.log(await db.statusText());
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
output.error('Unavailable');
|
|
175
|
+
}
|
|
176
|
+
await db.database.destroy();
|
|
177
|
+
console.log('Credentials authentication:', config.auth.credentials ? styleText('yellow', 'enabled') : 'disabled');
|
|
178
|
+
});
|
|
179
|
+
program
|
|
180
|
+
.command('init')
|
|
181
|
+
.description('Install Axium server')
|
|
182
|
+
.addOption(opts.force)
|
|
183
|
+
.addOption(opts.host)
|
|
184
|
+
.action(async (opt) => {
|
|
185
|
+
await db.init({ ...opt, skip: opt.dbSkip, output: db_output }).catch((e) => {
|
|
186
|
+
if (typeof e == 'number')
|
|
187
|
+
process.exit(e);
|
|
188
|
+
else
|
|
189
|
+
exit(e);
|
|
190
|
+
});
|
|
272
191
|
});
|
|
273
192
|
program.parse();
|