@axium/server 0.37.1 → 0.37.2

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.
@@ -64,7 +64,7 @@ async function POST(request) {
64
64
  deviceType: registrationInfo.credentialDeviceType,
65
65
  backedUp: registrationInfo.credentialBackedUp,
66
66
  }).catch(withError('Failed to create passkey', 500));
67
- return await createSessionData(userId);
67
+ return await createSessionData(userId, request);
68
68
  }
69
69
  addRoute({
70
70
  path: '/api/register',
package/dist/api/users.js CHANGED
@@ -100,7 +100,7 @@ addRoute({
100
100
  path: '/api/users/:id/auth',
101
101
  params,
102
102
  async OPTIONS(request, { id: userId }) {
103
- const { type } = await parseBody(request, UserAuthOptions);
103
+ const { type, client } = await parseBody(request, UserAuthOptions);
104
104
  const user = await getUser(userId).catch(withError('User does not exist', 404));
105
105
  if (user.isSuspended)
106
106
  error(403, 'User is suspended');
@@ -111,7 +111,7 @@ addRoute({
111
111
  rpID: config.auth.rp_id,
112
112
  allowCredentials: passkeys.map(passkey => pick(passkey, 'id', 'transports')),
113
113
  });
114
- challenges.set(userId, { data: options.challenge, type });
114
+ challenges.set(userId, { data: options.challenge, type, client });
115
115
  return options;
116
116
  },
117
117
  async POST(request, { id: userId }) {
@@ -119,7 +119,7 @@ addRoute({
119
119
  const auth = challenges.get(userId);
120
120
  if (!auth)
121
121
  error(404, 'No challenge');
122
- const { data: expectedChallenge, type } = auth;
122
+ const { data: expectedChallenge, type, client } = auth;
123
123
  challenges.delete(userId);
124
124
  const passkey = await getPasskey(response.id).catch(withError('Passkey does not exist', 404));
125
125
  if (passkey.userId !== userId)
@@ -137,14 +137,13 @@ addRoute({
137
137
  error(401, 'Verification failed');
138
138
  switch (type) {
139
139
  case 'login':
140
- return await createSessionData(userId);
140
+ return await createSessionData(userId, request);
141
141
  case 'client_login':
142
- /** @todo tag in DB so users can manage easier */
143
- return await createSessionData(userId, { noCookie: true });
142
+ return await createSessionData(userId, request, { noCookie: true, clientUA: client });
144
143
  case 'action':
145
144
  if ((Date.now() - passkey.createdAt.getTime()) / 60_000 < config.auth.passkey_probation)
146
145
  error(403, 'You can not authorize sensitive actions with a newly created passkey');
147
- return await createSessionData(userId, { elevated: true });
146
+ return await createSessionData(userId, request, { elevated: true });
148
147
  }
149
148
  },
150
149
  });
@@ -266,6 +265,6 @@ addRoute({
266
265
  async POST(request, { id: userId }) {
267
266
  const { token } = await parseBody(request, z.object({ token: z.string() }));
268
267
  await useVerification('login', userId, token).catch(withError('Invalid or expired verification token', 400));
269
- return await createSessionData(userId);
268
+ return await createSessionData(userId, request);
270
269
  },
271
270
  });
package/dist/auth.d.ts CHANGED
@@ -8,7 +8,7 @@ export declare function updateUser({ id, ...user }: WithRequired<Insertable<Sche
8
8
  export interface SessionInternal extends Session {
9
9
  token: string;
10
10
  }
11
- export declare function createSession(userId: string, elevated?: boolean): Promise<SessionInternal>;
11
+ export declare function createSession(userId: string, name: string | null, elevated?: boolean): Promise<SessionInternal>;
12
12
  export interface SessionAndUser extends SessionInternal {
13
13
  user: UserInternal;
14
14
  }
package/dist/auth.js CHANGED
@@ -13,10 +13,11 @@ export async function updateUser({ id, ...user }) {
13
13
  }
14
14
  const in30days = () => new Date(Date.now() + 2592000000);
15
15
  const in10minutes = () => new Date(Date.now() + 600000);
16
- export async function createSession(userId, elevated = false) {
16
+ export async function createSession(userId, name, elevated = false) {
17
17
  const session = {
18
18
  id: randomUUID(),
19
19
  userId,
20
+ name,
20
21
  token: randomBytes(64).toString('base64'),
21
22
  expires: elevated ? in10minutes() : in30days(),
22
23
  elevated,
@@ -77,6 +77,16 @@
77
77
  }
78
78
  }
79
79
  }
80
+ },
81
+ {
82
+ "delta": true,
83
+ "alter_tables": {
84
+ "sessions": {
85
+ "add_columns": {
86
+ "name": { "type": "text" }
87
+ }
88
+ }
89
+ }
80
90
  }
81
91
  ],
82
92
  "wipe": ["users", "verifications", "passkeys", "sessions", "audit_log"]
@@ -35,11 +35,13 @@ export declare function json(data: object, init?: ResponseInit): Response;
35
35
  export declare function parseBody<const Schema extends z.ZodType>(request: Request, schema: Schema): Promise<z.infer<Schema>>;
36
36
  export declare function parseSearch<const Schema extends z.ZodType>(request: Request, schema: Schema): z.infer<Schema>;
37
37
  export declare function getToken(request: Request, sensitive?: boolean): string | undefined;
38
+ export declare function getPrettyUA(request: Request, uaOverride?: string): Promise<string | null>;
38
39
  export interface CreateSessionOptions {
39
40
  elevated?: boolean;
40
41
  noCookie?: boolean;
42
+ clientUA?: string;
41
43
  }
42
- export declare function createSessionData(userId: string, { elevated, noCookie }?: CreateSessionOptions): Promise<Response>;
44
+ export declare function createSessionData(userId: string, request: Request, { elevated, noCookie, clientUA }?: CreateSessionOptions): Promise<Response>;
43
45
  export declare function stripUser(user: UserInternal, includeProtected?: boolean): User;
44
46
  export declare function withError(text: string, code?: number): (e: Error | ResponseError) => never;
45
47
  export declare function handleAPIRequest(request: Request, params: Record<string, any>, route: ServerRoute): Promise<Response>;
package/dist/requests.js CHANGED
@@ -62,8 +62,29 @@ export function getToken(request, sensitive = false) {
62
62
  return cookie.parse(request.headers.get('cookie') || '')[sensitive ? 'elevated_token' : 'session_token'];
63
63
  }
64
64
  }
65
- export async function createSessionData(userId, { elevated = false, noCookie } = {}) {
66
- const { token, expires } = await createSession(userId, elevated);
65
+ const uaParserExtension = {
66
+ browser: [[/(axium[- ]client)\/([\d.]+)/i], ['name', 'version']],
67
+ };
68
+ export async function getPrettyUA(request, uaOverride) {
69
+ const uaString = uaOverride || request.headers.get('User-Agent') || '';
70
+ try {
71
+ const { UAParser } = await import('ua-parser-js');
72
+ const ua = UAParser(uaString, uaParserExtension);
73
+ return [
74
+ ua.browser.name,
75
+ ua.browser.name && /axium[- ]client/i.test(ua.browser.name) ? ua.browser.version : ua.browser.major,
76
+ ua.os.name && ' on ' + ua.os.name,
77
+ ]
78
+ .filter(p => p)
79
+ .join(' ');
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ export async function createSessionData(userId, request, { elevated = false, noCookie, clientUA } = {}) {
86
+ const name = await getPrettyUA(request, clientUA);
87
+ const { token, expires } = await createSession(userId, name, elevated);
67
88
  const response = json({ userId, token: elevated ? '[[redacted:elevated]]' : token }, { status: 201 });
68
89
  if (noCookie)
69
90
  return response;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.37.1",
3
+ "version": "0.37.2",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -66,5 +66,8 @@
66
66
  "devDependencies": {
67
67
  "@sveltejs/adapter-node": "^5.2.12",
68
68
  "vite-plugin-mkcert": "^1.17.8"
69
+ },
70
+ "optionalDependencies": {
71
+ "ua-parser-js": "^2.0.9"
69
72
  }
70
73
  }
@@ -13,12 +13,14 @@ export async function load({ parent, url }: Omit<PageLoadEvent, 'parent'> & { pa
13
13
  const port = parseInt(url.searchParams.get('port') ?? '!');
14
14
  const localCallback = new URL('http://localhost');
15
15
 
16
+ const client = url.searchParams.get('client') ?? '';
17
+
16
18
  let options,
17
19
  error: string | null = null;
18
20
  try {
19
21
  if (Number.isNaN(port)) throw new Error('Invalid port number provided by local client');
20
22
  localCallback.port = port.toString();
21
- options = await fetchAPI('OPTIONS', 'users/:id/auth', { type: 'client_login' }, session.userId);
23
+ options = await fetchAPI('OPTIONS', 'users/:id/auth', { type: 'client_login', client }, session.userId);
22
24
  } catch (e: any) {
23
25
  error = e.message;
24
26
  }