@axium/server 0.9.0 → 0.10.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.
Files changed (70) hide show
  1. package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
  2. package/dist/api/index.js +5 -0
  3. package/dist/api/metadata.d.ts +1 -0
  4. package/dist/api/metadata.js +28 -0
  5. package/dist/api/passkeys.d.ts +1 -0
  6. package/dist/api/passkeys.js +50 -0
  7. package/dist/api/register.d.ts +1 -0
  8. package/dist/api/register.js +70 -0
  9. package/dist/api/session.d.ts +1 -0
  10. package/dist/api/session.js +31 -0
  11. package/dist/api/users.d.ts +1 -0
  12. package/dist/api/users.js +244 -0
  13. package/dist/apps.d.ts +0 -5
  14. package/dist/apps.js +2 -9
  15. package/dist/auth.d.ts +9 -21
  16. package/dist/auth.js +12 -18
  17. package/dist/cli.js +65 -26
  18. package/dist/config.d.ts +15 -8
  19. package/dist/config.js +38 -15
  20. package/dist/database.js +4 -0
  21. package/dist/io.d.ts +6 -4
  22. package/dist/io.js +26 -19
  23. package/dist/plugins.d.ts +4 -2
  24. package/dist/plugins.js +15 -14
  25. package/dist/requests.d.ts +11 -0
  26. package/dist/requests.js +58 -0
  27. package/dist/routes.d.ts +12 -13
  28. package/dist/routes.js +21 -22
  29. package/dist/serve.d.ts +7 -0
  30. package/dist/serve.js +11 -0
  31. package/dist/state.d.ts +4 -0
  32. package/dist/state.js +22 -0
  33. package/dist/sveltekit.d.ts +8 -0
  34. package/dist/sveltekit.js +90 -0
  35. package/package.json +10 -5
  36. package/svelte.config.js +36 -0
  37. package/web/hooks.server.ts +8 -3
  38. package/web/lib/Dialog.svelte +0 -1
  39. package/web/lib/FormDialog.svelte +0 -1
  40. package/web/lib/icons/Icon.svelte +2 -7
  41. package/web/template.html +18 -0
  42. package/web/tsconfig.json +3 -2
  43. package/web/api/metadata.ts +0 -35
  44. package/web/api/passkeys.ts +0 -56
  45. package/web/api/readme.md +0 -1
  46. package/web/api/register.ts +0 -83
  47. package/web/api/schemas.ts +0 -22
  48. package/web/api/session.ts +0 -33
  49. package/web/api/users.ts +0 -351
  50. package/web/api/utils.ts +0 -66
  51. package/web/app.html +0 -14
  52. package/web/auth.ts +0 -8
  53. package/web/index.server.ts +0 -1
  54. package/web/index.ts +0 -1
  55. package/web/lib/auth.ts +0 -12
  56. package/web/lib/index.ts +0 -5
  57. package/web/routes/+layout.svelte +0 -6
  58. package/web/routes/[...path]/+page.server.ts +0 -13
  59. package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
  60. package/web/routes/api/[...path]/+server.ts +0 -49
  61. package/web/utils.ts +0 -26
  62. /package/{web/lib → assets}/icons/light.svg +0 -0
  63. /package/{web/lib → assets}/icons/regular.svg +0 -0
  64. /package/{web/lib → assets}/icons/solid.svg +0 -0
  65. /package/{web/lib → assets}/styles.css +0 -0
  66. /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
  67. /package/{web/routes → routes}/account/+page.svelte +0 -0
  68. /package/{web/routes → routes}/login/+page.svelte +0 -0
  69. /package/{web/routes → routes}/logout/+page.svelte +0 -0
  70. /package/{web/routes → routes}/register/+page.svelte +0 -0
@@ -3,5 +3,3 @@ import './passkeys.js';
3
3
  import './register.js';
4
4
  import './session.js';
5
5
  import './users.js';
6
- export * from './schemas.js';
7
- export * from './utils.js';
@@ -0,0 +1,5 @@
1
+ import './metadata.js';
2
+ import './passkeys.js';
3
+ import './register.js';
4
+ import './session.js';
5
+ import './users.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { requestMethods } from '@axium/core/requests';
2
+ import { error } from '@sveltejs/kit';
3
+ import pkg from '../../package.json' with { type: 'json' };
4
+ import { config } from '../config.js';
5
+ import { plugins } from '../plugins.js';
6
+ import { addRoute, routes } from '../routes.js';
7
+ addRoute({
8
+ path: '/api/metadata',
9
+ async GET() {
10
+ if (config.api.disable_metadata) {
11
+ error(401, { message: 'API metadata is disabled' });
12
+ }
13
+ return {
14
+ version: pkg.version,
15
+ routes: Object.fromEntries(routes
16
+ .entries()
17
+ .filter(([path]) => path.startsWith('/api/'))
18
+ .map(([path, route]) => [
19
+ path,
20
+ {
21
+ params: Object.fromEntries(Object.entries(route.params || {}).map(([key, type]) => [key, type ? type.def.type : null])),
22
+ methods: requestMethods.filter(m => m in route),
23
+ },
24
+ ])),
25
+ plugins: Object.fromEntries(plugins.values().map(plugin => [plugin.name, plugin.version])),
26
+ };
27
+ },
28
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { PasskeyChangeable } from '@axium/core/schemas';
2
+ import { error } from '@sveltejs/kit';
3
+ import { omit } from 'utilium';
4
+ import z from 'zod/v4';
5
+ import { getPasskey } from '../auth.js';
6
+ import { database as db } from '../database.js';
7
+ import { addRoute } from '../routes.js';
8
+ import { checkAuth, parseBody, withError } from '../requests.js';
9
+ addRoute({
10
+ path: '/api/passkeys/:id',
11
+ params: {
12
+ id: z.string(),
13
+ },
14
+ async GET(event) {
15
+ const passkey = await getPasskey(event.params.id);
16
+ await checkAuth(event, passkey.userId);
17
+ return omit(passkey, 'counter', 'publicKey');
18
+ },
19
+ async PATCH(event) {
20
+ const body = await parseBody(event, PasskeyChangeable);
21
+ const passkey = await getPasskey(event.params.id);
22
+ await checkAuth(event, passkey.userId);
23
+ const result = await db
24
+ .updateTable('passkeys')
25
+ .set(body)
26
+ .where('id', '=', passkey.id)
27
+ .returningAll()
28
+ .executeTakeFirstOrThrow()
29
+ .catch(withError('Could not update passkey'));
30
+ return omit(result, 'counter', 'publicKey');
31
+ },
32
+ async DELETE(event) {
33
+ const passkey = await getPasskey(event.params.id);
34
+ await checkAuth(event, passkey.userId);
35
+ const { count } = await db
36
+ .selectFrom('passkeys')
37
+ .select(db.fn.countAll().as('count'))
38
+ .where('userId', '=', passkey.userId)
39
+ .executeTakeFirstOrThrow();
40
+ if (Number(count) <= 1)
41
+ error(409, 'At least one passkey is required');
42
+ const result = await db
43
+ .deleteFrom('passkeys')
44
+ .where('id', '=', passkey.id)
45
+ .returningAll()
46
+ .executeTakeFirstOrThrow()
47
+ .catch(withError('Could not delete passkey'));
48
+ return omit(result, 'counter', 'publicKey');
49
+ },
50
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { APIUserRegistration } from '@axium/core/schemas';
2
+ import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
3
+ import { error } from '@sveltejs/kit';
4
+ import { randomUUID } from 'node:crypto';
5
+ import z from 'zod/v4';
6
+ import { createPasskey, getUser } from '../auth.js';
7
+ import config from '../config.js';
8
+ import { database as db } from '../database.js';
9
+ import { addRoute } from '../routes.js';
10
+ import { createSessionData, parseBody, withError } from '../requests.js';
11
+ // Map of user ID => challenge
12
+ const registrations = new Map();
13
+ async function OPTIONS(event) {
14
+ const { name, email } = await parseBody(event, z.object({ name: z.string().optional(), email: z.email().optional() }));
15
+ const userId = randomUUID();
16
+ const user = await getUser(userId).catch(() => null);
17
+ if (user)
18
+ error(409, { message: 'Generated UUID is already in use, please retry.' });
19
+ const options = await generateRegistrationOptions({
20
+ rpName: config.auth.rp_name,
21
+ rpID: config.auth.rp_id,
22
+ userName: email ?? userId,
23
+ userDisplayName: name,
24
+ attestationType: 'none',
25
+ excludeCredentials: [],
26
+ authenticatorSelection: {
27
+ residentKey: 'preferred',
28
+ userVerification: 'preferred',
29
+ authenticatorAttachment: 'platform',
30
+ },
31
+ });
32
+ registrations.set(userId, options.challenge);
33
+ return { userId, options };
34
+ }
35
+ async function POST(event) {
36
+ const { userId, email, name, response } = await parseBody(event, APIUserRegistration);
37
+ const existing = await db.selectFrom('users').selectAll().where('email', '=', email).executeTakeFirst();
38
+ if (existing)
39
+ error(409, { message: 'Email already in use' });
40
+ const expectedChallenge = registrations.get(userId);
41
+ if (!expectedChallenge)
42
+ error(404, { message: 'No registration challenge found for this user' });
43
+ registrations.delete(userId);
44
+ const { verified, registrationInfo } = await verifyRegistrationResponse({
45
+ response,
46
+ expectedChallenge,
47
+ expectedOrigin: config.auth.origin,
48
+ }).catch(() => error(400, { message: 'Verification failed' }));
49
+ if (!verified || !registrationInfo)
50
+ error(401, { message: 'Verification failed' });
51
+ await db
52
+ .insertInto('users')
53
+ .values({ id: userId, name, email })
54
+ .executeTakeFirstOrThrow()
55
+ .catch(withError('Failed to create user'));
56
+ await createPasskey({
57
+ transports: [],
58
+ ...registrationInfo.credential,
59
+ userId,
60
+ deviceType: registrationInfo.credentialDeviceType,
61
+ backedUp: registrationInfo.credentialBackedUp,
62
+ }).catch(e => error(500, { message: 'Failed to create passkey' + (config.debug ? `: ${e.message}` : '') }));
63
+ return await createSessionData(event, userId);
64
+ }
65
+ addRoute({
66
+ path: '/api/register',
67
+ params: {},
68
+ OPTIONS,
69
+ POST,
70
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { error } from '@sveltejs/kit';
2
+ import { omit } from 'utilium';
3
+ import { authenticate } from '../auth.js';
4
+ import { connect, database as db } from '../database.js';
5
+ import { addRoute } from '../routes.js';
6
+ import { getToken, stripUser } from '../requests.js';
7
+ addRoute({
8
+ path: '/api/session',
9
+ async GET(event) {
10
+ const result = await authenticate(event);
11
+ if (!result)
12
+ error(404, 'Session does not exist');
13
+ return {
14
+ ...omit(result, 'token'),
15
+ user: stripUser(result.user, true),
16
+ };
17
+ },
18
+ async DELETE(event) {
19
+ const token = getToken(event);
20
+ if (!token)
21
+ error(401, 'Missing token');
22
+ connect();
23
+ const result = await db
24
+ .deleteFrom('sessions')
25
+ .where('sessions.token', '=', token)
26
+ .returningAll()
27
+ .executeTakeFirstOrThrow()
28
+ .catch((e) => (e.message == 'no result' ? error(404, 'Session does not exist') : error(400, 'Invalid session')));
29
+ return omit(result, 'token');
30
+ },
31
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,244 @@
1
+ import { LogoutSessions, PasskeyAuthenticationResponse, PasskeyRegistration, UserAuthOptions } from '@axium/core/schemas';
2
+ import { UserChangeable } from '@axium/core/user';
3
+ import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse, } from '@simplewebauthn/server';
4
+ import { error } from '@sveltejs/kit';
5
+ import { omit, pick } from 'utilium';
6
+ import z from 'zod/v4';
7
+ import { createPasskey, createVerification, getPasskey, getPasskeysByUserId, getSessions, getUser, useVerification, } from '../auth.js';
8
+ import { config } from '../config.js';
9
+ import { connect, database as db } from '../database.js';
10
+ import { addRoute } from '../routes.js';
11
+ import { checkAuth, createSessionData, parseBody, stripUser, withError } from '../requests.js';
12
+ const challenges = new Map();
13
+ const params = { id: z.uuid() };
14
+ /**
15
+ * Resolve a user's UUID using their email (in the future this might also include handles)
16
+ */
17
+ addRoute({
18
+ path: '/api/user_id',
19
+ async POST(event) {
20
+ const { value } = await parseBody(event, z.object({ using: z.literal('email'), value: z.email() }));
21
+ connect();
22
+ const { id } = await db
23
+ .selectFrom('users')
24
+ .select('id')
25
+ .where('email', '=', value)
26
+ .executeTakeFirstOrThrow()
27
+ .catch(withError('User not found', 404));
28
+ return { id };
29
+ },
30
+ });
31
+ addRoute({
32
+ path: '/api/users/:id',
33
+ params,
34
+ async GET(event) {
35
+ const userId = event.params.id;
36
+ const authed = await checkAuth(event, userId).catch(() => null);
37
+ const user = authed?.user || (await getUser(userId).catch(withError('User does not exist', 404)));
38
+ return stripUser(user, !!authed);
39
+ },
40
+ async PATCH(event) {
41
+ const userId = event.params.id;
42
+ const body = await parseBody(event, UserChangeable);
43
+ await checkAuth(event, userId);
44
+ if ('email' in body)
45
+ body.emailVerified = null;
46
+ const result = await db
47
+ .updateTable('users')
48
+ .set(body)
49
+ .where('id', '=', userId)
50
+ .returningAll()
51
+ .executeTakeFirstOrThrow()
52
+ .catch(withError('Failed to update user'));
53
+ return stripUser(result, true);
54
+ },
55
+ async DELETE(event) {
56
+ const userId = event.params.id;
57
+ await checkAuth(event, userId, true);
58
+ const result = await db
59
+ .deleteFrom('users')
60
+ .where('id', '=', userId)
61
+ .returningAll()
62
+ .executeTakeFirstOrThrow()
63
+ .catch(withError('Failed to delete user'));
64
+ return result;
65
+ },
66
+ });
67
+ addRoute({
68
+ path: '/api/users/:id/full',
69
+ params,
70
+ async GET(event) {
71
+ const userId = event.params.id;
72
+ const { user } = await checkAuth(event, userId);
73
+ const sessions = await getSessions(userId);
74
+ return {
75
+ ...stripUser(user, true),
76
+ sessions: sessions.map(s => omit(s, 'token')),
77
+ };
78
+ },
79
+ });
80
+ addRoute({
81
+ path: '/api/users/:id/auth',
82
+ params,
83
+ async OPTIONS(event) {
84
+ const userId = event.params.id;
85
+ const { type } = await parseBody(event, UserAuthOptions);
86
+ await getUser(userId).catch(withError('User does not exist', 404));
87
+ const passkeys = await getPasskeysByUserId(userId);
88
+ if (!passkeys)
89
+ error(409, { message: 'No passkeys exists for this user' });
90
+ const options = await generateAuthenticationOptions({
91
+ rpID: config.auth.rp_id,
92
+ allowCredentials: passkeys.map(passkey => pick(passkey, 'id', 'transports')),
93
+ });
94
+ challenges.set(userId, { data: options.challenge, type });
95
+ return options;
96
+ },
97
+ async POST(event) {
98
+ const userId = event.params.id;
99
+ const response = await parseBody(event, PasskeyAuthenticationResponse);
100
+ const auth = challenges.get(userId);
101
+ if (!auth)
102
+ error(404, { message: 'No challenge' });
103
+ const { data: expectedChallenge, type } = auth;
104
+ challenges.delete(userId);
105
+ const passkey = await getPasskey(response.id).catch(withError('Passkey does not exist', 404));
106
+ if (passkey.userId !== userId)
107
+ error(403, { message: 'Passkey does not belong to this user' });
108
+ const { verified } = await verifyAuthenticationResponse({
109
+ response,
110
+ credential: passkey,
111
+ expectedChallenge,
112
+ expectedOrigin: config.auth.origin,
113
+ expectedRPID: config.auth.rp_id,
114
+ }).catch(withError('Verification failed', 400));
115
+ if (!verified)
116
+ error(401, { message: 'Verification failed' });
117
+ switch (type) {
118
+ case 'login':
119
+ return await createSessionData(event, userId);
120
+ case 'action':
121
+ if ((Date.now() - passkey.createdAt.getTime()) / 60_000 < config.auth.passkey_probation)
122
+ error(403, { message: 'You can not authorize sensitive actions with a newly created passkey' });
123
+ return await createSessionData(event, userId, true);
124
+ }
125
+ },
126
+ });
127
+ // Map of user ID => challenge
128
+ const registrations = new Map();
129
+ addRoute({
130
+ path: '/api/users/:id/passkeys',
131
+ params,
132
+ /**
133
+ * Get passkey registration options for a user.
134
+ */
135
+ async OPTIONS(event) {
136
+ const userId = event.params.id;
137
+ const existing = await getPasskeysByUserId(userId);
138
+ const { user } = await checkAuth(event, userId);
139
+ const options = await generateRegistrationOptions({
140
+ rpName: config.auth.rp_name,
141
+ rpID: config.auth.rp_id,
142
+ userName: userId,
143
+ userDisplayName: user.email,
144
+ attestationType: 'none',
145
+ excludeCredentials: existing.map(passkey => pick(passkey, 'id', 'transports')),
146
+ authenticatorSelection: {
147
+ residentKey: 'preferred',
148
+ userVerification: 'preferred',
149
+ authenticatorAttachment: 'platform',
150
+ },
151
+ });
152
+ registrations.set(userId, options.challenge);
153
+ return options;
154
+ },
155
+ /**
156
+ * Get passkeys for a user.
157
+ */
158
+ async GET(event) {
159
+ const userId = event.params.id;
160
+ await checkAuth(event, userId);
161
+ const passkeys = await getPasskeysByUserId(userId);
162
+ return passkeys.map(p => omit(p, 'publicKey', 'counter'));
163
+ },
164
+ /**
165
+ * Register a new passkey for an existing user.
166
+ */
167
+ async PUT(event) {
168
+ const userId = event.params.id;
169
+ const response = await parseBody(event, PasskeyRegistration);
170
+ await checkAuth(event, userId);
171
+ const expectedChallenge = registrations.get(userId);
172
+ if (!expectedChallenge)
173
+ error(404, { message: 'No registration challenge found for this user' });
174
+ registrations.delete(userId);
175
+ const { verified, registrationInfo } = await verifyRegistrationResponse({
176
+ response,
177
+ expectedChallenge,
178
+ expectedOrigin: config.auth.origin,
179
+ }).catch(withError('Verification failed', 400));
180
+ if (!verified || !registrationInfo)
181
+ error(401, { message: 'Verification failed' });
182
+ const passkey = await createPasskey({
183
+ transports: [],
184
+ ...registrationInfo.credential,
185
+ userId,
186
+ deviceType: registrationInfo.credentialDeviceType,
187
+ backedUp: registrationInfo.credentialBackedUp,
188
+ }).catch(withError('Failed to create passkey'));
189
+ return omit(passkey, 'publicKey', 'counter');
190
+ },
191
+ });
192
+ addRoute({
193
+ path: '/api/users/:id/sessions',
194
+ params,
195
+ async GET(event) {
196
+ const userId = event.params.id;
197
+ await checkAuth(event, userId);
198
+ return (await getSessions(userId).catch(e => error(503, 'Failed to get sessions' + (config.debug ? ': ' + e : '')))).map(s => omit(s, 'token'));
199
+ },
200
+ async DELETE(event) {
201
+ const userId = event.params.id;
202
+ const body = await parseBody(event, LogoutSessions);
203
+ await checkAuth(event, userId, body.confirm_all);
204
+ if (!body.confirm_all && !Array.isArray(body.id))
205
+ error(400, { message: 'Invalid request body' });
206
+ const query = body.confirm_all ? db.deleteFrom('sessions') : db.deleteFrom('sessions').where('sessions.id', 'in', body.id);
207
+ const result = await query
208
+ .where('sessions.userId', '=', userId)
209
+ .returningAll()
210
+ .execute()
211
+ .catch(withError('Failed to delete one or more sessions'));
212
+ return result.map(s => omit(s, 'token'));
213
+ },
214
+ });
215
+ addRoute({
216
+ path: '/api/users/:id/verify_email',
217
+ params,
218
+ async OPTIONS(event) {
219
+ const userId = event.params.id;
220
+ if (!config.auth.email_verification)
221
+ return { enabled: false };
222
+ await checkAuth(event, userId);
223
+ if (!config.auth.email_verification)
224
+ return { enabled: false };
225
+ return { enabled: true };
226
+ },
227
+ async GET(event) {
228
+ const userId = event.params.id;
229
+ const { user } = await checkAuth(event, userId);
230
+ if (user.emailVerified)
231
+ error(409, { message: 'Email already verified' });
232
+ const verification = await createVerification('verify_email', userId, config.auth.verification_timeout * 60);
233
+ return omit(verification, 'token', 'role');
234
+ },
235
+ async POST(event) {
236
+ const userId = event.params.id;
237
+ const { token } = await parseBody(event, z.object({ token: z.string() }));
238
+ const { user } = await checkAuth(event, userId);
239
+ if (user.emailVerified)
240
+ error(409, { message: 'Email already verified' });
241
+ await useVerification('verify_email', userId, token).catch(withError('Invalid or expired verification token', 400));
242
+ return {};
243
+ },
244
+ });
package/dist/apps.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import type { LoadEvent, RequestEvent } from '@sveltejs/kit';
2
- import type { WebRoute, WebRouteOptions } from './routes.js';
3
1
  export interface CreateAppOptions {
4
2
  id: string;
5
3
  name?: string;
@@ -8,8 +6,5 @@ export declare const apps: Map<string, App>;
8
6
  export declare class App {
9
7
  readonly id: string;
10
8
  name: string;
11
- protected readonly routes: Map<string, WebRoute>;
12
9
  constructor(opt: CreateAppOptions);
13
- addRoute(route: WebRouteOptions): void;
14
- resolveRoute(event: RequestEvent | LoadEvent): WebRoute | undefined;
15
10
  }
package/dist/apps.js CHANGED
@@ -1,20 +1,13 @@
1
1
  import { pick } from 'utilium';
2
- import { addRoute, resolveRoute } from './routes.js';
3
- export const apps = new Map();
2
+ import { _unique } from './state.js';
3
+ export const apps = _unique('apps', new Map());
4
4
  export class App {
5
5
  id;
6
6
  name;
7
- routes = new Map();
8
7
  constructor(opt) {
9
8
  if (apps.has(opt.id))
10
9
  throw new ReferenceError(`App with ID "${opt.id}" already exists.`);
11
10
  Object.assign(this, pick(opt, 'id', 'name'));
12
11
  apps.set(this.id, this);
13
12
  }
14
- addRoute(route) {
15
- addRoute(route, this.routes);
16
- }
17
- resolveRoute(event) {
18
- return resolveRoute(event, this.routes);
19
- }
20
13
  }
package/dist/auth.d.ts CHANGED
@@ -1,40 +1,27 @@
1
1
  import type { Passkey, Session, Verification } from '@axium/core/api';
2
2
  import type { User } from '@axium/core/user';
3
+ import type { RequestEvent } from '@sveltejs/kit';
3
4
  export interface UserInternal extends User {
4
5
  password?: string | null;
5
6
  salt?: string | null;
6
7
  }
7
- export declare function getUser(id: string): Promise<{
8
- name: string;
8
+ export declare function getUser(id: string): Promise<UserInternal>;
9
+ export declare function updateUser({ id, ...user }: UserInternal): Promise<{
9
10
  id: string;
10
- image: string | null | undefined;
11
- email: string;
12
- emailVerified: Date | null | undefined;
13
- preferences: import("@axium/core/user").Preferences | undefined;
14
- } | null>;
15
- export declare function getUserByEmail(email: string): Promise<{
16
11
  name: string;
17
- id: string;
18
- image: string | null | undefined;
19
12
  email: string;
20
13
  emailVerified: Date | null | undefined;
21
- preferences: import("@axium/core/user").Preferences | undefined;
22
- } | null>;
23
- export declare function updateUser({ id, ...user }: UserInternal): Promise<{
24
- name: string;
25
- id: string;
26
14
  image: string | null | undefined;
27
- email: string;
28
- emailVerified: Date | null | undefined;
29
15
  preferences: import("@axium/core/user").Preferences | undefined;
30
16
  }>;
31
17
  export interface SessionInternal extends Session {
32
18
  token: string;
33
19
  }
34
20
  export declare function createSession(userId: string, elevated?: boolean): Promise<SessionInternal>;
35
- export declare function getSessionAndUser(token: string): Promise<SessionInternal & {
36
- user: UserInternal | null;
37
- }>;
21
+ export interface SessionAndUser extends SessionInternal {
22
+ user: UserInternal;
23
+ }
24
+ export declare function getSessionAndUser(token: string): Promise<SessionAndUser>;
38
25
  export declare function getSession(sessionId: string): Promise<SessionInternal>;
39
26
  export declare function getSessions(userId: string): Promise<SessionInternal[]>;
40
27
  export type VerificationRole = 'verify_email' | 'login';
@@ -52,7 +39,8 @@ export interface PasskeyInternal extends Passkey {
52
39
  publicKey: Uint8Array;
53
40
  counter: number;
54
41
  }
55
- export declare function getPasskey(id: string): Promise<PasskeyInternal | null>;
42
+ export declare function getPasskey(id: string): Promise<PasskeyInternal>;
56
43
  export declare function createPasskey(passkey: Omit<PasskeyInternal, 'createdAt'>): Promise<PasskeyInternal>;
57
44
  export declare function getPasskeysByUserId(userId: string): Promise<PasskeyInternal[]>;
58
45
  export declare function updatePasskeyCounter(id: PasskeyInternal['id'], newCounter: PasskeyInternal['counter']): Promise<PasskeyInternal>;
46
+ export declare function authenticate(event: RequestEvent): Promise<SessionAndUser | null>;
package/dist/auth.js CHANGED
@@ -3,17 +3,7 @@ import { randomBytes, randomUUID } from 'node:crypto';
3
3
  import { connect, database as db } from './database.js';
4
4
  export async function getUser(id) {
5
5
  connect();
6
- const result = await db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst();
7
- if (!result)
8
- return null;
9
- return result;
10
- }
11
- export async function getUserByEmail(email) {
12
- connect();
13
- const result = await db.selectFrom('users').selectAll().where('email', '=', email).executeTakeFirst();
14
- if (!result)
15
- return null;
16
- return result;
6
+ return await db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirstOrThrow();
17
7
  }
18
8
  export async function updateUser({ id, ...user }) {
19
9
  connect();
@@ -43,9 +33,9 @@ export async function getSessionAndUser(token) {
43
33
  .select(eb => jsonObjectFrom(eb.selectFrom('users').selectAll().whereRef('users.id', '=', 'sessions.userId')).as('user'))
44
34
  .where('sessions.token', '=', token)
45
35
  .where('sessions.expires', '>', new Date())
46
- .executeTakeFirst();
47
- if (!result)
48
- throw new Error('Session not found');
36
+ .executeTakeFirstOrThrow();
37
+ if (!result.user)
38
+ throw new Error('Session references non-existing user');
49
39
  return result;
50
40
  }
51
41
  export async function getSession(sessionId) {
@@ -86,10 +76,7 @@ export async function useVerification(role, userId, token) {
86
76
  }
87
77
  export async function getPasskey(id) {
88
78
  connect();
89
- const result = await db.selectFrom('passkeys').selectAll().where('id', '=', id).executeTakeFirst();
90
- if (!result)
91
- return null;
92
- return result;
79
+ return await db.selectFrom('passkeys').selectAll().where('id', '=', id).executeTakeFirstOrThrow();
93
80
  }
94
81
  export async function createPasskey(passkey) {
95
82
  connect();
@@ -108,3 +95,10 @@ export async function updatePasskeyCounter(id, newCounter) {
108
95
  throw new Error('Passkey not found');
109
96
  return passkey;
110
97
  }
98
+ export async function authenticate(event) {
99
+ const maybe_header = event.request.headers.get('Authorization');
100
+ const token = maybe_header?.startsWith('Bearer ') ? maybe_header.slice(7) : event.cookies.get('session_token');
101
+ if (!token)
102
+ return null;
103
+ return await getSessionAndUser(token).catch(() => null);
104
+ }